diff --git a/compiler/src/prog8/ast/AstToplevel.kt b/compiler/src/prog8/ast/AstToplevel.kt index 5c97f4a14..1d492a31f 100644 --- a/compiler/src/prog8/ast/AstToplevel.kt +++ b/compiler/src/prog8/ast/AstToplevel.kt @@ -105,4 +105,4 @@ object BuiltinFunctionScopePlaceholder : INameScope { // prefix for struct member variables -internal fun mangledStructMemberName(varName: String, memberName: String) = "_prog8struct_${varName}_$memberName" +internal fun mangledStructMemberName(varName: String, memberName: String) = "prog8struct_${varName}_$memberName" diff --git a/compiler/src/prog8/ast/Interfaces.kt b/compiler/src/prog8/ast/Interfaces.kt index aa77a8422..0cfa97e53 100644 --- a/compiler/src/prog8/ast/Interfaces.kt +++ b/compiler/src/prog8/ast/Interfaces.kt @@ -132,16 +132,17 @@ interface INameScope { fun lookup(scopedName: List, localContext: Node) : IStatement? { if(scopedName.size>1) { - // it's a qualified name, can either be: - // - the name of a field in a struct - // - the name of a symbol somewhere else starting from the root of the namespace. - - // check struct first - if(scopedName.size==2) { // TODO support for referencing structs in other scopes . see GlobalNamespace? - val mangledname = mangledStructMemberName(scopedName[0], scopedName[1]) - val vardecl = localContext.definingScope().getLabelOrVariable(mangledname) - if(vardecl!=null) - return vardecl + // a scoped name can a) refer to a member of a struct, or b) refer to a name in another module. + // try the struct first. + val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl + val struct = thing?.struct + if (struct != null) { + if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) { + // return ref to the mangled name variable + val mangled = mangledStructMemberName(thing.name, scopedName.last()) + val mangledVar = thing.definingScope().getLabelOrVariable(mangled) + return mangledVar + } } // it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program) diff --git a/compiler/src/prog8/ast/base/Base.kt b/compiler/src/prog8/ast/base/Base.kt index 62238f702..d712db068 100644 --- a/compiler/src/prog8/ast/base/Base.kt +++ b/compiler/src/prog8/ast/base/Base.kt @@ -5,19 +5,19 @@ import prog8.ast.Node /**************************** AST Data classes ****************************/ enum class DataType { - UBYTE, - BYTE, - UWORD, - WORD, - FLOAT, - STR, - STR_S, - ARRAY_UB, - ARRAY_B, - ARRAY_UW, - ARRAY_W, - ARRAY_F, - STRUCT; + UBYTE, // pass by value + BYTE, // pass by value + UWORD, // pass by value + WORD, // pass by value + FLOAT, // pass by value + STR, // pass by reference + STR_S, // pass by reference + ARRAY_UB, // pass by reference + ARRAY_B, // pass by reference + ARRAY_UW, // pass by reference + ARRAY_W, // pass by reference + ARRAY_F, // pass by reference + STRUCT; // pass by reference /** * is the type assignable to the given other type? @@ -25,14 +25,14 @@ enum class DataType { infix fun isAssignableTo(targetType: DataType) = // what types are assignable to others without loss of precision? when(this) { - UBYTE -> targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT - BYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT - UWORD -> targetType == UWORD || targetType == FLOAT - WORD -> targetType == WORD || targetType==UWORD || targetType == FLOAT + UBYTE -> targetType in setOf(UBYTE, UWORD, WORD, FLOAT) + BYTE -> targetType in setOf(BYTE, UBYTE, UWORD, WORD, FLOAT) + UWORD -> targetType in setOf(UWORD, FLOAT) + WORD -> targetType in setOf(WORD, UWORD, FLOAT) FLOAT -> targetType == FLOAT STR -> targetType == STR || targetType==STR_S STR_S -> targetType == STR || targetType==STR_S - in ArrayDatatypes -> targetType === this + in ArrayDatatypes -> targetType == this else -> false } @@ -97,17 +97,19 @@ enum class VarDeclType { MEMORY } -val IterableDatatypes = setOf( - DataType.STR, DataType.STR_S, - DataType.ARRAY_UB, DataType.ARRAY_B, - DataType.ARRAY_UW, DataType.ARRAY_W, - DataType.ARRAY_F) val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE) val WordDatatypes = setOf(DataType.UWORD, DataType.WORD) val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD) val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT) val StringDatatypes = setOf(DataType.STR, DataType.STR_S) val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F) +val IterableDatatypes = setOf( + DataType.STR, DataType.STR_S, + DataType.ARRAY_UB, DataType.ARRAY_B, + DataType.ARRAY_UW, DataType.ARRAY_W, + DataType.ARRAY_F) +val PassByValueDatatypes = NumericDatatypes +val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT) val ArrayElementTypes = mapOf( DataType.ARRAY_B to DataType.BYTE, DataType.ARRAY_UB to DataType.UBYTE, diff --git a/compiler/src/prog8/ast/processing/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt index 0ff72449a..92e0b48b8 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -451,7 +451,7 @@ internal class AstChecker(private val program: Program, checkResult.add(ExpressionError("pointer-of operand must be the name of a heap variable", addressOf.position)) else { if(variable.datatype !in ArrayDatatypes && variable.datatype !in StringDatatypes && variable.datatype!=DataType.STRUCT) - checkResult.add(ExpressionError("pointer-of operand must be the name of a string or array heap variable", addressOf.position)) + checkResult.add(ExpressionError("invalid pointer-of operand type", addressOf.position)) } if(addressOf.scopedname==null) throw FatalAstException("the scopedname of AddressOf should have been set by now $addressOf") @@ -858,8 +858,8 @@ internal class AstChecker(private val program: Program, for (arg in args.withIndex().zip(target.parameters)) { val argDt = arg.first.value.inferType(program) if(argDt!=null && !(argDt isAssignableTo arg.second.type)) { - // for asm subroutines having STR param it's okay to provide a UWORD too (pointer value) - if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt== DataType.UWORD)) + // for asm subroutines having STR param it's okay to provide a UWORD (address value) + if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt == DataType.UWORD)) checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position)) } @@ -1270,8 +1270,13 @@ internal class AstChecker(private val program: Program, } else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes) checkResult.add(ExpressionError("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)) - else - checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position)) + else { + if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes) + checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position)) + else + checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position)) + } + return false } diff --git a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt index cc9774c12..795e420a1 100644 --- a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt @@ -65,24 +65,8 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo 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 - val decls: MutableList = decl.struct!!.statements.withIndex().map { - val member = it.value as VarDecl - val initvalue = if(decl.value!=null) (decl.value as LiteralValue).arrayvalue!![it.index] else null - VarDecl( - VarDeclType.VAR, - member.datatype, - ZeropageWish.NOT_IN_ZEROPAGE, - member.arraysize, - mangledStructMemberName(decl.name, member.name), - decl.struct!!.name, - initvalue, - member.isArray, - true, - member.position - ) - }.toMutableList() + val decls = decl.flattenStructMembers() decls.add(decl) - decl.structHasBeenFlattened = true val result = AnonymousScope(decls, decl.position) result.linkParents(decl.parent) return result diff --git a/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt b/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt index 21ad099d2..67c55b038 100644 --- a/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt +++ b/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt @@ -23,18 +23,17 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope // Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc). - private val vardeclsToAdd = mutableMapOf>() + private val vardeclsToAdd = mutableMapOf>() override fun visit(module: Module) { vardeclsToAdd.clear() super.visit(module) // add any new vardecls to the various scopes - for(decl in vardeclsToAdd) - for(d in decl.value) { - d.value.linkParents(decl.key as Node) - decl.key.statements.add(0, d.value) - } + for((where, decls) in vardeclsToAdd) { + where.statements.addAll(0, decls) + decls.forEach { it.linkParents(where as Node) } + } } override fun visit(decl: VarDecl): IStatement { @@ -61,25 +60,6 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope decl.position ) } - -// if(decl.datatype==DataType.STRUCT) { -// println("STRUCT INIT DECL $decl") -// // a struct initialization value perhaps -// // 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 } @@ -104,7 +84,7 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList, parent: IStatement) { // functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead. for(argparam in subroutine.parameters.withIndex().zip(arglist)) { - if(argparam.first.value.type== DataType.UWORD || argparam.first.value.type in StringDatatypes) { + if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) { if(argparam.second is AddressOf) continue val idref = argparam.second as? IdentifierReference @@ -139,8 +119,10 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope private fun addVarDecl(scope: INameScope, variable: VarDecl) { if(scope !in vardeclsToAdd) - vardeclsToAdd[scope] = mutableMapOf() - vardeclsToAdd.getValue(scope)[variable.name]=variable + vardeclsToAdd[scope] = mutableListOf() + val declList = vardeclsToAdd.getValue(scope) + if(declList.all{it.name!=variable.name}) + declList.add(variable) } } diff --git a/compiler/src/prog8/ast/statements/AstStatements.kt b/compiler/src/prog8/ast/statements/AstStatements.kt index 04157c5ec..b42905fe2 100644 --- a/compiler/src/prog8/ast/statements/AstStatements.kt +++ b/compiler/src/prog8/ast/statements/AstStatements.kt @@ -154,8 +154,10 @@ class VarDecl(val type: VarDeclType, val hiddenButDoNotRemove: Boolean, override val position: Position) : IStatement { override lateinit var parent: Node - var struct: StructDecl? = null // set later - var structHasBeenFlattened = false + var struct: StructDecl? = null // set later (because at parse time, we only know the name) + private set + var structHasBeenFlattened = false // set later + private set override val expensiveToInline get() = value!=null && value !is LiteralValue @@ -202,11 +204,32 @@ class VarDecl(val type: VarDeclType, DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = 0.0, position = position) else -> throw FatalAstException("can only set a default value for a numeric type") } - val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, null, constValue, isArray, true, position) + val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, true, position) if(parent!=null) decl.linkParents(parent) return decl } + + fun flattenStructMembers(): MutableList { + val result = struct!!.statements.withIndex().map { + val member = it.value as VarDecl + val initvalue = if(value!=null) (value as LiteralValue).arrayvalue!![it.index] else null + VarDecl( + VarDeclType.VAR, + member.datatype, + ZeropageWish.NOT_IN_ZEROPAGE, + member.arraysize, + mangledStructMemberName(name, member.name), + struct!!.name, + initvalue, + member.isArray, + true, + member.position + ) as IStatement + }.toMutableList() + structHasBeenFlattened = true + return result + } } class ArrayIndex(var index: IExpression, override val position: Position) : Node { diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 0da5206c3..df41ae82f 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -2010,7 +2010,7 @@ internal class Compiler(private val program: Program) { DataType.UBYTE -> when(sourceDt) { DataType.UBYTE -> {} DataType.BYTE -> prog.instr(Opcode.CAST_B_TO_UB) - DataType.UWORD-> prog.instr(Opcode.CAST_UW_TO_UB) + DataType.UWORD -> prog.instr(Opcode.CAST_UW_TO_UB) DataType.WORD-> prog.instr(Opcode.CAST_W_TO_UB) DataType.FLOAT -> prog.instr(Opcode.CAST_F_TO_UB) else -> throw CompilerException("invalid cast $sourceDt to ${expr.type} -- should be an Ast check") diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index 21a2810e0..4da1c81ca 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -525,7 +525,8 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap if(parameters.zp==ZeropageWish.REQUIRE_ZEROPAGE) throw CompilerException("zp conflict") val valuestr = value.toString() - out.println("$vname ${value.type.name.toLowerCase()} $valuestr") + val struct = if(parameters.memberOfStruct==null) "" else "struct=${parameters.memberOfStruct.name}" + out.println("$vname ${value.type.name.toLowerCase()} $valuestr zp=${parameters.zp} $struct") } 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 9a6b00fa9..c39a8fdc0 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -299,10 +299,12 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter // 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) } // 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) diff --git a/examples/test.p8 b/examples/test.p8 index 69ac57716..56bd56ed6 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -9,18 +9,34 @@ ubyte blue } + str naam = "irmen" + word[] array = [1,2,3,4] + uword uw = $ab12 + Color rgb = [255,128,0] + Color rgb2 = [111,222,33] + + ubyte @zp zpvar=99 + sub start() { - Color col_one - Color col_two - Color col_three + uword fake_address + + fake_address = &naam + c64scr.print_uwhex(1, fake_address) + c64scr.print(", ") + + fake_address = &array + c64scr.print_uwhex(1, fake_address) + c64scr.print(", ") - col_one.red= 111 - col_two.blue= 222 + fake_address = &rgb + c64scr.print_uwhex(1, fake_address) + c64scr.print("\n") - c64scr.print_uwhex(1, &col_one) - c64scr.print_uwhex(1, &col_two) - c64scr.print_uwhex(1, &col_three) + ; @todo only works once reference types are actually references: + ;str name2 = naam ; @todo name2 points to same str as naam + ;str name2 = fake_address ; @todo fake_address hopefully points to a str + ;Color colz = fake_address ; @todo fake_address hopefully points to a Color return }