mirror of
https://github.com/irmen/prog8.git
synced 2025-04-28 04:37:46 +00:00
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:
commit
224f490455
@ -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
|
||||
------------
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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 -> {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
93
compiler/test/codegeneration/TestArrayInplaceAssign.kt
Normal file
93
compiler/test/codegeneration/TestArrayInplaceAssign.kt
Normal 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
|
||||
}
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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!
|
||||
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user