2021-11-07 16:25:53 +00:00
|
|
|
package prog8tests.ast
|
|
|
|
|
|
|
|
import io.kotest.assertions.throwables.shouldThrow
|
|
|
|
import io.kotest.assertions.withClue
|
|
|
|
import io.kotest.core.spec.style.FunSpec
|
|
|
|
import io.kotest.matchers.collections.shouldBeIn
|
2024-11-19 22:10:56 +00:00
|
|
|
import io.kotest.matchers.comparables.shouldBeGreaterThan
|
2021-11-07 16:25:53 +00:00
|
|
|
import io.kotest.matchers.shouldBe
|
2024-10-29 21:57:54 +00:00
|
|
|
import io.kotest.matchers.shouldNotBe
|
2021-11-07 16:25:53 +00:00
|
|
|
import io.kotest.matchers.string.shouldContain
|
2024-11-19 22:10:56 +00:00
|
|
|
import io.kotest.matchers.string.shouldStartWith
|
2021-11-07 16:25:53 +00:00
|
|
|
import io.kotest.matchers.types.shouldBeSameInstanceAs
|
|
|
|
import prog8.ast.Module
|
|
|
|
import prog8.ast.Program
|
2024-11-19 22:10:56 +00:00
|
|
|
import prog8.ast.statements.Block
|
|
|
|
import prog8.code.ast.PtBlock
|
2022-03-10 21:38:16 +00:00
|
|
|
import prog8.code.core.Position
|
2022-03-21 00:01:21 +00:00
|
|
|
import prog8.code.core.SourceCode
|
|
|
|
import prog8.code.core.internedStringsModuleName
|
2024-10-29 21:57:54 +00:00
|
|
|
import prog8.code.target.C64Target
|
2024-11-21 23:44:00 +00:00
|
|
|
import prog8.code.target.Cx16Target
|
2024-10-29 23:37:45 +00:00
|
|
|
import prog8.code.target.VMTarget
|
|
|
|
import prog8tests.helpers.*
|
2021-11-07 16:25:53 +00:00
|
|
|
|
|
|
|
class TestProgram: FunSpec({
|
|
|
|
|
|
|
|
context("Constructor") {
|
|
|
|
test("withNameBuiltinsAndMemsizer") {
|
|
|
|
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
|
|
|
program.modules.size shouldBe 1
|
|
|
|
program.modules[0].name shouldBe internedStringsModuleName
|
|
|
|
program.modules[0].program shouldBeSameInstanceAs program
|
|
|
|
program.modules[0].parent shouldBeSameInstanceAs program.namespace
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
context("AddModule") {
|
|
|
|
test("withEmptyModule") {
|
|
|
|
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
|
|
|
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
|
|
|
|
|
|
|
val retVal = program.addModule(m1)
|
|
|
|
|
|
|
|
retVal shouldBeSameInstanceAs program
|
|
|
|
program.modules.size shouldBe 2
|
|
|
|
m1 shouldBeIn program.modules
|
|
|
|
m1.program shouldBeSameInstanceAs program
|
|
|
|
m1.parent shouldBeSameInstanceAs program.namespace
|
|
|
|
|
|
|
|
withClue("module may not occur multiple times") {
|
|
|
|
val ex = shouldThrow<IllegalArgumentException> { program.addModule(m1) }
|
|
|
|
ex.message shouldContain m1.name
|
|
|
|
}
|
|
|
|
|
|
|
|
val m2 = Module(mutableListOf(), m1.position, m1.source)
|
|
|
|
withClue("other module but with same name may not occur multiple times") {
|
|
|
|
val ex = shouldThrow<IllegalArgumentException> { program.addModule(m2) }
|
|
|
|
ex.message shouldContain m1.name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
context("MoveModuleToFront") {
|
|
|
|
test("withInternedStringsModule") {
|
|
|
|
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
|
|
|
val m = program.modules[0]
|
|
|
|
m.name shouldBe internedStringsModuleName
|
|
|
|
|
|
|
|
val retVal = program.moveModuleToFront(m)
|
|
|
|
retVal shouldBeSameInstanceAs program
|
|
|
|
program.modules[0] shouldBeSameInstanceAs m
|
|
|
|
}
|
|
|
|
|
|
|
|
test("withForeignModule") {
|
|
|
|
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
|
|
|
val m = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
|
|
|
|
|
|
|
shouldThrow<IllegalArgumentException> { program.moveModuleToFront(m) }
|
|
|
|
}
|
|
|
|
|
|
|
|
test("withFirstOfPreviouslyAddedModules") {
|
|
|
|
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
|
|
|
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
|
|
|
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
|
|
|
|
program.addModule(m1)
|
|
|
|
program.addModule(m2)
|
|
|
|
|
|
|
|
val retVal = program.moveModuleToFront(m1)
|
|
|
|
retVal shouldBeSameInstanceAs program
|
|
|
|
program.modules.indexOf(m1) shouldBe 0
|
|
|
|
}
|
|
|
|
|
|
|
|
test("withSecondOfPreviouslyAddedModules") {
|
|
|
|
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
|
|
|
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
|
|
|
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
|
|
|
|
program.addModule(m1)
|
|
|
|
program.addModule(m2)
|
|
|
|
|
|
|
|
val retVal = program.moveModuleToFront(m2)
|
|
|
|
retVal shouldBeSameInstanceAs program
|
|
|
|
program.modules.indexOf(m2) shouldBe 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
context("Properties") {
|
|
|
|
test("modules") {
|
|
|
|
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
|
|
|
|
|
|
|
val ms1 = program.modules
|
|
|
|
val ms2 = program.modules
|
|
|
|
ms2 shouldBeSameInstanceAs ms1
|
|
|
|
}
|
|
|
|
}
|
2024-10-29 21:57:54 +00:00
|
|
|
|
|
|
|
context("block merge") {
|
|
|
|
test("merge works") {
|
|
|
|
val src = """
|
|
|
|
%import textio
|
|
|
|
|
|
|
|
main {
|
|
|
|
|
|
|
|
sub start() {
|
|
|
|
blah.test()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
txt {
|
|
|
|
; merges this block into the txt block coming from the textio library
|
|
|
|
%option merge
|
|
|
|
|
|
|
|
sub schrijf(str arg) {
|
|
|
|
print(arg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
blah {
|
|
|
|
; merges this block into the other 'blah' one
|
|
|
|
%option merge
|
|
|
|
|
|
|
|
sub test() {
|
|
|
|
printit("test merge")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
blah {
|
|
|
|
sub printit(str arg) {
|
|
|
|
txt.schrijf(arg)
|
|
|
|
}
|
|
|
|
}"""
|
|
|
|
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
|
|
|
}
|
2024-10-29 23:37:45 +00:00
|
|
|
|
|
|
|
test("merge override existing subroutine") {
|
|
|
|
val src="""
|
|
|
|
%import textio
|
|
|
|
|
|
|
|
main {
|
|
|
|
|
|
|
|
sub start() {
|
|
|
|
txt.print("sdfdsf")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
txt {
|
|
|
|
%option merge
|
|
|
|
|
|
|
|
sub print(str text) {
|
|
|
|
cx16.r0++
|
|
|
|
; just some dummy implementation to replace existing print
|
|
|
|
}
|
|
|
|
}"""
|
|
|
|
|
|
|
|
val result = compileText(VMTarget(), optimize=false, src, writeAssembly=false)
|
|
|
|
result shouldNotBe null
|
|
|
|
}
|
|
|
|
|
|
|
|
test("merge doesn't override existing subroutine if signature differs") {
|
|
|
|
val src="""
|
|
|
|
%import textio
|
|
|
|
|
|
|
|
main {
|
|
|
|
|
|
|
|
sub start() {
|
|
|
|
txt.print("sdfdsf")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
txt {
|
|
|
|
%option merge
|
|
|
|
|
|
|
|
sub print(str anotherparamname) {
|
|
|
|
cx16.r0++
|
|
|
|
; just some dummy implementation to replace existing print
|
|
|
|
}
|
|
|
|
}"""
|
|
|
|
val errors = ErrorReporterForTests()
|
|
|
|
compileText(VMTarget(), optimize=false, src, writeAssembly=false, errors = errors) shouldBe null
|
|
|
|
errors.errors.size shouldBe 1
|
|
|
|
errors.errors[0] shouldContain "name conflict"
|
|
|
|
}
|
2024-11-21 23:44:00 +00:00
|
|
|
|
|
|
|
test("merge of float stuff into sys and txt - import order 1") {
|
|
|
|
val src="""
|
|
|
|
%import textio
|
|
|
|
%import floats
|
|
|
|
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
txt.print_b(sys.MIN_BYTE)
|
|
|
|
txt.print_b(sys.MAX_BYTE)
|
|
|
|
txt.print_ub(sys.MIN_UBYTE)
|
|
|
|
txt.print_ub(sys.MAX_UBYTE)
|
|
|
|
txt.print_w(sys.MIN_WORD)
|
|
|
|
txt.print_w(sys.MAX_WORD)
|
|
|
|
txt.print_uw(sys.MIN_UWORD)
|
|
|
|
txt.print_uw(sys.MAX_UWORD)
|
|
|
|
|
|
|
|
txt.print_f(floats.EPSILON)
|
|
|
|
txt.print_f(sys.MIN_FLOAT)
|
|
|
|
txt.print_f(sys.MAX_FLOAT)
|
|
|
|
txt.print_f(floats.E)
|
|
|
|
txt.print_ub(sys.SIZEOF_FLOAT)
|
|
|
|
}
|
|
|
|
}"""
|
|
|
|
|
|
|
|
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
|
|
|
compileText(Cx16Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
|
|
|
}
|
|
|
|
|
|
|
|
test("merge of float stuff into sys and txt - import order 2") {
|
|
|
|
val src="""
|
|
|
|
%import floats
|
|
|
|
%import textio
|
|
|
|
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
txt.print_b(sys.MIN_BYTE)
|
|
|
|
txt.print_b(sys.MAX_BYTE)
|
|
|
|
txt.print_ub(sys.MIN_UBYTE)
|
|
|
|
txt.print_ub(sys.MAX_UBYTE)
|
|
|
|
txt.print_w(sys.MIN_WORD)
|
|
|
|
txt.print_w(sys.MAX_WORD)
|
|
|
|
txt.print_uw(sys.MIN_UWORD)
|
|
|
|
txt.print_uw(sys.MAX_UWORD)
|
|
|
|
|
|
|
|
txt.print_f(floats.EPSILON)
|
|
|
|
txt.print_f(sys.MIN_FLOAT)
|
|
|
|
txt.print_f(sys.MAX_FLOAT)
|
|
|
|
txt.print_f(floats.E)
|
|
|
|
txt.print_ub(sys.SIZEOF_FLOAT)
|
|
|
|
}
|
|
|
|
}"""
|
|
|
|
|
|
|
|
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
|
|
|
compileText(Cx16Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
|
|
|
}
|
2024-10-29 21:57:54 +00:00
|
|
|
}
|
2024-11-19 22:10:56 +00:00
|
|
|
|
|
|
|
test("block sort order") {
|
|
|
|
val src="""
|
|
|
|
%import textio
|
|
|
|
|
|
|
|
main ${'$'}0a00 {
|
|
|
|
sub start() {
|
|
|
|
otherblock1.foo()
|
|
|
|
otherblock2.foo()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
otherblock1 {
|
|
|
|
sub foo() {
|
|
|
|
txt.print("main.start: ")
|
|
|
|
txt.print_uwhex(&main.start, true)
|
|
|
|
txt.nl()
|
|
|
|
txt.print("datablock1 array: ")
|
|
|
|
txt.print_uwhex(&datablock1.array1, true)
|
|
|
|
txt.nl()
|
|
|
|
txt.print("datablock2 array: ")
|
|
|
|
txt.print_uwhex(&datablock2.array2, true)
|
|
|
|
txt.nl()
|
|
|
|
txt.print("otherblock1.foo: ")
|
|
|
|
txt.print_uwhex(&foo, true)
|
|
|
|
txt.nl()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
otherblock2 {
|
|
|
|
sub foo() {
|
|
|
|
txt.print("otherblock2.foo: ")
|
|
|
|
txt.print_uwhex(&otherblock2.foo, true)
|
|
|
|
txt.nl()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
datablock1 ${'$'}9000 {
|
|
|
|
ubyte[5] @shared array1 = [1,2,3,4,5]
|
|
|
|
}
|
|
|
|
|
|
|
|
datablock2 ${'$'}8000 {
|
|
|
|
ubyte[5] @shared array2 = [1,2,3,4,5]
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
|
|
val result = compileText(C64Target(), optimize=false, src, writeAssembly=true)!!
|
|
|
|
result.compilerAst.allBlocks.size shouldBeGreaterThan 5
|
|
|
|
result.compilerAst.modules.drop(2).all { it.isLibrary } shouldBe true
|
|
|
|
val mainMod = result.compilerAst.modules[0]
|
|
|
|
mainMod.name shouldStartWith "on_the_fly"
|
|
|
|
result.compilerAst.modules[1].name shouldBe "prog8_interned_strings"
|
|
|
|
val mainBlocks = mainMod.statements.filterIsInstance<Block>()
|
|
|
|
mainBlocks.size shouldBe 6
|
|
|
|
mainBlocks[0].name shouldBe "main"
|
|
|
|
mainBlocks[1].name shouldBe "p8_sys_startup"
|
|
|
|
mainBlocks[2].name shouldBe "otherblock1"
|
|
|
|
mainBlocks[3].name shouldBe "otherblock2"
|
|
|
|
mainBlocks[4].name shouldBe "datablock2"
|
|
|
|
mainBlocks[5].name shouldBe "datablock1"
|
|
|
|
|
|
|
|
result.codegenAst!!.children.size shouldBeGreaterThan 5
|
|
|
|
val blocks = result.codegenAst.children.filterIsInstance<PtBlock>()
|
|
|
|
blocks.size shouldBe 15
|
|
|
|
blocks[0].name shouldBe "p8b_main"
|
|
|
|
blocks[1].name shouldBe "p8_sys_startup"
|
|
|
|
blocks[2].name shouldBe "p8b_otherblock1"
|
|
|
|
blocks[3].name shouldBe "p8b_otherblock2"
|
|
|
|
blocks[4].name shouldBe "prog8_interned_strings"
|
|
|
|
blocks[5].name shouldBe "txt"
|
|
|
|
blocks[5].library shouldBe true
|
|
|
|
blocks[13].name shouldBe "p8b_datablock2"
|
|
|
|
blocks[14].name shouldBe "p8b_datablock1"
|
|
|
|
}
|
2021-11-07 16:25:53 +00:00
|
|
|
})
|