mirror of
https://github.com/irmen/prog8.git
synced 2025-04-07 16:41:46 +00:00
- fixed lookup of members in structs defined in another scope
- preserve order of variable definitions in the Ast (and thus, the output)
This commit is contained in:
parent
87c28cfdbc
commit
8a26b7b248
@ -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"
|
||||
|
@ -132,16 +132,17 @@ interface INameScope {
|
||||
|
||||
fun lookup(scopedName: List<String>, 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)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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<IStatement> = 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
|
||||
|
@ -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<INameScope, MutableMap<String, VarDecl>>()
|
||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||
|
||||
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<IExpression>, 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<IStatement> {
|
||||
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 {
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
col_one.red= 111
|
||||
col_two.blue= 222
|
||||
fake_address = &naam
|
||||
c64scr.print_uwhex(1, fake_address)
|
||||
c64scr.print(", ")
|
||||
|
||||
c64scr.print_uwhex(1, &col_one)
|
||||
c64scr.print_uwhex(1, &col_two)
|
||||
c64scr.print_uwhex(1, &col_three)
|
||||
fake_address = &array
|
||||
c64scr.print_uwhex(1, fake_address)
|
||||
c64scr.print(", ")
|
||||
|
||||
fake_address = &rgb
|
||||
c64scr.print_uwhex(1, fake_address)
|
||||
c64scr.print("\n")
|
||||
|
||||
; @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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user