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
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)

View File

@ -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}

View File

@ -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:
}

View File

@ -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<Pair<String, Statement>>
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<INamedStatement>().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<String>, 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<String>, 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<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 containsNoCodeNorVars get() = !containsCodeOrVars
}
@ -263,7 +288,7 @@ class Program(val name: String,
private val _modules = mutableListOf<Module>()
val modules: List<Module> = _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<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 position = Position("<<<global>>>", 0, 0, 0)
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) {
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 {

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)
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<String>, 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) {

View File

@ -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)
}

View File

@ -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))
}
}