package prog8tests.ast import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.engine.spec.tempdir import io.kotest.matchers.comparables.shouldBeGreaterThan import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldEndWith import io.kotest.matchers.types.instanceOf import prog8.ast.IFunctionCall import prog8.ast.expressions.* import prog8.ast.statements.* import prog8.code.ast.* import prog8.code.core.BaseDataType import prog8.code.core.DataType import prog8.code.core.Position import prog8.code.target.C64Target import prog8.code.target.Cx16Target import prog8.code.target.VMTarget import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.compileText class TestVariousCompilerAst: FunSpec({ val outputDir = tempdir().toPath() context("arrays") { test("invalid array element proper errormessage") { val text=""" main { sub start() { uword[] commands = ["abc", 1.234] } }""" val errors = ErrorReporterForTests() compileText(C64Target(), false, text, outputDir, writeAssembly = true, errors=errors) shouldBe null errors.errors.size shouldBe 1 errors.errors[0] shouldContain "value has incompatible type" } test("array literals") { val text=""" %zeropage basicsafe main { sub start() { ubyte b1 ubyte b2 ubyte[] array1 = [1,2,3] ubyte[] array2 = [9,8,7] uword[] @shared addresses1 = [&b1, &b2] uword[] @shared addresses2 = [array1, array2] uword[] @shared addresses3 = [&array1, &array2] uword[] @shared addresses4 = ["string1", "string2"] uword[] @shared addresses5 = [1111, 2222] } }""" compileText(C64Target(), false, text, outputDir, writeAssembly = true) shouldNotBe null } test("array init size mismatch error") { val text=""" main { sub start() { ubyte[10] uba = [1,2,3] bool[10] bba = [true, false, true] } }""" val errors = ErrorReporterForTests() compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors = errors) shouldBe null errors.errors.size shouldBe 2 errors.errors[0] shouldContain "size mismatch" errors.errors[1] shouldContain "size mismatch" } test("returning array as uword") { val src = """ main { sub start() { cx16.r0 = getarray() } sub getarray() -> uword { return [11,22,33] } }""" compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null } } context("alias") { test("aliases ok") { val src=""" main { alias print = txt.print alias width = txt.DEFAULT_WIDTH sub start() { alias print2 = txt.print alias width2 = txt.DEFAULT_WIDTH print("one") print2("two") txt.print_ub(width) txt.print_ub(width2) ; chained aliases alias chained = print2 chained("chained") } } txt { const ubyte DEFAULT_WIDTH = 80 sub print_ub(ubyte value) { ; nothing } sub print(str msg) { ; nothing } } """ compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null } test("wrong alias gives correct error") { val src=""" main { alias print = txt.print2222 alias width = txt.DEFAULT_WIDTH sub start() { alias print2 = txt.print alias width2 = txt.DEFAULT_WIDTH_XXX print("one") print2("two") txt.print_ub(width) txt.print_ub(width2) } } txt { const ubyte DEFAULT_WIDTH = 80 sub print_ub(ubyte value) { ; nothing } sub print(str msg) { ; nothing } } """ val errors = ErrorReporterForTests() compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors=errors) shouldBe null errors.errors.size shouldBe 2 errors.errors[0] shouldContain "undefined symbol: txt.print2222" errors.errors[1] shouldContain "undefined symbol: txt.DEFAULT_WIDTH_XXX" } } context("strings") { test("string comparisons") { val src=""" main { sub start() { str name = "name" uword nameptr = &name bool result result = name=="foo" result = name!="foo" result = name<"foo" result = name>"foo" result = nameptr=="foo" result = nameptr!="foo" result = nameptr<"foo" result = nameptr>"foo" void compare(name, "foo") void compare(name, "name") void compare(nameptr, "foo") void compare(nameptr, "name") } sub compare(str s1, str s2) -> ubyte { if s1==s2 return 42 return 0 } }""" val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=true)!! val stmts = result.compilerAst.entrypoint.statements stmts.size shouldBe 17 val result2 = compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=true)!! val stmts2 = result2.compilerAst.entrypoint.statements stmts2.size shouldBe 17 } test("string concatenation and repeats") { val src=""" main { sub start() { str @shared name = "part1" + "part2" str @shared rept = "rep"*4 const ubyte times = 3 name = "xx1" + "xx2" rept = "xyz" * (times+1) } }""" val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=true)!! val stmts = result.compilerAst.entrypoint.statements stmts.size shouldBe 6 val name1 = stmts[0] as VarDecl val rept1 = stmts[1] as VarDecl (name1.value as StringLiteral).value shouldBe "part1part2" (rept1.value as StringLiteral).value shouldBe "reprepreprep" val name2strcopy = stmts[3] as IFunctionCall val rept2strcopy = stmts[4] as IFunctionCall val name2 = name2strcopy.args.first() as IdentifierReference val rept2 = rept2strcopy.args.first() as IdentifierReference (name2.targetVarDecl()!!.value as StringLiteral).value shouldBe "xx1xx2" (rept2.targetVarDecl()!!.value as StringLiteral).value shouldBe "xyzxyzxyzxyz" } test("char as str param is error") { val src = """ main { sub start() { print('@') } sub print(str message) { } }""" val errors = ErrorReporterForTests() compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null errors.errors.single() shouldContain "cannot use byte value" } } context("return value") { test("missing return value is a syntax error") { val src=""" main { sub start() { cx16.r0 = runit1() cx16.r1 = runit2() } sub runit1() -> uword { repeat { cx16.r0++ goto runit1 } } sub runit2() -> uword { cx16.r0++ } }""" val errors = ErrorReporterForTests() compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null errors.errors.size shouldBe 2 errors.errors[0] shouldContain "has result value" errors.errors[1] shouldContain "has result value" } test("missing return value is not a syntax error if there's an external goto") { val src=""" main { sub start() { cx16.r0 = runit1() runit2() } sub runit1() -> uword { repeat { cx16.r0++ goto runit2 } } sub runit2() { cx16.r0++ } }""" compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null } } context("variable declarations") { test("multi-var decls in scope with initializer") { val src=""" main { sub start() { ubyte w for w in 0 to 20 { ubyte @zp x,y,z=13 ubyte q,r,s x++ y++ z++ } } }""" val result = compileText(VMTarget(), optimize = false, src, outputDir, writeAssembly = false)!! val st = result.compilerAst.entrypoint.statements /* sub start () { ubyte s s = 0 ubyte r r = 0 ubyte q q = 0 ubyte @zp z ubyte @zp y ubyte @zp x ubyte w for w in 0 to 20 step 1 { z = 13 y = 13 x = 13 x++ y++ z++ } } */ val vars = st.filterIsInstance() vars.size shouldBe 7 vars.all { it.names.size<=1 } shouldBe true vars.map { it.name }.toSet() shouldBe setOf("s","r","q","z","y","x","w") val forloop = st.single { it is ForLoop } as ForLoop forloop.body.statements[0] shouldBe instanceOf() forloop.body.statements[1] shouldBe instanceOf() forloop.body.statements[2] shouldBe instanceOf() } test("multi vardecls smart desugaring") { val src=""" main { sub start() { ubyte @shared x,y,z ubyte @shared k,l,m = 42 } }""" val result = compileText(Cx16Target(), optimize=true, src, outputDir, writeAssembly=false)!! val st = result.compilerAst.entrypoint.statements st.size shouldBe 12 st[0] shouldBe instanceOf() // x st[2] shouldBe instanceOf() // y st[4] shouldBe instanceOf() // z st[6] shouldBe instanceOf() // k st[8] shouldBe instanceOf() // l st[10] shouldBe instanceOf() // m val valX = (st[1] as Assignment).value (valX as NumericLiteral).number shouldBe 0.0 val valY = (st[3] as Assignment).value (valY as NumericLiteral).number shouldBe 0.0 val valZ = (st[5] as Assignment).value (valZ as NumericLiteral).number shouldBe 0.0 val valK = (st[7] as Assignment).value (valK as NumericLiteral).number shouldBe 42.0 val valL = (st[9] as Assignment).value (valL as NumericLiteral).number shouldBe 42.0 val valM = (st[11] as Assignment).value (valM as NumericLiteral).number shouldBe 42.0 } test("various multi var decl symbol lookups") { val src=""" main { sub start() { uword @shared a,b b = a cx16.r1L = lsb(a) funcw(a) funcb(lsb(a)) } sub funcw(uword arg) { arg++ } sub funcb(ubyte arg) { arg++ } }""" compileText(Cx16Target(), false, src, outputDir) shouldNotBe null } test("@dirty variables") { val src=""" %import floats main { uword @shared @dirty globw uword @shared globwi = 4444 float @shared @dirty globf float @shared globfi = 4 ubyte[5] @shared @dirty globarr1 ubyte[] @shared globarr2 = [11,22,33,44,55] sub start() { uword @shared @dirty locw uword @shared locwi = 4444 float @shared @dirty locf float @shared locfi = 4.0 ubyte[5] @shared @dirty locarr1 ubyte[] @shared locarr2 = [11,22,33,44,55] sys.clear_carry() } }""" val errors = ErrorReporterForTests(keepMessagesAfterReporting = true) val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors=errors)!! errors.warnings.size shouldBe 6 errors.infos.size shouldBe 0 errors.warnings.all { "dirty variable" in it } shouldBe true val start = result.compilerAst.entrypoint val st = start.statements st.size shouldBe 9 val assignments = st.filterIsInstance() assignments.size shouldBe 2 assignments[0].target.identifier?.nameInSource shouldBe listOf("locwi") assignments[1].target.identifier?.nameInSource shouldBe listOf("locfi") val blockst = start.definingBlock.statements blockst.size shouldBe(9) val blockassignments = blockst.filterIsInstance() blockassignments.size shouldBe 2 blockassignments[0].target.identifier?.nameInSource shouldBe listOf("globwi") blockassignments[1].target.identifier?.nameInSource shouldBe listOf("globfi") } } context("various") { test("no crash for all sorts of undefined variables in complex expression") { val src = """ %import floats main { sub start() { x_position = (((floats.cos(CORNER_ANGLE + theta) * distance_to_corner) + (position_offset as float)) * 256.0) as word } }""" val errors = ErrorReporterForTests() compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null errors.errors.size shouldBeGreaterThan 1 errors.clear() compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false, errors = errors) shouldBe null errors.errors.size shouldBeGreaterThan 1 } test("symbol names in inline assembly blocks") { val names1 = InlineAssembly(""" """, false, Position.DUMMY).names names1 shouldBe emptySet() val names2 = InlineAssembly(""" label: lda #255") { val src=""" main { sub start() { uword pointer = ${'$'}2000 @(pointer+${'$'}1000) = 123 ubyte @shared ub = @(pointer+${'$'}1000) pointer[${'$'}1000] = 99 ub = pointer[${'$'}1000] uword index = ${'$'}1000 pointer[index] = 55 ub = pointer[index] } }""" compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null } test("bitshift left of const byte not converted to word") { val src=""" main { sub start() { ubyte shift = 10 uword value = 1< ubyte { return lsb(x+y) } sub start() { uword[128] YY ubyte[] ARRAY = [1, 5, 2] repeat { bool pixel_side1 = pget(2, YY[2]+1) in ARRAY bool pixel_side2 = pget(2, 2) in ARRAY ubyte[] array2 = [1,2,3] } } }""" val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false)!! val stmts = result.compilerAst.entrypoint.statements stmts.size shouldBe 9 } test("alternative notation for negative containment check") { val src=""" main { sub start() { ubyte[] array=[1,2,3] bool result result = not (3 in array) result = 3 not in array } } """ val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false)!! val stmts = result.compilerAst.entrypoint.statements stmts.size shouldBe 4 val value1 = (stmts[2] as Assignment).value as PrefixExpression val value2 = (stmts[3] as Assignment).value as PrefixExpression value1.operator shouldBe "not" value2.operator shouldBe "not" value1.expression shouldBe instanceOf() value2.expression shouldBe instanceOf() } test("unroll good") { val src=""" main { sub start() { unroll 200 { cx16.r0++ poke(2000,2) } } } """ val errors = ErrorReporterForTests(keepMessagesAfterReporting = true) compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors=errors) shouldNotBe null errors.warnings.size shouldBe 1 errors.warnings[0] shouldContain "large number of unrolls" } test("unroll bad") { val src=""" main { sub start() { repeat { unroll 80 { cx16.r0++ when cx16.r0 { 1 -> cx16.r0++ else -> cx16.r0++ } break } } } } """ val errors = ErrorReporterForTests() compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null errors.errors.size shouldBe 2 errors.errors[0] shouldContain "invalid statement in unroll loop" errors.errors[1] shouldContain "invalid statement in unroll loop" } test("various curly brace styles") { val src=""" main { sub start() { ubyte @shared variable=55 when variable { 33 -> cx16.r0++ else -> cx16.r1++ } if variable!=0 { cx16.r0++ } else { cx16.r1++ } if variable!=0 { cx16.r0++ } else { cx16.r1++ } if variable!=0 { cx16.r0++ } else { cx16.r1++ } other.othersub() } } other { sub othersub() { cx16.r0++ } }""" compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null } test("when on booleans") { val src = """ main { sub start() { bool choiceVariable=true when choiceVariable { false -> cx16.r0++ true -> cx16.r1++ } } }""" val errors = ErrorReporterForTests() compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null errors.errors.size shouldBe 1 errors.errors[0] shouldContain "use if" } test("when on range expressions is ok") { val src=""" main { sub start() { when cx16.r0L { 21 to 29 step 2 -> cx16.r1L++ else -> cx16.r1L-- } } }""" compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false).shouldNotBeNull() } test("when on range expressions outside value datatype is error") { val src=""" main { sub start() { when cx16.r0L { 300 to 400 -> cx16.r1L++ else -> cx16.r1L-- } } }""" val errors = ErrorReporterForTests() compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null errors.errors.size shouldBe 1 errors.errors[0] shouldContain "values must be constant numbers" } test("sizeof number const evaluation in vardecl") { val src=""" main { sub start() { uword @shared size1 = sizeof(22222) uword @shared size2 = sizeof(2.2) } }""" compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null } test("'not in' operator parsing") { val src=""" main { sub start() { str test = "test" bool @shared insync if not insync insync=true if insync not in test insync=true } }""" compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null } test("no chained comparison modifying expression semantics") { val src=""" main { sub start() { ubyte @shared n=20 ubyte @shared x=10 bool @shared result1, result2 if n < x { ; nothing here, conditional gets inverted } else { cx16.r0++ } result1 = n=" (ifCond.left as IdentifierReference).nameInSource shouldBe listOf("n") (ifCond.right as IdentifierReference).nameInSource shouldBe listOf("x") val assign1 = (st[9] as Assignment).value as BinaryExpression val assign2 = (st[10] as Assignment).value as BinaryExpression assign1.operator shouldBe ">=" (assign1.left as IdentifierReference).nameInSource shouldBe listOf("n") (assign1.right as IdentifierReference).nameInSource shouldBe listOf("x") assign2.operator shouldBe ">=" (assign1.left as IdentifierReference).nameInSource shouldBe listOf("n") (assign1.right as IdentifierReference).nameInSource shouldBe listOf("x") } test("modulo is not directive") { val src=""" main { sub start() { ubyte @shared bb = 199 ubyte @shared cc = 12 ubyte @shared bb2 = bb%cc } }""" val result=compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=false)!! val st = result.compilerAst.entrypoint.statements st.size shouldBe 6 val value = (st[5] as Assignment).value as BinaryExpression value.operator shouldBe "%" } test("isSame on binary expressions") { val left1 = NumericLiteral.optimalInteger(1, Position.DUMMY) val right1 = NumericLiteral.optimalInteger(2, Position.DUMMY) val expr1 = BinaryExpression(left1, "/", right1, Position.DUMMY) val left2 = NumericLiteral.optimalInteger(1, Position.DUMMY) val right2 = NumericLiteral.optimalInteger(2, Position.DUMMY) val expr2 = BinaryExpression(left2, "/", right2, Position.DUMMY) (expr1 isSameAs expr2) shouldBe true val left3 = NumericLiteral.optimalInteger(2, Position.DUMMY) val right3 = NumericLiteral.optimalInteger(1, Position.DUMMY) val expr3 = BinaryExpression(left3, "/", right3, Position.DUMMY) (expr1 isSameAs expr3) shouldBe false } test("isSame on binary expressions with associative operators") { val left1 = NumericLiteral.optimalInteger(1, Position.DUMMY) val right1 = NumericLiteral.optimalInteger(2, Position.DUMMY) val expr1 = BinaryExpression(left1, "+", right1, Position.DUMMY) val left2 = NumericLiteral.optimalInteger(1, Position.DUMMY) val right2 = NumericLiteral.optimalInteger(2, Position.DUMMY) val expr2 = BinaryExpression(left2, "+", right2, Position.DUMMY) (expr1 isSameAs expr2) shouldBe true val left3 = NumericLiteral.optimalInteger(2, Position.DUMMY) val right3 = NumericLiteral.optimalInteger(1, Position.DUMMY) val expr3 = BinaryExpression(left3, "+", right3, Position.DUMMY) (expr1 isSameAs expr3) shouldBe true } test("mkword insertion with signed values gets correct type cast") { val src = """ main { sub start() { byte[10] @shared bottom byte @shared col = 20 col++ ubyte @shared ubb = lsb(col as uword) uword @shared vaddr = bottom[cx16.r0L] as uword << 8 ; a mkword will get inserted here } }""" val result = compileText(VMTarget(), optimize=true, src, outputDir, writeAssembly=false)!! val st = result.compilerAst.entrypoint.statements st.size shouldBe 8 val assignUbbVal = ((st[5] as Assignment).value as TypecastExpression) assignUbbVal.type shouldBe BaseDataType.UBYTE assignUbbVal.expression shouldBe instanceOf() val assignVaddr = (st[7] as Assignment).value as FunctionCallExpression assignVaddr.target.nameInSource shouldBe listOf("mkword") val tc = assignVaddr.args[0] as TypecastExpression tc.type shouldBe BaseDataType.UBYTE tc.expression shouldBe instanceOf() } test("void assignment is invalid") { val src=""" main { extsub $2000 = multi() -> ubyte @A, ubyte @Y extsub $3000 = single() -> ubyte @A sub start() { void, void = multi() ; ok cx16.r0L, void = multi() ; ok void, cx16.r0L = multi() ; ok void multi() ; ok void single() ; ok void = 3333 ; fail! void = single() ; fail! void = multi() ; fail! } }""" val errors = ErrorReporterForTests() compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=true, errors = errors) shouldBe null errors.errors.size shouldBe 3 errors.errors[0] shouldEndWith "cannot assign to 'void'" errors.errors[1] shouldEndWith "cannot assign to 'void', perhaps a void function call was intended" errors.errors[2] shouldEndWith "cannot assign to 'void', perhaps a void function call was intended" } test("datatype consistencies") { DataType.UNDEFINED.isUndefined shouldBe true DataType.LONG.isLong shouldBe true DataType.WORD.isWord shouldBe true DataType.UWORD.isWord shouldBe true DataType.BYTE.isByte shouldBe true DataType.UBYTE.isByte shouldBe true DataType.BOOL.isBool shouldBe true DataType.STR.isString shouldBe true DataType.FLOAT.isFloat shouldBe true DataType.forDt(BaseDataType.UNDEFINED).isUndefined shouldBe true DataType.forDt(BaseDataType.LONG).isLong shouldBe true DataType.forDt(BaseDataType.WORD).isWord shouldBe true DataType.forDt(BaseDataType.UWORD).isWord shouldBe true DataType.forDt(BaseDataType.BYTE).isByte shouldBe true DataType.forDt(BaseDataType.UBYTE).isByte shouldBe true DataType.forDt(BaseDataType.BOOL).isBool shouldBe true DataType.forDt(BaseDataType.STR).isString shouldBe true DataType.forDt(BaseDataType.FLOAT).isFloat shouldBe true DataType.arrayFor(BaseDataType.UBYTE, true).isUnsignedByteArray shouldBe true DataType.arrayFor(BaseDataType.FLOAT).isFloatArray shouldBe true DataType.arrayFor(BaseDataType.UWORD).isUnsignedWordArray shouldBe true DataType.arrayFor(BaseDataType.UWORD).isArray shouldBe true DataType.arrayFor(BaseDataType.UWORD).isSplitWordArray shouldBe true DataType.arrayFor(BaseDataType.UWORD, false).isSplitWordArray shouldBe false shouldThrow { DataType.forDt(BaseDataType.ARRAY) } shouldThrow { DataType.forDt(BaseDataType.ARRAY_SPLITW) } shouldThrow { DataType.arrayFor(BaseDataType.ARRAY) } shouldThrow { DataType.arrayFor(BaseDataType.LONG) } shouldThrow { DataType.arrayFor(BaseDataType.UNDEFINED) } } test("array of strings becomes array of uword pointers") { val src=""" main { sub start() { str variable = "name1" str[2] @shared names = [ variable, "name2" ] } }""" val result = compileText(C64Target(), false, src, outputDir, writeAssembly = true) result shouldNotBe null val st1 = result!!.compilerAst.entrypoint.statements st1.size shouldBe 3 st1[0] shouldBe instanceOf() st1[1] shouldBe instanceOf() (st1[0] as VarDecl).name shouldBe "variable" (st1[1] as VarDecl).name shouldBe "names" val array1 = (st1[1] as VarDecl).value as ArrayLiteral array1.type.isArray shouldBe true array1.type.getOrUndef() shouldBe DataType.arrayFor(BaseDataType.UWORD, true) val ast2 = result.codegenAst!! val st2 = ast2.entrypoint()!!.children st2.size shouldBe 3 (st2[0] as PtVariable).name shouldBe "p8v_variable" (st2[1] as PtVariable).name shouldBe "p8v_names" val array2 = (st2[1] as PtVariable).value as PtArray array2.type shouldBe DataType.arrayFor(BaseDataType.UWORD, true) } test("defer syntactic sugaring") { val src=""" main { sub start() { void test() } sub test() -> uword { defer { cx16.r0++ cx16.r1++ } if cx16.r0==0 { defer cx16.r1++ } if cx16.r0==0 return cx16.r0+cx16.r1 defer cx16.r2++ } }""" val result = compileText(Cx16Target(), optimize=true, src, outputDir, writeAssembly=true)!! val main = result.codegenAst!!.allBlocks().single {it.name=="p8b_main"} val sub = main.children[1] as PtSub sub.scopedName shouldBe "p8b_main.p8s_test" // check the desugaring of the defer statements (sub.children[0] as PtVariable).name shouldBe "p8v_prog8_defers_mask" val firstDefer = sub.children[2] as PtAugmentedAssign firstDefer.operator shouldBe "|=" firstDefer.target.identifier?.name shouldBe "p8b_main.p8s_test.p8v_prog8_defers_mask" firstDefer.value.asConstInteger() shouldBe 4 val firstIf = sub.children[3] as PtIfElse val deferInIf = firstIf.ifScope.children[0] as PtAugmentedAssign deferInIf.operator shouldBe "|=" deferInIf.target.identifier?.name shouldBe "p8b_main.p8s_test.p8v_prog8_defers_mask" deferInIf.value.asConstInteger() shouldBe 2 val lastDefer = sub.children[5] as PtAugmentedAssign lastDefer.operator shouldBe "|=" lastDefer.target.identifier?.name shouldBe "p8b_main.p8s_test.p8v_prog8_defers_mask" lastDefer.value.asConstInteger() shouldBe 1 val ifelse = sub.children[4] as PtIfElse val ifscope = ifelse.ifScope.children[0] as PtNodeGroup val ifscope_push = ifscope.children[0] as PtFunctionCall val ifscope_defer = ifscope.children[1] as PtFunctionCall val ifscope_return = ifscope.children[2] as PtReturn ifscope_defer.name shouldBe "p8b_main.p8s_test.p8s_prog8_invoke_defers" ifscope_push.name shouldBe "sys.pushw" (ifscope_return.children.single() as PtFunctionCall).name shouldBe "sys.popw" val ending = sub.children[6] as PtFunctionCall ending.name shouldBe "p8b_main.p8s_test.p8s_prog8_invoke_defers" sub.children[7] shouldBe instanceOf() val handler = sub.children[8] as PtSub handler.name shouldBe "p8s_prog8_invoke_defers" } test("unknown variable in for loop gives proper errors") { val src=""" main { sub start() { ubyte i for i in 0 to count - 1 { break } } }""" val errors = ErrorReporterForTests() compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null errors.errors.size shouldBe 3 errors.errors[0] shouldContain "loop variable can only loop over" errors.errors[1] shouldContain "undefined symbol" } test("multi vardecl with immediate initialization from multi-return value functioncall") { val src=""" main { sub start() { ubyte @shared x,y,z = multi() } sub multi() -> ubyte, ubyte, ubyte { return 1,2,3 } }""" val result1 = compileText(VMTarget(), optimize=true, src, outputDir, writeAssembly=true)!! val st1 = result1.codegenAst!!.entrypoint()!!.children st1.size shouldBe 5 (st1[0] as PtVariable).name shouldBe "main.start.x" (st1[1] as PtVariable).name shouldBe "main.start.y" (st1[2] as PtVariable).name shouldBe "main.start.z" st1[3].children.size shouldBe 4 st1[3].children.dropLast(1).map { (it as PtAssignTarget).identifier!!.name } shouldBe listOf("main.start.x", "main.start.y", "main.start.z") ((st1[3] as PtAssignment).value as PtFunctionCall).name shouldBe "main.multi" val result2 = compileText(Cx16Target(), optimize=true, src, outputDir, writeAssembly=true)!! val st2 = result2.codegenAst!!.entrypoint()!!.children st2.size shouldBe 5 (st2[0] as PtVariable).name shouldBe "p8v_x" (st2[1] as PtVariable).name shouldBe "p8v_y" (st2[2] as PtVariable).name shouldBe "p8v_z" st2[3].children.size shouldBe 4 st2[3].children.dropLast(1).map { (it as PtAssignTarget).identifier!!.name } shouldBe listOf("p8b_main.p8s_start.p8v_x", "p8b_main.p8s_start.p8v_y", "p8b_main.p8s_start.p8v_z") ((st2[3] as PtAssignment).value as PtFunctionCall).name shouldBe "p8b_main.p8s_multi" } test("address-of a uword pointer with word index should not overflow") { val src= """ main { sub start() { const uword cbuffer = $2000 uword @shared buffer = $2000 cx16.r1 = &cbuffer[2000] cx16.r5 = &buffer[2000] cx16.r3 = &cbuffer[cx16.r0] cx16.r4 = &buffer[cx16.r0] } }""" compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null } test("on..goto") { val src=""" main { sub start() { cx16.r13L = 1 cx16.r12L = 0 on cx16.r12L+1 call ( thing.func1, thing.func2, thing.func3) else { ; not jumped cx16.r0++ } on cx16.r13L+1 goto (thing.func1, thing.func2, thing.func3) } } thing { sub func1() { cx16.r10 += 1 } sub func2() { cx16.r10 += 2 } sub func3() { cx16.r10 += 3 } }""" compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null } } })