mirror of
				https://github.com/irmen/prog8.git
				synced 2025-11-03 19:16:13 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			488 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Kotlin
		
	
	
	
	
	
			
		
		
	
	
			488 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Kotlin
		
	
	
	
	
	
package prog8tests.ast
 | 
						|
 | 
						|
import io.kotest.core.spec.style.FunSpec
 | 
						|
import io.kotest.engine.spec.tempdir
 | 
						|
import io.kotest.matchers.shouldBe
 | 
						|
import io.kotest.matchers.shouldNotBe
 | 
						|
import io.kotest.matchers.string.shouldContain
 | 
						|
import prog8.code.target.C64Target
 | 
						|
import prog8.code.target.Cx16Target
 | 
						|
import prog8.code.target.VMTarget
 | 
						|
import prog8tests.helpers.ErrorReporterForTests
 | 
						|
import prog8tests.helpers.compileText
 | 
						|
 | 
						|
 | 
						|
class TestAstChecks: FunSpec({
 | 
						|
 | 
						|
    val outputDir = tempdir().toPath()
 | 
						|
    
 | 
						|
    test("conditional expression w/float works") {
 | 
						|
        val text = """
 | 
						|
            %import floats
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    uword xx
 | 
						|
                    if xx+99.99 == xx+1.234 {
 | 
						|
                        xx++
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
 | 
						|
        compileText(C64Target(), true, text, outputDir, writeAssembly = true, errors=errors) shouldNotBe null
 | 
						|
        errors.errors.size shouldBe 0
 | 
						|
        errors.infos.size shouldBe 2
 | 
						|
        errors.infos[0] shouldContain "converted to float"
 | 
						|
        errors.infos[1] shouldContain "converted to float"
 | 
						|
    }
 | 
						|
 | 
						|
    test("can't assign label or subroutine without using address-of") {
 | 
						|
        val text = """
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
            
 | 
						|
            label:
 | 
						|
                    uword @shared addr
 | 
						|
                    addr = label
 | 
						|
                    addr = thing
 | 
						|
                    addr = &label
 | 
						|
                    addr = &thing
 | 
						|
                }
 | 
						|
            
 | 
						|
                sub thing() {
 | 
						|
                }
 | 
						|
            }
 | 
						|
            """
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), true, text, outputDir, writeAssembly = true, errors=errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 2
 | 
						|
        errors.warnings.size shouldBe 0
 | 
						|
        errors.errors[0] shouldContain ":7:28: invalid assignment value, maybe forgot '&'"
 | 
						|
        errors.errors[1] shouldContain ":8:28: invalid assignment value, maybe forgot '&'"
 | 
						|
    }
 | 
						|
 | 
						|
    test("can't do str or array expression without using address-of") {
 | 
						|
        val text = """
 | 
						|
            %import textio
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    ubyte[] array = [1,2,3,4]
 | 
						|
                    str s1 = "test"
 | 
						|
                    ubyte ff = 1
 | 
						|
                    txt.print(s1+ff)
 | 
						|
                    txt.print(array+ff)
 | 
						|
                    txt.print_uwhex(s1+ff, true)
 | 
						|
                    txt.print_uwhex(array+ff, true)
 | 
						|
                }
 | 
						|
            }
 | 
						|
            """
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors=errors) shouldBe null
 | 
						|
        errors.errors.filter { it.contains("missing &") }.size shouldBe 4
 | 
						|
    }
 | 
						|
 | 
						|
    test("str or array expression with address-of") {
 | 
						|
        val text = """
 | 
						|
            %import textio
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    ubyte[] array = [1,2,3,4]
 | 
						|
                    str s1 = "test"
 | 
						|
                    bool bb1, bb2
 | 
						|
                    ubyte ff = 1
 | 
						|
                    txt.print(&s1+ff)
 | 
						|
                    txt.print(&array+ff)
 | 
						|
                    txt.print_uwhex(&s1+ff, true)
 | 
						|
                    txt.print_uwhex(&array+ff, true)
 | 
						|
                    ; also good:
 | 
						|
                    bb1 = (s1 == "derp")
 | 
						|
                    bb2 = (s1 != "derp")
 | 
						|
                }
 | 
						|
            }
 | 
						|
            """
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("const is not allowed on arrays") {
 | 
						|
        val text = """
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    const ubyte[5] a = [1,2,3,4,5]
 | 
						|
                    a[2]=42
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
 | 
						|
        compileText(C64Target(), true, text, outputDir, writeAssembly = true, errors=errors)
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.warnings.size shouldBe 0
 | 
						|
        errors.errors[0] shouldContain "const can only be used"
 | 
						|
    }
 | 
						|
 | 
						|
    test("array indexing is not allowed on a memory mapped variable") {
 | 
						|
        val text = """
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    &ubyte a = 10000
 | 
						|
                    uword @shared z = 500
 | 
						|
                    a[4] = (z % 3) as ubyte
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
 | 
						|
        compileText(C64Target(), true, text, outputDir, writeAssembly = true, errors=errors)
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.warnings.size shouldBe 0
 | 
						|
        errors.errors[0] shouldContain "indexing requires"
 | 
						|
    }
 | 
						|
 | 
						|
    test("unicode in identifier names is working") {
 | 
						|
        val text = """
 | 
						|
%import floats
 | 
						|
 | 
						|
main {
 | 
						|
    ubyte приблизительно = 99
 | 
						|
    ubyte นี่คือตัวอักษรภาษาไท = 42
 | 
						|
    
 | 
						|
    sub start() {
 | 
						|
        str knäckebröd = "crunchy"  ; with composed form
 | 
						|
        prt(knäckebröd)             ; with decomposed form
 | 
						|
        printf(2*floats.π)
 | 
						|
    }
 | 
						|
 | 
						|
    sub prt(str message) {
 | 
						|
        приблизительно++
 | 
						|
    }
 | 
						|
 | 
						|
    sub printf(float fl) {
 | 
						|
        นี่คือตัวอักษรภาษาไท++
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = true)  shouldNotBe null
 | 
						|
        compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)  shouldNotBe null
 | 
						|
        compileText(VMTarget(), false, text, outputDir, writeAssembly = true)  shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("return with a statement instead of a value is a syntax error") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
 | 
						|
    sub invalid() {
 | 
						|
        return cx16.r0++
 | 
						|
    }
 | 
						|
 | 
						|
    sub start() {
 | 
						|
        invalid()
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val errors=ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors=errors)  shouldBe null
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.errors[0] shouldContain "statement"
 | 
						|
    }
 | 
						|
 | 
						|
    test("redefined variable name in single declaration is reported") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        const ubyte count=11
 | 
						|
        cx16.r0++
 | 
						|
        ubyte count = 88        ; redefinition
 | 
						|
        cx16.r0 = count
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val errors=ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors=errors)  shouldBe null
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.errors[0] shouldContain "name conflict"
 | 
						|
 | 
						|
        errors.clear()
 | 
						|
        compileText(C64Target(), true, src, outputDir, writeAssembly = false, errors=errors)  shouldBe null
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.errors[0] shouldContain "name conflict"
 | 
						|
    }
 | 
						|
 | 
						|
    test("redefined variable name in multi declaration is reported") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        ubyte i
 | 
						|
        i++
 | 
						|
        ubyte i, j              ; redefinition
 | 
						|
        i++
 | 
						|
        j++
 | 
						|
    }
 | 
						|
}
 | 
						|
"""
 | 
						|
        val errors=ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors=errors)  shouldBe null
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.errors[0] shouldContain "name conflict"
 | 
						|
 | 
						|
        errors.clear()
 | 
						|
        compileText(C64Target(), true, src, outputDir, writeAssembly = false, errors=errors)  shouldBe null
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.errors[0] shouldContain "name conflict"
 | 
						|
    }
 | 
						|
 | 
						|
    test("various range datatype checks allow differences in type") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub func() -> ubyte {
 | 
						|
        cx16.r0++
 | 
						|
        return cx16.r0L
 | 
						|
    }
 | 
						|
 | 
						|
    sub start() {
 | 
						|
        bool[256] @shared cells
 | 
						|
        word starw
 | 
						|
        byte bb
 | 
						|
        uword uw
 | 
						|
        ubyte ub
 | 
						|
 | 
						|
        starw = (240-64 as word) + func()
 | 
						|
 | 
						|
        for starw in 50 downto 10  {
 | 
						|
            cx16.r0++
 | 
						|
        }
 | 
						|
        for starw in cx16.r0L downto 10  {
 | 
						|
            cx16.r0++
 | 
						|
        }
 | 
						|
 | 
						|
        for ub in 0 to len(cells)-1 {
 | 
						|
            cx16.r0++
 | 
						|
        }
 | 
						|
        for ub in cx16.r0L to len(cells)-1 {
 | 
						|
            cx16.r0++
 | 
						|
        }
 | 
						|
        for bb in 50 downto 10  {
 | 
						|
            cx16.r0++
 | 
						|
        }
 | 
						|
        for bb in cx16.r0sL downto 10  {
 | 
						|
            cx16.r0++
 | 
						|
        }
 | 
						|
 | 
						|
        for starw in 500 downto 10  {
 | 
						|
            cx16.r0++
 | 
						|
        }
 | 
						|
        for uw in 50 downto 10 {
 | 
						|
            cx16.r0++
 | 
						|
        }
 | 
						|
        for uw in 500 downto 10 {
 | 
						|
            cx16.r0++
 | 
						|
        }
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
        compileText(C64Target(), true, src, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("reg params cannot be statusflag") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        faulty(false)
 | 
						|
    }
 | 
						|
 | 
						|
    sub faulty(bool flag @Pc) {
 | 
						|
        cx16.r0++
 | 
						|
    }
 | 
						|
}"""
 | 
						|
 | 
						|
        // the syntax error is actually thrown by the parser, so we cannot catch it, but we know that there may not be a compilation result
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false) shouldBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("reg params cannot be cpu register") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        faulty(42)
 | 
						|
    }
 | 
						|
 | 
						|
    sub faulty(byte arg @Y) {
 | 
						|
        arg++
 | 
						|
    }
 | 
						|
}"""
 | 
						|
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.errors[0] shouldContain "can only use R0-R15"
 | 
						|
    }
 | 
						|
 | 
						|
    test("reg params must be all different") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        faulty3(9999,55)
 | 
						|
    }
 | 
						|
 | 
						|
    sub faulty3(uword arg @R1, ubyte arg2 @R1) {
 | 
						|
        arg += arg2
 | 
						|
    }
 | 
						|
}"""
 | 
						|
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.errors[0] shouldContain "register is used multiple times"
 | 
						|
    }
 | 
						|
 | 
						|
    test("reg params R0-R15 are ok") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        foo(42)
 | 
						|
        bar(9999,55)
 | 
						|
    }
 | 
						|
 | 
						|
    sub foo(ubyte arg @R2) {
 | 
						|
        arg++
 | 
						|
    }
 | 
						|
 | 
						|
    sub bar(uword arg @R0, ubyte arg2 @R1) {
 | 
						|
        arg += arg2
 | 
						|
    }
 | 
						|
}"""
 | 
						|
 | 
						|
        val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldNotBe null
 | 
						|
        errors.errors.size shouldBe 0
 | 
						|
        errors.warnings.size shouldBe 2
 | 
						|
        errors.warnings[0] shouldContain "footgun"
 | 
						|
        errors.warnings[1] shouldContain "footgun"
 | 
						|
    }
 | 
						|
 | 
						|
    test("reg params R0-R15 cannot be used for invalid types") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub func(str value @R1) {
 | 
						|
        return
 | 
						|
    }
 | 
						|
 | 
						|
    sub start() {
 | 
						|
        func(true)
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.warnings.size shouldBe 0
 | 
						|
        errors.errors[0] shouldContain "requires integer or boolean type"
 | 
						|
    }
 | 
						|
 | 
						|
    test("missing address of in expression operand") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        str name = "foo"
 | 
						|
        cx16.r0 =  name+2
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.warnings.size shouldBe 0
 | 
						|
        errors.errors[0] shouldContain "missing &"
 | 
						|
    }
 | 
						|
 | 
						|
    test("can't use uword[] as a parameter type give clear error") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        uword[] @nosplit array = [1111,2222]
 | 
						|
        funcw1(array)
 | 
						|
        funcw1([1111,2222])
 | 
						|
        funcw2(array)
 | 
						|
        funcw2([1111,2222])
 | 
						|
        funcb([11,22])
 | 
						|
    }
 | 
						|
 | 
						|
    sub funcw1(uword[] ptr) {
 | 
						|
        ; error
 | 
						|
    }
 | 
						|
 | 
						|
    sub funcw2(uword ptr) {
 | 
						|
        ; ok
 | 
						|
    }
 | 
						|
 | 
						|
    sub funcb(ubyte[] ptr) {
 | 
						|
        ; ok
 | 
						|
    }
 | 
						|
}
 | 
						|
"""
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 3
 | 
						|
        errors.warnings.size shouldBe 0
 | 
						|
        errors.errors[0] shouldContain  ":5:16: argument 1 type mismatch"
 | 
						|
        errors.errors[1] shouldContain  ":6:16: argument 1 type mismatch"
 | 
						|
        errors.errors[2] shouldContain  ":12:16: this pass-by-reference type can't be used as a parameter type"
 | 
						|
    }
 | 
						|
 | 
						|
    test("proper type checking for multi-value assigns") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        bool bb
 | 
						|
        ubyte ub
 | 
						|
        uword uw
 | 
						|
        uw, void = thing2()
 | 
						|
        uw, bb = thing2()
 | 
						|
        uw, ub = thing2()
 | 
						|
    }
 | 
						|
 | 
						|
    asmsub thing2() -> ubyte @A, bool @Pc {
 | 
						|
        %asm {{
 | 
						|
            rts
 | 
						|
        }}
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 4
 | 
						|
        errors.errors[0] shouldContain  "can't assign returnvalue #1 to corresponding target; ubyte vs uword"
 | 
						|
        errors.errors[1] shouldContain  "can't assign returnvalue #1 to corresponding target; ubyte vs uword"
 | 
						|
        errors.errors[2] shouldContain  "can't assign returnvalue #1 to corresponding target; ubyte vs uword"
 | 
						|
        errors.errors[3] shouldContain  "can't assign returnvalue #2 to corresponding target; bool vs ubyte"
 | 
						|
    }
 | 
						|
 | 
						|
    test("multi assigns with too few result values from the function") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        ubyte @shared x,y,z
 | 
						|
        x,y,z = sys.progend()
 | 
						|
        ubyte @shared k,l,m = sys.progend()
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(Cx16Target(), optimize=true, src, outputDir, writeAssembly=false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 2
 | 
						|
        errors.errors[0] shouldContain "too few values: expected 3 got 1"
 | 
						|
        errors.errors[1] shouldContain "too few values: expected 3 got 1"
 | 
						|
    }
 | 
						|
 | 
						|
    test("correct errors for wrong string initialization value") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
 | 
						|
    sub start() {
 | 
						|
        str minString1 = 1234
 | 
						|
        str minString2 = func()
 | 
						|
    }
 | 
						|
 | 
						|
    sub func() -> str {
 | 
						|
        return "zz"
 | 
						|
    }
 | 
						|
}"""
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 3
 | 
						|
        errors.errors[0] shouldContain "type of value uword doesn't match target str"
 | 
						|
        errors.errors[1] shouldContain "string var must be initialized with a string literal"
 | 
						|
        errors.errors[2] shouldContain "string var must be initialized with a string literal"
 | 
						|
    }
 | 
						|
})
 |