Merge branch 'master' into labeledchunks

# Conflicts:
#	codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
#	examples/test.p8
This commit is contained in:
Irmen de Jong 2022-10-29 18:24:26 +02:00
commit 224f490455
30 changed files with 411 additions and 257 deletions

View File

@ -78,6 +78,9 @@ It's handy to have an emulator (or a real machine perhaps!) to run the programs
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
Look in the [syntax-files](https://github.com/irmen/prog8/tree/master/syntax-files) directory in the github repository to find them.
Example code
------------

View File

@ -106,7 +106,7 @@ class AsmGen(internal val program: Program,
DataType.BYTE -> listOf("cx16", "r9sL")
DataType.UWORD -> listOf("cx16", "r9")
DataType.WORD -> listOf("cx16", "r9s")
DataType.FLOAT -> listOf("floats", "tempvar_swap_float") // defined in floats.p8
DataType.FLOAT -> TODO("no temporary float var available")
else -> throw FatalAstException("invalid dt $dt")
}
}

View File

@ -287,18 +287,24 @@ internal class AssignmentAsmGen(private val program: Program,
}
}
is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target.
translateNormalAssignment(AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
))
val target = virtualRegsToVariables(assign.target)
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
else -> throw AssemblyError("invalid prefix operator")
if(assign.target.array==null) {
// First assign the value to the target then apply the operator in place on the target.
// This saves a temporary variable
translateNormalAssignment(
AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
)
)
when (value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(assign)
"~" -> augmentableAsmGen.inplaceInvert(assign)
else -> throw AssemblyError("invalid prefix operator")
}
} else {
assignPrefixedExpressionToArrayElt(assign)
}
}
is ContainmentCheck -> {
@ -317,6 +323,22 @@ internal class AssignmentAsmGen(private val program: Program,
}
}
internal fun assignPrefixedExpressionToArrayElt(assign: AsmAssignment) {
// array[x] = -value ... use a tempvar then store that back into the array.
require(assign.source.expression is PrefixExpression)
val tempvar = asmgen.getTempVarName(assign.target.datatype).joinToString(".")
val assignToTempvar = AsmAssignment(assign.source,
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, assign.target.datatype, assign.target.scope, variableAsmName=tempvar, origAstTarget = assign.target.origAstTarget),
false, program.memsizer, assign.position)
asmgen.translateNormalAssignment(assignToTempvar)
when(assign.target.datatype) {
in ByteDatatypes -> assignVariableByte(assign.target, tempvar)
in WordDatatypes -> assignVariableWord(assign.target, tempvar)
DataType.FLOAT -> assignVariableFloat(assign.target, tempvar)
else -> throw AssemblyError("weird dt")
}
}
private fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
when(target.datatype) {
in ByteDatatypes -> {

View File

@ -21,13 +21,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when (val value = assign.source.expression!!) {
is PrefixExpression -> {
// A = -A , A = +A, A = ~A, A = not A
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
val itype = value.inferType(program)
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(target, type)
"~" -> inplaceInvert(target, type)
"-" -> inplaceNegate(assign)
"~" -> inplaceInvert(assign)
else -> throw AssemblyError("invalid prefix operator")
}
}
@ -1796,8 +1793,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
when (dt) {
internal fun inplaceInvert(assign: AsmAssignment) {
val target = assign.target
when (assign.target.datatype) {
DataType.UBYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1840,7 +1838,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
else -> throw AssemblyError("no asm gen for in-place invert ubyte for ${target.kind}")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.UWORD -> {
@ -1864,15 +1863,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
else -> throw AssemblyError("no asm gen for in-place invert uword for ${target.kind}")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
else -> throw AssemblyError("invert of invalid type")
}
}
internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
when (dt) {
internal fun inplaceNegate(assign: AsmAssignment) {
val target = assign.target
when (assign.target.datatype) {
DataType.BYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1896,9 +1897,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else -> throw AssemblyError("invalid reg dt for byte negate")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't in-place negate")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
else -> throw AssemblyError("no asm gen for in-place negate byte")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.WORD -> {
@ -1955,8 +1957,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else -> throw AssemblyError("invalid reg dt for word neg")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
else -> throw AssemblyError("no asm gen for in-place negate word")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.FLOAT -> {
@ -1970,10 +1974,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
""")
}
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target for float negation")
}
}
else -> throw AssemblyError("negate of invalid type $dt")
else -> throw AssemblyError("negate of invalid type")
}
}

View File

@ -242,7 +242,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
} else {
val indexReg = codeGen.registers.nextFree()
result += loadIndexReg(array, itemsize, indexReg)
result += IRCodeChunk(null, array.position, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable) }
result += IRCodeChunk(null, array.position, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = indexReg, fpReg1 = resultFpRegister, labelSymbol = variable) }
}
} else {
if(fixedIndex!=null) {

View File

@ -71,7 +71,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
translate(expr, resultRegister, resultFpRegister)
}
is PtPrefix -> {
translate(expr, resultRegister)
translate(expr, resultRegister, resultFpRegister)
}
is PtArrayIndexer -> {
translate(expr, resultRegister, resultFpRegister)
@ -162,13 +162,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return result
}
private fun translate(expr: PtPrefix, resultRegister: Int): IRCodeChunks {
private fun translate(expr: PtPrefix, resultRegister: Int, resultFpRegister: Int): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
result += translateExpression(expr.value, resultRegister, -1)
result += translateExpression(expr.value, resultRegister, resultFpRegister)
val vmDt = codeGen.irType(expr.type)
when(expr.operator) {
"+" -> { }
"-" -> addInstr(result, IRInstruction(Opcode.NEG, vmDt, reg1 = resultRegister), null, expr.position)
"-" -> {
if(vmDt==IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.NEG, vmDt, fpReg1 = resultFpRegister), null, expr.position)
else
addInstr(result, IRInstruction(Opcode.NEG, vmDt, reg1 = resultRegister), null, expr.position)
}
"~" -> {
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = resultRegister, value = mask), null, expr.position)

View File

@ -20,7 +20,9 @@ import kotlin.math.pow
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
class ExpressionSimplifier(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
class ExpressionSimplifier(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
@ -586,11 +588,13 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral(targetDt, 0.0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral(targetDt, 0.0, expr.position)
}
else if(amount==8) {
@ -625,6 +629,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
when (idt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> {
if (amount >= 8) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral.optimalInteger(0, expr.position)
}
}
@ -636,6 +641,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
}
DataType.UWORD -> {
if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral.optimalInteger(0, expr.position)
}
else if(amount==8) {

View File

@ -60,8 +60,8 @@ fun Program.inlineSubroutines(): Int {
return inliner.applyModifications()
}
fun Program.simplifyExpressions(target: ICompilationTarget) : Int {
val opti = ExpressionSimplifier(this, target)
fun Program.simplifyExpressions(errors: IErrorReporter, target: ICompilationTarget) : Int {
val opti = ExpressionSimplifier(this, errors, target)
opti.visit(this)
return opti.applyModifications()
}

View File

@ -97,11 +97,13 @@ class StatementOptimizer(private val program: Program,
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
errors.warn("condition is always true", ifElse.condition.position)
if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always true", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
} else {
// always false -> keep only else-part
errors.warn("condition is always false", ifElse.condition.position)
if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always false", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
}
}

View File

@ -37,14 +37,19 @@ cx16diskio {
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command VLOAD "filename",device,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file has to have the usual 2 byte header (which will be skipped)
%asm {{
; -- load a file into video ram
clc
internal_vload:
phx
pha
tya
tax
lda #1
ldy #0
bcc +
ldy #%00000010 ; headerless load mode
bne ++
+ ldy #0 ; normal load mode
+ lda #1
jsr c64.SETLFS
lda cx16.r0
ldy cx16.r0+1
@ -71,6 +76,15 @@ cx16diskio {
}}
}
asmsub vload_raw(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command BVLOAD "filename",device,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file is read fully including the first two bytes.
%asm {{
sec
jmp vload.internal_vload
}}
}
; replacement function that makes use of fast block read capability of the X16
; use this in place of regular diskio.f_read()
@ -97,7 +111,7 @@ cx16diskio {
size = 255
if num_bytes<size
size = num_bytes
size = cx16.macptr(lsb(size), bufferpointer)
size = cx16.macptr(lsb(size), bufferpointer, false)
if_cs
goto byte_read_loop ; macptr block read not supported, do fallback loop
diskio.list_blocks += size

View File

@ -363,7 +363,7 @@ romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
romsub $fecc = monitor() clobbers(A,X,Y)
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY) clobbers(A) -> ubyte @Pc, uword @XY
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()

View File

@ -361,7 +361,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
remover.applyModifications()
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = program.simplifyExpressions(compTarget)
val optsDone1 = program.simplifyExpressions(errors, compTarget)
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
val optsDone4 = program.inlineSubroutines()

View File

@ -315,8 +315,8 @@ internal class AstChecker(private val program: Program,
err("parameter '${param.first.name}' should be (u)word (an address) or str")
}
else if(param.second.statusflag!=null) {
if (param.first.type != DataType.UBYTE)
err("parameter '${param.first.name}' should be ubyte")
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BOOL)
err("parameter '${param.first.name}' should be bool or ubyte")
}
}
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
@ -330,8 +330,8 @@ internal class AstChecker(private val program: Program,
err("return type #${index + 1} should be (u)word/address")
}
else if(pair.second.statusflag!=null) {
if (pair.first != DataType.UBYTE)
err("return type #${index + 1} should be ubyte")
if (pair.first != DataType.UBYTE && pair.first != DataType.BOOL)
err("return type #${index + 1} should be bool or ubyte")
}
}

View File

@ -18,9 +18,8 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(parent !is VarDecl) {
// TODO move this / remove this, and make the codegen better instead.
// If the expression is pointervar[idx] where pointervar is uword and not a real array,
// replace it by a @(pointervar+idx) expression.
if(options.compTarget.name == VMTarget.NAME)
return noModifications // vm codegen deals correctly with all cases
// Don't replace the initializer value in a vardecl - this will be moved to a separate
// assignment statement soon in after(VarDecl)
return replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression, parent)
@ -29,15 +28,16 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
}
private fun replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(options.compTarget.name==VMTarget.NAME)
return noModifications // vm codegen deals correctly with all cases
// note: The CodeDesugarer already does something similar, but that is meant ONLY to take
// into account the case where the index value is a word type.
// The replacement here is to fix missing cases in the 6502 codegen.
// TODO make the 6502 codegen better so this work around can be removed
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
if(parent is AssignTarget) {
val assignment = parent.parent as? Assignment
if(assignment?.value is NumericLiteral || assignment?.value is IdentifierReference) {
// ONLY for a constant assignment, or direct variable assignment, the codegen contains correct optimized code.
// the codegen contains correct optimized code ONLY for a constant assignment, or direct variable assignment.
return noModifications
}
// Other cases aren't covered correctly by the 6502 codegen, and there are a LOT of cases.

View File

@ -129,4 +129,18 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator=="<<" || expr.operator==">>") {
val shifts = expr.right.constValue(program)
if(shifts!=null) {
val dt = expr.left.inferType(program)
if(dt.istype(DataType.UBYTE) && shifts.number>=8.0)
errors.warn("shift always results in 0", expr.position)
if(dt.istype(DataType.UWORD) && shifts.number>=16.0)
errors.warn("shift always results in 0", expr.position)
}
}
return noModifications
}
}

View File

@ -10,8 +10,7 @@ import prog8.code.core.IErrorReporter
import prog8.code.core.Position
internal class CodeDesugarer(val program: Program,
private val errors: IErrorReporter) : AstWalker() {
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
// Several changes have already been done by the StatementReorderer !
@ -23,6 +22,7 @@ internal class CodeDesugarer(val program: Program,
// - replace while and do-until loops by just jumps.
// - replace peek() and poke() by direct memory accesses.
// - repeat-forever loops replaced by label+jump.
// - pointer[word] replaced by @(pointer+word)
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
@ -135,4 +135,32 @@ _after:
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
// replace pointervar[word] by @(pointervar+word) to avoid the
// "array indexing is limited to byte size 0..255" error for pointervariables.
val indexExpr = arrayIndexedExpression.indexer.indexExpr
val indexerDt = indexExpr.inferType(program)
if(indexerDt.isWords) {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)!!
if(arrayVar.datatype==DataType.UWORD) {
val add: Expression =
if(indexExpr.constValue(program)?.number==0.0)
arrayIndexedExpression.arrayvar.copy()
else
BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", indexExpr, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// assignment to array
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
// read from array
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
}
}
}
return noModifications
}
}

View File

@ -1,13 +1,11 @@
package prog8.compiler.astprocessing
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.compiler.BuiltinFunctions
internal class StatementReorderer(val program: Program,
val errors: IErrorReporter,
@ -195,54 +193,6 @@ internal class StatementReorderer(val program: Program,
&& maySwapOperandOrder(expr))
return listOf(IAstModification.SwapOperands(expr))
// when using a simple bit shift and assigning it to a variable of a different type,
// try to make the bit shifting 'wide enough' to fall into the variable's type.
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
if(expr.operator=="<<" || expr.operator==">>") {
val leftDt = expr.left.inferType(program)
when (parent) {
is Assignment -> {
val targetDt = parent.target.inferType(program)
if(leftDt != targetDt) {
val cast = TypecastExpression(expr.left, targetDt.getOr(DataType.UNDEFINED), true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is VarDecl -> {
if(leftDt isnot parent.datatype) {
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is IFunctionCall -> {
val argnum = parent.args.indexOf(expr)
when (val callee = parent.target.targetStatement(program)) {
is Subroutine -> {
val paramType = callee.parameters[argnum].type
if(leftDt isAssignableTo paramType) {
val (replaced, cast) = expr.left.typecastTo(paramType, leftDt.getOr(DataType.UNDEFINED), true)
if(replaced)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(callee.name)
val paramTypes = func.parameters[argnum].possibleDatatypes
for(type in paramTypes) {
if(leftDt isAssignableTo type) {
val (replaced, cast) = expr.left.typecastTo(type, leftDt.getOr(DataType.UNDEFINED), true)
if(replaced)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
}
else -> throw FatalAstException("weird callee")
}
}
else -> return noModifications
}
}
return noModifications
}

View File

@ -100,7 +100,6 @@ main {
}
}"""
val result = compileText(C64Target(), optimize=false, src, writeAssembly=true)!!
printProgram(result.program)
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 6
val name1 = stmts[0] as VarDecl
@ -114,5 +113,22 @@ main {
(name2.targetVarDecl(result.program)!!.value as StringLiteral).value shouldBe "xx1xx2"
(rept2.targetVarDecl(result.program)!!.value as StringLiteral).value shouldBe "xyzxyzxyzxyz"
}
test("pointervariable indexing allowed with >255") {
val src="""
main {
sub start() {
uword pointer = ${'$'}2000
@(pointer+${'$'}1000) = 123
ubyte @shared ub = @(pointer+${'$'}1000)
pointer[${'$'}1000] = 99
ub = pointer[${'$'}1000]
uword index = ${'$'}1000
pointer[index] = 55
ub = pointer[index]
}
}"""
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
})

View File

@ -0,0 +1,93 @@
package prog8tests.codegeneration
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldNotBe
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8tests.helpers.compileText
class TestArrayInplaceAssign: FunSpec({
test("assign prefix var to array should compile fine and is not split into inplace array modification") {
val text = """
main {
sub start() {
byte[5] array
byte bb
array[1] = -bb
}
}
"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("array in-place negation (integer types)") {
val text = """
main {
byte[10] foo
ubyte[10] foou
word[10] foow
uword[10] foowu
sub start() {
foo[1] = 42
foo[1] = -foo[1]
foow[1] = 4242
foow[1] = -foow[1]
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("array in-place negation (float type) vm target") {
val text = """
%import floats
main {
float[10] flt
sub start() {
flt[1] = 42.42
flt[1] = -flt[1]
}
}"""
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
// TODO implement this in 6502 codegen and re-enable test
xtest("array in-place negation (float type) 6502 target") {
val text = """
%import floats
main {
float[10] flt
sub start() {
flt[1] = 42.42
flt[1] = -flt[1]
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("array in-place invert") {
val text = """
main {
ubyte[10] foo
uword[10] foow
sub start() {
foo[1] = 42
foo[1] = ~foo[1]
foow[1] = 4242
foow[1] = ~foow[1]
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
})

View File

@ -68,7 +68,7 @@ It contains all of the program's code and data and has a certain file format tha
allows it to be loaded directly on the target system. Prog8 currently has no built-in
support for programs that exceed 64 Kb of memory, nor for multi-part loaders.
For the Commodore-64, most programs will have a tiny BASIC launcher that does a SYS into the generated machine code.
For the Commodore 64, most programs will have a tiny BASIC launcher that does a SYS into the generated machine code.
This way the user can load it as any other program and simply RUN it to start. (This is a regular ".prg" program).
Prog8 can create those, but it is also possible to output plain binary programs
that can be loaded into memory anywhere.
@ -95,7 +95,7 @@ For normal use the compiler can be invoked with the command:
By default, assembly code is generated and written to ``sourcefile.asm``.
It is then (automatically) fed to the `64tass <https://sourceforge.net/projects/tass64/>`_ assembler tool
that creastes the final runnable program.
that creates the final runnable program.
Command line options
@ -138,7 +138,7 @@ One or more .p8 module files
``-noreinit``
Don't create code to reinitialize the global (block level) variables on every run of the program.
Also means that all such variables are no longer placed in the zero page.
Also means that all such variables are no longer placed in the zeropage.
Sometimes the program will be a lot shorter when using this, but sometimes the opposite happens.
When using this option, it is no longer be possible to run the program correctly more than once!
*Experimental feature*: still has some problems!
@ -182,7 +182,7 @@ One or more .p8 module files
``-esa <address>``
Override the base address of the evaluation stack. Has to be page-aligned.
You can specify an integer or hexadecimal address.
When not compiling for the CommanderX16 target, the location of the 16 virtual registers cx16.r0..r15
When not compiling for the Commander X16 target, the location of the 16 virtual registers cx16.r0..r15
is changed accordingly (to keep them in the same memory space as the evaluation stack).
@ -210,26 +210,26 @@ the ``srcdirs`` command line option.
.. _debugging:
Debugging (with Vice)
Debugging (with VICE)
---------------------
There's support for using the monitor and debugging capabilities of the rather excellent
`Vice emulator <http://vice-emu.sourceforge.net/>`_.
`VICE emulator <http://vice-emu.sourceforge.net/>`_.
The ``%breakpoint`` directive (see :ref:`directives`) in the source code instructs the compiler to put
a *breakpoint* at that position. Some systems use a BRK instruction for this, but
this will usually halt the machine altogether instead of just suspending execution.
Prog8 issues a NOP instruction instead and creates a 'virtual' breakpoint at this position.
All breakpoints are then written to a file called "programname.vice-mon-list",
which is meant to be used by the Vice emulator.
It contains a series of commands for Vice's monitor, including source labels and the breakpoint settings.
which is meant to be used by the VICE emulator.
It contains a series of commands for VICE's monitor, including source labels and the breakpoint settings.
If you use the emulator autostart feature of the compiler, it will take care of this for you.
If you launch Vice manually, you'll have to use a command line option to load this file:
If you launch VICE manually, you'll have to use a command line option to load this file:
``$ x64 -moncommands programname.vice-mon-list``
Vice will then use the label names in memory disassembly, and will activate any breakpoints as well.
If your running program hits one of the breakpoints, Vice will halt execution and drop you into the monitor.
VICE will then use the label names in memory disassembly, and will activate any breakpoints as well.
If your running program hits one of the breakpoints, VICE will halt execution and drop you into the monitor.
Troubleshooting

View File

@ -15,7 +15,7 @@ This is a compiled programming language targeting the 8-bit
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ /
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ microprocessors.
This CPU is from the late 1970's and early 1980's and was used in many home computers from that era,
such as the `Commodore-64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
such as the `Commodore 64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
The language aims to provide many conveniences over raw assembly code (even when using a macro assembler),
while still being low level enough to create high performance programs.
You can compile programs for various machines with this CPU:
@ -69,12 +69,12 @@ Language features
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Floating point math also supported if the target system provides floating point library routines (C64 and Cx16 both do).
- Strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest petscii equivalents.
- Strings can contain escaped characters but also many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest PETSCII equivalents.
- High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting.
- Many built-in functions, such as ``sin``, ``cos``, ``abs``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``sort`` and ``reverse``
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the other machines.
- If you only use standard kernal and core prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compilation target flag)!
- If you only use standard Kernal and core prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compilation target flag)!
Code example
@ -83,6 +83,7 @@ Code example
Here is a hello world program::
%import textio
%zeropage basicsafe
main {
sub start() {
@ -139,11 +140,11 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
}
when compiled an ran on a C-64 you get this:
when compiled an ran on a C64 you get this:
.. image:: _static/primes_example.png
:align: center
:alt: result when run on C-64
:alt: result when run on C64
when the exact same program is compiled for the Commander X16 target, and run on the emulator, you get this:
@ -169,19 +170,23 @@ Required additional tools
It's very easy to compile yourself.
A recent precompiled .exe (only for Windows) can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
*You need at least version 1.55.2257 of this assembler to correctly use the breakpoints feature.*
It's possible to use older versions, but it is very likely that the automatic Vice breakpoints won't work with them.
It's possible to use older versions, but it is very likely that the automatic VICE breakpoints won't work with them.
A **Java runtime (jre or jdk), version 11 or newer** is required to run the prog8 compiler itself.
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK in their packages repository instead.
For Windows it's possible to get that as well; check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
For MacOS you can use the Homebrew system to install a recent version of OpenJDK.
Finally: an **emulator** (or a real machine ofcourse) to test and run your programs on.
In C64 mode, the compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
If you're targeting the CommanderX16 instead, there's a choice of the official `x16emu <https://github.com/commanderx16/x16-emulator>`_
Finally: an **emulator** (or a real machine of course) to test and run your programs on.
In C64 mode, the compiler assumes the presence of the `VICE emulator <http://vice-emu.sourceforge.net/>`_.
If you're targeting the Commander X16 instead, there's a choice of the official `x16emu <https://github.com/commanderx16/x16-emulator>`_
and the unofficial `box16 <https://github.com/indigodarkwolf/box16>`_ (you can select which one you want to launch
using the ``-emu`` or ``-emu2`` command line options)
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
Look in the `syntax-files <https://github.com/irmen/prog8/tree/master/syntax-files>`_ directory in the github repository to find them.
.. toctree::
:maxdepth: 2

View File

@ -29,7 +29,7 @@ of these library modules automatically as required.
syslib
------
The "system library" for your target machine. It contains many system-specific definitions such
as ROM/kernal subroutine definitions, memory location constants, and utility subroutines.
as ROM/Kernal subroutine definitions, memory location constants, and utility subroutines.
Depending on the compilation target, other routines may also be available in here specific to that target.
Best is to check the source code of the correct syslib module.
@ -45,8 +45,8 @@ sys (part of syslib)
system when the program is running.
The following return values are currently defined:
- 16 = compiled for CommanderX16 with 65C02 CPU
- 64 = compiled for Commodore-64 with 6502/6510 CPU
- 16 = compiled for Commander X16 with 65C02 CPU
- 64 = compiled for Commodore 64 with 6502/6510 CPU
``exit(returncode)``
Immediately stops the program and exits it, with the returncode in the A register.
@ -105,7 +105,7 @@ sys (part of syslib)
note: a more accurate way to do this is by using a raster irq handler instead.
``reset_system()``
Soft-reset the system back to initial power-on Basic prompt.
Soft-reset the system back to initial power-on BASIC prompt.
(called automatically by Prog8 when the main subroutine returns and the program is not using basicsafe zeropage option)
@ -140,7 +140,7 @@ Provides several routines that deal with disk drive I/O, such as:
- delete and rename files on the disk
- send arbitrary CbmDos command to disk drive
On the Commander X16 it tries to use that machine's fast kernal loading routines if possible.
On the Commander X16 it tries to use that machine's fast Kernal loading routines if possible.
string
@ -150,7 +150,7 @@ Provides string manipulation routines.
``length(str) -> ubyte length``
Number of bytes in the string. This value is determined during runtime and counts upto
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
Don't confuse this with ``len`` and ``sizeof``
Don't confuse this with ``len`` and ``sizeof``!
``left(source, length, target)``
Copies the left side of the source string of the given length to target string.
@ -176,8 +176,8 @@ Provides string manipulation routines.
and the index in the string. Or 0+carry bit clear if the character was not found.
``compare(string1, string2) -> ubyte result``
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
Note that you can also directly compare strings and string values with eachother
Returns -1, 0 or 1 depending on whether string1 sorts before, equal or after string2.
Note that you can also directly compare strings and string values with each other
using ``==``, ``<`` etcetera (it will use string.compare for you under water automatically).
``copy(from, to) -> ubyte length``
@ -186,10 +186,10 @@ Provides string manipulation routines.
but this function is useful if you're dealing with addresses for instance.
``lower(string)``
Lowercases the petscii-string in place.
Lowercases the PETSCII-string in place.
``upper(string)``
Uppercases the petscii-string in place.
Uppercases the PETSCII-string in place.
``lowerchar(char)``
Returns lowercased character.
@ -210,7 +210,7 @@ Provides string manipulation routines.
floats
------
Provides definitions for the ROM/kernal subroutines and utility routines dealing with floating
Provides definitions for the ROM/Kernal subroutines and utility routines dealing with floating
point variables. This includes ``print_f``, the routine used to print floating point numbers,
``fabs`` to get the absolute value of a floating point number, and a dozen or so floating point
math routines.
@ -283,7 +283,7 @@ Use the ``gfx2`` library if you want full-screen graphics or non-monochrome draw
math
----
Low level integer math routines (which you usually don't have to bother with directly, but they are used by the compiler internally).
Low-level integer math routines (which you usually don't have to bother with directly, but they are used by the compiler internally).
Pseudo-Random number generators (byte and word).
Various 8-bit integer trig functions that use lookup tables to quickly calculate sine and cosines.
Usually a custom lookup table is the way to go if your application needs these,
@ -337,7 +337,7 @@ and allows you to print it anywhere on the screen.
prog8_lib
---------
Low level language support. You should not normally have to bother with this directly.
Low-level language support. You should not normally have to bother with this directly.
The compiler needs it for various built-in system routines.
@ -356,15 +356,16 @@ Full-screen multicolor bitmap graphics routines, available on the Cx16 machine o
palette (cx16 only)
--------------------
Available for the Cx16 target. Various routines to set the display color palette.
There are also a few better looking Commodore-64 color palettes available here,
There are also a few better looking Commodore 64 color palettes available here,
because the Commander X16's default colors for this (the first 16 colors) are too saturated
and are quite different than how they looked on a VIC-II chip in a C-64.
and are quite different than how they looked on a VIC-II chip in a C64.
cx16diskio (cx16 only)
-----------------------
Available for the Cx16 target. Contains extensions to the load and load_raw routines from the regular
diskio module, to deal with loading of potentially large files in to banked ram (HiRam).
Routines to directly load data into video ram are also present (vload and vload_raw).
Also contains a helper function to calculate the file size of a loaded file (although that is truncated
to 16 bits, 64Kb)

View File

@ -17,15 +17,15 @@ CPU
Memory Map
----------
Zero page
zeropage
=========
#. *Absolute requirement:* Provide three times 2 consecutive bytes (i.e. three 16-bit pointers) in the Zero page that are free to use at all times.
#. Provide list of any additional free Zero page locations for a normal running system (basic + kernal enabled)
#. Provide list of any additional free Zero page locations when basic is off, but floating point routines should still work
#. Provide list of any additional free Zero page locations when only the kernal remains enabled
#. *Absolute requirement:* Provide three times 2 consecutive bytes (i.e. three 16-bit pointers) in the zeropage that are free to use at all times.
#. Provide list of any additional free zeropage locations for a normal running system (BASIC + Kernal enabled)
#. Provide list of any additional free zeropage locations when BASIC is off, but floating point routines should still work
#. Provide list of any additional free zeropage locations when only the Kernal remains enabled
Only the three 16-bit pointers are absolutely required to be able to use prog8 on the system.
But more known available Zero page locations mean smaller and faster programs.
But more known available zeropage locations mean smaller and faster programs.
RAM, ROM, I/O
@ -40,7 +40,7 @@ RAM, ROM, I/O
Character encodings
-------------------
#. if not Petscii or CBM screencodes: provide the primary character encoding table that the system uses (i.e. how is text represented in memory)
#. if not PETSCII or CBM screencodes: provide the primary character encoding table that the system uses (i.e. how is text represented in memory)
#. provide alternate character encodings (if any)
#. what are the system's standard character screen dimensions?
#. is there a screen character matrix directly accessible in Ram? What's it address? Same for color attributes if any.
@ -50,7 +50,7 @@ ROM routines
------------
#. provide a list of the core ROM routines on the system, with names, addresses, and call signatures.
Ideally there are at least some routines to manipulate the screen and get some user input(clear, print text, print numbers, input strings from the keyboard)
Ideally there are at least some routines to manipulate the screen and get some user input (clear, print text, print numbers, input strings from the keyboard)
Routines to initialize the system to a sane state and to do a warm reset are useful too.
The more the merrier.
@ -74,7 +74,7 @@ the new target system.
There are several other support libraries that you may want to port (``diskio``, ``graphics`` to name a few).
Also ofcourse if there are unique things available on the new target system, don't hesitate to provide
Also of course if there are unique things available on the new target system, don't hesitate to provide
extensions to the ``syslib`` or perhaps a new special custom library altogether.

View File

@ -107,10 +107,6 @@ The name of a block must be unique in your entire program.
Be careful when importing other modules; blocks in your own code cannot have
the same name as a block defined in an imported module or library.
If you omit both the name and address, the entire block is *ignored* by the compiler (and a warning is displayed).
This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you
want to work on later, because the contents of the ignored block are not fully parsed either.
The address can be used to place a block at a specific location in memory.
Usually it is omitted, and the compiler will automatically choose the location (usually immediately after
the previous block in memory).
@ -201,12 +197,12 @@ Values will usually be part of an expression or assignment statement::
*putting a variable in zeropage:*
If you add the ``@zp`` tag to the variable declaration, the compiler will prioritize this variable
when selecting variables to put into zero page (but no guarantees). If there are enough free locations in the zeropage,
when selecting variables to put into zeropage (but no guarantees). If there are enough free locations in the zeropage,
it will try to fill it with as much other variables as possible (before they will be put in regular memory pages).
Use ``@requirezp`` tag to *force* the variable into zeropage, but if there is no more free space the compilation will fail.
It's possible to put strings, arrays and floats into zeropage too, however because Zp space is really scarce
this is not advised as they will eat up the available space very quickly. It's best to only put byte or word
variables in Zeropage.
variables in zeropage.
Example::
@ -230,7 +226,7 @@ Integers
Integers are 8 or 16 bit numbers and can be written in normal decimal notation,
in hexadecimal and in binary notation.
A single character in single quotes such as ``'a'`` is translated into a byte integer,
which is the Petscii value for that character.
which is the PETSCII value for that character.
Unsigned integers are in the range 0-255 for unsigned byte types, and 0-65535 for unsigned word types.
The signed integers integers are in the range -128..127 for bytes,
@ -260,9 +256,9 @@ Floating point numbers
^^^^^^^^^^^^^^^^^^^^^^
Floats are stored in the 5-byte 'MFLPT' format that is used on CBM machines,
and currently all floating point operations are specific to the Commodore-64.
This is because routines in the C-64 BASIC and KERNAL ROMs are used for that.
So floating point operations will only work if the C-64 BASIC ROM (and KERNAL ROM)
and currently all floating point operations are specific to the Commodore 64.
This is because routines in the C64 BASIC and Kernal ROMs are used for that.
So floating point operations will only work if the C64 BASIC ROM (and Kernal ROM)
are banked in.
Also your code needs to import the ``floats`` library to enable floating point support
@ -286,7 +282,7 @@ Here are some examples of arrays::
byte[10] array ; array of 10 bytes, initially set to 0
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
ubyte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to uword)
uword[] others = [names, array] ; array of pointers/addresses to other arrays
@ -341,7 +337,7 @@ Strings (without encoding prefix) will be encoded (translated from ASCII/UTF-8)
Alternative encodings can be specified with a ``encodingname:`` prefix to the string or character literal.
The following encodings are currently recognised:
- ``petscii`` Petscii, the default encoding on CBM machines (c64, c128, cx16)
- ``petscii`` PETSCII, the default encoding on CBM machines (c64, c128, cx16)
- ``sc`` CBM-screencodes aka 'poke' codes (c64, c128, cx16)
- ``iso`` iso-8859-15 text (supported on cx16)
@ -353,7 +349,7 @@ It can be correctly displayed on the screen only if a iso-8859-15 charset has be
You can concatenate two string literals using '+', which can be useful to
split long strings over separate lines. But remember that the length
of the total string still cannot exceed 255 characaters.
of the total string still cannot exceed 255 characters.
A string literal can also be repeated a given number of times using '*', where the repeat number must be a constant value.
And a new string value can be assigned to another string, but no bounds check is done
so be sure the destination string is large enough to contain the new value (it is overwritten in memory)::
@ -370,7 +366,7 @@ as newlines, quote characters themselves, and so on. The ones used most often ar
``\\``, ``\"``, ``\n``, ``\r``. For a detailed description of all of them and what they mean,
read the syntax reference on strings.
Using the ``in`` operator you can easily check if a characater is present in a string,
Using the ``in`` operator you can easily check if a character is present in a string,
example: ``if '@' in email_address {....}`` (however this gives no clue about the location
in the string where the character is present, if you need that, use the ``string.find()``
library function instead)
@ -412,10 +408,10 @@ for the constant itself). This is only valid for the simple numeric types (byte,
When using ``&`` (the address-of operator but now applied to a datatype), the variable will point to specific location in memory,
rather than being newly allocated. The initial value (mandatory) must be a valid
memory address. Reading the variable will read the given data type from the
address you specified, and setting the varible will directly modify that memory location(s)::
address you specified, and setting the variable will directly modify that memory location(s)::
const byte max_age = 2000 - 1974 ; max_age will be the constant value 26
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
&word SCREENCOLORS = $d020 ; a 16-bit word at the address $d020-$d021
.. _pointervars_programming:
@ -452,9 +448,9 @@ Many type conversions are possible by just writing ``as <type>`` at the end of a
f = 56.777
ub = f as ubyte ; ub will be 56
Sometimes it is a straight 'type cast' where the value is simply interpreted as being of the other type,
sometimes an actual value conversion is done to convert it into the targe type.
Try to avoid type conversions as much as possible.
Sometimes it is a straight reinterpretation of the given value as being of the other type,
sometimes an actual value conversion is done to convert it into the other type.
Try to avoid those type conversions as much as possible.
Initial values across multiple runs of the program
@ -511,7 +507,7 @@ Conditional Execution
if statements
^^^^^^^^^^^^^
Conditional execution means that the flow of execution changes based on certiain conditions,
Conditional execution means that the flow of execution changes based on certain conditions,
rather than having fixed gotos or subroutine calls::
if xx==5 {
@ -567,7 +563,7 @@ So ``if_cc goto target`` will directly translate into the single CPU instruction
.. caution::
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
that the status register (still) contains the correct status bits.
This is not always the case after a fuction call or other operations!
This is not always the case after a function call or other operations!
If in doubt, check the generated assembly code!
.. note::
@ -643,7 +639,7 @@ If possible, the expression is parsed and evaluated by the compiler itself at co
Expressions that cannot be compile-time evaluated will result in code that calculates them at runtime.
Expressions can contain procedure and function calls.
There are various built-in functions such as sin(), cos() that can be used in expressions (see :ref:`builtinfunctions`).
You can also reference idendifiers defined elsewhere in your code.
You can also reference identifiers defined elsewhere in your code.
Read the :ref:`syntaxreference` chapter for all details on the available operators and kinds of expressions you can write.
@ -680,7 +676,7 @@ Logical expressions are expressions that calculate a boolean result: true or fal
logical expressions will compile more efficiently than when you're using regular integer type operands
(because these have to be converted to 0 or 1 every time)
You can use parentheses to group parts of an expresion to change the precedence.
You can use parentheses to group parts of an expression to change the precedence.
Usually the normal precedence rules apply (``*`` goes before ``+`` etc.) but subexpressions
within parentheses will be evaluated first. So ``(4 + 8) * 2`` is 24 and not 20,
and ``(true or false) and false`` is false instead of true.
@ -845,18 +841,18 @@ pokemon(address, value)
Doesn't have anything to do with a certain video game.
push(value)
pushes a byte value on the CPU hardware stack. Lowlevel function that should normally not be used.
pushes a byte value on the CPU hardware stack. Low-level function that should normally not be used.
pushw(value)
pushes a 16-bit word value on the CPU hardware stack. Lowlevel function that should normally not be used.
pushes a 16-bit word value on the CPU hardware stack. Low-level function that should normally not be used.
pop(variable)
pops a byte value off the CPU hardware stack into the given variable. Only variables can be used.
Lowlevel function that should normally not be used.
Low-level function that should normally not be used.
popw(value)
pops a 16-bit word value off the CPU hardware stack into the given variable. Only variables can be used.
Lowlevel function that should normally not be used.
Low-level function that should normally not be used.
rol(x)
Rotate the bits in x (byte or word) one position to the left.
@ -889,7 +885,7 @@ ror2(x)
sizeof(name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
For an 10 element array of floats, it is 50 (on the C64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
memory(name, size, alignment)
@ -907,7 +903,7 @@ memory(name, size, alignment)
You can only treat it as a pointer or use it in inline assembly.
callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
Calls an assembly routine in another ram-bank on the CommanderX16 (using the ``jsrfar`` routine)
Calls an assembly routine in another ram-bank on the Commander X16 (using the ``jsrfar`` routine)
The banked RAM is located in the address range $A000-$BFFF (8 kilobyte), but you can specify
any address in system ram (why this can be useful is explained at the end of this paragraph)
The third argument can be used to designate the memory address
@ -921,7 +917,7 @@ callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 target for
This is not very efficient though, so maybe you should write a small piece of inline assembly for this instead.
callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
Calls an assembly routine in another rom-bank on the CommanderX16
Calls an assembly routine in another rom-bank on the Commander X16
The banked ROM is located in the address range $C000-$FFFF (16 kilobyte).
There are 32 banks (0 to 31).
The third argument can be used to designate the memory address
@ -935,7 +931,7 @@ callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for
syscall(callnr), syscall1(callnr, arg), syscall2(callnr, arg1, arg2), syscall3(callnr, arg1, arg2, arg3)
Functions for doing a system call on targets that support this. Currently no actual target
uses this though except, possibly, the experimental code generation target!
The regular 6502 based compiler targets just use a subroutine call to asmsub kernal routines at
The regular 6502 based compiler targets just use a subroutine call to asmsub Kernal routines at
specific memory locations. So these builtin function calls are not useful yet except for
experimentation in new code generation targets.

View File

@ -8,7 +8,7 @@ Module file
-----------
This is a file with the ``.p8`` suffix, containing *directives* and *code blocks*, described below.
The file is a text file wich can also contain:
The file is a text file which can also contain:
Lines, whitespace, indentation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -48,28 +48,28 @@ Directives
Global setting, selects the program launcher stub to use.
Only relevant when using the ``prg`` output type. Defaults to ``basic``.
- type ``basic`` : add a tiny C64 BASIC program, whith a SYS statement calling into the machine code
- type ``basic`` : add a tiny C64 BASIC program, with a SYS statement calling into the machine code
- type ``none`` : no launcher logic is added at all
.. data:: %zeropage <style>
Level: module.
Global setting, select ZeroPage handling style. Defaults to ``kernalsafe``.
Global setting, select zeropage handling style. Defaults to ``kernalsafe``.
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines,
and don't change anything else. This allows full use of KERNAL ROM routines (but not BASIC routines),
and don't change anything else. This allows full use of Kernal ROM routines (but not BASIC routines),
including default IRQs during normal system operation.
It's not possible to return cleanly to BASIC when the program exits. The only choice is
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
- style ``floatsafe`` -- like the previous one but also reserves the addresses that
are required to perform floating point operations (from the BASIC kernal). No clean exit is possible.
are required to perform floating point operations (from the BASIC Kernal). No clean exit is possible.
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
touch change anything else. This allows full use of BASIC and KERNAL ROM routines including default IRQs
touch change anything else. This allows full use of BASIC and Kernal ROM routines including default IRQs
during normal system operation.
When the program exits, it simply returns to the BASIC ready prompt.
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
except the few addresses mentioned above that are used by the system's IRQ routine.
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
Even though the default IRQ routine is still active, it is impossible to use most BASIC and Kernal ROM routines.
This includes many floating point operations and several utility routines that do I/O, such as ``print``.
This option makes programs smaller and faster because even more variables can
be stored in the ZP (which allows for more efficient assembly code).
@ -84,9 +84,9 @@ Directives
16 virtual registers cx16.r0...cx16.r15 from the Commander X16 into the zeropage as well
(but not on the same locations). They are relocated automatically by the compiler.
The other options need those locations for other things so those virtual registers have
to be put into memory elsewhere (outside of the zeropage). Trying to use them as zero page
to be put into memory elsewhere (outside of the zeropage). Trying to use them as zeropage
variables or pointers etc. will be a lot slower in those cases!
On the CommanderX16 the registers are always in zeropage. On other targets, for now, they
On the Commander X16 the registers are always in zeropage. On other targets, for now, they
are always outside of the zeropage.
.. data:: %zpreserved <fromaddress>,<toaddress>
@ -100,8 +100,8 @@ Directives
Level: module.
Global setting, set the program's start memory address. It's usually fixed at ``$0801`` because the
default launcher type is a CBM-basic program. But you have to specify this address yourself when
you don't use a CBM-basic launcher.
default launcher type is a CBM-BASIC program. But you have to specify this address yourself when
you don't use a CBM-BASIC launcher.
.. data:: %import <name>
@ -119,7 +119,7 @@ Directives
Sets special compiler options.
- ``enable_floats`` (module level) tells the compiler
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
to deal with floating point numbers (by using various subroutines from the Commodore 64 Kernal).
Otherwise, floating point support is not enabled. Normally you don't have to use this yourself as
importing the ``floats`` library is required anyway and that will enable it for you automatically.
- ``no_sysinit`` (module level) which cause the resulting program to *not* include
@ -150,7 +150,7 @@ Directives
The assembler will include the file as raw assembly source text at this point,
prog8 will not process this at all. Symbols defined in the included assembly can not be referenced
from prog8 code. However they can be referenced from other assembly code if properly prefixed.
You can ofcourse use a label in your prog8 code just before the %asminclude directive, and reference
You can of course use a label in your prog8 code just before the %asminclude directive, and reference
that particular label to get to (the start of) the included assembly.
Be careful: you risk symbol redefinitions or duplications if you include a piece of
assembly into a prog8 block that already defines symbols itself.
@ -225,7 +225,7 @@ and after that, a combination of letters, numbers, or underscores. Examples of v
Code blocks
-----------
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
A named block of actual program code. It defines a *scope* (also known as 'namespace') and
can only contain *directives*, *variable declarations*, *subroutines* or *inline assembly*::
<blockname> [<address>] {
@ -270,7 +270,7 @@ Variable declarations
Variables should be declared with their exact type and size so the compiler can allocate storage
for them. You can give them an initial value as well. That value can be a simple literal value,
or an expression. If you don't provide an intial value yourself, zero will be used.
or an expression. If you don't provide an initial value yourself, zero will be used.
You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
when selecting variables to be put into zeropage (but no guarantees). If the ZP is full,
the variable will be allocated in normal memory elsewhere.
@ -295,7 +295,7 @@ Various examples::
byte[5] values = 255 ; initialize with five 255 bytes
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
uword @requirezp zpaddr = $3000 ; we require this variable in Zeropage
uword @requirezp zpaddr = $3000 ; we require this variable in zeropage
word @shared asmvar ; variable is used in assembly code but not elsewhere
@ -329,7 +329,7 @@ type identifier type storage size example var declara
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
``bool[]`` boolean array depends on value ``bool[] myvar = [true, false, true]`` note: consider using bit flags in a byte or word instead to save space
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]``
``str`` string (petscii) varies ``str myvar = "hello."``
``str`` string (PETSCII) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte
=============== ======================= ================= =========================================
@ -342,7 +342,7 @@ value is given, the array size in the declaration can be omitted.
Note that ``%`` is also the remainder operator so be careful: if you want to take the remainder
of something with an operand starting with 1 or 0, you'll have to add a space in between.
**character values:** you can use a single character in quotes like this ``'a'`` for the Petscii byte value of that character.
**character values:** you can use a single character in quotes like this ``'a'`` for the PETSCII byte value of that character.
**``byte`` versus ``word`` values:**
@ -461,7 +461,7 @@ There are several escape sequences available to put special characters into your
- ``\uHHHH`` - a unicode codepoint \u0000 - \uffff (16-bit hexadecimal)
- ``\xHH`` - 8-bit hex value that will be copied verbatim *without encoding*
- String literals can contain many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳".
- String literals can contain many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳".
Characters like ^, _, \\, {, } and | (that have no direct PETSCII counterpart) are still accepted and converted to the closest PETSCII equivalents. (Make sure you save the source file in UTF-8 encoding if you use this.)
@ -505,7 +505,7 @@ logical: ``not`` ``and`` ``or`` ``xor``
the ``bool`` variable type instead, where this conversion doesn't need to occur.
.. note::
Unlike most other programming languages, there is no short-cirquit or McCarthy-evaluation
Unlike most other programming languages, there is no short-circuit or McCarthy evaluation
for the logical ``and`` and ``or`` operators. This means that prog8 currently always evaluates
all operands from these logical expressions, even when one of them already determines the outcome!
@ -580,7 +580,7 @@ Multiple return values
^^^^^^^^^^^^^^^^^^^^^^
Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
(referencing a routine in kernal ROM) can return more than one return value.
(referencing a routine in Kernal ROM) can return more than one return value.
For example a status in the carry bit and a number in A, or a 16-bit value in A/Y registers.
It is not possible to process the results of a call to these kind of routines
directly from the language, because only single value assignments are possible.
@ -662,7 +662,7 @@ flag such as Carry (Pc).
The 'virtual' 16-bit registers from the Commander X16 can also be specified as ``R0`` .. ``R15`` .
This means you don't have to set them up manually before calling a subroutine that takes
one or more parameters in those 'registers'. You can just list the arguments directly.
*This also works on the Commodore-64!* (however they are not as efficient there because they're not in zeropage)
*This also works on the Commodore 64!* (however they are not as efficient there because they're not in zeropage)
In prog8 and assembly code these 'registers' are directly accessible too via
``cx16.r0`` .. ``cx16.r15`` (these are memory mapped uword values),
``cx16.r0s`` .. ``cx16.r15s`` (these are memory mapped word values),
@ -765,7 +765,7 @@ Conditional Execution and Jumps
Unconditional jump: goto
^^^^^^^^^^^^^^^^^^^^^^^^
To jump to another part of the program, you use a ``goto`` statement with an addres or the name
To jump to another part of the program, you use a ``goto`` statement with an address or the name
of a label or subroutine::
goto $c000 ; address
@ -806,7 +806,7 @@ However if <statements> is a block of multiple statements, you'll have to enclos
**Special status register branch form:**
There is a special form of the if-statement that immediately translates into one of the 6502's branching instructions.
It is almost the same as the regular if-statement but it lacks a contional expression part, because the if-statement
It is almost the same as the regular if-statement but it lacks a conditional expression part, because the if-statement
itself defines on what status register bit it should branch on::
if_XX <statements> [else <statements> ]
@ -826,7 +826,7 @@ It can also be one of the four aliases that are easier to read: ``if_z``, ``if_n
.. caution::
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
that the status register (still) contains the correct status bits.
This is not always the case after a fuction call or other operations!
This is not always the case after a function call or other operations!
If in doubt, check the generated assembly code!

View File

@ -20,7 +20,7 @@ Currently these machines can be selected as a compilation target (via the ``-tar
This chapter explains some relevant system details of the c64 and cx16 machines.
.. hint::
If you only use standard kernal and prog8 library routines,
If you only use standard Kernal and prog8 library routines,
it is often possible to compile the *exact same program* for
different machines (just change the compilation target flag)!
@ -39,7 +39,7 @@ This is a hard limit: there is no built-in support for RAM expansions or bank sw
====================== ================== ========
memory area type note
====================== ================== ========
``$00``--``$ff`` ZeroPage contains many sensitive system variables
``$00``--``$ff`` zeropage contains many sensitive system variables
``$100``--``$1ff`` Hardware stack used by the CPU, normally not accessed directly
``$0200``--``$ffff`` Free RAM or ROM free to use memory area, often a mix of RAM and ROM
====================== ================== ========
@ -64,9 +64,9 @@ reserved address in use for
================== =======================
The actual machine will often have many other special addresses as well,
For example, the Commodore-64 has:
For example, the Commodore 64 has:
- ROMs installed in the machine: BASIC, kernal and character roms. Occupying ``$a000``--``$bfff`` and ``$e000``--``$ffff``.
- ROMs installed in the machine: BASIC, Kernal and character roms. Occupying ``$a000``--``$bfff`` and ``$e000``--``$ffff``.
- memory-mapped I/O registers, for the video and sound chips, and the CIA's. Occupying ``$d000``--``$dfff``.
- RAM areas that are used for screen graphics and sprite data: usually at ``$0400``--``$07ff``.
@ -75,18 +75,18 @@ Prog8 programs can access all of those special memory locations but it will have
.. _zeropage:
ZeroPage ("ZP")
Zeropage ("ZP")
---------------
The ZeroPage memory block ``$02``--``$ff`` can be regarded as 254 CPU 'registers', because
The zeropage memory block ``$02``--``$ff`` can be regarded as 254 CPU 'registers', because
they take less clock cycles to access and need fewer instruction bytes than accessing other memory locations outside of the ZP.
Theoretically they can all be used in a program, with the follwoing limitations:
Theoretically they can all be used in a program, with the following limitations:
- several addresses (``$02``, ``$03``, ``$fb - $fc``, ``$fd - $fe``) are reserved for internal use
- most other addresses will already be in use by the machine's operating system or kernal,
- most other addresses will already be in use by the machine's operating system or Kernal,
and overwriting them will probably crash the machine. It is possible to use all of these
yourself, but only if the program takes over the entire system (and seizes control from the regular kernal).
This means it can no longer use (most) BASIC and kernal routines from ROM.
yourself, but only if the program takes over the entire system (and seizes control from the regular Kernal).
This means it can no longer use (most) BASIC and Kernal routines from ROM.
- it's more convenient and safe to let the compiler allocate these addresses for you and just
use symbolic names in the program code.
@ -95,19 +95,19 @@ It will use the free ZP addresses to place its ZP variables in,
until they're all used up. If instructed to output a program that takes over the entire
machine, (almost) all of the ZP addresses are suddenly available and will be used.
**ZeroPage handling is configurable:**
**zeropage handling is configurable:**
There's a global program directive to specify the way the compiler
treats the ZP for the program. The default is to be reasonably restrictive to use the
part of the ZP that is not used by the C64's kernal routines.
It's possible to claim the whole ZP as well (by disabling the operating system or kernal).
If you want, it's also possible to be more restricive and stay clear of the addresses used by BASIC routines too.
part of the ZP that is not used by the C64's Kernal routines.
It's possible to claim the whole ZP as well (by disabling the operating system or Kernal).
If you want, it's also possible to be more restrictive and stay clear of the addresses used by BASIC routines too.
This allows the program to exit cleanly back to a BASIC ready prompt - something that is not possible in the other modes.
IRQs and the ZeroPage
IRQs and the zeropage
^^^^^^^^^^^^^^^^^^^^^
The normal IRQ routine in the C-64's kernal will read and write several addresses in the ZP
The normal IRQ routine in the C64's Kernal will read and write several addresses in the ZP
(such as the system's software jiffy clock which sits in ``$a0 - $a2``):
``$a0 - $a2``; ``$91``; ``$c0``; ``$c5``; ``$cb``; ``$f5 - $f6``
@ -144,7 +144,7 @@ IRQ Handling
Normally, the system's default IRQ handling is not interfered with.
You can however install your own IRQ handler (for clean separation, it is advised to define it inside its own block).
There are a few library routines available to make setting up C-64 60hz IRQs and Raster IRQs a lot easier (no assembly code required).
There are a few library routines available to make setting up C64 60hz IRQs and Raster IRQs a lot easier (no assembly code required).
For the C64 these routines are::
@ -155,7 +155,7 @@ For the C64 these routines are::
And for the Commander X16::
cx16.set_irq(uword handler_address, boolean useKernal) ; vsync irq
cx16.set_rasterirq(uword handler_address, uword rasterline) ; note: disables kernal irq handler! sys.wait() won't work anymore
cx16.set_rasterirq(uword handler_address, uword rasterline) ; note: disables Kernal irq handler! sys.wait() won't work anymore
cx16.restore_irq() ; set everything back to the systems default irq handler
@ -163,5 +163,5 @@ The Commander X16 provides two additional routines that should be used *in your
cx16.push_vera_context()
; ... do your work that uses vera here...
cx15.pop_vera_context()
cx16.pop_vera_context()

View File

@ -7,7 +7,7 @@ All variables are static in memory
All variables are allocated statically, there is no concept of dynamic heap or stack frames.
Essentially all variables are global (but scoped) and can be accessed and modified anywhere,
but care should be taken ofcourse to avoid unexpected side effects.
but care should be taken of course to avoid unexpected side effects.
Especially when you're dealing with interrupts or re-entrant routines: don't modify variables
that you not own or else you will break stuff.
@ -24,7 +24,7 @@ directly into the target variable, register, or memory location.
The software stack is implemented as follows:
- 2 pages of memory are allocated for this, exact locations vary per machine target.
For the C-64 they are set at $ce00 and $cf00 (so $ce00-$cfff is reserved).
For the C64 they are set at $ce00 and $cf00 (so $ce00-$cfff is reserved).
For the Commander X16 they are set at $0400 and $0500 (so $0400-$05ff are reserved).
This default location can be overridden using the `-esa` command line option.
- these are the high and low bytes of the values on the stack (it's a 'split 16 bit word stack')
@ -42,7 +42,7 @@ Calling a subroutine requires three steps:
#. preparing the arguments (if any) and passing them to the routine
#. calling the routine
#. preparig the return value (if any) and returning that from the call.
#. preparing the return value (if any) and returning that from the call.
Calling the routine is just a simple JSR instruction, but the other two work like this:
@ -51,7 +51,7 @@ Calling the routine is just a simple JSR instruction, but the other two work lik
``asmsub`` routines
^^^^^^^^^^^^^^^^^^^
These are usually declarations of kernal (ROM) routines or low-level assembly only routines,
These are usually declarations of Kernal (ROM) routines or low-level assembly only routines,
that have their arguments solely passed into specific registers.
Sometimes even via a processor status flag such as the Carry flag.
Return values also via designated registers.
@ -73,7 +73,7 @@ regular subroutines
- the return value is passed back to the caller via cpu register(s):
Byte values will be put in ``A`` .
Word values will be put in ``A`` + ``Y`` register pair.
Float values will be put in the ``FAC1`` float 'register' (Basic allocated this somewhere in ram).
Float values will be put in the ``FAC1`` float 'register' (BASIC allocated this somewhere in ram).
Calls to builtin functions are treated in a special way:
@ -83,7 +83,7 @@ Some builtin functions have a fully custom implementation.
The compiler will warn about routines that are called and that return a value, if you're not
doing something with that returnvalue. This can be on purpuse if you're simply not interested in it.
doing something with that returnvalue. This can be on purpose if you're simply not interested in it.
Use the ``void`` keyword in front of the subroutine call to get rid of the warning in that case.
@ -92,7 +92,7 @@ The 6502 CPU's X-register: off-limits
Prog8 uses the cpu's X-register as a pointer in its internal expression evaluation stack.
When only writing code in Prog8, this is taken care of behind the scenes for you by the compiler.
However when you are including or linking with assembly routines or kernal/ROM calls that *do*
However when you are including or linking with assembly routines or Kernal/ROM calls that *do*
use the X register (either clobbering it internally, or using it as a parameter, or return value register),
those calls will destroy Prog8's stack pointer and this will result in invalid calculations.
@ -138,7 +138,7 @@ Some notes and references into the compiler's source code modules:
Most notably, node type information is now baked in. (``codeCore`` module)
#. An *Intermediate Representation* has been defined that is generated from the intermediate AST. This IR
is more or less a machine code language for a virtual machine - and indeed this is what the built-in
prog8 VM will execute if you use the 'virtual' compilaton target and use ``-emu`` to launch the VM.
prog8 VM will execute if you use the 'virtual' compilation target and use ``-emu`` to launch the VM.
(``intermediate`` and ``codeGenIntermediate`` modules, and ``virtualmachine`` module for the VM related stuff)
#. Currently the 6502 ASM code generator still works directly on the *Compiler AST*. A future version
should replace this by working on the IR code, and should be much smaller and simpler.

View File

@ -1,23 +1,17 @@
%import textio
%import textio
%import floats
%zeropage basicsafe
main {
float[10] flt
sub start() {
%ir {{
nop
}}
; should flow into next statements
ubyte aa = 42
ubyte bb = 99
aa += bb
txt.print_ub(aa)
txt.spc()
aa += bb
txt.print_ub(aa)
txt.spc()
aa += bb
txt.print_ub(aa)
txt.nl()
}
sub start() {
float ff = 9.0
flt[1] = 42.42
flt[1] = -9.0
flt[1] = -ff
flt[1] = -flt[1] ; TODO also fix crash when selecting vm target: fpReg1 out of bounds
floats.print_f(flt[1])
txt.nl()
}
}

View File

@ -1,6 +1,6 @@
Prog8 syntax highlighting file for IntelliJ IDEA.
Prog8 syntax highlighting file for IntelliJ IDEA and Jetbrains Rider.
Copy the file Prog8.xml to your IDEA filetypes folder.
Copy the file Prog8.xml to your IDEA/Rider filetypes folder.
If this folder doesn't yet exist, simply create it.
After installing this file, restart the IDEA.