mirror of
https://github.com/irmen/prog8.git
synced 2024-11-24 13:32:28 +00:00
449 lines
15 KiB
Kotlin
449 lines
15 KiB
Kotlin
package prog8tests.compiler
|
|
|
|
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.compilerAst.toplevelModule
|
|
module.parent shouldBe instanceOf<GlobalNamespace>()
|
|
module.program shouldBeSameInstanceAs result.compilerAst
|
|
module.parent.parent shouldBe instanceOf<ParentSentinel>()
|
|
}
|
|
|
|
test("anon scope vars moved into subroutine scope") {
|
|
val src = """
|
|
main {
|
|
sub start() {
|
|
repeat 10 {
|
|
ubyte xx = 99
|
|
rol(xx)
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
|
|
val mainBlock = result.compilerAst.entrypoint.definingBlock
|
|
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<FunctionCallStatement>()
|
|
}
|
|
|
|
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 mainBlock = result.compilerAst.entrypoint.definingBlock
|
|
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"
|
|
errors.errors[0] shouldContain "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"
|
|
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"
|
|
}
|
|
|
|
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
|
|
/*
|
|
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...
|
|
*/
|
|
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 4
|
|
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 "internalOk"
|
|
errors.errors[3] shouldContain "line 11"
|
|
}
|
|
})
|