Skip to content

Commit

Permalink
- fixed lookup of members in structs defined in another scope
Browse files Browse the repository at this point in the history
- preserve order of variable definitions in the Ast (and thus, the output)
  • Loading branch information
irmen committed Jul 13, 2019
1 parent 87c28cf commit 8a26b7b
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 97 deletions.
2 changes: 1 addition & 1 deletion compiler/src/prog8/ast/AstToplevel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
21 changes: 11 additions & 10 deletions compiler/src/prog8/ast/Interfaces.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
48 changes: 25 additions & 23 deletions compiler/src/prog8/ast/base/Base.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,34 @@ 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?
*/
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
}

Expand Down Expand Up @@ -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,
Expand Down
15 changes: 10 additions & 5 deletions compiler/src/prog8/ast/processing/AstChecker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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))
}

Expand Down Expand Up @@ -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
}
Expand Down
18 changes: 1 addition & 17 deletions compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}

Expand All @@ -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
Expand Down Expand Up @@ -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)
}

}
29 changes: 26 additions & 3 deletions compiler/src/prog8/ast/statements/AstStatements.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/prog8/compiler/Compiler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/prog8/compiler/target/c64/AsmGen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 24 additions & 8 deletions examples/test.p8
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 8a26b7b

Please sign in to comment.