fix some scoping related symbol lookup issues, clarified scoping rules in docs

This commit is contained in:
Irmen de Jong 2021-11-23 23:43:23 +01:00
parent b00db4f8a2
commit e52d05c7db
4 changed files with 34 additions and 74 deletions

View File

@ -92,9 +92,9 @@ internal class AstChecker(private val program: Program,
fun checkUnsignedLoopDownto0(range: RangeExpr?) { fun checkUnsignedLoopDownto0(range: RangeExpr?) {
if(range==null) if(range==null)
return return
val step = range.step.constValue(program)?.number?.toDouble() ?: 1.0 val step = range.step.constValue(program)?.number ?: 1.0
if(step < -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) 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) 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) if (assignment.value !is FunctionCall)
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position) errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
} else { } else {
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE), assignTarget, checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position) sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position)
} }
} }
@ -833,7 +833,7 @@ internal class AstChecker(private val program: Program,
when(expr.operator){ when(expr.operator){
"/", "%" -> { "/", "%" -> {
val constvalRight = expr.right.constValue(program) val constvalRight = expr.right.constValue(program)
val divisor = constvalRight?.number?.toDouble() val divisor = constvalRight?.number
if(divisor==0.0) if(divisor==0.0)
errors.err("division by zero", expr.right.position) errors.err("division by zero", expr.right.position)
if(expr.operator=="%") { if(expr.operator=="%") {
@ -1280,7 +1280,7 @@ internal class AstChecker(private val program: Program,
} }
when (targetDt) { when (targetDt) {
DataType.FLOAT -> { DataType.FLOAT -> {
val number=value.number.toDouble() val number=value.number
if (number > 1.7014118345e+38 || number < -1.7014118345e+38) if (number > 1.7014118345e+38 || number < -1.7014118345e+38)
return err("value '$number' out of range for MFLPT format") 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, private fun checkAssignmentCompatible(targetDatatype: DataType,
target: AssignTarget,
sourceDatatype: DataType, sourceDatatype: DataType,
sourceValue: Expression, sourceValue: Expression,
position: Position) : Boolean { position: Position) : Boolean {

View File

@ -85,11 +85,9 @@ class TestScoping: FunSpec({
addr = &labelinside addr = &labelinside
addr = &labeloutside addr = &labeloutside
addr = &main.start.nested.nestedlabel addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto labeloutside goto labeloutside
goto iflabel goto iflabel
goto main.start.nested.nestedlabel goto main.start.nested.nestedlabel
goto nested.nestedlabel
} }
iflabel: iflabel:
} }
@ -99,11 +97,9 @@ class TestScoping: FunSpec({
addr = &labelinside addr = &labelinside
addr = &labeloutside addr = &labeloutside
addr = &main.start.nested.nestedlabel addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto iflabel goto iflabel
goto labelinside goto labelinside
goto main.start.nested.nestedlabel goto main.start.nested.nestedlabel
goto nested.nestedlabel
labelinside: labelinside:
} }
@ -119,9 +115,7 @@ class TestScoping: FunSpec({
addr = &labelinside addr = &labelinside
addr = &labeloutside addr = &labeloutside
addr = &main.start.nested.nestedlabel addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto main.start.nested.nestedlabel goto main.start.nested.nestedlabel
goto nested.nestedlabel
} }
} }
""" """
@ -306,7 +300,7 @@ class TestScoping: FunSpec({
compileText(C64Target, false, text, writeAssembly = false).assertSuccess() 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=""" val text="""
main { main {
sub start() { sub start() {
@ -315,12 +309,9 @@ class TestScoping: FunSpec({
routine(5) routine(5)
routine.value = 5 routine.value = 5
routine.arg = 5 routine.arg = 5
xx = &routine.nested
routine.nested(5)
routine.nested.nestedvalue = 5
routine.nested.arg2 = 5 routine.nested.arg2 = 5
xx = &wrong.routine routine.nested.nestedvalue = 5
wrong.routine.value = 5 nested.nestedvalue = 5
} }
sub routine(ubyte arg) { sub routine(ubyte arg) {
@ -334,16 +325,11 @@ class TestScoping: FunSpec({
""" """
val errors= ErrorReporterForTests() val errors= ErrorReporterForTests()
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure() compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 10 errors.errors.size shouldBe 5
errors.errors[0] shouldContain "undefined symbol: routine" errors.errors[0] shouldContain "undefined symbol: routine.value"
errors.errors[1] shouldContain "undefined symbol: routine" errors.errors[1] shouldContain "undefined symbol: routine.arg"
errors.errors[2] shouldContain "undefined symbol: routine.value" errors.errors[2] shouldContain "undefined symbol: routine.nested.arg2"
errors.errors[3] shouldContain "undefined symbol: routine.arg" errors.errors[3] shouldContain "undefined symbol: routine.nested.nestedvalue"
errors.errors[4] shouldContain "undefined symbol: routine.nested" errors.errors[4] shouldContain "undefined symbol: nested.nestedvalue"
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"
} }
}) })

View File

@ -7,7 +7,6 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.parser.SourceCode import prog8.parser.SourceCode
import kotlin.reflect.typeOf
const val internedStringsModuleName = "prog8_interned_strings" const val internedStringsModuleName = "prog8_interned_strings"
@ -76,16 +75,10 @@ interface IStatementContainer {
// is INamedStatement -> { // is INamedStatement -> {
// if(stmt.name==name) return stmt // if(stmt.name==name) return stmt
// } // }
is VarDecl -> { is VarDecl -> if(stmt.name==name) return stmt
if(stmt.name==name) return stmt is Label -> if(stmt.name==name) return stmt
} is Subroutine -> if(stmt.name==name) return stmt
is Label -> { is Block -> if(stmt.name==name) return stmt
if(stmt.name==name) return stmt
}
is Subroutine -> {
if(stmt.name==name)
return stmt
}
is AnonymousScope -> { is AnonymousScope -> {
val found = stmt.searchSymbol(name) val found = stmt.searchSymbol(name)
if(found!=null) if(found!=null)
@ -155,41 +148,22 @@ interface INameScope: IStatementContainer, INamedStatement {
} }
private fun lookupQualified(scopedName: List<String>): Statement? { private fun lookupQualified(scopedName: List<String>): Statement? {
// a scoped name refers to a name in another namespace. // a scoped name refers to a name in another namespace, and stars from the root.
// 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
for(module in (this as Node).definingModule.program.modules) { for(module in (this as Node).definingModule.program.modules) {
module.statements.forEach { val block = module.searchSymbol(scopedName[0])
if(it is Block && it.name==scopedName[0]) if(block!=null) {
return it.lookup(scopedName) var statement = block
for(name in scopedName.drop(1)) {
statement = (statement as? IStatementContainer)?.searchSymbol(name)
if(statement==null)
return null
}
return statement
} }
} }
return null return null
} }
private fun resolveLocally(startScope: INameScope, name: List<String>): 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? { private fun lookupUnqualified(name: String): Statement? {
val builtinFunctionsNames = (this as Node).definingModule.program.builtinFunctions.names val builtinFunctionsNames = (this as Node).definingModule.program.builtinFunctions.names
if(name in builtinFunctionsNames) { 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) // 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 var statementScope = this
while(statementScope !is GlobalNamespace) { while(statementScope !is GlobalNamespace) {
val symbol = statementScope.searchSymbol(name) val symbol = statementScope.searchSymbol(name)

View File

@ -119,18 +119,20 @@ It must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$1ff
.. _scopes: .. _scopes:
**Scopes** **Scoping rules**
.. sidebar:: .. 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``, So, accessing a variable ``counter`` defined in subroutine ``worker`` in block ``main``,
can be done from anywhere by using ``main.worker.counter``. 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 *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, 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: Scopes are created using either of these two statements: