2021-10-27 00:30:58 +00:00
|
|
|
package prog8tests
|
|
|
|
|
2021-11-08 14:50:29 +00:00
|
|
|
import io.kotest.assertions.withClue
|
2021-11-07 23:16:58 +00:00
|
|
|
import io.kotest.core.spec.style.FunSpec
|
2021-11-08 14:50:29 +00:00
|
|
|
import io.kotest.matchers.shouldBe
|
2021-11-23 21:36:28 +00:00
|
|
|
import io.kotest.matchers.string.shouldContain
|
2021-11-08 14:50:29 +00:00
|
|
|
import io.kotest.matchers.types.instanceOf
|
|
|
|
import io.kotest.matchers.types.shouldBeSameInstanceAs
|
2021-10-29 22:25:34 +00:00
|
|
|
import prog8.ast.GlobalNamespace
|
|
|
|
import prog8.ast.base.ParentSentinel
|
2021-10-27 00:30:58 +00:00
|
|
|
import prog8.ast.expressions.NumericLiteralValue
|
|
|
|
import prog8.ast.statements.*
|
2021-12-28 13:23:36 +00:00
|
|
|
import prog8.codegen.target.C64Target
|
2021-11-23 21:36:28 +00:00
|
|
|
import prog8tests.helpers.ErrorReporterForTests
|
|
|
|
import prog8tests.helpers.assertFailure
|
2021-10-27 00:30:58 +00:00
|
|
|
import prog8tests.helpers.assertSuccess
|
|
|
|
import prog8tests.helpers.compileText
|
|
|
|
|
|
|
|
|
2021-11-07 23:16:58 +00:00
|
|
|
class TestScoping: FunSpec({
|
2021-10-27 00:30:58 +00:00
|
|
|
|
2021-11-23 21:36:28 +00:00
|
|
|
test("modules parent is global namespace") {
|
2021-10-29 22:25:34 +00:00
|
|
|
val src = """
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
|
|
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
|
|
|
val module = result.program.toplevelModule
|
2021-11-08 14:50:29 +00:00
|
|
|
module.parent shouldBe instanceOf<GlobalNamespace>()
|
|
|
|
module.program shouldBeSameInstanceAs result.program
|
|
|
|
module.parent.parent shouldBe instanceOf<ParentSentinel>()
|
2021-10-29 22:25:34 +00:00
|
|
|
}
|
|
|
|
|
2021-11-23 21:36:28 +00:00
|
|
|
test("anon scope vars moved into subroutine scope") {
|
2021-10-27 00:30:58 +00:00
|
|
|
val src = """
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
repeat {
|
|
|
|
ubyte xx = 99
|
|
|
|
xx++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
|
|
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
2021-10-29 22:25:34 +00:00
|
|
|
val module = result.program.toplevelModule
|
2021-10-27 00:30:58 +00:00
|
|
|
val mainBlock = module.statements.single() as Block
|
|
|
|
val start = mainBlock.statements.single() as Subroutine
|
|
|
|
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
|
2021-11-08 14:50:29 +00:00
|
|
|
withClue("no vars moved to main block") {
|
|
|
|
mainBlock.statements.any { it is VarDecl } shouldBe false
|
|
|
|
}
|
2021-10-27 00:30:58 +00:00
|
|
|
val subroutineVars = start.statements.filterIsInstance<VarDecl>()
|
2021-11-08 14:50:29 +00:00
|
|
|
withClue("var from repeat anonscope must be moved up to subroutine") {
|
|
|
|
subroutineVars.size shouldBe 1
|
|
|
|
}
|
|
|
|
subroutineVars[0].name shouldBe "xx"
|
|
|
|
withClue("var should have been removed from repeat anonscope") {
|
|
|
|
repeatbody.statements.any { it is VarDecl } shouldBe false
|
|
|
|
}
|
2021-10-27 00:30:58 +00:00
|
|
|
val initassign = repeatbody.statements[0] as? Assignment
|
2021-11-08 14:50:29 +00:00
|
|
|
withClue("vardecl in repeat should be replaced by init assignment") {
|
|
|
|
initassign?.target?.identifier?.nameInSource shouldBe listOf("xx")
|
|
|
|
}
|
|
|
|
withClue("vardecl in repeat should be replaced by init assignment") {
|
|
|
|
(initassign?.value as? NumericLiteralValue)?.number?.toInt() shouldBe 99
|
|
|
|
}
|
|
|
|
repeatbody.statements[1] shouldBe instanceOf<PostIncrDecr>()
|
2021-10-27 00:30:58 +00:00
|
|
|
}
|
|
|
|
|
2021-11-23 21:36:28 +00:00
|
|
|
test("labels with anon scopes") {
|
2021-10-27 00:30:58 +00:00
|
|
|
val src = """
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
uword addr
|
|
|
|
goto labeloutside
|
|
|
|
|
|
|
|
if true {
|
|
|
|
if true {
|
|
|
|
addr = &iflabel
|
|
|
|
addr = &labelinside
|
|
|
|
addr = &labeloutside
|
|
|
|
addr = &main.start.nested.nestedlabel
|
|
|
|
goto labeloutside
|
|
|
|
goto iflabel
|
|
|
|
goto main.start.nested.nestedlabel
|
|
|
|
}
|
|
|
|
iflabel:
|
|
|
|
}
|
|
|
|
|
|
|
|
repeat {
|
|
|
|
addr = &iflabel
|
|
|
|
addr = &labelinside
|
|
|
|
addr = &labeloutside
|
|
|
|
addr = &main.start.nested.nestedlabel
|
|
|
|
goto iflabel
|
|
|
|
goto labelinside
|
|
|
|
goto main.start.nested.nestedlabel
|
|
|
|
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
|
|
|
|
goto main.start.nested.nestedlabel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
|
|
val result = compileText(C64Target, false, src, writeAssembly = true).assertSuccess()
|
2021-10-29 22:25:34 +00:00
|
|
|
val module = result.program.toplevelModule
|
2021-10-27 00:30:58 +00:00
|
|
|
val mainBlock = module.statements.single() as Block
|
|
|
|
val start = mainBlock.statements.single() as Subroutine
|
|
|
|
val labels = start.statements.filterIsInstance<Label>()
|
2021-11-08 14:50:29 +00:00
|
|
|
withClue("only one label in subroutine scope") {
|
|
|
|
labels.size shouldBe 1
|
|
|
|
}
|
2021-10-27 00:30:58 +00:00
|
|
|
}
|
|
|
|
|
2021-11-23 21:36:28 +00:00
|
|
|
test("good subroutine call without qualified names") {
|
|
|
|
val text="""
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
routine()
|
|
|
|
routine2()
|
|
|
|
|
|
|
|
sub routine2() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub routine() {
|
|
|
|
start()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
|
|
|
}
|
|
|
|
|
|
|
|
test("wrong subroutine call without qualified names") {
|
|
|
|
val text="""
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
sub routine2() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub routine() {
|
|
|
|
routine2()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val errors= ErrorReporterForTests()
|
|
|
|
compileText(C64Target, false, text, writeAssembly = false, errors = errors).assertFailure()
|
|
|
|
errors.errors.size shouldBe 1
|
|
|
|
errors.errors[0] shouldContain "undefined symbol: routine2"
|
|
|
|
}
|
|
|
|
|
|
|
|
test("good subroutine calls with qualified names (from root)") {
|
|
|
|
val text="""
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
main.routine()
|
|
|
|
main.start.routine2()
|
|
|
|
|
|
|
|
sub routine2() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub routine() {
|
|
|
|
main.start.routine2()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
|
|
|
}
|
|
|
|
|
|
|
|
test("wrong subroutine calls with qualified names (not from root)") {
|
|
|
|
val text="""
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
start.routine2()
|
|
|
|
wrong.start.routine2()
|
|
|
|
sub routine2() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub routine() {
|
|
|
|
start.routine2()
|
|
|
|
wrong.start.routine2()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val errors= ErrorReporterForTests()
|
|
|
|
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
|
|
|
errors.errors.size shouldBe 4
|
|
|
|
errors.errors[0] shouldContain "undefined symbol: start.routine2"
|
|
|
|
errors.errors[1] shouldContain "undefined symbol: wrong.start.routine2"
|
|
|
|
errors.errors[2] shouldContain "undefined symbol: start.routine2"
|
|
|
|
errors.errors[3] shouldContain "undefined symbol: wrong.start.routine2"
|
|
|
|
}
|
|
|
|
|
|
|
|
test("good variables without qualified names") {
|
|
|
|
val text="""
|
|
|
|
main {
|
|
|
|
ubyte v1
|
|
|
|
|
|
|
|
sub start() {
|
|
|
|
ubyte v2
|
|
|
|
v1=1
|
|
|
|
v2=2
|
|
|
|
|
|
|
|
sub routine2() {
|
|
|
|
ubyte v3
|
|
|
|
v1=1
|
|
|
|
v2=2
|
|
|
|
v3=3
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub routine() {
|
|
|
|
ubyte v4
|
|
|
|
v1=1
|
|
|
|
v4=4
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
|
|
|
}
|
|
|
|
|
|
|
|
test("wrong variables without qualified names") {
|
|
|
|
val text="""
|
|
|
|
main {
|
|
|
|
ubyte v1
|
|
|
|
|
|
|
|
sub start() {
|
|
|
|
ubyte v2
|
|
|
|
v1=1
|
|
|
|
v2=2
|
|
|
|
v3=3 ; can't access
|
|
|
|
v4=4 ; can't access
|
|
|
|
sub routine2() {
|
|
|
|
ubyte v3
|
|
|
|
v1=1
|
|
|
|
v2=2
|
|
|
|
v3=3
|
|
|
|
v4=3 ;can't access
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub routine() {
|
|
|
|
ubyte v4
|
|
|
|
v1=1
|
|
|
|
v2=2 ; can't access
|
|
|
|
v3=3 ; can't access
|
|
|
|
v4=4
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val errors= ErrorReporterForTests()
|
|
|
|
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
|
|
|
errors.errors.size shouldBe 5
|
|
|
|
errors.errors[0] shouldContain "undefined symbol: v3"
|
|
|
|
errors.errors[1] shouldContain "undefined symbol: v4"
|
|
|
|
errors.errors[2] shouldContain "undefined symbol: v4"
|
|
|
|
errors.errors[3] shouldContain "undefined symbol: v2"
|
|
|
|
errors.errors[4] shouldContain "undefined symbol: v3"
|
|
|
|
}
|
|
|
|
|
|
|
|
test("good variable refs with qualified names (from root)") {
|
|
|
|
val text="""
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
uword xx
|
|
|
|
xx = &main.routine
|
|
|
|
main.routine(5)
|
|
|
|
main.routine.value = 5
|
|
|
|
main.routine.arg = 5
|
|
|
|
xx = &main.routine.nested
|
|
|
|
main.routine.nested(5)
|
|
|
|
main.routine.nested.nestedvalue = 5
|
|
|
|
main.routine.nested.arg2 = 5
|
|
|
|
}
|
|
|
|
|
|
|
|
sub routine(ubyte arg) {
|
|
|
|
ubyte value
|
|
|
|
|
|
|
|
sub nested(ubyte arg2) {
|
|
|
|
ubyte nestedvalue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
|
|
|
}
|
|
|
|
|
2021-11-23 22:43:23 +00:00
|
|
|
test("wrong variable refs with qualified names 1 (not from root)") {
|
2021-11-23 21:36:28 +00:00
|
|
|
val text="""
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
uword xx
|
|
|
|
xx = &routine
|
|
|
|
routine(5)
|
|
|
|
routine.value = 5
|
|
|
|
routine.arg = 5
|
|
|
|
routine.nested.arg2 = 5
|
2021-11-23 22:43:23 +00:00
|
|
|
routine.nested.nestedvalue = 5
|
|
|
|
nested.nestedvalue = 5
|
2021-11-23 21:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub routine(ubyte arg) {
|
|
|
|
ubyte value
|
|
|
|
|
|
|
|
sub nested(ubyte arg2) {
|
|
|
|
ubyte nestedvalue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val errors= ErrorReporterForTests()
|
|
|
|
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
2021-11-23 22:43:23 +00:00
|
|
|
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"
|
2021-11-23 21:36:28 +00:00
|
|
|
}
|
2021-11-07 23:16:58 +00:00
|
|
|
})
|