From 08d2f8568bb3c78ca4fbac62ebeea3f3d171ce18 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 27 Oct 2021 23:48:02 +0200 Subject: [PATCH] refactoring symbol lookups --- .../compiler/astprocessing/AstChecker.kt | 4 +- .../astprocessing/AstIdentifiersChecker.kt | 5 +- compiler/test/TestScoping.kt | 2 +- compilerAst/src/prog8/ast/AstToplevel.kt | 151 ++++++++++-------- .../prog8/ast/expressions/AstExpressions.kt | 4 +- .../src/prog8/ast/statements/AstStatements.kt | 2 +- examples/test.p8 | 93 +++++++++-- 7 files changed, 166 insertions(+), 95 deletions(-) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index f56f44210..7c41512b3 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -456,7 +456,7 @@ internal class AstChecker(private val program: Program, val targetIdentifier = assignTarget.identifier if (targetIdentifier != null) { val targetName = targetIdentifier.nameInSource - when (val targetSymbol = program.namespace.lookup(targetName, assignment)) { + when (val targetSymbol = program.namespace.lookup(targetName, assignment.definingScope)) { null -> { errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position) return @@ -1073,7 +1073,7 @@ internal class AstChecker(private val program: Program, override fun visit(postIncrDecr: PostIncrDecr) { if(postIncrDecr.target.identifier != null) { val targetName = postIncrDecr.target.identifier!!.nameInSource - val target = program.namespace.lookup(targetName, postIncrDecr) + val target = program.namespace.lookup(targetName, postIncrDecr.definingScope) if(target==null) { val symbol = postIncrDecr.target.identifier!! errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) diff --git a/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt index caa12e98b..3ae260908 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt @@ -42,7 +42,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e if(decl.name in compTarget.machine.opcodeNames) 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) 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 paramsToCheck = paramNames.intersect(namesInSub) 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) nameError(name, labelOrVar.position, subroutine) val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name} diff --git a/compiler/test/TestScoping.kt b/compiler/test/TestScoping.kt index 08901e109..ede65305d 100644 --- a/compiler/test/TestScoping.kt +++ b/compiler/test/TestScoping.kt @@ -62,7 +62,7 @@ class TestScoping { goto labeloutside goto iflabel goto main.start.nested.nestedlabel - ; goto nested.nestedlabel ; TODO should also work!! + goto nested.nestedlabel } iflabel: } diff --git a/compilerAst/src/prog8/ast/AstToplevel.kt b/compilerAst/src/prog8/ast/AstToplevel.kt index bc5970d65..7c3bc415c 100644 --- a/compilerAst/src/prog8/ast/AstToplevel.kt +++ b/compilerAst/src/prog8/ast/AstToplevel.kt @@ -84,7 +84,7 @@ interface IStatementContainer { fun isEmpty(): Boolean = statements.isEmpty() 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, // but adding a memoization cache didn't make much of a practical runtime difference... for (stmt in statements) { @@ -98,44 +98,48 @@ interface IStatementContainer { is Label -> { if(stmt.name==name) return stmt } + is Subroutine -> { + if(alsoSubroutine && stmt.name==name) + return stmt + } is AnonymousScope -> { - val found = stmt.searchLabelOrVariableNotSubscoped(name) + val found = stmt.searchLabelOrVariableNotSubscoped(name, alsoSubroutine) if(found!=null) return found } 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) return found } 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) return found } is ForLoop -> { - val found = stmt.body.searchLabelOrVariableNotSubscoped(name) + val found = stmt.body.searchLabelOrVariableNotSubscoped(name, alsoSubroutine) if(found!=null) return found } is WhileLoop -> { - val found = stmt.body.searchLabelOrVariableNotSubscoped(name) + val found = stmt.body.searchLabelOrVariableNotSubscoped(name, alsoSubroutine) if(found!=null) return found } is RepeatLoop -> { - val found = stmt.body.searchLabelOrVariableNotSubscoped(name) + val found = stmt.body.searchLabelOrVariableNotSubscoped(name, alsoSubroutine) if(found!=null) return found } is UntilLoop -> { - val found = stmt.body.searchLabelOrVariableNotSubscoped(name) + val found = stmt.body.searchLabelOrVariableNotSubscoped(name, alsoSubroutine) if(found!=null) return found } is WhenStatement -> { stmt.choices.forEach { - val found = it.statements.searchLabelOrVariableNotSubscoped(name) + val found = it.statements.searchLabelOrVariableNotSubscoped(name, alsoSubroutine) if(found!=null) return found } @@ -151,59 +155,80 @@ interface IStatementContainer { val allDefinedSymbols: List> get() { - return statements.mapNotNull { - 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 - } - } + return statements.filterIsInstance().map { Pair(it.name, it as Statement) } } } interface INameScope: IStatementContainer, INamedStatement { fun subScope(name: String): INameScope? = statements.firstOrNull { it is INameScope && it.name==name } as? INameScope - fun lookup(scopedName: List, localContext: Node) : Statement? { // TODO return INamedStatement instead? - if(scopedName.size>1) { - // a scoped name refers to a name in another module. - // it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program) - for(module in localContext.definingModule.program.modules) { - 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 + fun lookup(scopedName: List, localScope: INameScope) : Statement? { // TODO return INamedStatement instead? + return if(scopedName.size>1) + lookupQualified(scopedName, localScope) + else { + lookupUnqualified(scopedName[0], localScope) } } + private fun lookupQualified(scopedName: List, 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 containsNoCodeNorVars get() = !containsCodeOrVars } @@ -263,7 +288,7 @@ class Program(val name: String, private val _modules = mutableListOf() val modules: List = _modules - val namespace: GlobalNamespace = GlobalNamespace(modules, builtinFunctions.names) + val namespace: GlobalNamespace = GlobalNamespace(modules) init { // insert a container module for all interned strings later @@ -414,7 +439,7 @@ open class Module(final override var statements: MutableList, } -class GlobalNamespace(val modules: Iterable, private val builtinFunctionNames: Set): Node, INameScope { +class GlobalNamespace(val modules: Iterable): Node, INameScope { override val name = "<<>>" override val position = Position("<<>>", 0, 0, 0) override val statements = mutableListOf() // not used @@ -427,22 +452,6 @@ class GlobalNamespace(val modules: Iterable, private val builtinFunction override fun replaceChildNode(node: Node, replacement: Node) { throw FatalAstException("cannot replace anything in the namespace") } - - override fun lookup(scopedName: List, 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 { diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index e0022f077..7a0344e30 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -730,7 +730,7 @@ data class IdentifierReference(val nameInSource: List, override val posi if(nameInSource.size==1 && nameInSource[0] in program.builtinFunctions.names) BuiltinFunctionStatementPlaceholder(nameInSource[0], position, parent) else - program.namespace.lookup(nameInSource, this) + program.namespace.lookup(nameInSource, this.definingScope) fun targetVarDecl(program: Program): VarDecl? = targetStatement(program) as? VarDecl fun targetSubroutine(program: Program): Subroutine? = targetStatement(program) as? Subroutine @@ -747,7 +747,7 @@ data class IdentifierReference(val nameInSource: List, override val posi } override fun constValue(program: Program): NumericLiteralValue? { - val node = program.namespace.lookup(nameInSource, this) + val node = program.namespace.lookup(nameInSource, this.definingScope) ?: throw UndefinedSymbolError(this) val vardecl = node as? VarDecl if(vardecl==null) { diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index b13c22f37..641a9ce48 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -395,7 +395,7 @@ data class AssignTarget(var identifier: IdentifierReference?, fun inferType(program: Program): InferredTypes.InferredType { 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) } diff --git a/examples/test.p8 b/examples/test.p8 index 0bb89f262..f5b9d8f8b 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,24 +1,85 @@ + +%import syslib +%import textio +%zeropage basicsafe +%option no_sysinit + +; Create a custom character set on the C64. + main { + sub start() { - goto labeloutside + txt.color(1) + txt.print("creating charset...\n") + charset.make_custom_charset() - if true { - if true { - goto labeloutside - goto iflabel - } -iflabel: - } + ; activate the new charset in RAM + ubyte block = c64.CIA2PRA + const ubyte PAGE1 = ((c64.Screen >> 6) & $F0) | ((charset.CHARSET >> 10) & $0E) - repeat { - goto labelinside -labelinside: - } + c64.CIA2PRA = (block & $FC) | (lsb(c64.Screen >> 14) ^ $03) + c64.VMCSB = PAGE1 -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: - start: - start: + sub make_custom_charset() { + copy_rom_charset() + + ; 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)) + + } }