mirror of
				https://github.com/irmen/prog8.git
				synced 2025-11-03 19:16:13 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			490 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Kotlin
		
	
	
	
	
	
			
		
		
	
	
			490 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Kotlin
		
	
	
	
	
	
package prog8tests.compiler
 | 
						|
 | 
						|
import io.kotest.assertions.withClue
 | 
						|
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 io.kotest.matchers.types.instanceOf
 | 
						|
import io.kotest.matchers.types.shouldBeSameInstanceAs
 | 
						|
import prog8.ast.GlobalNamespace
 | 
						|
import prog8.ast.ParentSentinel
 | 
						|
import prog8.ast.expressions.NumericLiteral
 | 
						|
import prog8.ast.statements.*
 | 
						|
import prog8.code.target.C64Target
 | 
						|
import prog8tests.helpers.ErrorReporterForTests
 | 
						|
import prog8tests.helpers.compileText
 | 
						|
 | 
						|
 | 
						|
class TestScoping: FunSpec({
 | 
						|
 | 
						|
    val outputDir = tempdir().toPath()
 | 
						|
    
 | 
						|
    test("modules parent is global namespace") {
 | 
						|
        val src = """
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
 | 
						|
        val result = compileText(C64Target(), false, src, outputDir, writeAssembly = false)!!
 | 
						|
        val module = result.compilerAst.toplevelModule
 | 
						|
        module.parent shouldBe instanceOf<GlobalNamespace>()
 | 
						|
        module.program shouldBeSameInstanceAs result.compilerAst
 | 
						|
        module.parent.parent shouldBe instanceOf<ParentSentinel>()
 | 
						|
    }
 | 
						|
 | 
						|
    test("anon scope vars moved into subroutine scope") {
 | 
						|
        val src = """
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    repeat 10 {
 | 
						|
                        ubyte xx = 99
 | 
						|
                        rol(xx)
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
 | 
						|
        val result = compileText(C64Target(), false, src, outputDir, writeAssembly = false)!!
 | 
						|
        val mainBlock = result.compilerAst.entrypoint.definingBlock
 | 
						|
        val start = mainBlock.statements.single() as Subroutine
 | 
						|
        val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
 | 
						|
        withClue("no vars moved to main block") {
 | 
						|
            mainBlock.statements.any { it is VarDecl } shouldBe false
 | 
						|
        }
 | 
						|
        val subroutineVars = start.statements.filterIsInstance<VarDecl>()
 | 
						|
        withClue("var from repeat anonscope must be moved up to subroutine") {
 | 
						|
            subroutineVars.size shouldBe 1
 | 
						|
        }
 | 
						|
        subroutineVars[0].name shouldBe "xx"
 | 
						|
        withClue("var should have been removed from repeat anonscope") {
 | 
						|
            repeatbody.statements.any { it is VarDecl } shouldBe false
 | 
						|
        }
 | 
						|
        val initassign = repeatbody.statements[0] as? Assignment
 | 
						|
        withClue("vardecl in repeat should be replaced by init assignment") {
 | 
						|
            initassign?.target?.identifier?.nameInSource shouldBe listOf("xx")
 | 
						|
        }
 | 
						|
        withClue("vardecl in repeat should be replaced by init assignment") {
 | 
						|
            (initassign?.value as? NumericLiteral)?.number?.toInt() shouldBe 99
 | 
						|
        }
 | 
						|
        repeatbody.statements[1] shouldBe instanceOf<FunctionCallStatement>()
 | 
						|
    }
 | 
						|
 | 
						|
    test("labels with anon scopes") {
 | 
						|
        val src = """
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    uword addr
 | 
						|
                    goto labeloutside
 | 
						|
        
 | 
						|
                    if true {
 | 
						|
                        if true {
 | 
						|
                            addr = &iflabel
 | 
						|
                            addr = &labelinside
 | 
						|
                            addr = &labeloutside
 | 
						|
                            addr = &main.start.nested.nestedlabel
 | 
						|
                            goto labeloutside
 | 
						|
                            goto iflabel
 | 
						|
                            goto main.start.nested.nestedlabel
 | 
						|
                        }
 | 
						|
            iflabel:
 | 
						|
                    }
 | 
						|
        
 | 
						|
                    repeat 10 {
 | 
						|
                        addr = &iflabel
 | 
						|
                        addr = &labelinside
 | 
						|
                        addr = &labeloutside
 | 
						|
                        addr = &main.start.nested.nestedlabel
 | 
						|
                        goto iflabel
 | 
						|
                        goto labelinside
 | 
						|
                        goto main.start.nested.nestedlabel
 | 
						|
            labelinside:
 | 
						|
                    }
 | 
						|
 | 
						|
                    sub nested () {
 | 
						|
            nestedlabel:
 | 
						|
                        addr = &nestedlabel
 | 
						|
                        goto nestedlabel
 | 
						|
                        goto main.start.nested.nestedlabel
 | 
						|
                    }
 | 
						|
 | 
						|
            labeloutside:
 | 
						|
                    addr = &iflabel
 | 
						|
                    addr = &labelinside
 | 
						|
                    addr = &labeloutside
 | 
						|
                    addr = &main.start.nested.nestedlabel
 | 
						|
                    goto main.start.nested.nestedlabel
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
 | 
						|
        val result = compileText(C64Target(), false, src, outputDir, writeAssembly = true)!!
 | 
						|
        val mainBlock = result.compilerAst.entrypoint.definingBlock
 | 
						|
        val start = mainBlock.statements.single() as Subroutine
 | 
						|
        val labels = start.statements.filterIsInstance<Label>()
 | 
						|
        withClue("only one label in subroutine scope") {
 | 
						|
            labels.size shouldBe 1
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    test("good subroutine call without qualified names") {
 | 
						|
        val text="""
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    routine() 
 | 
						|
                    routine2()
 | 
						|
                    
 | 
						|
                    sub routine2() {
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                sub routine() {
 | 
						|
                    start()
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("wrong subroutine call without qualified names") {
 | 
						|
        val text="""
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    sub routine2() {
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                sub routine() {
 | 
						|
                    routine2()
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val errors= ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 1
 | 
						|
        errors.errors[0] shouldContain "undefined"
 | 
						|
        errors.errors[0] shouldContain "routine2"
 | 
						|
    }
 | 
						|
 | 
						|
    test("good subroutine calls with qualified names (from root)") {
 | 
						|
        val text="""
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    main.routine() 
 | 
						|
                    main.start.routine2()
 | 
						|
                    
 | 
						|
                    sub routine2() {
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                sub routine() {
 | 
						|
                    main.start.routine2()
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("wrong subroutine calls with qualified names (not from root)") {
 | 
						|
        val text="""
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    start.routine2()     
 | 
						|
                    wrong.start.routine2()
 | 
						|
                    sub routine2() {
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                sub routine() {
 | 
						|
                    start.routine2()
 | 
						|
                    wrong.start.routine2()
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val errors= ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors=errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 4
 | 
						|
        errors.errors[0] shouldContain "undefined"
 | 
						|
        errors.errors[0] shouldContain "start.routine2"
 | 
						|
        errors.errors[1] shouldContain "undefined"
 | 
						|
        errors.errors[1] shouldContain "wrong.start.routine2"
 | 
						|
        errors.errors[2] shouldContain "undefined"
 | 
						|
        errors.errors[2] shouldContain "start.routine2"
 | 
						|
        errors.errors[3] shouldContain "undefined"
 | 
						|
        errors.errors[3] shouldContain "wrong.start.routine2"
 | 
						|
    }
 | 
						|
 | 
						|
    test("good variables without qualified names") {
 | 
						|
        val text="""
 | 
						|
            main {
 | 
						|
                ubyte v1
 | 
						|
                
 | 
						|
                sub start() {
 | 
						|
                    ubyte v2
 | 
						|
                    v1=1
 | 
						|
                    v2=2
 | 
						|
                    
 | 
						|
                    sub routine2() {
 | 
						|
                        ubyte v3
 | 
						|
                        v1=1
 | 
						|
                        v2=2
 | 
						|
                        v3=3
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                sub routine() {
 | 
						|
                    ubyte v4
 | 
						|
                    v1=1
 | 
						|
                    v4=4
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("wrong variables without qualified names") {
 | 
						|
        val text="""
 | 
						|
            main {
 | 
						|
                ubyte v1
 | 
						|
                
 | 
						|
                sub start() {
 | 
						|
                    ubyte v2
 | 
						|
                    v1=1
 | 
						|
                    v2=2
 | 
						|
                    v3=3    ; can't access
 | 
						|
                    v4=4    ; can't access
 | 
						|
                    sub routine2() {
 | 
						|
                        ubyte v3
 | 
						|
                        v1=1
 | 
						|
                        v2=2
 | 
						|
                        v3=3
 | 
						|
                        v4=3    ;can't access
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                sub routine() {
 | 
						|
                    ubyte v4
 | 
						|
                    v1=1
 | 
						|
                    v2=2    ; can't access
 | 
						|
                    v3=3    ; can't access
 | 
						|
                    v4=4
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val errors= ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors=errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 5
 | 
						|
        errors.errors[0] shouldContain "undefined symbol: v3"
 | 
						|
        errors.errors[1] shouldContain "undefined symbol: v4"
 | 
						|
        errors.errors[2] shouldContain "undefined symbol: v4"
 | 
						|
        errors.errors[3] shouldContain "undefined symbol: v2"
 | 
						|
        errors.errors[4] shouldContain "undefined symbol: v3"
 | 
						|
    }
 | 
						|
 | 
						|
    test("good variable refs with qualified names (from root)") {
 | 
						|
        val text="""
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    uword xx 
 | 
						|
                    xx = &main.routine
 | 
						|
                    main.routine(5)
 | 
						|
                    main.routine.value = 5
 | 
						|
                    main.routine.arg = 5
 | 
						|
                    xx = &main.routine.nested
 | 
						|
                    main.routine.nested(5)
 | 
						|
                    main.routine.nested.nestedvalue = 5
 | 
						|
                    main.routine.nested.arg2 = 5
 | 
						|
                }
 | 
						|
                
 | 
						|
                sub routine(ubyte arg) {
 | 
						|
                    ubyte value
 | 
						|
                    
 | 
						|
                    sub nested(ubyte arg2) {
 | 
						|
                        ubyte nestedvalue
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("wrong variable refs with qualified names 1 (not from root)") {
 | 
						|
        val text="""
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    uword xx 
 | 
						|
                    xx = &routine
 | 
						|
                    routine(5)
 | 
						|
                    routine.value = 5
 | 
						|
                    routine.arg = 5
 | 
						|
                    routine.nested.arg2 = 5
 | 
						|
                    routine.nested.nestedvalue = 5
 | 
						|
                    nested.nestedvalue = 5
 | 
						|
                }
 | 
						|
                
 | 
						|
                sub routine(ubyte arg) {
 | 
						|
                    ubyte value
 | 
						|
                    
 | 
						|
                    sub nested(ubyte arg2) {
 | 
						|
                        ubyte nestedvalue
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val errors= ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors=errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 5
 | 
						|
        errors.errors[0] shouldContain "undefined symbol: routine.value"
 | 
						|
        errors.errors[1] shouldContain "undefined symbol: routine.arg"
 | 
						|
        errors.errors[2] shouldContain "undefined symbol: routine.nested.arg2"
 | 
						|
        errors.errors[3] shouldContain "undefined symbol: routine.nested.nestedvalue"
 | 
						|
        errors.errors[4] shouldContain "undefined symbol: nested.nestedvalue"
 | 
						|
    }
 | 
						|
 | 
						|
    test("various good goto targets") {
 | 
						|
        val text="""
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    uword address = $4000
 | 
						|
                    
 | 
						|
                    goto ${'$'}c000
 | 
						|
                    goto address        ; indirect jump
 | 
						|
                    goto main.routine
 | 
						|
                    goto main.jumplabel
 | 
						|
                    
 | 
						|
                    if_cc
 | 
						|
                        goto ${'$'}c000
 | 
						|
                    if_cc
 | 
						|
                        goto address        ; indirect jump
 | 
						|
                    if_cc
 | 
						|
                        goto main.routine
 | 
						|
                    if_cc
 | 
						|
                        goto main.jumplabel
 | 
						|
                }
 | 
						|
 | 
						|
            jumplabel:
 | 
						|
                %asm {{
 | 
						|
                    rts
 | 
						|
                }}
 | 
						|
                sub routine() {
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
    test("various wrong goto targets") {
 | 
						|
        val text = """
 | 
						|
            main {
 | 
						|
                sub start() {
 | 
						|
                    byte wrongaddress = 100
 | 
						|
                    
 | 
						|
                    goto wrongaddress   ; must be uword
 | 
						|
                    goto main.routine   ; can't take args
 | 
						|
                }
 | 
						|
 | 
						|
                sub routine(ubyte arg) {
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors = errors) shouldBe null
 | 
						|
        errors.errors.size shouldBe 2
 | 
						|
        errors.errors[0] shouldContain "wrong address"
 | 
						|
        errors.errors[1] shouldContain "takes parameters"
 | 
						|
    }
 | 
						|
 | 
						|
    test("name shadowing and redefinition errors") {
 | 
						|
        val text = """
 | 
						|
            main {
 | 
						|
                ubyte var1Warn
 | 
						|
                
 | 
						|
                sub start() {
 | 
						|
                    ubyte var1Warn
 | 
						|
                    ubyte var1Warn
 | 
						|
                    ubyte main 
 | 
						|
                    ubyte start
 | 
						|
                    ubyte outer         ; is ok
 | 
						|
                    ubyte internalOk
 | 
						|
                    ubyte internalOk    ; double defined
 | 
						|
                }
 | 
						|
                
 | 
						|
                sub outer() {
 | 
						|
                    ubyte var1Warn
 | 
						|
                    ubyte internalOk
 | 
						|
                }
 | 
						|
            }
 | 
						|
        """
 | 
						|
        val errors = ErrorReporterForTests()
 | 
						|
        compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors = errors) shouldBe null
 | 
						|
        /*
 | 
						|
There are 4 errors and 3 warnings.
 | 
						|
ERROR name conflict 'start', also defined...
 | 
						|
ERROR name conflict 'var1Warn', also defined...
 | 
						|
ERROR name conflict 'main', also defined...
 | 
						|
ERROR name conflict 'internalOk', also defined...
 | 
						|
WARN name 'var1Warn' shadows occurrence at...
 | 
						|
WARN name 'var1Warn' shadows occurrence at...
 | 
						|
WARN name 'var1Warn' shadows occurrence at...
 | 
						|
         */
 | 
						|
        errors.warnings.size shouldBe 3
 | 
						|
        errors.warnings[0] shouldContain "var1Warn"
 | 
						|
        errors.warnings[0] shouldContain "shadows"
 | 
						|
        errors.warnings[0] shouldContain "line 3"
 | 
						|
        errors.warnings[1] shouldContain "var1Warn"
 | 
						|
        errors.warnings[1] shouldContain "shadows"
 | 
						|
        errors.warnings[1] shouldContain "line 3"
 | 
						|
        errors.warnings[2] shouldContain "var1Warn"
 | 
						|
        errors.warnings[2] shouldContain "shadows"
 | 
						|
        errors.warnings[2] shouldContain "line 3"
 | 
						|
        errors.errors.size shouldBe 4
 | 
						|
        errors.errors[0] shouldContain "name conflict"
 | 
						|
        errors.errors[0] shouldContain "start"
 | 
						|
        errors.errors[0] shouldContain "line 5"
 | 
						|
        errors.errors[1] shouldContain "name conflict"
 | 
						|
        errors.errors[1] shouldContain "var1Warn"
 | 
						|
        errors.errors[1] shouldContain "line 6"
 | 
						|
        errors.errors[2] shouldContain "name conflict"
 | 
						|
        errors.errors[2] shouldContain "main"
 | 
						|
        errors.errors[2] shouldContain "line 2"
 | 
						|
        errors.errors[3] shouldContain "name conflict"
 | 
						|
        errors.errors[3] shouldContain "internalOk"
 | 
						|
        errors.errors[3] shouldContain "line 11"
 | 
						|
    }
 | 
						|
 | 
						|
    test("ast node linkage and lookups ok even with no symbol prefixing") {
 | 
						|
        val src="""
 | 
						|
main {
 | 
						|
    sub start() {
 | 
						|
        thing.routine()
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
thing {
 | 
						|
    %option no_symbol_prefixing
 | 
						|
 | 
						|
    sub routine() {
 | 
						|
        other.something()
 | 
						|
        other.counter++
 | 
						|
        other.asmsomething()
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
other {
 | 
						|
    sub something() {
 | 
						|
    }
 | 
						|
 | 
						|
    asmsub asmsomething() {
 | 
						|
        %asm {{
 | 
						|
            nop
 | 
						|
            rts
 | 
						|
        }}
 | 
						|
    }
 | 
						|
 | 
						|
    uword @shared counter
 | 
						|
}
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
        compileText(C64Target(), false, src, outputDir, writeAssembly = true) shouldNotBe null
 | 
						|
    }
 | 
						|
 | 
						|
})
 |