mirror of
https://github.com/irmen/prog8.git
synced 2024-12-24 01:29:28 +00:00
fix nested label lookups in anon scopes (partly)
This commit is contained in:
parent
3cc7ad7d20
commit
ac5f45d2d4
@ -75,7 +75,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
||||||
val paramsToCheck = paramNames.intersect(namesInSub)
|
val paramsToCheck = paramNames.intersect(namesInSub)
|
||||||
for(name in paramsToCheck) {
|
for(name in paramsToCheck) {
|
||||||
val labelOrVar = subroutine.getLabelOrVariable(name)
|
val labelOrVar = subroutine.searchLabelOrVariableNotSubscoped(name)
|
||||||
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
|
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
|
||||||
nameError(name, labelOrVar.position, subroutine)
|
nameError(name, labelOrVar.position, subroutine)
|
||||||
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
|
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
|
||||||
|
112
compiler/test/TestScoping.kt
Normal file
112
compiler/test/TestScoping.kt
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package prog8tests
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.target.C64Target
|
||||||
|
import prog8tests.helpers.assertSuccess
|
||||||
|
import prog8tests.helpers.compileText
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
class TestScoping {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAnonScopeVarsMovedIntoSubroutineScope() {
|
||||||
|
val src = """
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
repeat {
|
||||||
|
ubyte xx = 99
|
||||||
|
xx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
||||||
|
val module = result.programAst.toplevelModule
|
||||||
|
val mainBlock = module.statements.single() as Block
|
||||||
|
val start = mainBlock.statements.single() as Subroutine
|
||||||
|
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
|
||||||
|
assertFalse(mainBlock.statements.any { it is VarDecl }, "no vars moved to main block")
|
||||||
|
val subroutineVars = start.statements.filterIsInstance<VarDecl>()
|
||||||
|
assertEquals(1, subroutineVars.size, "var from repeat anonscope must be moved up to subroutine")
|
||||||
|
assertEquals("xx", subroutineVars[0].name)
|
||||||
|
assertFalse(repeatbody.statements.any { it is VarDecl }, "var should have been removed from repeat anonscope")
|
||||||
|
val initassign = repeatbody.statements[0] as? Assignment
|
||||||
|
assertEquals(listOf("xx"), initassign?.target?.identifier?.nameInSource, "vardecl in repeat should be replaced by init assignment")
|
||||||
|
assertEquals(99, (initassign?.value as? NumericLiteralValue)?.number?.toInt(), "vardecl in repeat should be replaced by init assignment")
|
||||||
|
assertTrue(repeatbody.statements[1] is PostIncrDecr)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLabelsWithAnonScopes() {
|
||||||
|
val src = """
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
uword addr
|
||||||
|
goto labeloutside
|
||||||
|
|
||||||
|
if true {
|
||||||
|
if true {
|
||||||
|
addr = &iflabel
|
||||||
|
addr = &labelinside
|
||||||
|
addr = &labeloutside
|
||||||
|
addr = &main.start.nested.nestedlabel
|
||||||
|
; addr = &nested.nestedlabel ; TODO should also work!!
|
||||||
|
goto labeloutside
|
||||||
|
goto iflabel
|
||||||
|
goto main.start.nested.nestedlabel
|
||||||
|
; goto nested.nestedlabel ; TODO should also work!!
|
||||||
|
}
|
||||||
|
iflabel:
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat {
|
||||||
|
addr = &iflabel
|
||||||
|
addr = &labelinside
|
||||||
|
addr = &labeloutside
|
||||||
|
addr = &main.start.nested.nestedlabel
|
||||||
|
; addr = &nested.nestedlabel ; TODO should also work!!
|
||||||
|
goto iflabel
|
||||||
|
goto labelinside
|
||||||
|
goto main.start.nested.nestedlabel
|
||||||
|
; goto nested.nestedlabel ; TODO should also work!!
|
||||||
|
labelinside:
|
||||||
|
}
|
||||||
|
|
||||||
|
sub nested () {
|
||||||
|
nestedlabel:
|
||||||
|
addr = &nestedlabel
|
||||||
|
goto nestedlabel
|
||||||
|
goto main.start.nested.nestedlabel
|
||||||
|
}
|
||||||
|
|
||||||
|
labeloutside:
|
||||||
|
addr = &iflabel
|
||||||
|
addr = &labelinside
|
||||||
|
addr = &labeloutside
|
||||||
|
addr = &main.start.nested.nestedlabel
|
||||||
|
; addr = &nested.nestedlabel ; TODO should also work!!
|
||||||
|
goto main.start.nested.nestedlabel
|
||||||
|
; goto nested.nestedlabel ; TODO should also work!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val result = compileText(C64Target, false, src, writeAssembly = true).assertSuccess()
|
||||||
|
val module = result.programAst.toplevelModule
|
||||||
|
val mainBlock = module.statements.single() as Block
|
||||||
|
val start = mainBlock.statements.single() as Subroutine
|
||||||
|
val labels = start.statements.filterIsInstance<Label>()
|
||||||
|
assertEquals(1, labels.size, "only one label in subroutine scope")
|
||||||
|
|
||||||
|
TODO("fix the partial scoped lookups above as well")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,7 +5,6 @@ import org.junit.jupiter.api.Test
|
|||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
|
||||||
import prog8.ast.expressions.TypecastExpression
|
import prog8.ast.expressions.TypecastExpression
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.target.C64Target
|
import prog8.compiler.target.C64Target
|
||||||
@ -182,33 +181,4 @@ class TestSubroutines {
|
|||||||
assertEquals(DataType.ARRAY_UB, func.parameters.single().type)
|
assertEquals(DataType.ARRAY_UB, func.parameters.single().type)
|
||||||
assertTrue(func.statements.isEmpty())
|
assertTrue(func.statements.isEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testAnonScopeVarsMovedIntoSubroutineScope() {
|
|
||||||
val src = """
|
|
||||||
main {
|
|
||||||
sub start() {
|
|
||||||
repeat {
|
|
||||||
ubyte xx = 99
|
|
||||||
xx++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
|
||||||
val module = result.programAst.toplevelModule
|
|
||||||
val mainBlock = module.statements.single() as Block
|
|
||||||
val start = mainBlock.statements.single() as Subroutine
|
|
||||||
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
|
|
||||||
assertFalse(mainBlock.statements.any { it is VarDecl }, "no vars moved to main block")
|
|
||||||
val subroutineVars = start.statements.filterIsInstance<VarDecl>()
|
|
||||||
assertEquals(1, subroutineVars.size, "var from repeat anonscope must be moved up to subroutine")
|
|
||||||
assertEquals("xx", subroutineVars[0].name)
|
|
||||||
assertFalse(repeatbody.statements.any { it is VarDecl }, "var should have been removed from repeat anonscope")
|
|
||||||
val initassign = repeatbody.statements[0] as? Assignment
|
|
||||||
assertEquals(listOf("xx"), initassign?.target?.identifier?.nameInSource, "vardecl in repeat should be replaced by init assignment")
|
|
||||||
assertEquals(99, (initassign?.value as? NumericLiteralValue)?.number?.toInt(), "vardecl in repeat should be replaced by init assignment")
|
|
||||||
assertTrue(repeatbody.statements[1] is PostIncrDecr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,71 @@ interface IStatementContainer {
|
|||||||
fun isEmpty(): Boolean = statements.isEmpty()
|
fun isEmpty(): Boolean = statements.isEmpty()
|
||||||
fun isNotEmpty(): Boolean = statements.isNotEmpty()
|
fun isNotEmpty(): Boolean = statements.isNotEmpty()
|
||||||
|
|
||||||
|
fun searchLabelOrVariableNotSubscoped(name: String): 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) {
|
||||||
|
when(stmt) {
|
||||||
|
// 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 AnonymousScope -> {
|
||||||
|
val found = stmt.searchLabelOrVariableNotSubscoped(name)
|
||||||
|
if(found!=null)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
is IfStatement -> {
|
||||||
|
val found = stmt.truepart.searchLabelOrVariableNotSubscoped(name) ?: stmt.elsepart.searchLabelOrVariableNotSubscoped(name)
|
||||||
|
if(found!=null)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
is BranchStatement -> {
|
||||||
|
val found = stmt.truepart.searchLabelOrVariableNotSubscoped(name) ?: stmt.elsepart.searchLabelOrVariableNotSubscoped(name)
|
||||||
|
if(found!=null)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
is ForLoop -> {
|
||||||
|
val found = stmt.body.searchLabelOrVariableNotSubscoped(name)
|
||||||
|
if(found!=null)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
is WhileLoop -> {
|
||||||
|
val found = stmt.body.searchLabelOrVariableNotSubscoped(name)
|
||||||
|
if(found!=null)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
is RepeatLoop -> {
|
||||||
|
val found = stmt.body.searchLabelOrVariableNotSubscoped(name)
|
||||||
|
if(found!=null)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
is UntilLoop -> {
|
||||||
|
val found = stmt.body.searchLabelOrVariableNotSubscoped(name)
|
||||||
|
if(found!=null)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
is WhenStatement -> {
|
||||||
|
stmt.choices.forEach {
|
||||||
|
val found = it.statements.searchLabelOrVariableNotSubscoped(name)
|
||||||
|
if(found!=null)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// NOTE: when other nodes containing AnonymousScope are introduced,
|
||||||
|
// these should be added here as well to look into!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
val allDefinedSymbols: List<Pair<String, Statement>>
|
val allDefinedSymbols: List<Pair<String, Statement>>
|
||||||
get() {
|
get() {
|
||||||
return statements.mapNotNull {
|
return statements.mapNotNull {
|
||||||
@ -101,17 +166,7 @@ interface IStatementContainer {
|
|||||||
interface INameScope: IStatementContainer, INamedStatement {
|
interface INameScope: IStatementContainer, INamedStatement {
|
||||||
fun subScope(name: String): INameScope? = statements.firstOrNull { it is INameScope && it.name==name } as? INameScope
|
fun subScope(name: String): INameScope? = statements.firstOrNull { it is INameScope && it.name==name } as? INameScope
|
||||||
|
|
||||||
fun getLabelOrVariable(name: String): Statement? {
|
fun lookup(scopedName: List<String>, localContext: Node) : Statement? { // TODO return INamedStatement instead?
|
||||||
// this is called 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) {
|
|
||||||
if (stmt is VarDecl && stmt.name==name) return stmt
|
|
||||||
if (stmt is Label && stmt.name==name) return stmt
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun lookup(scopedName: List<String>, localContext: Node) : Statement? {
|
|
||||||
if(scopedName.size>1) {
|
if(scopedName.size>1) {
|
||||||
// a scoped name refers to a name in another module.
|
// 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)
|
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
|
||||||
@ -123,7 +178,7 @@ interface INameScope: IStatementContainer, INamedStatement {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if(scope!=null) {
|
if(scope!=null) {
|
||||||
val result = scope.getLabelOrVariable(scopedName.last())
|
val result = scope.searchLabelOrVariableNotSubscoped(scopedName.last())
|
||||||
if(result!=null)
|
if(result!=null)
|
||||||
return result
|
return result
|
||||||
return scope.subScope(scopedName.last()) as Statement?
|
return scope.subScope(scopedName.last()) as Statement?
|
||||||
@ -136,7 +191,7 @@ interface INameScope: IStatementContainer, INamedStatement {
|
|||||||
var statementScope = localContext
|
var statementScope = localContext
|
||||||
while(statementScope !is ParentSentinel) {
|
while(statementScope !is ParentSentinel) {
|
||||||
val localScope = statementScope.definingScope
|
val localScope = statementScope.definingScope
|
||||||
val result = localScope.getLabelOrVariable(scopedName[0])
|
val result = localScope.searchLabelOrVariableNotSubscoped(scopedName[0])
|
||||||
if (result != null)
|
if (result != null)
|
||||||
return result
|
return result
|
||||||
val subscope = localScope.subScope(scopedName[0]) as Statement?
|
val subscope = localScope.subScope(scopedName[0]) as Statement?
|
||||||
@ -168,7 +223,6 @@ interface Node {
|
|||||||
|
|
||||||
val definingSubroutine: Subroutine? get() = findParentNode<Subroutine>(this)
|
val definingSubroutine: Subroutine? get() = findParentNode<Subroutine>(this)
|
||||||
|
|
||||||
// TODO CHECK IF THIS IS ACTUALLY WHAT'S DESIRED: (namescope v.s. statementcontainer)
|
|
||||||
val definingScope: INameScope
|
val definingScope: INameScope
|
||||||
get() {
|
get() {
|
||||||
val scope = findParentNode<INameScope>(this)
|
val scope = findParentNode<INameScope>(this)
|
||||||
@ -374,7 +428,7 @@ class GlobalNamespace(val modules: Iterable<Module>, private val builtinFunction
|
|||||||
throw FatalAstException("cannot replace anything in the namespace")
|
throw FatalAstException("cannot replace anything in the namespace")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
|
override fun lookup(scopedName: List<String>, localContext: Node): Statement? { // TODO return INamedStatement instead?
|
||||||
if (scopedName.size == 1 && scopedName[0] in builtinFunctionNames) {
|
if (scopedName.size == 1 && scopedName[0] in builtinFunctionNames) {
|
||||||
// builtin functions always exist, return a dummy localContext for them
|
// builtin functions always exist, return a dummy localContext for them
|
||||||
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
|
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
|
||||||
|
@ -632,4 +632,35 @@ class TestProg8Parser {
|
|||||||
assertTrue(repeatbody.statements[1] is PostIncrDecr)
|
assertTrue(repeatbody.statements[1] is PostIncrDecr)
|
||||||
// the ast processing steps used in the compiler, will eventually move the var up to the containing scope (subroutine).
|
// the ast processing steps used in the compiler, will eventually move the var up to the containing scope (subroutine).
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLabelsWithAnonScopesParsesFine() {
|
||||||
|
val src = SourceCode.Text("""
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
goto mylabeloutside
|
||||||
|
|
||||||
|
if true {
|
||||||
|
if true {
|
||||||
|
goto labeloutside
|
||||||
|
goto iflabel
|
||||||
|
}
|
||||||
|
iflabel:
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat {
|
||||||
|
goto labelinside
|
||||||
|
labelinside:
|
||||||
|
}
|
||||||
|
|
||||||
|
labeloutside:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
val module = parseModule(src)
|
||||||
|
val mainBlock = module.statements.single() as Block
|
||||||
|
val start = mainBlock.statements.single() as Subroutine
|
||||||
|
val labels = start.statements.filterIsInstance<Label>()
|
||||||
|
assertEquals(1, labels.size, "only one label in subroutine scope")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,24 @@
|
|||||||
|
|
||||||
main {
|
main {
|
||||||
sub start() {
|
sub start() {
|
||||||
ubyte col
|
goto labeloutside
|
||||||
ubyte row
|
|
||||||
repeat {
|
if true {
|
||||||
col = rnd() % 33
|
if true {
|
||||||
row = rnd() % 33
|
goto labeloutside
|
||||||
;cx16logo.logo_at(col, row)
|
goto iflabel
|
||||||
;txt.plot(col-3, row+7 )
|
}
|
||||||
;txt.print("commander x16")
|
iflabel:
|
||||||
col++
|
|
||||||
row++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repeat {
|
||||||
|
goto labelinside
|
||||||
|
labelinside:
|
||||||
|
}
|
||||||
|
|
||||||
|
labeloutside:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start:
|
||||||
|
start:
|
||||||
|
start:
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user