prog8/compiler/test/ast/TestSubroutines.kt

220 lines
8.6 KiB
Kotlin
Raw Normal View History

package prog8tests.ast
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
2024-03-25 22:20:03 +00:00
import prog8.code.ast.PtAssignTarget
import prog8.code.ast.PtAssignment
import prog8.code.core.DataType
2022-03-21 00:01:21 +00:00
import prog8.code.core.SourceCode
import prog8.code.target.Cx16Target
2024-03-25 22:20:03 +00:00
import prog8.code.target.VMTarget
2022-05-22 21:11:22 +00:00
import prog8.parser.Prog8Parser.parseModule
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
class TestSubroutines: FunSpec({
test("stringParameter AcceptedInParser") {
// note: the *parser* should accept this as it is valid *syntax*,
// however, the compiler itself may or may not complain about it later.
val text = """
main {
asmsub asmfunc(str thing @AY) {
}
sub func(str thing) {
}
}
"""
val src = SourceCode.Text(text)
val module = parseModule(src)
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
asmfunc.isAsmSubroutine shouldBe true
asmfunc.parameters.single().type shouldBe DataType.STR
asmfunc.statements.isEmpty() shouldBe true
func.isAsmSubroutine shouldBe false
func.parameters.single().type shouldBe DataType.STR
func.statements.isEmpty() shouldBe true
}
test("arrayParameter AcceptedInParser") {
// note: the *parser* should accept this as it is valid *syntax*,
// however, the compiler itself may or may not complain about it later.
val text = """
main {
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
val src = SourceCode.Text(text)
val module = parseModule(src)
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
asmfunc.isAsmSubroutine shouldBe true
asmfunc.parameters.single().type shouldBe DataType.ARRAY_UB
asmfunc.statements.isEmpty() shouldBe true
func.isAsmSubroutine shouldBe false
func.parameters.single().type shouldBe DataType.ARRAY_UB
func.statements.isEmpty() shouldBe true
}
test("cannot call a subroutine via pointer") {
val src="""
main {
sub start() {
uword func = 12345
func() ; error
func(1,2,3) ; error
cx16.r0 = func() ; error
}
}"""
val errors = ErrorReporterForTests()
compileText(Cx16Target(), false, src, errors, false) shouldBe null
errors.errors.size shouldBe 3
errors.errors[0] shouldContain "cannot call that"
errors.errors[1] shouldContain "cannot call that"
errors.errors[2] shouldContain "cannot call that"
}
test("can call a subroutine pointer using call") {
val src="""
main {
sub start() {
uword func = 12345
2023-12-22 21:24:11 +00:00
void call(func) ; ok
void call(12345) ; ok
cx16.r0 = call(func) ; ok
void call(&start) ; error
void call(start) ; error
}
}"""
val errors = ErrorReporterForTests()
compileText(Cx16Target(), false, src, errors, false) shouldBe null
2023-12-22 21:24:11 +00:00
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "can't call this indirectly"
errors.errors[1] shouldContain "can't call this indirectly"
}
2024-03-25 22:20:03 +00:00
test("multi-assign from asmsub") {
val src="""
main {
sub start() {
bool @shared flag
ubyte @shared bytevar
2024-03-25 22:20:03 +00:00
cx16.r0L, flag = test2(12345, 5566, flag, -42)
cx16.r1, flag, bytevar = test3()
cx16.r1, void, bytevar = test3()
void, void, void = test3()
2024-03-25 22:20:03 +00:00
}
asmsub test2(uword arg @AY, uword arg2 @R1, bool flag @Pc, byte value @X) -> ubyte @A, bool @Pc {
%asm {{
txa
sec
rts
}}
}
asmsub test3() -> uword @R1, bool @Pc, ubyte @X {
2024-03-25 22:20:03 +00:00
%asm {{
lda #0
ldy #0
ldx #0
2024-03-25 22:20:03 +00:00
rts
}}
}
}"""
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
2024-03-25 22:20:03 +00:00
val errors = ErrorReporterForTests()
val result = compileText(Cx16Target(), false, src, errors, true)!!
errors.errors.size shouldBe 0
val start = result.codegenAst!!.entrypoint()!!
start.children.size shouldBe 9
val a1_1 = start.children[4] as PtAssignment
val a1_2 = start.children[5] as PtAssignment
val a1_3 = start.children[6] as PtAssignment
val a1_4 = start.children[7] as PtAssignment
2024-03-25 22:20:03 +00:00
a1_1.multiTarget shouldBe true
a1_2.multiTarget shouldBe true
a1_3.multiTarget shouldBe true
a1_4.multiTarget shouldBe true
a1_1.children.size shouldBe 3
a1_2.children.size shouldBe 4
a1_3.children.size shouldBe 4
a1_4.children.size shouldBe 4
2024-03-25 22:20:03 +00:00
(a1_1.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r0L")
(a1_1.children[1] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_flag")
(a1_2.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r1")
(a1_2.children[1] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_flag")
(a1_2.children[2] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_bytevar")
(a1_3.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r1")
(a1_3.children[1] as PtAssignTarget).void shouldBe true
(a1_3.children[2] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_bytevar")
(a1_4.children[0] as PtAssignTarget).void shouldBe true
(a1_4.children[1] as PtAssignTarget).void shouldBe true
(a1_4.children[2] as PtAssignTarget).void shouldBe true
2024-03-25 22:20:03 +00:00
}
test("multi-assign from romsub") {
val src="""
main {
sub start() {
bool @shared flag
ubyte @shared bytevar
2024-03-25 22:20:03 +00:00
flag = test(42)
cx16.r0L, flag = test2(12345, 5566, flag, -42)
cx16.r1, flag, bytevar = test3()
cx16.r1, void, bytevar = test3()
void, void, void = test3()
2024-03-25 22:20:03 +00:00
}
romsub ${'$'}8000 = test(ubyte arg @A) -> bool @Pc
romsub ${'$'}8002 = test2(uword arg @AY, uword arg2 @R1, bool flag @Pc, byte value @X) -> ubyte @A, bool @Pc
romsub ${'$'}8003 = test3() -> uword @R1, bool @Pc, ubyte @X
2024-03-25 22:20:03 +00:00
}"""
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
2024-03-25 22:20:03 +00:00
val errors = ErrorReporterForTests()
val result = compileText(Cx16Target(), false, src, errors, true)!!
errors.errors.size shouldBe 0
val start = result.codegenAst!!.entrypoint()!!
start.children.size shouldBe 10
val a1_1 = start.children[5] as PtAssignment
val a1_2 = start.children[6] as PtAssignment
val a1_3 = start.children[7] as PtAssignment
val a1_4 = start.children[8] as PtAssignment
2024-03-25 22:20:03 +00:00
a1_1.multiTarget shouldBe true
a1_2.multiTarget shouldBe true
a1_3.multiTarget shouldBe true
a1_4.multiTarget shouldBe true
a1_1.children.size shouldBe 3
a1_2.children.size shouldBe 4
a1_3.children.size shouldBe 4
a1_4.children.size shouldBe 4
2024-03-25 22:20:03 +00:00
(a1_1.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r0L")
(a1_1.children[1] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_flag")
(a1_2.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r1")
(a1_2.children[1] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_flag")
(a1_2.children[2] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_bytevar")
(a1_3.children[0] as PtAssignTarget).identifier!!.name shouldBe("cx16.r1")
(a1_3.children[1] as PtAssignTarget).void shouldBe true
(a1_3.children[2] as PtAssignTarget).identifier!!.name shouldBe("p8b_main.p8s_start.p8v_bytevar")
(a1_4.children[0] as PtAssignTarget).void shouldBe true
(a1_4.children[1] as PtAssignTarget).void shouldBe true
(a1_4.children[2] as PtAssignTarget).void shouldBe true
2024-03-25 22:20:03 +00:00
}
})