From d4e83b28bbc3ffe6bcd74cc8279fc528f1fe2f58 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 2 Aug 2025 13:02:11 +0200 Subject: [PATCH] error messages and trying to improve support for struct allocs in arrays added sorting example --- .../codegen/cpu6502/BuiltinFunctionsAsmGen.kt | 2 +- .../codegen/intermediate/BuiltinFuncGen.kt | 2 +- .../compiler/astprocessing/AstChecker.kt | 14 +- .../astprocessing/SimplifiedAstMaker.kt | 4 +- .../astprocessing/VerifyFunctionArgTypes.kt | 25 +++- compiler/test/TestCompilerOnExamples.kt | 3 +- compiler/test/TestPointers.kt | 4 +- docs/source/todo.rst | 4 +- examples/pointers/sorting.p8 | 130 ++++++++++++++++++ examples/test.p8 | 93 +------------ simpleAst/src/prog8/code/SymbolTable.kt | 2 +- simpleAst/src/prog8/code/SymbolTableMaker.kt | 6 +- simpleAst/src/prog8/code/ast/AstPrinter.kt | 6 +- 13 files changed, 188 insertions(+), 107 deletions(-) create mode 100644 examples/pointers/sorting.p8 diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt index dd1567f9b..bc221c44e 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt @@ -44,7 +44,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, "setlsb" -> funcSetLsbMsb(fcall, false) "setmsb" -> funcSetLsbMsb(fcall, true) "memory" -> funcMemory(fcall, discardResult, resultRegister) - "structalloc" -> funcStructAlloc(fcall, discardResult, resultRegister) "peekw" -> funcPeekW(fcall, resultRegister) "peekf" -> funcPeekF(fcall, resultRegister) "peek" -> throw AssemblyError("peek() should have been replaced by @()") @@ -67,6 +66,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, "callfar" -> funcCallFar(fcall, resultRegister) "callfar2" -> funcCallFar2(fcall, resultRegister) "call" -> funcCall(fcall) + "prog8_lib_structalloc" -> funcStructAlloc(fcall, discardResult, resultRegister) "prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister) "prog8_lib_square_byte" -> funcSquare(fcall, BaseDataType.UBYTE, resultRegister) "prog8_lib_square_word" -> funcSquare(fcall, BaseDataType.UWORD, resultRegister) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index 8b3493ac3..b35b1cfac 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -50,7 +50,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe "prog8_lib_stringcompare" -> funcStringCompare(call) "prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE) "prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD) - "structalloc" -> funcStructAlloc(call) + "prog8_lib_structalloc" -> funcStructAlloc(call) "sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant") else -> throw AssemblyError("missing builtinfunc for ${call.name}") } diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 22c9313f3..3110a8cb6 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -1082,7 +1082,10 @@ internal class AstChecker(private val program: Program, } if(decl.datatype.isStructInstance && decl.origin!=VarDeclOrigin.SUBROUTINEPARAM) { - errors.err("struct instances cannot be declared directly, use pointer and allocation call instead", decl.position) + if(decl.type==VarDeclType.MEMORY) + errors.err("cannot declare memory mapped struct instances, use a pointer and memory allocation call or direct address assignment instead", decl.position) + else + errors.err("struct instances cannot be declared directly, use a pointer and memory allocation call or direct address assignment instead", decl.position) } if (decl.dirty) { @@ -1261,8 +1264,15 @@ internal class AstChecker(private val program: Program, } if(array.parent is VarDecl) { - if (!array.value.all { it is NumericLiteral || it is AddressOf }) + if (!array.value.all { it is NumericLiteral || it is AddressOf || (it is FunctionCallExpression && it.target.targetStructDecl()!=null) }) { errors.err("initialization value contains non-constant elements", array.value[0].position) + } + + if(array.value.any { it is FunctionCallExpression }) { + errors.err("it is not yet possible to use struct initializations in an array, you have to do it one by one for now", array.value[0].position) + // TODO this is because later in the simplified AST the allocate struct variable is still missing somehow + } + } else if(array.parent is ForLoop) { if (!array.value.all { it.constValue(program) != null }) errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position) diff --git a/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt index 69d09343b..0b415810c 100644 --- a/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt +++ b/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt @@ -410,7 +410,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro val call = if(targetStruct!=null) { // a call to a struct yields a pointer to a struct instance and means: allocate a statically initialized struct instance of that type - PtBuiltinFunctionCall("structalloc", false, true, DataType.pointer(targetStruct), srcCall.position) + PtBuiltinFunctionCall("prog8_lib_structalloc", false, true, DataType.pointer(targetStruct), srcCall.position) } else { // regular function call val (target, _) = srcCall.target.targetNameAndType(program) @@ -740,7 +740,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro val arr = PtArray(srcArr.inferType(program).getOrElse { throw FatalAstException("array must know its type") }, srcArr.position) for (elt in srcArr.value) { val child = transformExpression(elt) - require(child is PtAddressOf || child is PtBool || child is PtNumber) { "array element invalid type $child" } + require(child is PtAddressOf || child is PtBool || child is PtNumber || (child is PtBuiltinFunctionCall && child.name=="prog8_lib_structalloc")) {"array element invalid type $child" } arr.add(child) } return arr diff --git a/compiler/src/prog8/compiler/astprocessing/VerifyFunctionArgTypes.kt b/compiler/src/prog8/compiler/astprocessing/VerifyFunctionArgTypes.kt index 2c616f141..8b9620b70 100644 --- a/compiler/src/prog8/compiler/astprocessing/VerifyFunctionArgTypes.kt +++ b/compiler/src/prog8/compiler/astprocessing/VerifyFunctionArgTypes.kt @@ -128,13 +128,24 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila if(mismatch>=0) { val actual = argtypes[mismatch] val expected = consideredParamTypes[mismatch] - if(expected.isPointer && expected.sub?.isWord==true) { - val arg = call.args[mismatch] - val argArray = if(arg is AddressOf) arg.identifier else arg - return if(argArray?.inferType(program)?.getOrUndef()?.isSplitWordArray==true) - Pair("argument ${mismatch + 1} cannot pass address to a split words array where a word pointer argument is expected, use a @nosplit word array instead", call.args[mismatch].position) - else - Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position) + if (expected.isPointer) { + if (expected.sub?.isWord == true) { + val arg = call.args[mismatch] + val argArray = if(arg is AddressOf) arg.identifier else arg + return if(argArray?.inferType(program)?.getOrUndef()?.isSplitWordArray==true) + Pair("argument ${mismatch + 1} cannot pass address to a split words array where a word pointer argument is expected, use a @nosplit word array instead", call.args[mismatch].position) + else + Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position) + } + else if(actual.isPointer && actual.sub?.isByte==true) { + val addrOf = call.args[mismatch] as? AddressOf + if(addrOf!=null) { + val identType = addrOf.identifier?.inferType(program)?.getOrUndef() + if(identType?.isSplitWordArray==true) { + return Pair("argument ${mismatch + 1} type mismatch, was: $actual (because arg is a @split word array) expected: $expected", call.args[mismatch].position) + } + } + } } return Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position) } diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index a9101af85..0a7fa5e62 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -246,7 +246,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({ "pointers/animalgame", "pointers/binarytree", // TODO add to "c64" later as well "pointers/sortedlist", // TODO add to "c64" later as well - "pointers/fountain" // TODO add to "c64" later as well + "pointers/fountain", // TODO add to "c64" later as well + "pointers/sorting" // TODO add to "c64" later as well ), listOf(false, true) ) diff --git a/compiler/test/TestPointers.kt b/compiler/test/TestPointers.kt index 60adbcf99..34ed56ddb 100644 --- a/compiler/test/TestPointers.kt +++ b/compiler/test/TestPointers.kt @@ -1551,8 +1551,8 @@ main { errors.warnings.size shouldBe 0 errors.infos.size shouldBe 0 errors.errors[0] shouldContain "pointer arrays can only be @split" - errors.errors[1] shouldContain "was: ^^ubyte expected: ^^main.Node" - errors.errors[2] shouldContain "was: ^^ubyte expected: ^^main.Node" + errors.errors[1] shouldContain "was: ^^ubyte (because arg is a @split word array) expected: ^^main.Node" + errors.errors[2] shouldContain "was: ^^ubyte (because arg is a @split word array) expected: ^^main.Node" } test("passing split array of structpointers to a subroutine in various forms should be param type ptr to ubyte (the lsb part of the split array)") { diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 9e18ad534..042147e30 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -62,7 +62,9 @@ STRUCTS and TYPED POINTERS - DONE: fixed support for (assigntarget) array index dereferencing "array[2]^^" where array contains pointers to primitives: replace with poke() - DONE: replace str or ubyte[] param and returnvalue type into ^^ubyte rather than uword - DONE: allow sizeof(^^type) to return the size of a pointer -- TODO: allow initializing a pointer array with initialized structs: ^^Node[] nodes = [ Node(), Node(), Node() ] + +- TODO: allow initializing a pointer array with initialized structs: ^^Node[] nodes = [ Node(), Node(), Node() ] (fix missing variable error) (update sorting example) + - try to add support for array index dereferencing as assign target "array[2]^^.value = 99" where array is struct pointers (currently a 'no support' error) - try to add support for array index dereferencing as assign target "array[2].value = 99" where array is struct pointers (currently a parser error) - try to fix parse error l1^^.s[0] = 4242 (equivalent to l1.s[0]=4242 , which does parse correctly) diff --git a/examples/pointers/sorting.p8 b/examples/pointers/sorting.p8 new file mode 100644 index 000000000..9a69c27c6 --- /dev/null +++ b/examples/pointers/sorting.p8 @@ -0,0 +1,130 @@ +%import floats +%import strings +%import textio + +main{ + struct Country { + str name + float population ; millions + uword area ; 1000 km^2 + } + + ^^Country[100] countries ; won't be fully filled + ubyte num_countries + + sub start() { + ; because pointer array initialization is not supported yet, we have to add the countries in separate statements for now + add(Country("Indonesia", 285.72, 1904)) + add(Country("Congo", 112.83, 2344)) + add(Country("Vietnam", 101.60, 331)) + add(Country("United States", 347.28, 9372)) + add(Country("Iran", 92.42, 1648)) + add(Country("Turkey", 87.69, 783)) + add(Country("Brazil", 212.81, 8515)) + add(Country("Bangladesh", 175.69, 147)) + add(Country("Germany", 84.08, 357)) + add(Country("Japan", 123.10, 377)) + add(Country("India", 1463.87, 3287)) + add(Country("China", 1416.10, 9596)) + add(Country("Philippines", 116.79, 300)) + add(Country("Russia", 143.99, 17098)) + add(Country("Pakistan", 255.22, 881)) + add(Country("Nigeria", 237.53, 923)) + add(Country("Ethiopia", 135.47, 1104)) + add(Country("Mexico", 131.95, 1964)) + add(Country("Thailand", 71.62, 513)) + add(Country("Egypt", 118.37, 1002)) + + txt.print("UNSORTED:\n") + dump() + + sort_by_population() + txt.print("\nSORTED BY POPULATION:\n") + dump() + + sort_by_area() + txt.print("\nSORTED BY AREA:\n") + dump() + + sort_by_name() + txt.print("\nSORTED BY NAME:\n") + dump() + } + + sub sort_by_name() { + ; stupid slow bubble sort + ubyte n = num_countries + do { + ubyte newn=0 + ubyte i + for i in 1 to n-1 { + if strings.compare(countries[i-1].name, countries[i].name) > 0 { + swap(i, i-1) + newn = i + } + } + n = newn + } until n<=1 + } + + sub sort_by_population() { + ; stupid slow bubble sort + ubyte n = num_countries + do { + ubyte newn=0 + ubyte i + for i in 1 to n-1 { + if countries[i-1].population < countries[i].population { + swap(i, i-1) + newn = i + } + } + n = newn + } until n<=1 + } + + sub sort_by_area() { + ; stupid slow bubble sort + ubyte n = num_countries + do { + ubyte newn=0 + ubyte i + for i in 1 to n-1 { + if countries[i-1].area < countries[i].area { + swap(i, i-1) + newn = i + } + } + n = newn + } until n<=1 + } + + sub swap(ubyte i, ubyte j) { + ^^Country temp = countries[i] + countries[i] = countries[j] + countries[j] = temp + } + + sub dump() { + txt.print("name pop.(millions) area (1000 km^2)\n") + txt.print("-------------- --------------- ----------------\n") + + ^^Country c + for c in countries { + if c==0 + break + txt.print(c.name) + txt.column(15) + txt.print_f(c.population) + txt.column(31) + txt.print_uw(c.area) + txt.nl() + } + } + + sub add(^^Country c) { + countries[num_countries] = c + num_countries++ + } +} + diff --git a/examples/test.p8 b/examples/test.p8 index f2593a8a5..3a6557bd1 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -6,92 +6,11 @@ main { } sub start() { - txt.print_ub(sizeof(^^Node)) - txt.spc() - txt.print_ub(sizeof(^^bool)) - txt.spc() - txt.print_ub(sizeof(^^uword)) - txt.spc() + ^^Node[] nodes = [ Node(), Node(), Node() ] + ^^Node n1 = Node() + txt.print_uw(n1) + txt.print_uw(nodes[0]) + txt.print_uw(nodes[1]) + txt.print_uw(nodes[2]) } } - - -;%import floats -;%import textio -; -;main{ -; struct Country { -; str name -; float population ; millions -; uword area ; 1000 km^2 -; } -; -; ^^Country[100] countries -; ubyte num_countries -; -; sub start() { -; -; str[2] @shared names = [ "aaa", "bb"] -; -; add(Country("India", 1463.87, 3287)) -; add(Country("China", 1416.10, 9596)) -; add(Country("United States", 347.28, 9372)) -; add(Country("Indonesia", 285.72, 1904)) -; add(Country("Pakistan", 255.22, 881)) -; add(Country("Nigeria", 237.53, 923)) -; add(Country("Brazil", 212.81, 8515)) -; add(Country("Bangladesh", 175.69, 147)) -; add(Country("Russia", 143.99, 17098)) -; add(Country("Ethiopia", 135.47, 1104)) -; add(Country("Mexico", 131.95, 1964)) -; add(Country("Japan", 123.10, 377)) -; add(Country("Egypt", 118.37, 1002)) -; add(Country("Philippines", 116.79, 300)) -; add(Country("Congo", 112.83, 2344)) -; add(Country("Vietnam", 101.60, 331)) -; add(Country("Iran", 92.42, 1648)) -; add(Country("Turkey", 87.69, 783)) -; add(Country("Germany", 84.08, 357)) -; add(Country("Thailand", 71.62, 513)) -; -; txt.print("UNSORTED:\n") -; dump() -; -; sort_by_population(countries, num_countries) -; txt.print("SORTED BY POPULATION:\n") -; dump() -; -; sort_by_area(countries, num_countries) -; txt.print("SORTED BY AREA:\n") -; dump() -; } -; -; sub sort_by_population(^^Country cs, ubyte length) { -; -; } -; -; sub sort_by_area(^^Country cs, ubyte length) { -; -; } -; -; sub dump() { -; txt.print("name pop.(millions) area (1000 km^2)\n") -; txt.print("-------------- --------------- ----------------\n") -; ubyte ci -; for ci in 0 to num_countries-1 { -; ^^Country cc = countries[ci] -; txt.print(cc.name) -; txt.column(15) -; txt.print_f(cc.population) -; txt.column(31) -; txt.print_uw(cc.area) -; txt.nl() -; } -; } -; -; sub add(^^Country c) { -; countries[num_countries] = c -; num_countries++ -; } -;} -; diff --git a/simpleAst/src/prog8/code/SymbolTable.kt b/simpleAst/src/prog8/code/SymbolTable.kt index 2b986b3b2..45e83cafc 100644 --- a/simpleAst/src/prog8/code/SymbolTable.kt +++ b/simpleAst/src/prog8/code/SymbolTable.kt @@ -109,7 +109,7 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL companion object { fun labelnameForStructInstance(call: PtBuiltinFunctionCall): String { - require(call.name == "structalloc") + require(call.name == "prog8_lib_structalloc") val structname = call.type.subType!!.scopedNameString // each individual call to the pseudo function structalloc(), // needs to generate a separate unique struct instance label. diff --git a/simpleAst/src/prog8/code/SymbolTableMaker.kt b/simpleAst/src/prog8/code/SymbolTableMaker.kt index 120ee647f..43ce1e16f 100644 --- a/simpleAst/src/prog8/code/SymbolTableMaker.kt +++ b/simpleAst/src/prog8/code/SymbolTableMaker.kt @@ -121,7 +121,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp // don't add memory slabs in nested scope, just put them in the top level of the ST scope.first().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node)) } - else if(node.name=="structalloc") { + else if(node.name=="prog8_lib_structalloc") { val struct = node.type.subType!! if(struct is StStruct) { val label = SymbolTable.labelnameForStructInstance(node) @@ -163,6 +163,10 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp } is PtNumber -> StArrayElement(it.number, null, null) is PtBool -> StArrayElement(null, null, it.value) + is PtBuiltinFunctionCall -> { + val labelname = SymbolTable.labelnameForStructInstance(it) + StArrayElement(null, labelname, null) + } else -> throw AssemblyError("invalid array element $it") } } diff --git a/simpleAst/src/prog8/code/ast/AstPrinter.kt b/simpleAst/src/prog8/code/ast/AstPrinter.kt index 614eb8c31..6da4287dc 100644 --- a/simpleAst/src/prog8/code/ast/AstPrinter.kt +++ b/simpleAst/src/prog8/code/ast/AstPrinter.kt @@ -38,6 +38,10 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni else "& ${txt(it.dereference!!)}" } + is PtBuiltinFunctionCall -> { + require(it.name=="prog8_lib_structalloc") + txt(it) + } else -> "invalid array element $it" } } @@ -46,7 +50,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni is PtArrayIndexer -> " ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}" is PtBinaryExpression -> " ${node.operator} ${type(node.type)}" is PtBuiltinFunctionCall -> { - if(node.name=="structalloc") { + if(node.name=="prog8_lib_structalloc") { node.type.subType!!.scopedNameString+"() " } else { val str = if (node.void) "void " else ""