From e52d05c7dbd926eb65fe55acfc8ee1ef38b43f96 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 23 Nov 2021 23:43:23 +0100 Subject: [PATCH] fix some scoping related symbol lookup issues, clarified scoping rules in docs --- .../compiler/astprocessing/AstChecker.kt | 11 ++-- compiler/test/TestScoping.kt | 32 +++-------- compilerAst/src/prog8/ast/AstToplevel.kt | 55 +++++-------------- docs/source/programming.rst | 10 ++-- 4 files changed, 34 insertions(+), 74 deletions(-) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 82ab03e41..f438ef8f8 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -92,9 +92,9 @@ internal class AstChecker(private val program: Program, fun checkUnsignedLoopDownto0(range: RangeExpr?) { if(range==null) return - val step = range.step.constValue(program)?.number?.toDouble() ?: 1.0 + val step = range.step.constValue(program)?.number ?: 1.0 if(step < -1.0) { - val limit = range.to.constValue(program)?.number?.toDouble() + val limit = range.to.constValue(program)?.number if(limit==0.0 && range.from.constValue(program)==null) errors.err("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping", forLoop.position) } @@ -496,7 +496,7 @@ internal class AstChecker(private val program: Program, if (assignment.value !is FunctionCall) errors.err("assignment value is invalid or has no proper datatype", assignment.value.position) } else { - checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE), assignTarget, + checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE), sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position) } } @@ -833,7 +833,7 @@ internal class AstChecker(private val program: Program, when(expr.operator){ "/", "%" -> { val constvalRight = expr.right.constValue(program) - val divisor = constvalRight?.number?.toDouble() + val divisor = constvalRight?.number if(divisor==0.0) errors.err("division by zero", expr.right.position) if(expr.operator=="%") { @@ -1280,7 +1280,7 @@ internal class AstChecker(private val program: Program, } when (targetDt) { DataType.FLOAT -> { - val number=value.number.toDouble() + val number=value.number if (number > 1.7014118345e+38 || number < -1.7014118345e+38) return err("value '$number' out of range for MFLPT format") } @@ -1356,7 +1356,6 @@ internal class AstChecker(private val program: Program, } private fun checkAssignmentCompatible(targetDatatype: DataType, - target: AssignTarget, sourceDatatype: DataType, sourceValue: Expression, position: Position) : Boolean { diff --git a/compiler/test/TestScoping.kt b/compiler/test/TestScoping.kt index cb4d77ce5..6133f535b 100644 --- a/compiler/test/TestScoping.kt +++ b/compiler/test/TestScoping.kt @@ -85,11 +85,9 @@ class TestScoping: FunSpec({ addr = &labelinside addr = &labeloutside addr = &main.start.nested.nestedlabel - addr = &nested.nestedlabel goto labeloutside goto iflabel goto main.start.nested.nestedlabel - goto nested.nestedlabel } iflabel: } @@ -99,11 +97,9 @@ class TestScoping: FunSpec({ addr = &labelinside addr = &labeloutside addr = &main.start.nested.nestedlabel - addr = &nested.nestedlabel goto iflabel goto labelinside goto main.start.nested.nestedlabel - goto nested.nestedlabel labelinside: } @@ -119,9 +115,7 @@ class TestScoping: FunSpec({ addr = &labelinside addr = &labeloutside addr = &main.start.nested.nestedlabel - addr = &nested.nestedlabel goto main.start.nested.nestedlabel - goto nested.nestedlabel } } """ @@ -306,7 +300,7 @@ class TestScoping: FunSpec({ compileText(C64Target, false, text, writeAssembly = false).assertSuccess() } - test("wrong variable refs with qualified names (not from root)") { + test("wrong variable refs with qualified names 1 (not from root)") { val text=""" main { sub start() { @@ -315,12 +309,9 @@ class TestScoping: FunSpec({ routine(5) routine.value = 5 routine.arg = 5 - xx = &routine.nested - routine.nested(5) - routine.nested.nestedvalue = 5 routine.nested.arg2 = 5 - xx = &wrong.routine - wrong.routine.value = 5 + routine.nested.nestedvalue = 5 + nested.nestedvalue = 5 } sub routine(ubyte arg) { @@ -334,16 +325,11 @@ class TestScoping: FunSpec({ """ val errors= ErrorReporterForTests() compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure() - errors.errors.size shouldBe 10 - errors.errors[0] shouldContain "undefined symbol: routine" - errors.errors[1] shouldContain "undefined symbol: routine" - errors.errors[2] shouldContain "undefined symbol: routine.value" - errors.errors[3] shouldContain "undefined symbol: routine.arg" - errors.errors[4] shouldContain "undefined symbol: routine.nested" - errors.errors[5] shouldContain "undefined symbol: routine.nested" - errors.errors[6] shouldContain "undefined symbol: routine.nested.nestedvalue" - errors.errors[7] shouldContain "undefined symbol: routine.nested.arg2" - errors.errors[8] shouldContain "undefined symbol: wrong.routine" - errors.errors[9] shouldContain "undefined symbol: wrong.routine.value" + errors.errors.size shouldBe 5 + errors.errors[0] shouldContain "undefined symbol: routine.value" + errors.errors[1] shouldContain "undefined symbol: routine.arg" + errors.errors[2] shouldContain "undefined symbol: routine.nested.arg2" + errors.errors[3] shouldContain "undefined symbol: routine.nested.nestedvalue" + errors.errors[4] shouldContain "undefined symbol: nested.nestedvalue" } }) diff --git a/compilerAst/src/prog8/ast/AstToplevel.kt b/compilerAst/src/prog8/ast/AstToplevel.kt index 137bc015e..a7e5c8f0f 100644 --- a/compilerAst/src/prog8/ast/AstToplevel.kt +++ b/compilerAst/src/prog8/ast/AstToplevel.kt @@ -7,7 +7,6 @@ import prog8.ast.statements.* import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstVisitor import prog8.parser.SourceCode -import kotlin.reflect.typeOf const val internedStringsModuleName = "prog8_interned_strings" @@ -76,16 +75,10 @@ interface IStatementContainer { // is INamedStatement -> { // if(stmt.name==name) return stmt // } - is VarDecl -> { - if(stmt.name==name) return stmt - } - is Label -> { - if(stmt.name==name) return stmt - } - is Subroutine -> { - if(stmt.name==name) - return stmt - } + is VarDecl -> if(stmt.name==name) return stmt + is Label -> if(stmt.name==name) return stmt + is Subroutine -> if(stmt.name==name) return stmt + is Block -> if(stmt.name==name) return stmt is AnonymousScope -> { val found = stmt.searchSymbol(name) if(found!=null) @@ -155,41 +148,22 @@ interface INameScope: IStatementContainer, INamedStatement { } private fun lookupQualified(scopedName: List): Statement? { - // a scoped name refers to a name in another namespace. - // look "up" from our current scope to search for the correct one. - val localScope = this.subScope(scopedName[0]) - if(localScope!=null) - return resolveLocally(localScope, scopedName.drop(1)) - - var statementScope = this - while(statementScope !is GlobalNamespace) { - if(statementScope !is Module && statementScope.name==scopedName[0]) { - return resolveLocally(statementScope, scopedName.drop(1)) - } 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 + // a scoped name refers to a name in another namespace, and stars from the root. for(module in (this as Node).definingModule.program.modules) { - module.statements.forEach { - if(it is Block && it.name==scopedName[0]) - return it.lookup(scopedName) + val block = module.searchSymbol(scopedName[0]) + if(block!=null) { + var statement = block + for(name in scopedName.drop(1)) { + statement = (statement as? IStatementContainer)?.searchSymbol(name) + if(statement==null) + return null + } + return statement } } return null } - private fun resolveLocally(startScope: INameScope, name: List): Statement? { - var scope: INameScope? = startScope - for(part in name.dropLast(1)) { - scope = scope!!.subScope(part) - if(scope==null) - return null - } - return scope!!.searchSymbol(name.last()) - } - private fun lookupUnqualified(name: String): Statement? { val builtinFunctionsNames = (this as Node).definingModule.program.builtinFunctions.names if(name in builtinFunctionsNames) { @@ -200,7 +174,6 @@ interface INameScope: IStatementContainer, INamedStatement { } // 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 = this while(statementScope !is GlobalNamespace) { val symbol = statementScope.searchSymbol(name) diff --git a/docs/source/programming.rst b/docs/source/programming.rst index c66e06c91..30c667e0a 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -119,18 +119,20 @@ It must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$1ff .. _scopes: -**Scopes** +**Scoping rules** .. sidebar:: - Scoped access to symbols / "dotted names" + Use qualified names ("dotted names") to reference symbols defined elsewhere - Every symbol is 'public' and can be accessed from elsewhere given its full "dotted name". + In prog8 every symbol is 'public' and can be accessed from anywhere else, given its *full* "dotted name". So, accessing a variable ``counter`` defined in subroutine ``worker`` in block ``main``, can be done from anywhere by using ``main.worker.counter``. *Symbols* are names defined in a certain *scope*. Inside the same scope, you can refer to them by their 'short' name directly. If the symbol is not found in the same scope, -the enclosing scope is searched for it, and so on, until the symbol is found. +the enclosing scope is searched for it, and so on, up to the top level block, until the symbol is found. +If the symbol was not found the compiler will issue an error message. + Scopes are created using either of these two statements: