2024-09-08 14:03:57 +00:00
|
|
|
package prog8tests.compiler
|
2021-10-27 00:30:58 +00:00
|
|
|
|
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
|
2022-03-07 20:41:12 +00:00
|
|
|
import io.kotest.matchers.shouldNotBe
|
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
|
2022-03-10 00:41:42 +00:00
|
|
|
import prog8.ast.ParentSentinel
|
2022-02-10 23:21:40 +00:00
|
|
|
import prog8.ast.expressions.NumericLiteral
|
2021-10-27 00:30:58 +00:00
|
|
|
import prog8.ast.statements.*
|
2022-03-11 19:35:25 +00:00
|
|
|
import prog8.code.target.C64Target
|
2021-11-23 21:36:28 +00:00
|
|
|
import prog8tests.helpers.ErrorReporterForTests
|
2021-10-27 00:30:58 +00:00
|
|
|
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() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
2022-03-07 20:41:12 +00:00
|
|
|
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
|
2023-02-09 00:46:23 +00:00
|
|
|
val module = result.compilerAst.toplevelModule
|
2021-11-08 14:50:29 +00:00
|
|
|
module.parent shouldBe instanceOf<GlobalNamespace>()
|
2023-02-09 00:46:23 +00:00
|
|
|
module.program shouldBeSameInstanceAs result.compilerAst
|
2021-11-08 14:50:29 +00:00
|
|
|
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() {
|
2022-03-08 02:25:34 +00:00
|
|
|
repeat 10 {
|
2021-10-27 00:30:58 +00:00
|
|
|
ubyte xx = 99
|
2024-02-06 17:50:08 +00:00
|
|
|
rol(xx)
|
2021-10-27 00:30:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
2022-03-07 20:41:12 +00:00
|
|
|
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
|
2023-02-09 00:46:23 +00:00
|
|
|
val module = result.compilerAst.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") {
|
2022-02-10 23:21:40 +00:00
|
|
|
(initassign?.value as? NumericLiteral)?.number?.toInt() shouldBe 99
|
2021-11-08 14:50:29 +00:00
|
|
|
}
|
2024-02-06 17:50:08 +00:00
|
|
|
repeatbody.statements[1] shouldBe instanceOf<FunctionCallStatement>()
|
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:
|
|
|
|
}
|
|
|
|
|
2022-03-08 02:25:34 +00:00
|
|
|
repeat 10 {
|
2021-10-27 00:30:58 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
2022-03-07 20:41:12 +00:00
|
|
|
val result = compileText(C64Target(), false, src, writeAssembly = true)!!
|
2023-02-09 00:46:23 +00:00
|
|
|
val module = result.compilerAst.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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
2022-03-07 20:41:12 +00:00
|
|
|
compileText(C64Target(), false, text, writeAssembly = false) shouldNotBe null
|
2021-11-23 21:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test("wrong subroutine call without qualified names") {
|
|
|
|
val text="""
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
sub routine2() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub routine() {
|
|
|
|
routine2()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val errors= ErrorReporterForTests()
|
2022-03-07 20:41:12 +00:00
|
|
|
compileText(C64Target(), false, text, writeAssembly = false, errors = errors) shouldBe null
|
2021-11-23 21:36:28 +00:00
|
|
|
errors.errors.size shouldBe 1
|
2023-03-10 23:26:19 +00:00
|
|
|
errors.errors[0] shouldContain "undefined"
|
|
|
|
errors.errors[0] shouldContain "routine2"
|
2021-11-23 21:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
2022-03-07 20:41:12 +00:00
|
|
|
compileText(C64Target(), false, text, writeAssembly = false) shouldNotBe null
|
2021-11-23 21:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
2022-03-07 20:41:12 +00:00
|
|
|
compileText(C64Target(), false, text, writeAssembly = false, errors=errors) shouldBe null
|
2021-11-23 21:36:28 +00:00
|
|
|
errors.errors.size shouldBe 4
|
2023-03-10 23:26:19 +00:00
|
|
|
errors.errors[0] shouldContain "undefined"
|
|
|
|
errors.errors[0] shouldContain "start.routine2"
|
|
|
|
errors.errors[1] shouldContain "undefined"
|
|
|
|
errors.errors[1] shouldContain "wrong.start.routine2"
|
|
|
|
errors.errors[2] shouldContain "undefined"
|
|
|
|
errors.errors[2] shouldContain "start.routine2"
|
|
|
|
errors.errors[3] shouldContain "undefined"
|
|
|
|
errors.errors[3] shouldContain "wrong.start.routine2"
|
2021-11-23 21:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
2022-03-07 20:41:12 +00:00
|
|
|
compileText(C64Target(), false, text, writeAssembly = false) shouldNotBe null
|
2021-11-23 21:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
2022-03-07 20:41:12 +00:00
|
|
|
compileText(C64Target(), false, text, writeAssembly = false, errors=errors) shouldBe null
|
2021-11-23 21:36:28 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
2022-03-07 20:41:12 +00:00
|
|
|
compileText(C64Target(), false, text, writeAssembly = false) shouldNotBe null
|
2021-11-23 21:36:28 +00:00
|
|
|
}
|
|
|
|
|
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()
|
2022-03-07 20:41:12 +00:00
|
|
|
compileText(C64Target(), false, text, writeAssembly = false, errors=errors) shouldBe null
|
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
|
|
|
}
|
2022-01-21 21:46:10 +00:00
|
|
|
|
|
|
|
test("various good goto targets") {
|
|
|
|
val text="""
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
uword address = $4000
|
|
|
|
|
|
|
|
goto ${'$'}c000
|
|
|
|
goto address ; indirect jump
|
|
|
|
goto main.routine
|
|
|
|
goto main.jumplabel
|
|
|
|
|
|
|
|
if_cc
|
|
|
|
goto ${'$'}c000
|
|
|
|
if_cc
|
|
|
|
goto address ; indirect jump
|
|
|
|
if_cc
|
|
|
|
goto main.routine
|
|
|
|
if_cc
|
|
|
|
goto main.jumplabel
|
|
|
|
}
|
|
|
|
|
|
|
|
jumplabel:
|
|
|
|
%asm {{
|
|
|
|
rts
|
|
|
|
}}
|
|
|
|
sub routine() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
2022-03-07 20:41:12 +00:00
|
|
|
compileText(C64Target(), false, text, writeAssembly = false) shouldNotBe null
|
2022-01-21 21:46:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test("various wrong goto targets") {
|
|
|
|
val text = """
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
byte wrongaddress = 100
|
|
|
|
|
|
|
|
goto wrongaddress ; must be uword
|
|
|
|
goto main.routine ; can't take args
|
|
|
|
}
|
|
|
|
|
|
|
|
sub routine(ubyte arg) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val errors = ErrorReporterForTests()
|
2022-03-07 20:41:12 +00:00
|
|
|
compileText(C64Target(), false, text, writeAssembly = false, errors = errors) shouldBe null
|
2022-01-21 21:46:10 +00:00
|
|
|
errors.errors.size shouldBe 2
|
|
|
|
errors.errors[0] shouldContain "wrong address"
|
|
|
|
errors.errors[1] shouldContain "takes parameters"
|
|
|
|
}
|
2022-06-04 19:35:48 +00:00
|
|
|
|
|
|
|
test("name shadowing and redefinition errors") {
|
|
|
|
val text = """
|
|
|
|
main {
|
|
|
|
ubyte var1Warn
|
|
|
|
|
|
|
|
sub start() {
|
|
|
|
ubyte var1Warn
|
|
|
|
ubyte var1Warn
|
|
|
|
ubyte main
|
|
|
|
ubyte start
|
|
|
|
ubyte outer ; is ok
|
|
|
|
ubyte internalOk
|
|
|
|
ubyte internalOk ; double defined
|
|
|
|
}
|
|
|
|
|
|
|
|
sub outer() {
|
|
|
|
ubyte var1Warn
|
|
|
|
ubyte internalOk
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val errors = ErrorReporterForTests()
|
|
|
|
compileText(C64Target(), false, text, writeAssembly = false, errors = errors) shouldBe null
|
2023-03-11 13:55:13 +00:00
|
|
|
/*
|
|
|
|
There are 4 errors and 3 warnings.
|
|
|
|
ERROR name conflict 'start', also defined...
|
|
|
|
ERROR name conflict 'var1Warn', also defined...
|
|
|
|
ERROR name conflict 'main', also defined...
|
|
|
|
ERROR name conflict 'internalOk', also defined...
|
|
|
|
WARN name 'var1Warn' shadows occurrence at...
|
|
|
|
WARN name 'var1Warn' shadows occurrence at...
|
|
|
|
WARN name 'var1Warn' shadows occurrence at...
|
|
|
|
*/
|
2022-06-04 19:35:48 +00:00
|
|
|
errors.warnings.size shouldBe 3
|
|
|
|
errors.warnings[0] shouldContain "var1Warn"
|
|
|
|
errors.warnings[0] shouldContain "shadows"
|
|
|
|
errors.warnings[0] shouldContain "line 3"
|
|
|
|
errors.warnings[1] shouldContain "var1Warn"
|
|
|
|
errors.warnings[1] shouldContain "shadows"
|
|
|
|
errors.warnings[1] shouldContain "line 3"
|
|
|
|
errors.warnings[2] shouldContain "var1Warn"
|
|
|
|
errors.warnings[2] shouldContain "shadows"
|
|
|
|
errors.warnings[2] shouldContain "line 3"
|
2023-03-11 13:55:13 +00:00
|
|
|
errors.errors.size shouldBe 4
|
2022-06-04 19:35:48 +00:00
|
|
|
errors.errors[0] shouldContain "name conflict"
|
|
|
|
errors.errors[0] shouldContain "start"
|
|
|
|
errors.errors[0] shouldContain "line 5"
|
|
|
|
errors.errors[1] shouldContain "name conflict"
|
|
|
|
errors.errors[1] shouldContain "var1Warn"
|
|
|
|
errors.errors[1] shouldContain "line 6"
|
|
|
|
errors.errors[2] shouldContain "name conflict"
|
|
|
|
errors.errors[2] shouldContain "main"
|
|
|
|
errors.errors[2] shouldContain "line 2"
|
|
|
|
errors.errors[3] shouldContain "name conflict"
|
2023-03-11 13:55:13 +00:00
|
|
|
errors.errors[3] shouldContain "internalOk"
|
|
|
|
errors.errors[3] shouldContain "line 11"
|
2022-06-04 19:35:48 +00:00
|
|
|
}
|
2021-11-07 23:16:58 +00:00
|
|
|
})
|