prog8/compiler/test/TestScoping.kt
2022-06-04 21:35:48 +02:00

440 lines
15 KiB
Kotlin

package prog8tests
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import io.kotest.matchers.types.shouldBeSameInstanceAs
import prog8.ast.GlobalNamespace
import prog8.ast.ParentSentinel
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.*
import prog8.code.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
class TestScoping: FunSpec({
test("modules parent is global namespace") {
val src = """
main {
sub start() {
}
}
"""
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
val module = result.program.toplevelModule
module.parent shouldBe instanceOf<GlobalNamespace>()
module.program shouldBeSameInstanceAs result.program
module.parent.parent shouldBe instanceOf<ParentSentinel>()
}
test("anon scope vars moved into subroutine scope") {
val src = """
main {
sub start() {
repeat 10 {
ubyte xx = 99
xx++
}
}
}
"""
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
withClue("no vars moved to main block") {
mainBlock.statements.any { it is VarDecl } shouldBe false
}
val subroutineVars = start.statements.filterIsInstance<VarDecl>()
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
}
val initassign = repeatbody.statements[0] as? Assignment
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? NumericLiteral)?.number?.toInt() shouldBe 99
}
repeatbody.statements[1] shouldBe instanceOf<PostIncrDecr>()
}
test("labels with anon scopes") {
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 10 {
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)!!
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val start = mainBlock.statements.single() as Subroutine
val labels = start.statements.filterIsInstance<Label>()
withClue("only one label in subroutine scope") {
labels.size shouldBe 1
}
}
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) shouldNotBe null
}
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) shouldBe null
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) shouldNotBe null
}
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) shouldBe null
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) shouldNotBe null
}
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) shouldBe null
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) shouldNotBe null
}
test("wrong variable refs with qualified names 1 (not from root)") {
val text="""
main {
sub start() {
uword xx
xx = &routine
routine(5)
routine.value = 5
routine.arg = 5
routine.nested.arg2 = 5
routine.nested.nestedvalue = 5
nested.nestedvalue = 5
}
sub routine(ubyte arg) {
ubyte value
sub nested(ubyte arg2) {
ubyte nestedvalue
}
}
}
"""
val errors= ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = false, errors=errors) shouldBe null
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"
}
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() {
}
}
"""
compileText(C64Target(), false, text, writeAssembly = false) shouldNotBe null
}
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()
compileText(C64Target(), false, text, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "wrong address"
errors.errors[1] shouldContain "takes parameters"
}
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
println(errors.errors)
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"
errors.errors.size shouldBe 5
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"
errors.errors[3] shouldContain "start"
errors.errors[3] shouldContain "line 5"
errors.errors[4] shouldContain "name conflict"
errors.errors[4] shouldContain "internalOk"
errors.errors[4] shouldContain "line 11"
}
})