refactoring symbol lookups

This commit is contained in:
Irmen de Jong 2021-10-27 23:48:02 +02:00
parent ac5f45d2d4
commit 08d2f8568b
7 changed files with 166 additions and 95 deletions

View File

@ -456,7 +456,7 @@ internal class AstChecker(private val program: Program,
val targetIdentifier = assignTarget.identifier val targetIdentifier = assignTarget.identifier
if (targetIdentifier != null) { if (targetIdentifier != null) {
val targetName = targetIdentifier.nameInSource val targetName = targetIdentifier.nameInSource
when (val targetSymbol = program.namespace.lookup(targetName, assignment)) { when (val targetSymbol = program.namespace.lookup(targetName, assignment.definingScope)) {
null -> { null -> {
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position) errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
return return
@ -1073,7 +1073,7 @@ internal class AstChecker(private val program: Program,
override fun visit(postIncrDecr: PostIncrDecr) { override fun visit(postIncrDecr: PostIncrDecr) {
if(postIncrDecr.target.identifier != null) { if(postIncrDecr.target.identifier != null) {
val targetName = postIncrDecr.target.identifier!!.nameInSource val targetName = postIncrDecr.target.identifier!!.nameInSource
val target = program.namespace.lookup(targetName, postIncrDecr) val target = program.namespace.lookup(targetName, postIncrDecr.definingScope)
if(target==null) { if(target==null) {
val symbol = postIncrDecr.target.identifier!! val symbol = postIncrDecr.target.identifier!!
errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)

View File

@ -42,7 +42,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if(decl.name in compTarget.machine.opcodeNames) if(decl.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position) errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
val existing = program.namespace.lookup(listOf(decl.name), decl) val existing = program.namespace.lookup(listOf(decl.name), decl.definingScope)
if (existing != null && existing !== decl) if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing) nameError(decl.name, decl.position, existing)
@ -75,7 +75,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
val paramNames = subroutine.parameters.map { it.name }.toSet() val paramNames = subroutine.parameters.map { it.name }.toSet()
val paramsToCheck = paramNames.intersect(namesInSub) val paramsToCheck = paramNames.intersect(namesInSub)
for(name in paramsToCheck) { for(name in paramsToCheck) {
val labelOrVar = subroutine.searchLabelOrVariableNotSubscoped(name) // TODO clean this up? no two separate lookups?
val labelOrVar = subroutine.searchLabelOrVariableNotSubscoped(name, false)
if(labelOrVar!=null && labelOrVar.position != subroutine.position) if(labelOrVar!=null && labelOrVar.position != subroutine.position)
nameError(name, labelOrVar.position, subroutine) nameError(name, labelOrVar.position, subroutine)
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name} val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}

View File

@ -62,7 +62,7 @@ class TestScoping {
goto labeloutside goto labeloutside
goto iflabel goto iflabel
goto main.start.nested.nestedlabel goto main.start.nested.nestedlabel
; goto nested.nestedlabel ; TODO should also work!! goto nested.nestedlabel
} }
iflabel: iflabel:
} }

View File

@ -84,7 +84,7 @@ interface IStatementContainer {
fun isEmpty(): Boolean = statements.isEmpty() fun isEmpty(): Boolean = statements.isEmpty()
fun isNotEmpty(): Boolean = statements.isNotEmpty() fun isNotEmpty(): Boolean = statements.isNotEmpty()
fun searchLabelOrVariableNotSubscoped(name: String): Statement? { // TODO return INamedStatement instead? and rename to searchSymbol ? fun searchLabelOrVariableNotSubscoped(name: String, alsoSubroutine: Boolean): Statement? { // TODO return INamedStatement instead? and rename to searchSymbol ?
// this is called quite a lot and could perhaps be optimized a bit more, // this is called quite a lot and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference... // but adding a memoization cache didn't make much of a practical runtime difference...
for (stmt in statements) { for (stmt in statements) {
@ -98,44 +98,48 @@ interface IStatementContainer {
is Label -> { is Label -> {
if(stmt.name==name) return stmt if(stmt.name==name) return stmt
} }
is Subroutine -> {
if(alsoSubroutine && stmt.name==name)
return stmt
}
is AnonymousScope -> { is AnonymousScope -> {
val found = stmt.searchLabelOrVariableNotSubscoped(name) val found = stmt.searchLabelOrVariableNotSubscoped(name, alsoSubroutine)
if(found!=null) if(found!=null)
return found return found
} }
is IfStatement -> { is IfStatement -> {
val found = stmt.truepart.searchLabelOrVariableNotSubscoped(name) ?: stmt.elsepart.searchLabelOrVariableNotSubscoped(name) val found = stmt.truepart.searchLabelOrVariableNotSubscoped(name, alsoSubroutine) ?: stmt.elsepart.searchLabelOrVariableNotSubscoped(name, alsoSubroutine)
if(found!=null) if(found!=null)
return found return found
} }
is BranchStatement -> { is BranchStatement -> {
val found = stmt.truepart.searchLabelOrVariableNotSubscoped(name) ?: stmt.elsepart.searchLabelOrVariableNotSubscoped(name) val found = stmt.truepart.searchLabelOrVariableNotSubscoped(name, alsoSubroutine) ?: stmt.elsepart.searchLabelOrVariableNotSubscoped(name, alsoSubroutine)
if(found!=null) if(found!=null)
return found return found
} }
is ForLoop -> { is ForLoop -> {
val found = stmt.body.searchLabelOrVariableNotSubscoped(name) val found = stmt.body.searchLabelOrVariableNotSubscoped(name, alsoSubroutine)
if(found!=null) if(found!=null)
return found return found
} }
is WhileLoop -> { is WhileLoop -> {
val found = stmt.body.searchLabelOrVariableNotSubscoped(name) val found = stmt.body.searchLabelOrVariableNotSubscoped(name, alsoSubroutine)
if(found!=null) if(found!=null)
return found return found
} }
is RepeatLoop -> { is RepeatLoop -> {
val found = stmt.body.searchLabelOrVariableNotSubscoped(name) val found = stmt.body.searchLabelOrVariableNotSubscoped(name, alsoSubroutine)
if(found!=null) if(found!=null)
return found return found
} }
is UntilLoop -> { is UntilLoop -> {
val found = stmt.body.searchLabelOrVariableNotSubscoped(name) val found = stmt.body.searchLabelOrVariableNotSubscoped(name, alsoSubroutine)
if(found!=null) if(found!=null)
return found return found
} }
is WhenStatement -> { is WhenStatement -> {
stmt.choices.forEach { stmt.choices.forEach {
val found = it.statements.searchLabelOrVariableNotSubscoped(name) val found = it.statements.searchLabelOrVariableNotSubscoped(name, alsoSubroutine)
if(found!=null) if(found!=null)
return found return found
} }
@ -151,59 +155,80 @@ interface IStatementContainer {
val allDefinedSymbols: List<Pair<String, Statement>> val allDefinedSymbols: List<Pair<String, Statement>>
get() { get() {
return statements.mapNotNull { return statements.filterIsInstance<INamedStatement>().map { Pair(it.name, it as Statement) }
when (it) {
is Label -> it.name to it
is VarDecl -> it.name to it
is Subroutine -> it.name to it
is Block -> it.name to it
else -> null
}
}
} }
} }
interface INameScope: IStatementContainer, INamedStatement { interface INameScope: IStatementContainer, INamedStatement {
fun subScope(name: String): INameScope? = statements.firstOrNull { it is INameScope && it.name==name } as? INameScope fun subScope(name: String): INameScope? = statements.firstOrNull { it is INameScope && it.name==name } as? INameScope
fun lookup(scopedName: List<String>, localContext: Node) : Statement? { // TODO return INamedStatement instead? fun lookup(scopedName: List<String>, localScope: INameScope) : Statement? { // TODO return INamedStatement instead?
if(scopedName.size>1) { return if(scopedName.size>1)
// a scoped name refers to a name in another module. lookupQualified(scopedName, localScope)
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program) else {
for(module in localContext.definingModule.program.modules) { lookupUnqualified(scopedName[0], localScope)
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScope(name)
if(scope==null)
break
}
if(scope!=null) {
val result = scope.searchLabelOrVariableNotSubscoped(scopedName.last())
if(result!=null)
return result
return scope.subScope(scopedName.last()) as Statement?
}
}
return null
} else {
// unqualified name
// find the scope the localContext is in, look in that first
var statementScope = localContext
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope
val result = localScope.searchLabelOrVariableNotSubscoped(scopedName[0])
if (result != null)
return result
val subscope = localScope.subScope(scopedName[0]) as Statement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
statementScope = statementScope.parent
}
return null
} }
} }
private fun lookupQualified(scopedName: List<String>, startScope: INameScope): Statement? {
// a scoped name refers to a name in another namespace.
// look "up" from our current scope to search for the correct one.
if(scopedName==listOf("nested", "nestedlabel"))
println("$scopedName") // TODO weg
// TODO FIX qualified lookup.
var statementScope = startScope
while(statementScope !is GlobalNamespace) {
if(statementScope !is Module && statementScope.name==scopedName[0]) {
// drill down to get the full scope
var scope: INameScope? = statementScope
for (name in scopedName.drop(1).dropLast(1)) {
scope = scope!!.subScope(name)
if (scope == null)
return null
}
return scope!!.searchLabelOrVariableNotSubscoped(scopedName.last(), true)
} else {
statementScope = (statementScope as Node).definingScope
}
}
// not found, try again but now assume it's a globally scoped name starting with the name of a block
for(module in (startScope as Node).definingModule.program.modules) {
module.statements.forEach {
if(it is Block && it.name==scopedName[0])
return it.lookup(scopedName, it)
}
}
return null
}
private fun lookupUnqualified(name: String, startScope: INameScope): Statement? {
val builtinFunctionsNames = (startScope as Node).definingModule.program.builtinFunctions.names
if(name in builtinFunctionsNames) {
// builtin functions always exist, return a dummy placeholder for them
val builtinPlaceholder = Label("builtin::$name", startScope.position)
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
// search for the unqualified name in the current scope (and possibly in any anonymousscopes it may contain)
// if it's not found there, jump up one higher in the namespaces and try again.
var statementScope = startScope
while(statementScope !is GlobalNamespace) {
val symbol = statementScope.searchLabelOrVariableNotSubscoped(name, true)
if(symbol!=null)
return symbol
else
statementScope = (statementScope as Node).definingScope
}
return null
}
// private fun getNamedSymbol(name: String): Statement? =
// statements.singleOrNull { it is INamedStatement && it.name==name }
val containsCodeOrVars get() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm" } val containsCodeOrVars get() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm" }
val containsNoCodeNorVars get() = !containsCodeOrVars val containsNoCodeNorVars get() = !containsCodeOrVars
} }
@ -263,7 +288,7 @@ class Program(val name: String,
private val _modules = mutableListOf<Module>() private val _modules = mutableListOf<Module>()
val modules: List<Module> = _modules val modules: List<Module> = _modules
val namespace: GlobalNamespace = GlobalNamespace(modules, builtinFunctions.names) val namespace: GlobalNamespace = GlobalNamespace(modules)
init { init {
// insert a container module for all interned strings later // insert a container module for all interned strings later
@ -414,7 +439,7 @@ open class Module(final override var statements: MutableList<Statement>,
} }
class GlobalNamespace(val modules: Iterable<Module>, private val builtinFunctionNames: Set<String>): Node, INameScope { class GlobalNamespace(val modules: Iterable<Module>): Node, INameScope {
override val name = "<<<global>>>" override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0) override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<Statement>() // not used override val statements = mutableListOf<Statement>() // not used
@ -427,22 +452,6 @@ class GlobalNamespace(val modules: Iterable<Module>, private val builtinFunction
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("cannot replace anything in the namespace") throw FatalAstException("cannot replace anything in the namespace")
} }
override fun lookup(scopedName: List<String>, localContext: Node): Statement? { // TODO return INamedStatement instead?
if (scopedName.size == 1 && scopedName[0] in builtinFunctionNames) {
// builtin functions always exist, return a dummy localContext for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
// lookup something from the module.
return when (val stmt = localContext.definingModule.lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null
else -> throw SyntaxError("invalid identifier target type", stmt.position)
}
}
} }
object BuiltinFunctionScopePlaceholder : INameScope { object BuiltinFunctionScopePlaceholder : INameScope {

View File

@ -730,7 +730,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
if(nameInSource.size==1 && nameInSource[0] in program.builtinFunctions.names) if(nameInSource.size==1 && nameInSource[0] in program.builtinFunctions.names)
BuiltinFunctionStatementPlaceholder(nameInSource[0], position, parent) BuiltinFunctionStatementPlaceholder(nameInSource[0], position, parent)
else else
program.namespace.lookup(nameInSource, this) program.namespace.lookup(nameInSource, this.definingScope)
fun targetVarDecl(program: Program): VarDecl? = targetStatement(program) as? VarDecl fun targetVarDecl(program: Program): VarDecl? = targetStatement(program) as? VarDecl
fun targetSubroutine(program: Program): Subroutine? = targetStatement(program) as? Subroutine fun targetSubroutine(program: Program): Subroutine? = targetStatement(program) as? Subroutine
@ -747,7 +747,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
} }
override fun constValue(program: Program): NumericLiteralValue? { override fun constValue(program: Program): NumericLiteralValue? {
val node = program.namespace.lookup(nameInSource, this) val node = program.namespace.lookup(nameInSource, this.definingScope)
?: throw UndefinedSymbolError(this) ?: throw UndefinedSymbolError(this)
val vardecl = node as? VarDecl val vardecl = node as? VarDecl
if(vardecl==null) { if(vardecl==null) {

View File

@ -395,7 +395,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
fun inferType(program: Program): InferredTypes.InferredType { fun inferType(program: Program): InferredTypes.InferredType {
if (identifier != null) { if (identifier != null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, this) ?: return InferredTypes.unknown() val symbol = program.namespace.lookup(identifier!!.nameInSource, this.definingScope) ?: return InferredTypes.unknown()
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype) if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
} }

View File

@ -1,24 +1,85 @@
%import syslib
%import textio
%zeropage basicsafe
%option no_sysinit
; Create a custom character set on the C64.
main { main {
sub start() { sub start() {
goto labeloutside txt.color(1)
txt.print("creating charset...\n")
charset.make_custom_charset()
if true { ; activate the new charset in RAM
if true { ubyte block = c64.CIA2PRA
goto labeloutside const ubyte PAGE1 = ((c64.Screen >> 6) & $F0) | ((charset.CHARSET >> 10) & $0E)
goto iflabel
}
iflabel:
}
repeat { c64.CIA2PRA = (block & $FC) | (lsb(c64.Screen >> 14) ^ $03)
goto labelinside c64.VMCSB = PAGE1
labelinside:
}
labeloutside: txt.print("\n @ @ @ @\n")
}
}
charset {
const uword CHARSET = $2000
sub copy_rom_charset() {
; copies the charset from ROM to RAM so we can modify it
sys.set_irqd()
ubyte bank = @($0001)
@($0001) = bank & %11111011 ; enable CHAREN, so the character rom accessible at $d000
sys.memcopy($d000, CHARSET, 256*8*2) ; copy the charset to RAM
@($0001) = bank ; reset previous memory banking
sys.clear_irqd()
} }
start: sub make_custom_charset() {
start: copy_rom_charset()
start:
; make all characters italic
ubyte c
for c in 0 to 255 {
uword ptr = CHARSET + c*$0008
@(ptr) >>= 2
@(ptr+1) >>= 2
@(ptr+2) >>= 1
@(ptr+3) >>= 1
;@(ptr+4) >>= 0
;@(ptr+5) >>= 0
@(ptr+6) <<= 1
@(ptr+7) <<= 1
ptr = CHARSET + 256*8 + c*$0008
@(ptr) >>= 2
@(ptr+1) >>= 2
@(ptr+2) >>= 1
@(ptr+3) >>= 1
;@(ptr+4) >>= 0
;@(ptr+5) >>= 0
@(ptr+6) <<= 1
@(ptr+7) <<= 1
}
; add a smiley over the '@'
ubyte[] smiley = [
%00111100,
%01000010,
%10100101,
%10000001,
%10100101,
%10011001,
%01000010,
%00111100
]
sys.memcopy(smiley, CHARSET, len(smiley))
}
} }