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 import prog8.code.ast.PtAssignTarget import prog8.code.ast.PtAssignment import prog8.code.core.DataType import prog8.code.core.SourceCode import prog8.code.target.C64Target import prog8.code.target.Cx16Target 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().single { it.name=="asmfunc"} val func = mainBlock.statements.filterIsInstance().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().single { it.name=="asmfunc"} val func = mainBlock.statements.filterIsInstance().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 uword[] pointers = [1234,6789] void call(func) ; ok void call(12345) ; ok void call(pointers[1]) ; ok void call(cx16.r0+10) ; 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 errors.errors.size shouldBe 2 errors.errors[0] shouldContain "can't call this indirectly" errors.errors[1] shouldContain "can't call this indirectly" } test("multi-assign from asmsub") { val src=""" main { sub start() { bool @shared flag ubyte @shared bytevar cx16.r0L, flag = test2(12345, 5566, flag, -42) cx16.r1, flag, bytevar = test3() cx16.r1, void, bytevar = test3() void, void, void = test3() } 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 { %asm {{ lda #0 ldy #0 ldx #0 rts }} } }""" compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null 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 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 (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 } test("multi-assign from extsub") { val src=""" main { sub start() { bool @shared flag ubyte @shared bytevar 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() } extsub ${'$'}8000 = test(ubyte arg @A) -> bool @Pc extsub ${'$'}8002 = test2(uword arg @AY, uword arg2 @R1, bool flag @Pc, byte value @X) -> ubyte @A, bool @Pc extsub ${'$'}8003 = test3() -> uword @R1, bool @Pc, ubyte @X }""" compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null 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 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 (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 } test("extsub with (non)const addresses") { val src=""" main { const uword address = ${'$'}2000 uword nonconst = ${'$'}3000 extsub address = foo1() extsub address+3 = foo2() extsub nonconst = foo3() sub start() { } }""" val errors = ErrorReporterForTests() compileText(Cx16Target(), false, src, errors, false) shouldBe null errors.errors.size shouldBe 1 errors.errors[0] shouldContain ":8:5: address must be a constant" } })