diff --git a/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt b/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt index 343a69757..ec903f087 100644 --- a/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt +++ b/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt @@ -69,7 +69,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement { if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, vd.arrayindex()?.toAst(), vd.varname.text, - vd.structname?.text, + null, it.expression().toAst(), vd.ARRAYSIG() != null || vd.arrayindex() != null, false, @@ -77,6 +77,22 @@ private fun prog8Parser.StatementContext.toAst() : IStatement { ) } + structvarinitializer()?.let { + val vd = it.structvardecl() + return VarDecl( + VarDeclType.VAR, + DataType.STRUCT, + ZeropageWish.NOT_IN_ZEROPAGE, + null, + vd.varname.text, + vd.structname.text, + it.expression().toAst(), + isArray = false, + autogeneratedDontRemove = false, + position = it.toPosition() + ) + } + constdecl()?.let { val cvarinit = it.varinitializer() val vd = cvarinit.vardecl() @@ -86,7 +102,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement { if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, vd.arrayindex()?.toAst(), vd.varname.text, - vd.structname?.text, + null, cvarinit.expression().toAst(), vd.ARRAYSIG() != null || vd.arrayindex() != null, false, @@ -103,7 +119,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement { if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, vd.arrayindex()?.toAst(), vd.varname.text, - vd.structname?.text, + null, mvarinit.expression().toAst(), vd.ARRAYSIG() != null || vd.arrayindex() != null, false, @@ -576,7 +592,7 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl { if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, arrayindex()?.toAst(), varname.text, - structname?.text, + null, null, ARRAYSIG() != null || arrayindex() != null, false, diff --git a/compiler/src/prog8/ast/processing/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt index 405ed9fa3..0b50bed27 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -1202,22 +1202,22 @@ internal class AstChecker(private val program: Program, } val array = program.heap.get(value.heapId!!) - if(array.type !in ArrayDatatypes || array.array==null) + if(array.type !in ArrayDatatypes || (array.array==null && array.doubleArray==null)) throw FatalAstException("should have an array in the heapvar $array") val correct: Boolean when(type) { DataType.ARRAY_UB -> { - correct= array.array.all { it.integer!=null && it.integer in 0..255 } + correct= array.array?.all { it.integer!=null && it.integer in 0..255 } ?: false } DataType.ARRAY_B -> { - correct=array.array.all { it.integer!=null && it.integer in -128..127 } + correct=array.array?.all { it.integer!=null && it.integer in -128..127 } ?: false } DataType.ARRAY_UW -> { - correct=array.array.all { (it.integer!=null && it.integer in 0..65535) || it.addressOf!=null} + correct=array.array?.all { (it.integer!=null && it.integer in 0..65535) || it.addressOf!=null} ?: false } DataType.ARRAY_W -> { - correct=array.array.all { it.integer!=null && it.integer in -32768..32767 } + correct=array.array?.all { it.integer!=null && it.integer in -32768..32767 } ?: false } DataType.ARRAY_F -> correct = array.doubleArray!=null else -> throw AstException("invalid array type $type") diff --git a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt index ef06220d5..dbe9e1b4f 100644 --- a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt @@ -66,10 +66,15 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi // and include the original decl as well. if(decl.datatype==DataType.STRUCT) { if(decl.structHasBeenFlattened) - return decl // don't do this multiple times + return super.visit(decl) // don't do this multiple times + + if(decl.struct==null) { + checkResult.add(NameError("undefined struct type", decl.position)) + return super.visit(decl) + } if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes}) - return decl // a non-numeric member, not supported. proper error is given by AstChecker later + return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later val decls = decl.flattenStructMembers() decls.add(decl) diff --git a/compiler/src/prog8/ast/statements/AstStatements.kt b/compiler/src/prog8/ast/statements/AstStatements.kt index 9ea7dd5f2..e7f18573b 100644 --- a/compiler/src/prog8/ast/statements/AstStatements.kt +++ b/compiler/src/prog8/ast/statements/AstStatements.kt @@ -201,7 +201,9 @@ class VarDecl(val type: VarDeclType, arraysize?.linkParents(this) value?.linkParents(this) if(structName!=null) { - struct = definingScope().lookup(listOf(structName), this) as StructDecl + val structStmt = definingScope().lookup(listOf(structName), this) + if(structStmt!=null) + struct = definingScope().lookup(listOf(structName), this) as StructDecl } } diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index 14772be8f..e3f2723c4 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -8,23 +8,21 @@ import prog8.ast.expressions.ReferenceLiteralValue import prog8.ast.statements.StructDecl import prog8.ast.statements.VarDecl import prog8.ast.statements.ZeropageWish +import prog8.compiler.* import prog8.vm.RuntimeValue -import prog8.compiler.CompilerException -import prog8.compiler.HeapValues -import prog8.compiler.Zeropage -import prog8.compiler.ZeropageDepletedError import java.io.PrintStream import java.nio.file.Path class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) { - data class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?) + class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?, val uninitializedArraySize: Int?) + class Variable(val scopedname: String, val value: RuntimeValue, val params: VariableParameters) class ProgramBlock(val name: String, var address: Int?, val instructions: MutableList = mutableListOf(), - val variables: MutableList> = mutableListOf(), // names are fully scoped + val variables: MutableList = mutableListOf(), val memoryPointers: MutableMap> = mutableMapOf(), val labels: MutableMap = mutableMapOf(), // names are fully scoped val force_output: Boolean) @@ -50,16 +48,16 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap // allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP) var notAllocated = 0 for(block in blocks) { - val zpVariables = block.variables.filter { it.first in block.variablesMarkedForZeropage } + val zpVariables = block.variables.filter { it.scopedname in block.variablesMarkedForZeropage } if (zpVariables.isNotEmpty()) { - for ((varname, value, varparams) in zpVariables) { - if(varparams.zp==ZeropageWish.NOT_IN_ZEROPAGE || varparams.memberOfStruct!=null) + for (variable in zpVariables) { + if(variable.params.zp==ZeropageWish.NOT_IN_ZEROPAGE || variable.params.memberOfStruct!=null) throw CompilerException("zp conflict") try { - val address = zeropage.allocate(varname, value.type, null) - allocatedZeropageVariables[varname] = Pair(address, value.type) + val address = zeropage.allocate(variable.scopedname, variable.value.type, null) + allocatedZeropageVariables[variable.scopedname] = Pair(address, variable.value.type) } catch (x: ZeropageDepletedError) { - printWarning(x.toString() + " variable $varname type ${value.type}") + printWarning(x.toString() + " variable ${variable.scopedname} type ${variable.value.type}") notAllocated++ } } @@ -405,7 +403,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap if(decl.parent is StructDecl) return - val valueparams = VariableParameters(decl.zeropage, decl.struct) + val valueparams = VariableParameters(decl.zeropage, decl.struct, null) val value = when(decl.datatype) { in NumericDatatypes -> { RuntimeValue(decl.datatype, (decl.value as NumericLiteralValue).number) @@ -420,7 +418,15 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap val litval = (decl.value as? ReferenceLiteralValue) if(litval!=null && litval.heapId==null) throw CompilerException("array should already be in the heap") - RuntimeValue(decl.datatype, heapId = litval?.heapId ?: -999) + if(litval!=null){ + RuntimeValue(decl.datatype, heapId = litval.heapId) + } else { + // uninitialized array rather than one filled with zero + val value = RuntimeValue(decl.datatype, heapId=-999) + currentBlock.variables.add(Variable(scopedname, value, + VariableParameters(ZeropageWish.NOT_IN_ZEROPAGE, null, decl.arraysize!!.size()!!))) + return + } } DataType.STRUCT -> { // struct variables have been flattened already @@ -428,7 +434,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap } else -> throw CompilerException("weird datatype") } - currentBlock.variables.add(Triple(scopedname, value, valueparams)) + currentBlock.variables.add(Variable(scopedname, value, valueparams)) if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE) currentBlock.variablesMarkedForZeropage.add(scopedname) else if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE) @@ -522,12 +528,12 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}") out.println("%variables") - for ((vname, value, parameters) in blk.variables) { - if(parameters.zp==ZeropageWish.REQUIRE_ZEROPAGE) + for (variable in blk.variables) { + if(variable.params.zp==ZeropageWish.REQUIRE_ZEROPAGE) throw CompilerException("zp conflict") - val valuestr = value.toString() - val struct = if(parameters.memberOfStruct==null) "" else "struct=${parameters.memberOfStruct.name}" - out.println("$vname ${value.type.name.toLowerCase()} $valuestr zp=${parameters.zp} $struct") + val valuestr = variable.value.toString() + val struct = if(variable.params.memberOfStruct==null) "" else "struct=${variable.params.memberOfStruct.name}" + out.println("${variable.scopedname} ${variable.value.type.name.toLowerCase()} $valuestr zp=${variable.params.zp} s=$struct u=${variable.params.uninitializedArraySize}") } out.println("%end_variables") out.println("%memorypointers") diff --git a/compiler/src/prog8/compiler/target/c64/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/AsmGen.kt index c39a8fdc0..9eca48831 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -45,7 +45,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter // Convert invalid label names (such as "") to something that's allowed. val newblocks = mutableListOf() for(block in program.blocks) { - val newvars = block.variables.map { Triple(symname(it.first, block), it.second, it.third) }.toMutableList() + val newvars = block.variables.map { IntermediateProgram.Variable(symname(it.scopedname, block), it.value, it.params) }.toMutableList() val newvarsZeropaged = block.variablesMarkedForZeropage.map{symname(it, block)}.toMutableSet() val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap() val newinstructions = block.instructions.asSequence().map { @@ -238,20 +238,20 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter } // deal with zeropage variables - for((varname, value, parameters) in blk.variables) { - val sym = symname(blk.name+"."+varname, null) + for(variable in blk.variables) { + val sym = symname(blk.name+"."+variable.scopedname, null) val zpVar = program.allocatedZeropageVariables[sym] if(zpVar==null) { // This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space) - if(parameters.zp != ZeropageWish.NOT_IN_ZEROPAGE && - value.type in zeropage.allowedDatatypes - && value.type != DataType.FLOAT) { + if(variable.params.zp != ZeropageWish.NOT_IN_ZEROPAGE && + variable.value.type in zeropage.allowedDatatypes + && variable.value.type != DataType.FLOAT) { try { - val address = zeropage.allocate(sym, value.type, null) - out("$varname = $address\t; auto zp ${value.type}") + val address = zeropage.allocate(sym, variable.value.type, null) + out("${variable.scopedname} = $address\t; auto zp ${variable.value.type}") // make sure we add the var to the set of zpvars for this block - blk.variablesMarkedForZeropage.add(varname) - program.allocatedZeropageVariables[sym] = Pair(address, value.type) + blk.variablesMarkedForZeropage.add(variable.scopedname) + program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type) } catch (x: ZeropageDepletedError) { // leave it as it is. } @@ -259,7 +259,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter } else { // it was already allocated on the zp - out("$varname = ${zpVar.first}\t; zp ${zpVar.second}") + out("${variable.scopedname} = ${zpVar.first}\t; zp ${zpVar.second}") } } @@ -293,23 +293,23 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter } private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) { - val uniqueNames = block.variables.map { it.first }.toSet() + val uniqueNames = block.variables.map { it.scopedname }.toSet() if (uniqueNames.size != block.variables.size) throw AssemblyError("not all variables have unique names") // these are the non-zeropage variables. // first get all the flattened struct members, they MUST remain in order out("; flattened struct members") - val (structMembers, normalVars) = block.variables.partition { it.third.memberOfStruct!=null } - structMembers.forEach { vardecl2asm(it.first, it.second, it.third) } + val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null } + structMembers.forEach { vardecl2asm(it.scopedname, it.value, it.params) } // leave outsort the other variables by type out("; other variables sorted by type") - val sortedVars = normalVars.sortedBy { it.second.type } - for ((varname, value, parameters) in sortedVars) { - if(varname in block.variablesMarkedForZeropage) + val sortedVars = normalVars.sortedBy { it.value.type } + for (variable in sortedVars) { + if(variable.scopedname in block.variablesMarkedForZeropage) continue // skip the ones that belong in the zero page - vardecl2asm(varname, value, parameters) + vardecl2asm(variable.scopedname, variable.value, variable.params) } } @@ -329,55 +329,75 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter } DataType.ARRAY_UB -> { // unsigned integer byte arraysize - val data = makeArrayFillDataUnsigned(value) - if (data.size <= 16) - out("$varname\t.byte ${data.joinToString()}") - else { - out(varname) - for (chunk in data.chunked(16)) - out(" .byte " + chunk.joinToString()) + if(parameters.uninitializedArraySize!=null) { + out("$varname\t.fill ${parameters.uninitializedArraySize}") // uninitialized array + } else { + val data = makeArrayFillDataUnsigned(value) + if (data.size <= 16) + out("$varname\t.byte ${data.joinToString()}") + else { + out(varname) + for (chunk in data.chunked(16)) + out(" .byte " + chunk.joinToString()) + } } } DataType.ARRAY_B -> { // signed integer byte arraysize - val data = makeArrayFillDataSigned(value) - if (data.size <= 16) - out("$varname\t.char ${data.joinToString()}") - else { - out(varname) - for (chunk in data.chunked(16)) - out(" .char " + chunk.joinToString()) + if(parameters.uninitializedArraySize!=null) { + out("$varname\t.fill ${parameters.uninitializedArraySize}") // uninitialized array + } else { + val data = makeArrayFillDataSigned(value) + if (data.size <= 16) + out("$varname\t.char ${data.joinToString()}") + else { + out(varname) + for (chunk in data.chunked(16)) + out(" .char " + chunk.joinToString()) + } } } DataType.ARRAY_UW -> { // unsigned word arraysize - val data = makeArrayFillDataUnsigned(value) - if (data.size <= 16) - out("$varname\t.word ${data.joinToString()}") - else { - out(varname) - for (chunk in data.chunked(16)) - out(" .word " + chunk.joinToString()) + if(parameters.uninitializedArraySize!=null) { + out("$varname\t.fill ${parameters.uninitializedArraySize}*2") // uninitialized array + } else { + val data = makeArrayFillDataUnsigned(value) + if (data.size <= 16) + out("$varname\t.word ${data.joinToString()}") + else { + out(varname) + for (chunk in data.chunked(16)) + out(" .word " + chunk.joinToString()) + } } } DataType.ARRAY_W -> { // signed word arraysize - val data = makeArrayFillDataSigned(value) - if (data.size <= 16) - out("$varname\t.sint ${data.joinToString()}") - else { - out(varname) - for (chunk in data.chunked(16)) - out(" .sint " + chunk.joinToString()) + if(parameters.uninitializedArraySize!=null) { + out("$varname\t.fill ${parameters.uninitializedArraySize}*2") // uninitialized array + } else { + val data = makeArrayFillDataSigned(value) + if (data.size <= 16) + out("$varname\t.sint ${data.joinToString()}") + else { + out(varname) + for (chunk in data.chunked(16)) + out(" .sint " + chunk.joinToString()) + } } } DataType.ARRAY_F -> { // float arraysize - val array = heap.get(value.heapId!!).doubleArray!! - val floatFills = array.map { makeFloatFill(MachineDefinition.Mflpt5.fromNumber(it)) } - out(varname) - for(f in array.zip(floatFills)) - out(" .byte ${f.second} ; float ${f.first}") + if(parameters.uninitializedArraySize!=null) { + out("$varname\t.fill ${parameters.uninitializedArraySize}*${MachineDefinition.Mflpt5.MemorySize}") // uninitialized array + } else { + val array = heap.get(value.heapId!!).doubleArray!! + val floatFills = array.map { makeFloatFill(MachineDefinition.Mflpt5.fromNumber(it)) } + out(varname) + for (f in array.zip(floatFills)) + out(" .byte ${f.second} ; float ${f.first}") + } } DataType.STRUCT -> throw AssemblyError("vars of type STRUCT should have been removed because flattened") } diff --git a/compiler/src/prog8/optimizer/ConstantFolding.kt b/compiler/src/prog8/optimizer/ConstantFolding.kt index c85d77aa4..55d9eb785 100644 --- a/compiler/src/prog8/optimizer/ConstantFolding.kt +++ b/compiler/src/prog8/optimizer/ConstantFolding.kt @@ -65,7 +65,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor { val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position) decl.value = newValue optimizationsDone++ - return decl + return super.visit(decl) } } in StringDatatypes -> { @@ -96,7 +96,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor { } decl.value!!.linkParents(decl) optimizationsDone++ - return decl + return super.visit(decl) } } if(numericLv!=null && numericLv.type== DataType.FLOAT) @@ -127,21 +127,26 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor { val heapId = program.heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) }) decl.value = ReferenceLiteralValue(decl.datatype, initHeapId = heapId, position = numericLv.position) optimizationsDone++ - return decl + return super.visit(decl) } } DataType.ARRAY_F -> { - val litval = decl.value as? NumericLiteralValue val size = decl.arraysize?.size() ?: return decl - // arraysize initializer is empty or a single int, and we know the size; create the arraysize. - val fillvalue = if (litval == null) 0.0 else litval.number.toDouble() - if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE) - errors.add(ExpressionError("float value overflow", litval?.position ?: decl.position)) - else { - val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue }) - decl.value = ReferenceLiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval?.position ?: decl.position) - optimizationsDone++ - return decl + val litval = decl.value as? NumericLiteralValue + if(litval==null) { + // there's no initialization value, but the size is known, so we're ok. + return super.visit(decl) + } else { + // arraysize initializer is a single int, and we know the size. + val fillvalue = litval.number.toDouble() + if (fillvalue < FLOAT_MAX_NEGATIVE || fillvalue > FLOAT_MAX_POSITIVE) + errors.add(ExpressionError("float value overflow", litval.position ?: decl.position)) + else { + val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue }) + decl.value = ReferenceLiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval.position) + optimizationsDone++ + return super.visit(decl) + } } } else -> { diff --git a/examples/test.p8 b/examples/test.p8 index 8e3370c5c..d33705af7 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -6,9 +6,11 @@ ~ main { sub start() { - - str naam = "irmen" byte[] array=[1,2,3,4,5] + word[5] warray + float[5] flarray + + warray[0]=flarray[0] as word ubyte length = len(array) c64scr.print_ub(length) diff --git a/parser/antlr/prog8.g4 b/parser/antlr/prog8.g4 index 23787a2fc..5cc774e24 100644 --- a/parser/antlr/prog8.g4 +++ b/parser/antlr/prog8.g4 @@ -73,7 +73,9 @@ block: '~' identifier integerliteral? statement_block EOL ; statement : directive | varinitializer + | structvarinitializer | vardecl + | structvardecl | constdecl | memoryvardecl | structdecl @@ -110,10 +112,14 @@ directive : directivearg : stringliteral | identifier | integerliteral ; -vardecl: (datatype | structname=identifier) ZEROPAGE? (arrayindex | ARRAYSIG) ? varname=identifier ; +vardecl: datatype ZEROPAGE? (arrayindex | ARRAYSIG) ? varname=identifier ; + +structvardecl: structname=identifier varname=identifier ; varinitializer : vardecl '=' expression ; +structvarinitializer : structvardecl '=' expression ; + constdecl: 'const' varinitializer ; memoryvardecl: ADDRESS_OF varinitializer;