diff --git a/codeCore/src/prog8/code/Globals.kt b/codeCore/src/prog8/code/Globals.kt index 3fec9897d..297a0cbaf 100644 --- a/codeCore/src/prog8/code/Globals.kt +++ b/codeCore/src/prog8/code/Globals.kt @@ -5,9 +5,10 @@ import java.nio.file.Path import kotlin.io.path.absolute -// the automatically generated module where all string literals are interned to: const val INTERNED_STRINGS_MODULENAME = "prog8_interned_strings" +val PROG8_CONTAINER_MODULES = arrayOf(INTERNED_STRINGS_MODULENAME) // option to add more if needed one day + // all automatically generated labels everywhere need to have the same label name prefix: const val GENERATED_LABEL_PREFIX = "p8_label_gen_" diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index 5bb5ff046..fdbbcfd2c 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -252,6 +252,14 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable { newValue.add(newAddr) } } + is PtBuiltinFunctionCall -> { + // could be a struct instance or memory slab "allocation" + if (elt.name != "prog8_lib_structalloc" && elt.name != "memory") + throw AssemblyError("weird array value element $elt") + else { + newValue.add(elt) + } + } else -> throw AssemblyError("weird array value element $elt") } } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt index 83fee7fbd..90ea4fcf8 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt @@ -1,5 +1,7 @@ package prog8.codegen.cpu6502 +import prog8.code.StMemorySlabBlockName +import prog8.code.StStructInstanceBlockName import prog8.code.SymbolTable import prog8.code.ast.* import prog8.code.core.* @@ -385,7 +387,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, val name = (fcall.args[0] as PtString).value require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"} - val slabname = PtIdentifier("prog8_slabs.prog8_memoryslab_$name", DataType.UWORD, fcall.position) + val slabname = PtIdentifier("$StMemorySlabBlockName.memory_$name", DataType.UWORD, fcall.position) val addressOf = PtAddressOf(DataType.pointer(BaseDataType.UBYTE), false, fcall.position) addressOf.add(slabname) addressOf.parent = fcall @@ -399,9 +401,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, if(discardResult) throw AssemblyError("should not discard result of struct allocation at $fcall") // ... don't need to pay attention to args here because struct instance is put together elsewhere we just have to get a pointer to it - val slabname = SymbolTable.labelnameForStructInstance(fcall) + val prefix = if(fcall.args.isEmpty()) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName + val labelname = PtIdentifier("$prefix.${SymbolTable.labelnameForStructInstance(fcall)}", fcall.type, fcall.position) val addressOf = PtAddressOf(fcall.type, true, fcall.position) - addressOf.add(PtIdentifier(slabname, fcall.type, fcall.position)) + addressOf.add(labelname) addressOf.parent = fcall val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, fcall.type, expression = addressOf) val target = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, null, asmgen) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt index a561b8d39..90f7140d7 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt @@ -210,7 +210,7 @@ internal class ProgramAndVarsGen( private fun memorySlabs() { if(symboltable.allMemorySlabs.isNotEmpty()) { asmgen.out("; memory slabs\n .section BSS_SLABS") - asmgen.out("prog8_slabs\t.block") + asmgen.out("$StMemorySlabBlockName\t.block") for (slab in symboltable.allMemorySlabs) { if (slab.align > 1u) asmgen.out("\t.align ${slab.align.toHex()}") @@ -434,6 +434,7 @@ internal class ProgramAndVarsGen( val (instancesNoInit, instances) = symboltable.allStructInstances.partition { it.initialValues.isEmpty() } asmgen.out("; struct instances without initialization values, as BSS zeroed at startup\n") asmgen.out(" .section BSS\n") + asmgen.out("${StStructInstanceBlockName}_bss .block\n") instancesNoInit.forEach { val structtype: StStruct = symboltable.lookup(it.structName) as StStruct val zerovalues = structtype.fields.map { field -> @@ -445,11 +446,17 @@ internal class ProgramAndVarsGen( } asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n") } + asmgen.out(" .endblock\n") asmgen.out(" .send BSS\n") asmgen.out("; struct instances with initialization values\n") asmgen.out(" .section STRUCTINSTANCES\n") - instances.forEach { asmgen.out("${it.name} .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n") } + asmgen.out("$StStructInstanceBlockName .block\n") + instances.forEach { + val instancename = it.name.substringAfter('.') + asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n") + } + asmgen.out(" .endblock\n") asmgen.out(" .send STRUCTINSTANCES\n") } @@ -866,7 +873,7 @@ internal class ProgramAndVarsGen( } } dt.isSplitWordArray -> { - if(dt.elementType().isUnsignedWord) { + if(dt.elementType().isUnsignedWord || dt.elementType().isPointer) { val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros) asmgen.out("_array_$varname := ${data.joinToString()}") asmgen.out("${varname}_lsb\t.byte <_array_$varname") @@ -914,7 +921,7 @@ internal class ProgramAndVarsGen( private fun zeroFilledArray(numElts: Int): StArray { val values = mutableListOf() repeat(numElts) { - values.add(StArrayElement(0.0, null, null)) + values.add(StArrayElement(0.0, null, null,null,null)) } return values } @@ -972,7 +979,7 @@ internal class ProgramAndVarsGen( val number = it.number!!.toInt() "$"+number.toString(16).padStart(2, '0') } - dt.isArray && dt.elementType().isUnsignedWord -> array.map { + dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map { if(it.number!=null) { "$" + it.number!!.toInt().toString(16).padStart(4, '0') } @@ -984,8 +991,15 @@ internal class ProgramAndVarsGen( else asmgen.asmSymbolName(addrOfSymbol) } - else + else if(it.structInstance!=null) { + asmgen.asmSymbolName("${StStructInstanceBlockName}.${it.structInstance!!}") + } + else if(it.structInstanceUninitialized!=null) { + asmgen.asmSymbolName("${StStructInstanceBlockName}_bss.${it.structInstanceUninitialized!!}") + } + else { throw AssemblyError("weird array elt") + } } else -> throw AssemblyError("invalid dt") } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index 3659a2a30..567b87f43 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -1,5 +1,7 @@ package prog8.codegen.intermediate +import prog8.code.StMemorySlabBlockName +import prog8.code.StStructInstanceBlockName import prog8.code.SymbolTable import prog8.code.ast.* import prog8.code.core.AssemblyError @@ -500,7 +502,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val name = (call.args[0] as PtString).value val code = IRCodeChunk(null, null) val resultReg = codeGen.registers.next(IRDataType.WORD) - code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabPrefix.prog8_memoryslab_$name") + code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabBlockName.memory_$name") return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1) } @@ -508,7 +510,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val code = IRCodeChunk(null, null) val resultReg = codeGen.registers.next(IRDataType.WORD) val labelname = SymbolTable.labelnameForStructInstance(call) - code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = labelname) + code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "${StStructInstanceBlockName}.$labelname") return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1) } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/StConvert.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/StConvert.kt index 9ad5e86a5..8e519fe80 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/StConvert.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/StConvert.kt @@ -72,7 +72,9 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable { val newArray = mutableListOf() array.forEach { if(it.addressOfSymbol!=null) { - val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}") + println("LOOKUP ${it.addressOfSymbol}") + val target = variable.lookup(it.addressOfSymbol!!) ?: + throw NoSuchElementException("can't find variable ${it.addressOfSymbol}") newArray.add(IRStArrayElement(null, null, target.scopedNameString)) } else { newArray.add(convertArrayElt(it)) @@ -129,11 +131,11 @@ private fun convert(constant: StConstant): IRStConstant { } -private fun convert(variable: StMemorySlab): IRStMemorySlab { - return if('.' in variable.name) - IRStMemorySlab(variable.name, variable.size, variable.align) +private fun convert(mem: StMemorySlab): IRStMemorySlab { + return if('.' in mem.name) + IRStMemorySlab(mem.name, mem.size, mem.align) else - IRStMemorySlab("$StMemorySlabPrefix.${variable.name}", variable.size, variable.align) + IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align) } @@ -142,8 +144,8 @@ private fun convert(instance: StStructInstance, fields: Iterable { if("force_output" !in block.options()) { if (block.containsNoCodeNorVars) { - if (block.name != INTERNED_STRINGS_MODULENAME && "ignore_unused" !in block.options()) { + if (block.name !in PROG8_CONTAINER_MODULES && "ignore_unused" !in block.options()) { if (!block.statements.any { it is Subroutine && it.hasBeenInlined }) errors.info("removing unused block '${block.name}'", block.position) } diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 2a70ec7a5..7118e08ab 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -5,6 +5,7 @@ import prog8.ast.* import prog8.ast.expressions.Expression import prog8.ast.expressions.NumericLiteral import prog8.ast.statements.Directive +import prog8.code.SymbolTable import prog8.code.SymbolTableMaker import prog8.code.ast.PtProgram import prog8.code.ast.printAst @@ -176,17 +177,21 @@ fun compileProgram(args: CompilerArguments): CompilationResult? { println("*********** COMPILER AST END *************\n") } + var symbolTable: SymbolTable + val (intermediateAst, simplifiedAstDuration2) = measureTimedValue { val intermediateAst = SimplifiedAstMaker(program, args.errors).transform() val stMaker = SymbolTableMaker(intermediateAst, compilationOptions) - val symbolTable = stMaker.make() + symbolTable = stMaker.make() postprocessSimplifiedAst(intermediateAst, symbolTable, compilationOptions, args.errors) args.errors.report() + symbolTable = stMaker.make() // need an updated ST because the postprocessing changes stuff if (compilationOptions.optimize) { optimizeSimplifiedAst(intermediateAst, compilationOptions, symbolTable, args.errors) args.errors.report() + symbolTable = stMaker.make() // need an updated ST because the optimization changes stuff } if (args.printAst2) { @@ -204,6 +209,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? { createAssemblyDuration = measureTime { if (!createAssemblyAndAssemble( intermediateAst, + symbolTable, args.errors, compilationOptions, program.generatedLabelSequenceNumber @@ -558,6 +564,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt } private fun createAssemblyAndAssemble(program: PtProgram, + symbolTable: SymbolTable, errors: IErrorReporter, compilerOptions: CompilationOptions, lastGeneratedLabelSequenceNr: Int @@ -572,10 +579,6 @@ private fun createAssemblyAndAssemble(program: PtProgram, else throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.cpu}") - // need to make a new symboltable here to capture possible changes made by optimization steps performed earlier! - val stMaker = SymbolTableMaker(program, compilerOptions) - val symbolTable = stMaker.make() - val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors) errors.report() diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 9aa8cd2d7..932f8b2f0 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -1277,11 +1277,6 @@ internal class AstChecker(private val program: Program, errors.err("initialization value contains non-constant elements", array.value[0].position) } - if(array.value.any { it is StaticStructInitializer }) { - 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/SimplifiedAstPostprocess.kt b/compiler/src/prog8/compiler/astprocessing/SimplifiedAstPostprocess.kt index 935e5dd7b..d9feb1820 100644 --- a/compiler/src/prog8/compiler/astprocessing/SimplifiedAstPostprocess.kt +++ b/compiler/src/prog8/compiler/astprocessing/SimplifiedAstPostprocess.kt @@ -21,6 +21,8 @@ internal fun postprocessSimplifiedAst( private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable) { fun getStStruct(subType: ISubType): StStruct { + if(subType is StStruct) + return subType val stNode = st.lookup(subType.scopedNameString) as? StStruct if(stNode != null) return stNode @@ -28,7 +30,7 @@ private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable) throw FatalAstException("cannot find in ST: ${subType.scopedNameString} $subType") } - fun fixSubtype(type: DataType) { + fun fixSubtypeIntoStType(type: DataType) { if(type.subType!=null && type.subType !is StStruct) { type.subType = getStStruct(type.subType!!) } @@ -36,13 +38,21 @@ private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable) fun fixSubtypes(node: PtNode) { when(node) { - is IPtVariable -> fixSubtype(node.type) - is PtPointerDeref -> fixSubtype(node.type) - is PtStructDecl -> node.fields.forEach { fixSubtype(it.first) } - is PtAsmSub -> node.returns.forEach { fixSubtype(it.second) } - is PtExpression -> fixSubtype(node.type) - is PtSubSignature -> node.returns.forEach { fixSubtype(it) } - is PtSubroutineParameter -> fixSubtype(node.type) + is IPtVariable -> { + fixSubtypeIntoStType(node.type) + // if it's an array, fix the subtypes of its elements as well + if(node.type.isArray && node is PtVariable) { + (node.value as? PtArray)?.let {array -> + array.children.forEach { fixSubtypes(it) } + } + } + } + is PtPointerDeref -> fixSubtypeIntoStType(node.type) + is PtStructDecl -> node.fields.forEach { fixSubtypeIntoStType(it.first) } + is PtAsmSub -> node.returns.forEach { fixSubtypeIntoStType(it.second) } + is PtExpression -> fixSubtypeIntoStType(node.type) + is PtSubSignature -> node.returns.forEach { fixSubtypeIntoStType(it) } + is PtSubroutineParameter -> fixSubtypeIntoStType(node.type) else -> { /* has no datatype */ } } } diff --git a/compiler/test/ModuleImporterTests.kt b/compiler/test/ModuleImporterTests.kt index 4b743130c..f6ea5b66f 100644 --- a/compiler/test/ModuleImporterTests.kt +++ b/compiler/test/ModuleImporterTests.kt @@ -10,6 +10,7 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import prog8.ast.Program import prog8.code.INTERNED_STRINGS_MODULENAME +import prog8.code.PROG8_CONTAINER_MODULES import prog8.code.core.IErrorReporter import prog8.code.source.SourceCode import prog8.compiler.ModuleImporter @@ -49,7 +50,7 @@ class TestModuleImporter: FunSpec({ withClue(".file should point to specified path") { error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}" } - program.modules.size shouldBe 1 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size val error2 = importer.importMainModule(srcPathAbs).getErrorOrElse { error("should have import error") } withClue(".file should be normalized") { "${error2.file}" shouldBe "${error2.file.normalize()}" @@ -57,7 +58,7 @@ class TestModuleImporter: FunSpec({ withClue(".file should point to specified path") { error2.file.absolutePath shouldBe "${srcPathAbs.normalize()}" } - program.modules.size shouldBe 1 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size } test("testDirectory") { @@ -75,7 +76,7 @@ class TestModuleImporter: FunSpec({ it.file.absolutePath shouldBe "${srcPathAbs.normalize()}" } } - program.modules.size shouldBe 1 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size shouldThrow { importer.importMainModule(srcPathAbs) } .let { @@ -86,7 +87,7 @@ class TestModuleImporter: FunSpec({ it.file.absolutePath shouldBe "${srcPathAbs.normalize()}" } } - program.modules.size shouldBe 1 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size } } @@ -101,7 +102,7 @@ class TestModuleImporter: FunSpec({ val path = assumeReadableFile(searchIn[0], fileName) val module = importer.importMainModule(path.absolute()).getOrElse { throw it } - program.modules.size shouldBe 2 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1 module shouldBeIn program.modules module.program shouldBe program } @@ -118,7 +119,7 @@ class TestModuleImporter: FunSpec({ } val module = importer.importMainModule(path).getOrElse { throw it } - program.modules.size shouldBe 2 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1 module shouldBeIn program.modules module.program shouldBe program } @@ -131,7 +132,7 @@ class TestModuleImporter: FunSpec({ assumeReadableFile(searchIn, path) val module = importer.importMainModule(path).getOrElse { throw it } - program.modules.size shouldBe 2 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1 module shouldBeIn program.modules module.program shouldBe program } @@ -152,7 +153,7 @@ class TestModuleImporter: FunSpec({ withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 } } } - program.modules.size shouldBe 1 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size } } @@ -170,9 +171,10 @@ class TestModuleImporter: FunSpec({ withClue("line; should be 1-based") { it.position.line shouldBe 2 } withClue("startCol; should be 0-based") { it.position.startCol shouldBe 4 } withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 } + } } - } - withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 } + withClue("imported module with error in it should not be present") { program.modules.size shouldBe PROG8_CONTAINER_MODULES.size } + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME } } @@ -203,14 +205,14 @@ class TestModuleImporter: FunSpec({ withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false } errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist" errors.report() - program.modules.size shouldBe 1 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size val result2 = importer.importImplicitLibraryModule(filenameWithExt) withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null } withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false } errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8" errors.report() - program.modules.size shouldBe 1 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size } } } @@ -232,7 +234,7 @@ class TestModuleImporter: FunSpec({ withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 } } } - program.modules.size shouldBe 1 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size } } @@ -254,7 +256,7 @@ class TestModuleImporter: FunSpec({ withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 } } } - withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 } + withClue("imported module with error in it should not be present") { program.modules.size shouldBe PROG8_CONTAINER_MODULES.size } program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME importer.errors.report() } diff --git a/compiler/test/TestPointers.kt b/compiler/test/TestPointers.kt index 37656f3c6..b8c2044ef 100644 --- a/compiler/test/TestPointers.kt +++ b/compiler/test/TestPointers.kt @@ -1731,6 +1731,30 @@ main { compileText(Cx16Target(), false, src, outputDir) shouldNotBe null } + test("struct initializers in array") { + val src=""" +main { + struct Node { + ubyte id + str name + uword array + } + + sub start() { + ^^Node[] @shared nodes = [ + ^^Node:[1,"one", 1000 ], + ^^Node:[2,"two", 2000 ], + ^^Node:[3,"three", 3000], + ^^Node:[], + ^^Node:[], + ^^Node:[], + ] + } +}""" + compileText(C64Target(), false, src, outputDir) shouldNotBe null + compileText(VMTarget(), false, src, outputDir) shouldNotBe null + } + test("type error for invalid bool field initializer") { val src=""" main { diff --git a/compiler/test/TestSymbolTable.kt b/compiler/test/TestSymbolTable.kt index aca819134..fe8fbc1e7 100644 --- a/compiler/test/TestSymbolTable.kt +++ b/compiler/test/TestSymbolTable.kt @@ -88,8 +88,8 @@ class TestSymbolTable: FunSpec({ val stVar1 = StStaticVariable("initialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0u, false,node) stVar1.setOnetimeInitNumeric(99.0) val stVar2 = StStaticVariable("uninitialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0u, false, node) - val arrayInitNonzero = listOf(StArrayElement(1.1, null, null), StArrayElement(2.2, null, null), StArrayElement(3.3, null, null)) - val arrayInitAllzero = listOf(StArrayElement(0.0, null, null), StArrayElement(0.0, null, null), StArrayElement(0.0, null, null)) + val arrayInitNonzero = listOf(StArrayElement(1.1, null, null, null, null), StArrayElement(2.2, null, null, null, null), StArrayElement(3.3, null, null,null, null)) + val arrayInitAllzero = listOf(StArrayElement(0.0, null, null, null, null), StArrayElement(0.0, null, null,null, null), StArrayElement(0.0, null, null,null, null)) val stVar3 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitNonzero, 3u, ZeropageWish.DONTCARE, 0u, false, node) val stVar4 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitAllzero, 3u, ZeropageWish.DONTCARE, 0u, false, node) val stVar5 = StStaticVariable("uninitialized", DataType.arrayFor(BaseDataType.UWORD), null, null, 3u, ZeropageWish.DONTCARE, 0u, false, node) diff --git a/compiler/test/ast/TestAstToSourceText.kt b/compiler/test/ast/TestAstToSourceText.kt index 16de7c08d..43233220e 100644 --- a/compiler/test/ast/TestAstToSourceText.kt +++ b/compiler/test/ast/TestAstToSourceText.kt @@ -5,7 +5,7 @@ import io.kotest.matchers.string.shouldContain import prog8.ast.AstToSourceTextConverter import prog8.ast.Module import prog8.ast.Program -import prog8.code.INTERNED_STRINGS_MODULENAME +import prog8.code.PROG8_CONTAINER_MODULES import prog8.code.source.SourceCode import prog8.parser.ParseError import prog8.parser.Prog8Parser.parseModule @@ -38,10 +38,12 @@ class TestAstToSourceText: AnnotationSpec() { } @Test - fun testMentionsInternedStringsModule() { + fun testMentionsProg8ContainerModules() { val orig = SourceCode.Text("\n") val (txt, _) = roundTrip(parseModule(orig)) - txt shouldContain Regex(";.*$INTERNED_STRINGS_MODULENAME") + PROG8_CONTAINER_MODULES.forEach { + txt shouldContain Regex(";.*$it") + } } @Test diff --git a/compiler/test/ast/TestProgram.kt b/compiler/test/ast/TestProgram.kt index d48fb5b4b..24b9a4b22 100644 --- a/compiler/test/ast/TestProgram.kt +++ b/compiler/test/ast/TestProgram.kt @@ -13,10 +13,11 @@ import io.kotest.matchers.types.shouldBeSameInstanceAs import prog8.ast.Module import prog8.ast.Program import prog8.ast.statements.Block +import prog8.code.INTERNED_STRINGS_MODULENAME +import prog8.code.PROG8_CONTAINER_MODULES import prog8.code.ast.PtBlock import prog8.code.core.Position import prog8.code.source.SourceCode -import prog8.code.INTERNED_STRINGS_MODULENAME import prog8.code.target.C64Target import prog8tests.helpers.DummyFunctions import prog8tests.helpers.DummyMemsizer @@ -30,7 +31,7 @@ class TestProgram: FunSpec({ context("Constructor") { test("withNameBuiltinsAndMemsizer") { val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder) - program.modules.size shouldBe 1 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME program.modules[0].program shouldBeSameInstanceAs program program.modules[0].parent shouldBeSameInstanceAs program.namespace @@ -45,7 +46,7 @@ class TestProgram: FunSpec({ val retVal = program.addModule(m1) retVal shouldBeSameInstanceAs program - program.modules.size shouldBe 2 + program.modules.size shouldBe PROG8_CONTAINER_MODULES.size + 1 m1 shouldBeIn program.modules m1.program shouldBeSameInstanceAs program m1.parent shouldBeSameInstanceAs program.namespace @@ -163,7 +164,7 @@ datablock2 ${'$'}8000 { val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=true)!! result.compilerAst.allBlocks.size shouldBeGreaterThan 5 - result.compilerAst.modules.drop(2).all { it.isLibrary } shouldBe true + result.compilerAst.modules.drop(PROG8_CONTAINER_MODULES.size+1).all { it.isLibrary } shouldBe true val mainMod = result.compilerAst.modules[0] mainMod.name shouldStartWith "on_the_fly" result.compilerAst.modules[1].name shouldBe "prog8_interned_strings" @@ -183,7 +184,7 @@ datablock2 ${'$'}8000 { blocks[1].name shouldBe "p8_sys_startup" blocks[2].name shouldBe "p8b_otherblock1" blocks[3].name shouldBe "p8b_otherblock2" - blocks[4].name shouldBe "prog8_interned_strings" + blocks[4].name shouldBe INTERNED_STRINGS_MODULENAME blocks[5].name shouldBe "txt" blocks[5].library shouldBe true blocks[13].name shouldBe "p8b_datablock2" diff --git a/compilerAst/src/prog8/ast/Program.kt b/compilerAst/src/prog8/ast/Program.kt index 6214cec07..dc56a960e 100644 --- a/compilerAst/src/prog8/ast/Program.kt +++ b/compilerAst/src/prog8/ast/Program.kt @@ -6,6 +6,7 @@ import prog8.ast.statements.* import prog8.ast.walk.IAstVisitor import prog8.code.GENERATED_LABEL_PREFIX import prog8.code.INTERNED_STRINGS_MODULENAME +import prog8.code.PROG8_CONTAINER_MODULES import prog8.code.core.* import prog8.code.source.SourceCode @@ -22,17 +23,19 @@ class Program(val name: String, val namespace: GlobalNamespace = GlobalNamespace(_modules) init { - // insert a container module for all interned strings later - val internedStringsModule = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(INTERNED_STRINGS_MODULENAME)) - val block = Block(INTERNED_STRINGS_MODULENAME, null, mutableListOf(), true, Position.DUMMY) - val directive = Directive("%option", listOf(DirectiveArg("no_symbol_prefixing", null, Position.DUMMY)), Position.DUMMY) - block.statements.add(directive) - directive.linkParents(block) - internedStringsModule.statements.add(block) + // insert container modules for all interned strings and struct instances + PROG8_CONTAINER_MODULES.forEach { containername -> + val module = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(containername)) + val block = Block(containername, null, mutableListOf(), true, Position.DUMMY) + val directive = Directive("%option", listOf(DirectiveArg("no_symbol_prefixing", null, Position.DUMMY)), Position.DUMMY) + block.statements.add(directive) + directive.linkParents(block) + module.statements.add(block) - _modules.add(0, internedStringsModule) - internedStringsModule.linkParents(namespace) - internedStringsModule.program = this + _modules.add(0, module) + module.linkParents(namespace) + module.program = this + } } fun addModule(module: Module): Program { @@ -67,7 +70,7 @@ class Program(val name: String, } val toplevelModule: Module - get() = modules.first { it.name!= INTERNED_STRINGS_MODULENAME } + get() = modules.first { it.name !in PROG8_CONTAINER_MODULES } private val internedStringsReferenceCounts = mutableMapOf() diff --git a/docs/source/conf.py b/docs/source/conf.py index df180951e..d468c8d63 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -52,10 +52,7 @@ needs_sphinx = '5.3' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ 'sphinxcontrib.jquery', 'sphinx_rtd_dark_mode'] - -# user starts in light mode -default_dark_mode = False +extensions = [ 'sphinxcontrib.jquery', 'sphinx_rtd_dark_mode', 'sphinx.ext.imgconverter'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -177,6 +174,9 @@ texinfo_documents = [ # -- Extension configuration ------------------------------------------------- +# user starts in light mode +default_dark_mode = False + # -- Options for to do extension ---------------------------------------------- # todo_include_todos = True diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 7dbf89052..bb54b2df4 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -58,7 +58,8 @@ and for example the below code omits line 5:: STRUCTS and TYPED POINTERS -------------------------- -- allow struct initialization syntax in an array such as [ ^^Node:[], ^^Node:[], ^^Node:[] ], update sorting example to use list of countries like that +- can we have some syntactic sugar to avoid the struct name pointer prefix for all array elements that are a struct instance? +- fix VM so that pointers/sorting.p8 example works again (it worked when adding the struct instances in a loop, no longer now that they're static) - fix code size regressions (if any left) - optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0 - update structpointers.rst docs with 6502 specific things? @@ -79,6 +80,7 @@ STRUCTS and TYPED POINTERS Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ +- allow memory() to occur in array initializer - %breakpoint after an assignment is parsed as part of the expression (x % breakpoint), that should not happen - when a complete block is removed because unused, suppress all info messages about everything in the block being removed - fix the line, cols in Position, sometimes they count from 0 sometimes from 1 diff --git a/docs/source/variables.rst b/docs/source/variables.rst index 45d28a3b5..ccee4fc54 100644 --- a/docs/source/variables.rst +++ b/docs/source/variables.rst @@ -282,9 +282,10 @@ For instance ``30_000.999_999`` is a valid floating point number 30000.999999. Arrays ^^^^^^ -Arrays can be created from a list of booleans, bytes, words, floats, or addresses of other variables -(such as explicit address-of expressions, strings, or other array variables) - values in an array literal -always have to be constants. A trailing comma is allowed, sometimes this is easier when copying values +Arrays can be created from a list of booleans, bytes, words, floats, addresses of other variables +(such as explicit address-of expressions, strings, or other array variables), and struct initializers. +The values in an array literal always have to be constants. +A trailing comma is allowed, sometimes this is easier when copying values or when adding more stuff to the array later. Here are some examples of arrays:: byte[10] array ; array of 10 bytes, initially set to 0 diff --git a/examples/pointers/sorting.p8 b/examples/pointers/sorting.p8 index 5329dbb30..4d39298aa 100644 --- a/examples/pointers/sorting.p8 +++ b/examples/pointers/sorting.p8 @@ -11,34 +11,32 @@ main{ uword area ; 1000 km^2 } - ^^Country[100] countries ; won't be fully filled - ubyte num_countries + ^^Country[] countries = [ + ^^Country:["Indonesia", 285.72, 1904], + ^^Country:["Congo", 112.83, 2344], + ^^Country:["Vietnam", 101.60, 331], + ^^Country:["United States", 347.28, 9372], + ^^Country:["Iran", 92.42, 1648], + ^^Country:["Turkey", 87.69, 783], + ^^Country:["Brazil", 212.81, 8515], + ^^Country:["Bangladesh", 175.69, 147], + ^^Country:["Germany", 84.08, 357], + ^^Country:["Japan", 123.10, 377], + ^^Country:["India", 1463.87, 3287], + ^^Country:["China", 1416.10, 9596], + ^^Country:["Philippines", 116.79, 300], + ^^Country:["Russia", 143.99, 17098], + ^^Country:["Pakistan", 255.22, 881], + ^^Country:["Nigeria", 237.53, 923], + ^^Country:["Ethiopia", 135.47, 1104], + ^^Country:["Mexico", 131.95, 1964], + ^^Country:["Thailand", 71.62, 513], + ^^Country:["Egypt", 118.37, 1002], + ] sub start() { txt.lowercase() - ; 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() @@ -57,7 +55,7 @@ main{ sub sort_by_name() { ; stupid slow bubble sort - ubyte n = num_countries + ubyte n = len(countries) do { ubyte newn=0 ubyte i @@ -73,7 +71,7 @@ main{ sub sort_by_population() { ; stupid slow bubble sort - ubyte n = num_countries + ubyte n = len(countries) do { ubyte newn=0 ubyte i @@ -89,7 +87,7 @@ main{ sub sort_by_area() { ; stupid slow bubble sort - ubyte n = num_countries + ubyte n = len(countries) do { ubyte newn=0 ubyte i @@ -125,10 +123,5 @@ main{ txt.nl() } } - - sub add(^^Country c) { - countries[num_countries] = c - num_countries++ - } } diff --git a/examples/test.p8 b/examples/test.p8 index 0c6547ea1..3c63ba15d 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -8,19 +8,20 @@ main { uword array } - ^^Node @shared @zp node = 2000 - sub start() { ^^Node[] nodes = [ ^^Node:[1,"one", 1000 ], ^^Node:[2,"two", 2000 ], - ^^Node:[3,"three", 3000] + ^^Node:[3,"three", 3000], + ^^Node:[], + ^^Node:[], + ^^Node:[], ] - txt.print_uw(nodes[0]) - txt.spc() - txt.print_uw(nodes[1]) - txt.spc() - txt.print_uw(nodes[2]) + + for cx16.r0 in nodes { + txt.print_uw(cx16.r0) + txt.spc() + } txt.nl() } } diff --git a/intermediate/src/prog8/intermediate/IRSymbolTable.kt b/intermediate/src/prog8/intermediate/IRSymbolTable.kt index dd54afbe3..99846cf6e 100644 --- a/intermediate/src/prog8/intermediate/IRSymbolTable.kt +++ b/intermediate/src/prog8/intermediate/IRSymbolTable.kt @@ -1,6 +1,6 @@ package prog8.intermediate -import prog8.code.INTERNED_STRINGS_MODULENAME +import prog8.code.PROG8_CONTAINER_MODULES import prog8.code.core.BaseDataType import prog8.code.core.DataType import prog8.code.core.Encoding @@ -49,10 +49,9 @@ class IRSymbolTable { val prefix = "$label." val vars = table.filter { it.key.startsWith(prefix) } vars.forEach { - // check if attempt is made to delete interned strings, if so, refuse that. - if(!it.key.startsWith(INTERNED_STRINGS_MODULENAME)) { + // check if attempt is made to delete fixed modules, if so, refuse that. + if(!PROG8_CONTAINER_MODULES.any { containername -> it.key.startsWith(containername)}) table.remove(it.key) - } } } diff --git a/simpleAst/src/prog8/code/SymbolTable.kt b/simpleAst/src/prog8/code/SymbolTable.kt index f3ca0c350..a6dd648e4 100644 --- a/simpleAst/src/prog8/code/SymbolTable.kt +++ b/simpleAst/src/prog8/code/SymbolTable.kt @@ -123,7 +123,7 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL val scopehash = call.parent.hashCode().toUInt().toString(16) val pos = "${call.position.line}_${call.position.startCol}" val hash = call.position.file.hashCode().toUInt().toString(16) - return "prog8_struct_${structname.replace('.', '_')}_${hash}_${pos}_${scopehash}" + return "${structname.replace('.', '_')}_${hash}_${pos}_${scopehash}" } } } @@ -338,13 +338,18 @@ class StExtSub(name: String, class StSubroutineParameter(val name: String, val type: DataType, val register: RegisterOrPair?) class StExtSubParameter(val register: RegisterOrStatusflag, val type: DataType) -class StArrayElement(val number: Double?, val addressOfSymbol: String?, val boolean: Boolean?) { +class StArrayElement(val number: Double?, val addressOfSymbol: String?, val structInstance: String?, val structInstanceUninitialized: String?, val boolean: Boolean?) { init { - if(number!=null) require(addressOfSymbol==null && boolean==null) - if(addressOfSymbol!=null) require(number==null && boolean==null) - if(boolean!=null) require(addressOfSymbol==null && number==null) + if(number!=null) require(addressOfSymbol==null && boolean==null && structInstance==null && structInstanceUninitialized==null) + if(addressOfSymbol!=null) require(number==null && boolean==null && structInstance==null && structInstanceUninitialized==null) + if(structInstance!=null) require(number==null && boolean==null && addressOfSymbol==null && structInstanceUninitialized==null) + if(structInstanceUninitialized!=null) require(number==null && boolean==null && addressOfSymbol==null && structInstance==null) + if(boolean!=null) require(addressOfSymbol==null && number==null &&structInstance==null && structInstanceUninitialized==null) } } typealias StString = Pair typealias StArray = List + +const val StMemorySlabBlockName = "prog8_slabs" +const val StStructInstanceBlockName = "prog8_struct_instances" diff --git a/simpleAst/src/prog8/code/SymbolTableMaker.kt b/simpleAst/src/prog8/code/SymbolTableMaker.kt index 449c699fa..1c298ac0c 100644 --- a/simpleAst/src/prog8/code/SymbolTableMaker.kt +++ b/simpleAst/src/prog8/code/SymbolTableMaker.kt @@ -82,7 +82,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp numElements = (value.value.length + 1).toUInt() // include the terminating 0-byte } is PtArray -> { - initialArray = makeInitialArray(value) + initialArray = makeInitialArray(value, scope) initialString = null initialNumeric = null numElements = initialArray.size.toUInt() @@ -119,22 +119,12 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp val size = (node.args[1] as PtNumber).number.toUInt() val align = (node.args[2] as PtNumber).number.toUInt() // 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)) + scope.first().add(StMemorySlab("memory_$slabname", size, align, node)) } else if(node.name=="prog8_lib_structalloc") { - val struct = node.type.subType!! - if(struct is StStruct) { - val label = SymbolTable.labelnameForStructInstance(node) - val initialValues = node.args.map { - when(it) { - is PtAddressOf -> StArrayElement(null, it.identifier!!.name, null) - is PtBool -> StArrayElement(null, null, it.value) - is PtNumber -> StArrayElement(it.number, null, null) - else -> throw AssemblyError("invalid structalloc argument type $it") - } - } - val scopedName = if(struct.astNode!=null) (struct.astNode as PtNamedNode).scopedName else struct.scopedNameString - scope.first().add(StStructInstance(label, scopedName, initialValues, struct.size, null)) + val instance = handleStructAllocation(node) + if(instance!=null) { + scope.first().add(instance) // don't add struct instances in nested scope, just put them in the top level of the ST } } null @@ -153,20 +143,50 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp scope.removeLast() } - private fun makeInitialArray(value: PtArray): List { + private fun handleStructAllocation(node: PtBuiltinFunctionCall): StStructInstance? { + val struct = node.type.subType as? StStruct ?: return null + val initialValues = node.args.map { + when(it) { + is PtAddressOf -> StArrayElement(null, it.identifier!!.name, null, null,null) + is PtBool -> StArrayElement(null, null, null, null, it.value) + is PtNumber -> StArrayElement(it.number, null, null, null, null) + else -> throw AssemblyError("invalid structalloc argument type $it") + } + } + val label = SymbolTable.labelnameForStructInstance(node) + val scopedStructName = if(struct.astNode!=null) (struct.astNode as PtNamedNode).scopedName else struct.scopedNameString + return StStructInstance(label, scopedStructName, initialValues, struct.size, null) + } + + private fun makeInitialArray(value: PtArray, scope: ArrayDeque): List { return value.children.map { when(it) { is PtAddressOf -> { when { it.isFromArrayElement -> TODO("address-of array element $it in initial array value") - else -> StArrayElement(null, it.identifier!!.name, null) + else -> StArrayElement(null, it.identifier!!.name, null, null,null) } } - is PtNumber -> StArrayElement(it.number, null, null) - is PtBool -> StArrayElement(null, null, it.value) + is PtNumber -> StArrayElement(it.number, null, null,null,null) + is PtBool -> StArrayElement(null, null, null,null,it.value) is PtBuiltinFunctionCall -> { - val labelname = SymbolTable.labelnameForStructInstance(it) - StArrayElement(null, labelname, null) + if(it.name=="prog8_lib_structalloc") { + val instance = handleStructAllocation(it) + if(instance==null) { + val label = SymbolTable.labelnameForStructInstance(it) + if (it.args.isEmpty()) + StArrayElement(null, null, null, label, null) + else + StArrayElement(null, null, label, null, null) + } else { + scope.first().add(instance) // don't add struct instances in nested scope, just put them in the top level of the ST + if (it.args.isEmpty()) + StArrayElement(null, null, null, instance.name, null) + else + StArrayElement(null, null, instance.name, null, null) + } + } else + TODO("support for initial array element via ${it.name} ${it.position}") } else -> throw AssemblyError("invalid array element $it") }