From 6b91a464c0e390186ecd1a1acb8348c3d8ac2955 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 20 Aug 2026 02:27:37 +0200 Subject: [PATCH 1/3] fix spelling errors --- README.md | 4 +-- .../optimizer/ConstantIdentifierReplacer.kt | 2 +- .../compiler/astprocessing/AstChecker.kt | 2 +- docs/source/programming.rst | 4 +-- examples/test.p8 | 33 ++++++++++--------- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 800663e02..6f1ebd395 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ which aims to provide many conveniences over raw assembly code (even when using **Want to buy me a coffee or a pizza perhaps?** -This project was created over the last couple of years by dedicating a huge amoung of my free time to it, to make it the best I possibly can. +This project was created over the last couple of years by dedicating a huge amount of my free time to it, to make it the best I possibly can. If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza, you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen) or [PayPal](https://paypal.me/irmendejong) @@ -94,7 +94,7 @@ What does Prog8 provide? *Rapid edit-compile-run-debug cycle:* - use a modern PC to do the work on, use nice editors and enjoy quick compilation times -- can automatically run the program in the Vice emulator after succesful compilation +- can automatically run the program in the Vice emulator after successful compilation - breakpoints, that let the Vice emulator drop into the monitor if execution hits them - source code labels automatically loaded in Vice emulator so it can show them in disassembly diff --git a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt index 0577b661f..e4f386c02 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt @@ -59,7 +59,7 @@ class VarConstantValueTypeAdjuster( if (decl.value != null && !isConstantInitializer(decl.value!!)) return noModifications if (decl.value != null && isMemorySlabInit(decl.value!!)) - return noModifications // a pointer to a memory slab is usually accessed often and/or in ways that benefit more from the pointer being an actual variable (preferrably in zeropage) + return noModifications // a pointer to a memory slab is usually accessed often and/or in ways that benefit more from the pointer being an actual variable (preferably in zeropage) val usages = callGraph.usages(decl) val (writes, reads) = usages .partition { diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 804d1bef2..3e6d61291 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -2071,7 +2071,7 @@ internal class AstChecker(private val program: Program, private fun checkPointer(pointervar: IdentifierReference) { val vardecl = pointervar.targetVarDecl() if(vardecl?.zeropage == ZeropageWish.NOT_IN_ZEROPAGE) - errors.info("pointer variable should preferrably be in zeropage but is marked nozp", vardecl.position) + errors.info("pointer variable should preferably be in zeropage but is marked nozp", vardecl.position) } override fun visit(arrayIndexedExpression: ArrayIndexedExpression) { diff --git a/docs/source/programming.rst b/docs/source/programming.rst index c4dc2a511..b085aa6a2 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -125,7 +125,7 @@ scoped names always need to be fully scoped (because they always start in the gl .. index:: pair: Identifiers; Aliases The ``alias`` statement makes it easier to refer to symbols from other places, and they can save -you from having to type the fully scoped name everytime you need to access that symbol. +you from having to type the fully scoped name every time you need to access that symbol. Aliases can be created in any scope except at the module level. An alias is created with ``alias = `` and then you can use ```` as if it were ````. It is possible to alias variables, labels and subroutines, and even whole blocks and words @@ -196,7 +196,7 @@ to access a variable from a nested subroutine:: } **:index:`Aliases`** make it easier to refer to symbols from other places. They save -you from having to type the fully scoped name everytime you need to access that symbol. +you from having to type the fully scoped name every time you need to access that symbol. Aliases can be created in any scope except at the module level. You can create and use an alias with the ``alias`` statement like so:: diff --git a/examples/test.p8 b/examples/test.p8 index d1d06c139..c9e74cea5 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,21 +1,24 @@ -%import textio - main { - ubyte @shared counter - bool @shared flag + + struct Thing { + uword port + ^^Thing next + bool flag + } sub start() { - if flag { - ubyte a,b = multi() ; compiler error when inside if block, OK if in subroutine directly - txt.print_ub(a) - txt.spc() - txt.print_ub(b) - txt.nl() - } - } + ^^Thing t - sub multi() -> ubyte, ubyte { - counter++ - return 11,22 + ; this compiles fine: + bool derp1 = t.next.flag + ubyte derp2 = t.next.flag as ubyte + + ; TODO fix compiler crash: + bool derp3 = (t.next).flag + ubyte derp4 = (t.next).flag as ubyte + + ; TODO fix invalid error messages: + bool derp5 = (t.port as ^^Thing).flag + ubyte derp6 = (t.port as ^^Thing).flag as ubyte } } From ba6179a4d3c16e4da5315a7f38f9e7f67a634ca3 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 21 Aug 2026 00:46:27 +0200 Subject: [PATCH 2/3] fix crash and errors on parenthesized/casted struct pointer field access (e.g. (t.next).flag, (t.port as ^^Thing).flag) --- .../compiler/astprocessing/CodeDesugarer.kt | 54 ++++++++++- compiler/test/TestPointers.kt | 97 +++++++++++++++++++ .../prog8/ast/expressions/AstExpressions.kt | 6 +- examples/test.p8 | 16 ++- 4 files changed, 162 insertions(+), 11 deletions(-) diff --git a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt index 5ec406eed..da0ee547a 100644 --- a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt +++ b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt @@ -589,7 +589,13 @@ _after: if(expr.operator==".") { if(expr.left is IdentifierReference) { - require(expr.right !is IdentifierReference) + if(expr.right is IdentifierReference) { + // (a.b).c -> a.b.c - merge dotted names (handles parenthesized pointer chains like (t.next).flag) + val leftIdent = expr.left as IdentifierReference + val rightIdent = expr.right as IdentifierReference + val combined = IdentifierReference(leftIdent.nameInSource + rightIdent.nameInSource, expr.position) + return listOf(AstReplaceNode(expr, combined, parent)) + } val ri = expr.right as? ArrayIndexedExpression if(ri!=null && ri.plainarrayvar!=null) { val leftIdent = expr.left as IdentifierReference @@ -648,6 +654,52 @@ _after: val ident = IdentifierReference(right.chain, right.position) return listOf(AstReplaceNode(expr.right, ident, expr)) } + + // Generic pointer field access on complex expressions, e.g. (expr as ^^Struct).field + // Simple identifier/array left-hand-sides (a.b.c, ptr[i].field) are handled elsewhere. + // Writes through such an expression can't occur: the grammar only allows them as read expressions. + if(expr.left !is IdentifierReference && expr.left !is ArrayIndexedExpression) { + val fieldIdent = expr.right as? IdentifierReference + if(fieldIdent!=null) { + val leftDt = expr.left.inferType(program).getOrUndef() + val struct = if(leftDt.isPointer) leftDt.subType as? StructDecl else null + val fieldName = fieldIdent.nameInSource.first() + val fieldDt = struct?.getFieldType(fieldName) + if(struct!=null && fieldDt!=null) { + // (ptr).field --> peekXXX(ptr as uword + offsetof(Struct.field)) + val offset = struct.offsetof(fieldName, program.memsizer)!!.toInt() + val ptrAsUword = TypecastExpression(expr.left.copy(), DataType.UWORD, true, expr.left.position) + val address: Expression = if(offset==0) ptrAsUword + else BinaryExpression(ptrAsUword, "+", NumericLiteral.optimalInteger(offset, expr.position), expr.position) + fun peekCall(func: String) = + FunctionCallExpression(IdentifierReference(listOf(func), expr.position), mutableListOf(address), expr.position) + val readExpr: Expression = when { + fieldDt.isBool -> peekCall("peekbool") + fieldDt.isUnsignedByte -> DirectMemoryRead(address, expr.position) + fieldDt.isSignedByte -> TypecastExpression(DirectMemoryRead(address, expr.position), DataType.BYTE, true, expr.position) + fieldDt.isUnsignedWord -> peekCall("peekw") + fieldDt.isSignedWord -> TypecastExpression(peekCall("peekw"), DataType.WORD, true, expr.position) + fieldDt.isLong -> peekCall("peekl") + fieldDt.isFloat -> peekCall("peekf") + fieldDt.isPointer -> TypecastExpression(peekCall("peekw"), fieldDt, true, expr.position) + else -> { + errors.err("unsupported field type for pointer dereference", expr.position) + return noModifications + } + } + if(fieldIdent.nameInSource.size==1) + return listOf(AstReplaceNode(expr, readExpr, parent)) + // (ptr).field.rest... --> (read of first field) . rest... (will be desugared further in the next pass) + if(!fieldDt.isPointer) { + errors.err("cannot dereference non-pointer field '$fieldName'", fieldIdent.position) + return noModifications + } + val rest = IdentifierReference(fieldIdent.nameInSource.drop(1), fieldIdent.position) + val chained = BinaryExpression(readExpr, ".", rest, expr.position) + return listOf(AstReplaceNode(expr, chained, parent)) + } + } + } } return noModifications diff --git a/compiler/test/TestPointers.kt b/compiler/test/TestPointers.kt index 94866792d..fb3ae6d78 100644 --- a/compiler/test/TestPointers.kt +++ b/compiler/test/TestPointers.kt @@ -2823,4 +2823,101 @@ main { result shouldNotBe null } } + + test("parenthesized pointer field access desugars same as unparenthesized") { + val src=""" +main { + struct Thing { + uword port + ^^Thing next + bool flag + } + + sub start() { + ^^Thing @shared t + bool @shared d1 = t.next.flag + bool @shared d2 = (t.next).flag + ubyte @shared d3 = t.next.flag as ubyte + ubyte @shared d4 = (t.next).flag as ubyte + bool @shared d5 = t.next.next.flag + bool @shared d6 = (t.next).next.flag + } +}""" + val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!! + val assigns = result.compilerAst.entrypoint.statements.filterIsInstance() + .associateBy { it.target.identifier!!.nameInSource.last() } + + val d1 = assigns.getValue("d1").value as PtrDereference + d1.chain shouldBe listOf("t", "next", "flag") + (assigns.getValue("d2").value as PtrDereference).chain shouldBe d1.chain + ((assigns.getValue("d3").value as TypecastExpression).expression as PtrDereference).chain shouldBe d1.chain + ((assigns.getValue("d4").value as TypecastExpression).expression as PtrDereference).chain shouldBe d1.chain + val d5 = assigns.getValue("d5").value as PtrDereference + d5.chain shouldBe listOf("t", "next", "next", "flag") + (assigns.getValue("d6").value as PtrDereference).chain shouldBe d5.chain + + compileText(VMTarget(), true, src, outputDir) shouldNotBe null + compileText(C64Target(), false, src, outputDir) shouldNotBe null + compileText(Cx16Target(), false, src, outputDir) shouldNotBe null + } + + test("field access on casted struct pointer expression") { + val src=""" +main { + struct Thing { + uword port + ^^Thing next + bool flag + } + + sub start() { + ^^Thing @shared t + bool @shared d1 = (t.port as ^^Thing).flag + ubyte @shared d2 = (t.port as ^^Thing).flag as ubyte + ^^Thing @shared d3 = (t.port as ^^Thing).next + bool @shared d4 = (t.port as ^^Thing).next.flag + bool @shared d5 = ((t.port as ^^Thing).next).flag + } +}""" + val errors = ErrorReporterForTests() + val result = compileText(VMTarget(), false, src, outputDir, errors, writeAssembly = false) + withClue(errors.errors.joinToString("\n")) { + result shouldNotBe null + } + val assigns = result!!.compilerAst.entrypoint.statements.filterIsInstance() + .associateBy { it.target.identifier!!.nameInSource.last() } + + // (t.port as ^^Thing).field --> peekXXX((t.port as ^^Thing as uword) + offsetof(field)) + val d1 = assigns.getValue("d1").value as FunctionCallExpression + d1.target.nameInSource shouldBe listOf("peekbool") + val d1addr = d1.args.single() as BinaryExpression + d1addr.operator shouldBe "+" + (d1addr.right as NumericLiteral).number shouldBe 4.0 // offsetof(Thing.flag) + + val d2 = assigns.getValue("d2").value as TypecastExpression + d2.type shouldBe DataType.UBYTE + (d2.expression as FunctionCallExpression).target.nameInSource shouldBe listOf("peekbool") + + // pointer-typed field read: peekw + cast back to the pointer type + val d3 = assigns.getValue("d3").value as TypecastExpression + d3.type.isPointer shouldBe true + val d3peek = d3.expression as FunctionCallExpression + d3peek.target.nameInSource shouldBe listOf("peekw") + ((d3peek.args.single() as BinaryExpression).right as NumericLiteral).number shouldBe 2.0 // offsetof(Thing.next) + + // chained: inner pointer-typed field is read via peekw first, then .flag via peekbool + val d4 = assigns.getValue("d4").value as FunctionCallExpression + d4.target.nameInSource shouldBe listOf("peekbool") + val d4addr = d4.args.single() as BinaryExpression + (d4addr.right as NumericLiteral).number shouldBe 4.0 + val d4inner = (d4addr.left as TypecastExpression).expression as TypecastExpression + (d4inner.expression as FunctionCallExpression).target.nameInSource shouldBe listOf("peekw") + + // explicit parenthesized chain desugars the same + (assigns.getValue("d5").value as FunctionCallExpression).target.nameInSource shouldBe listOf("peekbool") + + compileText(VMTarget(), true, src, outputDir) shouldNotBe null + compileText(C64Target(), false, src, outputDir) shouldNotBe null + compileText(Cx16Target(), false, src, outputDir) shouldNotBe null + } }) diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index 49c31a6ae..84d1dcf5f 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -276,7 +276,11 @@ class BinaryExpression( // SOMEEXPRESSION . NAME val leftDt = leftExpr.inferType(program) if (leftDt.isPointer) leftDt.getOrUndef().subType else null - } else null + } else { + // generic pointer expression . field (e.g. (expr as ^^Struct).field, or (ptrNext).field) + val leftDt = left.inferType(program) + if (leftDt.isPointer) leftDt.getOrUndef().subType else null + } if (struct == null) InferredTypes.unknown() else { diff --git a/examples/test.p8 b/examples/test.p8 index c9e74cea5..b065f34a9 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -7,18 +7,16 @@ main { } sub start() { - ^^Thing t + ^^Thing @shared t ; this compiles fine: - bool derp1 = t.next.flag - ubyte derp2 = t.next.flag as ubyte + bool @shared derp1 = t.next.flag + ubyte @shared derp2 = t.next.flag as ubyte - ; TODO fix compiler crash: - bool derp3 = (t.next).flag - ubyte derp4 = (t.next).flag as ubyte + bool @shared derp3 = (t.next).flag + ubyte @shared derp4 = (t.next).flag as ubyte - ; TODO fix invalid error messages: - bool derp5 = (t.port as ^^Thing).flag - ubyte derp6 = (t.port as ^^Thing).flag as ubyte + bool @shared derp5 = (t.port as ^^Thing).flag + ubyte @shared derp6 = (t.port as ^^Thing).flag as ubyte } } From 65a1300147bddfeb461894804568f4a966d3ee3f Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 21 Aug 2026 01:06:29 +0200 Subject: [PATCH 3/3] cpu6502: implement pointer inplace invert/negate (byte/word/long/float) --- .../cpu6502/assignment/AssignmentAsmGen.kt | 1 + .../assignment/PointerAssignmentsGen.kt | 103 +++++++++++++++++- compiler/test/TestPointers.kt | 57 ++++++++++ docs/source/todo.rst | 11 +- 4 files changed, 162 insertions(+), 10 deletions(-) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt index 467f81aef..656052ae4 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt @@ -5940,6 +5940,7 @@ $endLabel""") eor #255 sta ${target.asmVarname}+3""") } + TargetStorageKind.POINTER -> pointergen.inplaceLongInvert(PtrTarget(target)) else -> TODO("LONG INVERT ${target.kind} ${target.position}") } } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/PointerAssignmentsGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/PointerAssignmentsGen.kt index 23031d0a8..5641f3ccb 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/PointerAssignmentsGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/PointerAssignmentsGen.kt @@ -113,27 +113,118 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri } internal fun inplaceByteInvert(target: PtrTarget) { - TODO("inplace byte invert pointer deref ${target.position}") + val (zpPtrVar, offset) = deref(target.pointer) + val eorVal = if(target.dt.isBool) 1 else 255 + if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) { + asmgen.out(" lda ($zpPtrVar) | eor #$eorVal | sta ($zpPtrVar)") + } else { + asmgen.out(""" + ldy #$offset + lda ($zpPtrVar),y + eor #$eorVal + sta ($zpPtrVar),y""") + } } internal fun inplaceWordInvert(target: PtrTarget) { - TODO("inplace word invert pointer deref ${target.position}") + val (zpPtrVar, offset) = deref(target.pointer) + asmgen.out(""" + ldy #$offset + lda ($zpPtrVar),y + eor #255 + sta ($zpPtrVar),y + iny + lda ($zpPtrVar),y + eor #255 + sta ($zpPtrVar),y""") + } + + internal fun inplaceLongInvert(target: PtrTarget) { + val (zpPtrVar, offset) = deref(target.pointer) + asmgen.out(""" + ldy #$offset + lda ($zpPtrVar),y + eor #255 + sta ($zpPtrVar),y + iny + lda ($zpPtrVar),y + eor #255 + sta ($zpPtrVar),y + iny + lda ($zpPtrVar),y + eor #255 + sta ($zpPtrVar),y + iny + lda ($zpPtrVar),y + eor #255 + sta ($zpPtrVar),y""") } internal fun inplaceByteNegate(target: PtrTarget, scope: IPtSubroutine?) { - TODO("inplace byte negate to pointer deref ${target.position}") + val (zpPtrVar, offset) = deref(target.pointer) + if(asmgen.isTargetCpu(CpuType.CPU65C02)) { + asmgen.out(""" + ldy #$offset + lda ($zpPtrVar),y + eor #255 + ina + sta ($zpPtrVar),y""") + } else { + asmgen.out(""" + ldy #$offset + lda ($zpPtrVar),y + eor #255 + clc + adc #1 + sta ($zpPtrVar),y""") + } } internal fun inplaceWordNegate(target: PtrTarget, scope: IPtSubroutine?) { - TODO("inplace word negate pointer deref ${target.position}") + val (zpPtrVar, offset) = deref(target.pointer) + asmgen.out(""" + ldy #$offset + sec + lda #0 + sbc ($zpPtrVar),y + sta ($zpPtrVar),y + iny + lda #0 + sbc ($zpPtrVar),y + sta ($zpPtrVar),y""") } internal fun inplaceLongNegate(target: PtrTarget, scope: IPtSubroutine?) { - TODO("inplace long negate pointer deref ${target.position}") + val (zpPtrVar, offset) = deref(target.pointer) + asmgen.out(""" + ldy #$offset + sec + lda #0 + sbc ($zpPtrVar),y + sta ($zpPtrVar),y + iny + lda #0 + sbc ($zpPtrVar),y + sta ($zpPtrVar),y + iny + lda #0 + sbc ($zpPtrVar),y + sta ($zpPtrVar),y + iny + lda #0 + sbc ($zpPtrVar),y + sta ($zpPtrVar),y""") } internal fun inplaceFloatNegate(target: PtrTarget, scope: IPtSubroutine?) { - TODO("inplace float negate pointer deref ${target.position}") + val (zpPtrVar, offset) = deref(target.pointer) + // flip sign bit in second byte of 5-byte float + asmgen.out(""" + ldy #$offset + iny + lda ($zpPtrVar),y + eor #${'$'}80 + sta ($zpPtrVar),y""") } diff --git a/compiler/test/TestPointers.kt b/compiler/test/TestPointers.kt index fb3ae6d78..92d3f5d23 100644 --- a/compiler/test/TestPointers.kt +++ b/compiler/test/TestPointers.kt @@ -2920,4 +2920,61 @@ main { compileText(C64Target(), false, src, outputDir) shouldNotBe null compileText(Cx16Target(), false, src, outputDir) shouldNotBe null } + + xtest("function call returning pointer - field access chaining as assignment target [nasty parser problem]") { + val src=""" +main { + struct Thing { + uword port + ^^Thing next + bool flag + } + + sub getThing() -> ^^Thing { + return 0 + } + + sub start() { + getThing().flag = true + getThing().next.flag = true + getThing().next.next.flag = false + getThing().port = 1234 + getThing().next.port = 5678 + } +}""" + // currently fails at parse stage, should eventually compile for both VM and 6502 without needing assembly generation + compileText(VMTarget(), false, src, outputDir, writeAssembly = false) shouldNotBe null + compileText(Cx16Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null + } + + test("function call returning pointer - field access chaining as value (RHS) works") { + val src=""" +main { + struct Thing { + uword port + ^^Thing next + bool flag + } + + sub getThing() -> ^^Thing { + return 0 + } + + sub start() { + bool a = getThing().flag + bool b = getThing().next.flag + bool c = getThing().next.next.flag + uword d = getThing().port + uword e = getThing().next.port + bool f = getThing().flag and getThing().next.flag + uword g = getThing().port + getThing().next.port + if getThing().flag { + a = true + } + } +}""" + // RHS value case already works for both VM and 6502 without needing assembly generation + compileText(VMTarget(), false, src, outputDir, writeAssembly = false) shouldNotBe null + compileText(Cx16Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null + } }) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 3e677c95d..29c38b8df 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -9,28 +9,31 @@ Future Things and Ideas - symboldump: some sort of javadocs generated from the p8 source files (instead of just the function signatures). Use markdown for formatting, not html. - when implementing unsigned longs: remove the (multiple?) "TODO "hack" to allow unsigned long constants to be used as values for signed longs, without needing a cast - struct/ptr: implement the remaining TODOs in PointerAssignmentsGen. -- struct/ptr: support pointer to pointer? - struct/ptr: typed function pointers (simplified): ``&&subroutine`` returns opaque typed pointer (no ``funcptr`` keyword), assignment allowed where target type is inferred, calling via ``ptr(args)``. No explicit signature syntax. Useful mostly for 68000 targets. - struct/ptr: really fixing the pointer dereferencing issues (cursed hybrid between IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator" -- struct/ptr: (later, nasty parser problem:) support chaining pointer dereference on function calls that return a pointer. (type checking now fails on stuff like func().field and func().next.field) +- struct/ptr: (later, nasty parser problem:) support chaining pointer dereference on function calls that return a pointer as assignment target (LHS). RHS value case ``a = func().field`` / ``a = func().next.field`` now works for both VM and 6502 via generic ``peek`` desugar (``AstExpressions.kt:280``, ``CodeDesugarer.kt:654``); still fails for ``func().field = a`` / ``func().next.field = a`` which requires ``Prog8ANTLR.g4:239`` ``assign_target`` grammar change to allow ``functioncall '.' field`` chain (see ``compiler/test/TestPointers.kt`` xtest). - Make all constants long by default? or not? (remove type name altogether), reduce to target type implicitly if the actual value fits. -> long-consts branch This will break some existing programs that depend on value wraparound, but gives more intuitive constant number handling. Can give descriptive error message for old syntax that still includes the type name? - add documentation for more library modules instead of just linking to the source code -- sizeof(pointer) is now always 2 (an uword), make this a variable in the ICompilationTarget so that it could be 4 at the time we might ad a 32-bits 68000 target for example. Much code assumes word size addresses though. +- sizeof(pointer) is now always 2 (an uword), make this a variable in the ICompilationTarget so that it could be 4 at the time we might ad a 32-bits 68000 target for example. Much code assumes word size addresses though. (already done on new-codegens branch) - add float support to the configurable compiler targets. Restrictions: just have "cbm-style floats" as an option (to that it can slot into the current float codegen), where all you have to specify is the addresses of AYINT and GIVAYF and FADDT and all their friends. - Change scoping rules for qualified symbols so that they don't always start from the root but behave like other programming languages (look in local scope first), maybe only when qualified symbol starts with '.' such as: .local.value = 33 - implement the signed remainder byte and word routines on 6502 (virtual target already has them working) - implement the signed divmod byte and word routines on 6502 (virtual target already has them working) - make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type (this is already done hardcoded for several of the builtin functions) -- migrate CLI argument parsing from the obsolete kotlinx-cli library to Clikt (com.github.ajalt:clikt) +- migrate CLI argument parsing from the obsolete kotlinx-cli library to Clikt (com.github.ajalt:clikt) (already done on new-codegens branch) - add new directives ``%bssaddress`` and ``%slabsaddress`` to set the memory address for the BSS area and memory slabs (analogous to ``%address`` for program load address). Note: these should be mutually exclusive with the existing CLI options (``-varsgolden``, ``-varshigh``, ``-slabsgolden``, ``-slabshigh``) because the CLI options are target-aware shorthands (set bank symbols, do bounds checking against predefined ranges) while the directives are raw addresses — they'd conflict if both specified for the same area. - the c64 sprite multiplexer example may need timing adjustments after compiler changes (not a compiler bug — cycle-exact C64 code is inherently fragile) - once 'new-codegen' branch is merged, re-enable bunch of xtest() in TestConst.kt +- add an ``atexit`` mechanism: like ``defer`` but runs only at program exit, regardless of which exit path is taken (fall-through, ``sys.exit``, poweroff). + Mainly useful for library-owned cleanup (heap pools, opened devices/libraries/windows on the amiga500 target) without requiring the main program to call cleanup explicitly; + ``defer`` cannot do this because it is bound to subroutine scope. Prototype as a stdlib module first (fixed-capacity registry of subroutine addresses + explicit ``run_all()``, LIFO order), + consider promoting to a language feature that invokes the handlers automatically in each target's program epilogue. Romable (%option romable)