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

View File

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

View File

@ -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<String>): 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<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? {
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)

View File

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