allow struct initializers to occur in array literals

This commit is contained in:
Irmen de Jong
2025-09-14 13:29:16 +02:00
parent d285d37fdb
commit 8b48a295b6
24 changed files with 245 additions and 154 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -72,7 +72,9 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
val newArray = mutableListOf<IRStArrayElement>()
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<Pair<DataType,
val elt = convertArrayElt(value)
IRStructInitValue(field.first.base, elt)
}
return IRStStructInstance(instance.name, instance.structName, values, instance.size)
return if('.' in instance.name)
IRStStructInstance(instance.name, instance.structName, values, instance.size)
else
IRStStructInstance("${StStructInstanceBlockName}.${instance.name}", instance.structName, values, instance.size)
}
internal const val StMemorySlabPrefix = "prog8_slabs" // TODO also add ".prog8_memoryslab_" ?

View File

@@ -8,7 +8,7 @@ import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.compiler.CallGraph
@@ -93,7 +93,7 @@ class UnusedCodeRemover(private val program: Program,
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
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)
}

View File

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

View File

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

View File

@@ -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 */ }
}
}

View File

@@ -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<FileSystemException> { 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()
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<VarDecl, Int>()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String, Encoding>
typealias StArray = List<StArrayElement>
const val StMemorySlabBlockName = "prog8_slabs"
const val StStructInstanceBlockName = "prog8_struct_instances"

View File

@@ -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<StArrayElement> {
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<StNode>): List<StArrayElement> {
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")
}