error messages and trying to improve support for struct allocs in arrays

added sorting example
This commit is contained in:
Irmen de Jong
2025-08-02 13:02:11 +02:00
parent bc58a25765
commit d4e83b28bb
13 changed files with 188 additions and 107 deletions

View File

@@ -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)

View File

@@ -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}")
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
)

View File

@@ -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)") {

View File

@@ -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)

View File

@@ -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++
}
}

View File

@@ -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++
; }
;}
;

View File

@@ -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.

View File

@@ -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")
}
}

View File

@@ -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 -> "<arrayindexer> ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
is PtBuiltinFunctionCall -> {
if(node.name=="structalloc") {
if(node.name=="prog8_lib_structalloc") {
node.type.subType!!.scopedNameString+"() <structalloc>"
} else {
val str = if (node.void) "void " else ""