diff --git a/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt b/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt index cc9065283..5ee45174f 100644 --- a/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt +++ b/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt @@ -571,7 +571,7 @@ private fun prog8Parser.When_choiceContext.toAst(): WhenChoice { private fun prog8Parser.VardeclContext.toAst(): VarDecl { return VarDecl( - if(structname!=null) VarDeclType.STRUCT else VarDeclType.VAR, + VarDeclType.VAR, datatype()?.toAst() ?: DataType.STRUCT, ZEROPAGE() != null, arrayindex()?.toAst(), diff --git a/compiler/src/prog8/ast/base/Base.kt b/compiler/src/prog8/ast/base/Base.kt index 27f08bea8..62238f702 100644 --- a/compiler/src/prog8/ast/base/Base.kt +++ b/compiler/src/prog8/ast/base/Base.kt @@ -94,8 +94,7 @@ enum class BranchCondition { enum class VarDeclType { VAR, CONST, - MEMORY, - STRUCT + MEMORY } val IterableDatatypes = setOf( diff --git a/compiler/src/prog8/ast/base/Extensions.kt b/compiler/src/prog8/ast/base/Extensions.kt index 5d1ab1471..08173760a 100644 --- a/compiler/src/prog8/ast/base/Extensions.kt +++ b/compiler/src/prog8/ast/base/Extensions.kt @@ -6,7 +6,7 @@ import prog8.ast.processing.* import prog8.ast.statements.Assignment import prog8.ast.statements.ForLoop import prog8.compiler.CompilationOptions -import prog8.optimizer.RemoveNops +import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops // the name of the subroutine that should be called for every block to initialize its variables @@ -17,9 +17,9 @@ internal const val initvarsSubName="prog8_init_vars" internal const val autoHeapValuePrefix = "auto_heap_value_" -internal fun Program.removeNops() { - val remover = RemoveNops() - remover.visit(this) +internal fun Program.removeNopsFlattenAnonScopes() { + val flattener = FlattenAnonymousScopesAndRemoveNops() + flattener.visit(this) } diff --git a/compiler/src/prog8/ast/processing/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt index de620df49..0eb29c3f6 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -489,6 +489,14 @@ internal class AstChecker(private val program: Program, when(decl.type) { VarDeclType.VAR, VarDeclType.CONST -> { + if(decl.struct!=null || decl.datatype==DataType.STRUCT) { + if(decl.datatype!=DataType.STRUCT) + throw FatalAstException("struct vardecl should be of data type struct $decl") + if(decl.struct==null) + throw FatalAstException("struct vardecl should be linked to its struct $decl") + if(decl.zeropage) + err("struct can not be in zeropage") + } if (decl.value == null) { when { decl.datatype in NumericDatatypes -> { @@ -502,6 +510,9 @@ internal class AstChecker(private val program: Program, litVal.parent = decl decl.value = litVal } + decl.datatype == DataType.STRUCT -> { + // TODO structs are not initialized with a literal value yet, should be an array of zeros! + } decl.type== VarDeclType.VAR -> { val litVal = LiteralValue(decl.datatype, initHeapId = heapStringSentinel, position = decl.position) // point to the sentinel heap value instead litVal.parent=decl @@ -558,16 +569,6 @@ internal class AstChecker(private val program: Program, } } } - VarDeclType.STRUCT -> { - if(decl.struct==null) - throw FatalAstException("struct vardecl should be linked to its struct $decl") - if(decl.datatype!=DataType.STRUCT) - throw FatalAstException("struct vardecl should be of data type struct $decl") - if(decl.zeropage) - err("struct can not be in zeropage") - if(decl.value!=null) - err("struct can not have an initialization value") // TODO allow struct to have initalization values - } } return super.visit(decl) @@ -1274,7 +1275,7 @@ internal class AstChecker(private val program: Program, else { if(decl.zeropage) checkResult.add(SyntaxError("struct can not contain zeropage members", decl.position)) - if(decl.type == VarDeclType.STRUCT) + if(decl.datatype==DataType.STRUCT) checkResult.add(SyntaxError("structs can not be nested", decl.position)) } } diff --git a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt index 80054e9f7..273a4470d 100644 --- a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt @@ -58,7 +58,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo // is it a struct variable? then define all its struct members as mangled names, // and include the original decl as well. - if(decl.type==VarDeclType.STRUCT) { + if(decl.datatype==DataType.STRUCT) { if(decl.structHasBeenFlattened) return decl // don't do this multiple times diff --git a/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt b/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt index 5c6ab76b7..6d495172e 100644 --- a/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt +++ b/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt @@ -8,6 +8,8 @@ import prog8.ast.expressions.FunctionCall import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.LiteralValue import prog8.ast.statements.* +import prog8.compiler.CompilerException + internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstModifyingVisitor { // For VarDecls that declare an initialization value: @@ -59,6 +61,24 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope decl.position ) } + + if(decl.datatype==DataType.STRUCT) { + // a struct initialization value + // flatten it to assignment statements + val sourceArray = (decl.value as LiteralValue).arrayvalue!! + val memberAssignments = decl.struct!!.statements.zip(sourceArray).map { member -> + val memberDecl = member.first as VarDecl + val mangled = mangledStructMemberName(decl.name, memberDecl.name) + val idref = IdentifierReference(listOf(mangled), decl.position) + val target = AssignTarget(null, idref, null, null, decl.position) + val assign = VariableInitializationAssignment(target, null, member.second, member.second.position) + assign + } + val scope = AnonymousScope(memberAssignments.toMutableList(), decl.position) + scope.linkParents(decl.parent) + return scope + } + return decl } diff --git a/compiler/src/prog8/compiler/AstToSourceCode.kt b/compiler/src/prog8/compiler/AstToSourceCode.kt index 70a879ed0..b79cbff98 100644 --- a/compiler/src/prog8/compiler/AstToSourceCode.kt +++ b/compiler/src/prog8/compiler/AstToSourceCode.kt @@ -103,16 +103,10 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor { } override fun visit(decl: VarDecl) { -// if(decl.hiddenButDoNotRemove) { -// // skip autogenerated vardecl -// return -// } - when(decl.type) { VarDeclType.VAR -> {} VarDeclType.CONST -> output("const ") VarDeclType.MEMORY -> output("&") - VarDeclType.STRUCT -> output("${decl.struct!!.name} ") } output(decl.struct?.name ?: "") output(datatypeString(decl.datatype)) @@ -185,7 +179,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor { private fun outputStatements(statements: List) { for(stmt in statements) { if(stmt is VarDecl && stmt.hiddenButDoNotRemove) - continue // skip autogenerated decls + continue // skip autogenerated decls (to avoid generating a newline) outputi("") stmt.accept(this) output("\n") @@ -436,6 +430,6 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor { outputln("") } override fun visit(nopStatement: NopStatement) { - output("; NOP") + output("; NOP @ ${nopStatement.position} $nopStatement") } } diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 8467529ce..2fe0f37fb 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -682,7 +682,6 @@ internal class Compiler(private val program: Program) { else -> throw CompilerException("invalid datatype for memory variable expression: $target") } } - VarDeclType.STRUCT -> TODO("decltype struct") } } @@ -1448,23 +1447,7 @@ internal class Compiler(private val program: Program) { } DataType.STRUCT -> { // Assume the value is an array. Flatten the struct assignment into memberwise assignments. - val identifier = stmt.target.identifier!! - val identifierName = identifier.nameInSource.single() - val targetVar = identifier.targetVarDecl(program.namespace)!! - val struct = targetVar.struct!! - val sourceVar = (stmt.value as IdentifierReference).targetVarDecl(program.namespace)!! - if(!sourceVar.isArray) - throw CompilerException("can only assign arrays to structs") - val sourceArray = (sourceVar.value as LiteralValue).arrayvalue!! - for(member in struct.statements.zip(sourceArray)) { - val decl = member.first as VarDecl - val value = member.second.constValue(program)!! - val mangled = mangledStructMemberName(identifierName, decl.name) - val idref = IdentifierReference(listOf(mangled), stmt.position) - val assign = Assignment(AssignTarget(null, idref, null, null, stmt.position), null, value, value.position) - assign.linkParents(stmt) - translate(assign) - } + flattenStructAssignment(stmt, program).forEach { translate(it) } return } in StringDatatypes -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt") @@ -1481,6 +1464,26 @@ internal class Compiler(private val program: Program) { popValueIntoTarget(stmt.target, datatype) } + private fun flattenStructAssignment(structAssignment: Assignment, program: Program): List { + val identifier = structAssignment.target.identifier!! + val identifierName = identifier.nameInSource.single() + val targetVar = identifier.targetVarDecl(program.namespace)!! + val struct = targetVar.struct!! + val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!! + if(!sourceVar.isArray) + throw CompilerException("can only assign arrays to structs") + val sourceArray = (sourceVar.value as LiteralValue).arrayvalue!! + return struct.statements.zip(sourceArray).map { member -> + val decl = member.first as VarDecl + val mangled = mangledStructMemberName(identifierName, decl.name) + val idref = IdentifierReference(listOf(mangled), structAssignment.position) + val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position), + null, member.second, member.second.position) + assign.linkParents(structAssignment) + assign + } + } + private fun pushHeapVarAddress(value: IExpression, removeLastOpcode: Boolean) { when (value) { is LiteralValue -> throw CompilerException("can only push address of string or array (value on the heap)") @@ -1520,9 +1523,6 @@ internal class Compiler(private val program: Program) { prog.instr(opcode, RuntimeValue(DataType.UWORD, address)) } VarDeclType.CONST -> throw CompilerException("cannot assign to const") - VarDeclType.STRUCT -> { - TODO("decltype struct $assignTarget") - } } } else throw CompilerException("invalid assignment target type ${target::class}") } diff --git a/compiler/src/prog8/compiler/Main.kt b/compiler/src/prog8/compiler/Main.kt index cf92d4cda..7ce44951c 100644 --- a/compiler/src/prog8/compiler/Main.kt +++ b/compiler/src/prog8/compiler/Main.kt @@ -84,7 +84,7 @@ fun compileProgram(filepath: Path, } } - programAst.removeNops() + programAst.removeNopsFlattenAnonScopes() programAst.checkValid(compilerOptions) // check if final tree is valid programAst.checkRecursion() // check if there are recursive subroutine calls diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index ebd7b3957..0b1399ef6 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -415,6 +415,10 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap throw CompilerException("array should already be in the heap") RuntimeValue(decl.datatype, heapId = litval.heapId) } + DataType.STRUCT -> { + // struct variables have been flattened already + return + } else -> throw CompilerException("weird datatype") } currentBlock.variables[scopedname] = value @@ -435,9 +439,6 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap if(lv.type in IntegerDatatypes) currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype) } - VarDeclType.STRUCT -> { - // the struct decl itself will be replaced by mangled declarations for each of their members. - } } } diff --git a/compiler/src/prog8/optimizer/Extensions.kt b/compiler/src/prog8/optimizer/Extensions.kt index a0d0796c5..7921716d5 100644 --- a/compiler/src/prog8/optimizer/Extensions.kt +++ b/compiler/src/prog8/optimizer/Extensions.kt @@ -31,14 +31,6 @@ internal fun Program.constantFold() { internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int { val optimizer = StatementOptimizer(this, optimizeInlining) optimizer.visit(this) - for(scope in optimizer.scopesToFlatten.reversed()) { - val namescope = scope.parent as INameScope - val idx = namescope.statements.indexOf(scope as IStatement) - if(idx>=0) { - namescope.statements[idx] = NopStatement.insteadOf(namescope.statements[idx]) - namescope.statements.addAll(idx, scope.statements) - } - } modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration return optimizer.optimizationsDone diff --git a/compiler/src/prog8/optimizer/StatementOptimizer.kt b/compiler/src/prog8/optimizer/StatementOptimizer.kt index b6cb3066d..e56dcde2a 100644 --- a/compiler/src/prog8/optimizer/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizer/StatementOptimizer.kt @@ -20,7 +20,6 @@ import kotlin.math.floor internal class StatementOptimizer(private val program: Program, private val optimizeInlining: Boolean) : IAstModifyingVisitor { var optimizationsDone: Int = 0 private set - var scopesToFlatten = mutableListOf() private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure } private val callgraph = CallGraph(program) @@ -621,11 +620,6 @@ internal class StatementOptimizer(private val program: Program, private val opti if(linesToRemove.isNotEmpty()) { linesToRemove.reversed().forEach{scope.statements.removeAt(it)} } - - if(scope.parent is INameScope) { - scopesToFlatten.add(scope) // get rid of the anonymous scope - } - return super.visit(scope) } @@ -642,17 +636,38 @@ internal class StatementOptimizer(private val program: Program, private val opti -internal class RemoveNops: IAstVisitor { - val nopStatements = mutableListOf() +internal class FlattenAnonymousScopesAndRemoveNops: IAstVisitor { + private var scopesToFlatten = mutableListOf() + private val nopStatements = mutableListOf() override fun visit(program: Program) { super.visit(program) - // at the end, remove the encountered NOP statements + for(scope in scopesToFlatten.reversed()) { + val namescope = scope.parent as INameScope + val idx = namescope.statements.indexOf(scope as IStatement) + if(idx>=0) { + val nop = NopStatement.insteadOf(namescope.statements[idx]) + nop.parent = namescope as Node + namescope.statements[idx] = nop + namescope.statements.addAll(idx, scope.statements) + scope.statements.forEach { it.parent = namescope as Node } + visit(nop) + } + } + this.nopStatements.forEach { it.definingScope().remove(it) } } + override fun visit(scope: AnonymousScope) { + if(scope.parent is INameScope) { + scopesToFlatten.add(scope) // get rid of the anonymous scope + } + + return super.visit(scope) + } + override fun visit(nopStatement: NopStatement) { nopStatements.add(nopStatement) } diff --git a/compiler/src/prog8/vm/astvm/AstVm.kt b/compiler/src/prog8/vm/astvm/AstVm.kt index f0919a7c6..316b0fc4a 100644 --- a/compiler/src/prog8/vm/astvm/AstVm.kt +++ b/compiler/src/prog8/vm/astvm/AstVm.kt @@ -396,7 +396,6 @@ class AstVm(val program: Program) { mem.setUByte(addr,newval.toShort()) } VarDeclType.CONST -> throw VmExecutionException("can't be const") - VarDeclType.STRUCT -> TODO("struct decltype") } } stmt.target.memoryAddress != null -> { diff --git a/compiler/src/prog8/vm/astvm/Expressions.kt b/compiler/src/prog8/vm/astvm/Expressions.kt index ed9c43b11..070cbfe11 100644 --- a/compiler/src/prog8/vm/astvm/Expressions.kt +++ b/compiler/src/prog8/vm/astvm/Expressions.kt @@ -101,7 +101,7 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue { if(variable is VarDecl) { if(variable.type==VarDeclType.VAR) return ctx.runtimeVars.get(variable.definingScope(), variable.name) - else if(variable.type==VarDeclType.STRUCT) { + else if(variable.datatype==DataType.STRUCT) { throw VmExecutionException("cannot process structs by-value. at ${expr.position}") } else { diff --git a/compiler/src/prog8/vm/astvm/VariablesCreator.kt b/compiler/src/prog8/vm/astvm/VariablesCreator.kt index ba52ac295..704c117cb 100644 --- a/compiler/src/prog8/vm/astvm/VariablesCreator.kt +++ b/compiler/src/prog8/vm/astvm/VariablesCreator.kt @@ -49,9 +49,6 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v VarDeclType.CONST -> { // consts should have been const-folded away } - VarDeclType.STRUCT -> { - // struct vardecl can be skipped because its members have been flattened out - } } } return super.visit(decl) diff --git a/examples/test.p8 b/examples/test.p8 index 5a06745e4..073551f98 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -7,47 +7,47 @@ uword derp ubyte[] v = [22,33,44] - Color foreground - Color foreground2 = [11,22,33] + Color foreground = [1,2,3] + c64scr.print_ub(foreground.red) + c64.CHROUT(':') + c64scr.print_ub(foreground.green) + c64.CHROUT(':') + c64scr.print_ub(foreground.blue) + c64.CHROUT('\n') - foreground.red = 111 - ; foreground2.red = 111 + + Color background = [255,255,255] ; @todo make zeros if no value is given + Color cursor + + foreground.red=99 + background.blue=foreground.red + + cursor = [1,2,3] ; assign all members at once + cursor = v + ;cursor=foreground ; @todo memberwise assignment + + c64scr.print_ub(foreground.red) + c64.CHROUT(':') + c64scr.print_ub(foreground.green) + c64.CHROUT(':') + c64scr.print_ub(foreground.blue) + c64.CHROUT('\n') + c64scr.print_ub(background.red) + c64.CHROUT(':') + c64scr.print_ub(background.green) + c64.CHROUT(':') + c64scr.print_ub(background.blue) + c64.CHROUT('\n') + c64scr.print_ub(cursor.red) + c64.CHROUT(':') + c64scr.print_ub(cursor.green) + c64.CHROUT(':') + c64scr.print_ub(cursor.blue) + c64.CHROUT('\n') + + return } -; sub test() { -; Color foreground ; = [0,1,2] ;@todo init values -; Color background -; Color cursor -; -; foreground.red=99 -; background.blue=foreground.red -; -; cursor = [1,2,3] ; assign all members at once -; cursor = v -; cursor=foreground ; @todo memberwise assignment -; -; c64scr.print_ub(foreground.red) -; c64.CHROUT(':') -; c64scr.print_ub(foreground.green) -; c64.CHROUT(':') -; c64scr.print_ub(foreground.blue) -; c64.CHROUT('\n') -; c64scr.print_ub(background.red) -; c64.CHROUT(':') -; c64scr.print_ub(background.green) -; c64.CHROUT(':') -; c64scr.print_ub(background.blue) -; c64.CHROUT('\n') -; c64scr.print_ub(cursor.red) -; c64.CHROUT(':') -; c64scr.print_ub(cursor.green) -; c64.CHROUT(':') -; c64scr.print_ub(cursor.blue) -; c64.CHROUT('\n') -; -; return -; } - struct Color { ubyte red ubyte green