mirror of
https://github.com/irmen/prog8.git
synced 2025-02-25 04:29:36 +00:00
fix some scoping related symbol lookup issues, clarified scoping rules in docs
This commit is contained in:
parent
b00db4f8a2
commit
e52d05c7db
@ -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 {
|
||||||
|
@ -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"
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user