prog8/compiler/test/codegeneration/TestArrayThings.kt

408 lines
12 KiB
Kotlin

package prog8tests.codegeneration
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.code.ast.PtBuiltinFunctionCall
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import kotlin.io.path.readText
class TestArrayThings: FunSpec({
test("assign prefix var to array should compile fine and is not split into inplace array modification") {
val text = """
main {
sub start() {
byte[5] array
byte bb
array[1] = -bb
}
}
"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("array in-place negation (integer types)") {
val text = """
main {
byte[10] foo
ubyte[10] foou
word[10] foow
uword[10] foowu
sub start() {
foo[1] = 42
foo[1] = -foo[1]
foow[1] = 4242
foow[1] = -foow[1]
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("array in-place negation (float type) vm target") {
val text = """
%import floats
main {
float[10] flt
sub start() {
flt[1] = 42.42
flt[1] = -flt[1]
}
}"""
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("array in-place negation (float type) 6502 target") {
val text = """
%import floats
main {
float[10] flt
sub start() {
flt[1] = 42.42
flt[1] = -flt[1]
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("array in-place invert") {
val text = """
main {
ubyte[10] foo
uword[10] foow
sub start() {
foo[1] = 42
foo[1] = ~foo[1]
foow[1] = 4242
foow[1] = ~foow[1]
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("split only for word arrays") {
val srcGood = """
main {
uword[10] @split sw
word[10] @split sw2
sub start() {
}
}"""
compileText(C64Target(), false, srcGood, writeAssembly = false) shouldNotBe null
val srcWrong1 = """
main {
ubyte[10] @split sb
sub start() {
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, srcWrong1, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "split can only be used on word arrays"
val srcWrong2 = """
%option enable_floats
main {
float[10] @split sf
sub start() {
}
}"""
errors.clear()
compileText(C64Target(), false, srcWrong2, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "split can only be used on word arrays"
}
test("split word arrays in asm as lsb/msb") {
val text = """
main {
uword[10] @split @shared uw
word[10] @split @shared sw
uword[10] @shared normal
sub start() {
%asm {{
lda p8v_normal
lda p8v_uw_lsb
lda p8v_uw_msb
lda p8v_sw_lsb
lda p8v_sw_msb
}}
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("split array assignments") {
val text = """
main {
sub start() {
str name1 = "name1"
str name2 = "name2"
uword[] @split names = [name1, name2, "name3"]
uword[] @split names2 = [name1, name2, "name3"]
uword[] addresses = [0,0,0]
names = [1111,2222,3333]
addresses = names
names = addresses
names2 = names
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("array target with expression for index") {
val text = """
main {
sub start() {
ubyte[] array = [1,2,3]
array[cx16.r0L+1] += 42
cx16.r0L = array[cx16.r0L+1]
}
}"""
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("split array in zeropage is okay") {
val text = """
main {
sub start() {
uword[3] @zp @split @shared thearray
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val assemblyFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
val assembly = assemblyFile.readText()
assembly shouldContain "thearray_lsb"
assembly shouldContain "thearray_msb"
}
test("indexing str or pointervar with expression") {
val text = """
main {
sub start() {
str name = "thing"
modify(name)
sub modify(str arg) {
ubyte n=1
uword pointervar
arg[n+1] = arg[1]
pointervar[n+1] = pointervar[1]
}
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("address of a uword pointer array expression") {
val src="""
main {
sub start() {
set_state(12345, 1)
}
sub set_state(uword buffer, ubyte i) {
uword addr = &buffer[i]
addr++
}
}"""
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("negative array index variables are not allowed, but ptr indexing is allowed") {
val src="""
main {
sub start() {
uword @shared pointer
ubyte[10] array
str name = "hello"
byte sindex
array[sindex] = 10
cx16.r0L = array[sindex]
name[sindex] = 0
cx16.r0L = name[sindex]
pointer[sindex] = 10
cx16.r0L=pointer[sindex]
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 4
errors.errors[0] shouldContain "signed variables"
errors.errors[1] shouldContain "signed variables"
errors.errors[2] shouldContain "signed variables"
errors.errors[3] shouldContain "signed variables"
}
test("bounds checking for both positive and negative indexes, correct cases") {
val src="""
main {
sub start() {
ubyte[10] array
array[0] = 0
array[9] = 0
array[-1] = 0
array[-10] = 0
}
}"""
compileText(VMTarget(), false, src, writeAssembly = false) shouldNotBe null
}
test("bounds checking on strings, correct cases") {
val src="""
main {
sub start() {
str name = "1234567890"
name[0] = 0
name[9] = 0
}
}"""
compileText(VMTarget(), false, src, writeAssembly = false) shouldNotBe null
}
test("bounds checking for positive indexes, invalid case") {
val src="""
main {
sub start() {
ubyte[10] array
array[10] = 0
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "out of bounds"
}
test("bounds checking for negative indexes, invalid case") {
val src="""
main {
sub start() {
ubyte[10] array
array[-11] = 0
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "out of bounds"
}
test("bounds checking on strings invalid cases") {
val src="""
main {
sub start() {
str name = "1234567890"
name[10] = 0
name[-1] = 0
name[-11] = 0
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 3
errors.errors[0] shouldContain "out of bounds"
errors.errors[1] shouldContain "out of bounds"
errors.errors[2] shouldContain "out of bounds"
}
test("array assignments should check for number of elements and element type correctness") {
val src="""
%option enable_floats
main {
sub start() {
ubyte[] array = 1 to 4
ubyte[] array2 = [1,2,3,4]
str[] names = ["apple", "banana", "tomato"]
array = [10,11,12,13] ; ok!
array = 20 to 23 ; ok!
names = ["x1", "x2", "x3"] ; ok!
ubyte[] array3 = [1,2,3,4000] ; error: element type
array = 10 to 15 ; error: array size
array = 1000 to 1003 ; error: element type
names = ["x1", "x2", "x3", "x4"] ; error: array size
names = [1.1, 2.2, 3.3, 4.4] ; error: array size AND element type
names = [1.1, 2.2, 999999.9] ; error: element type
names = [1.1, 2.2, 9.9] ; error: element type
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = true, errors = errors) shouldBe null
errors.errors.size shouldBe 8
errors.errors[0] shouldContain "incompatible type"
errors.errors[1] shouldContain "array size mismatch"
errors.errors[2] shouldContain "array element out of range"
errors.errors[3] shouldContain "array size mismatch"
errors.errors[4] shouldContain "array size mismatch"
errors.errors[5] shouldContain "value has incompatible type"
errors.errors[6] shouldContain "value has incompatible type"
errors.errors[7] shouldContain "value has incompatible type"
}
test("array assignments should work via array copy call") {
val src="""
%option enable_floats
main {
sub start() {
ubyte[] array = [1,2,3]
ubyte[3] array2
float[] flarray = [1.1, 2.2, 3.3]
float[3] flarray2
word[] warray = [-2222,42,3333]
word[3] warray2
str[] names = ["apple", "banana", "tomato"]
str[3] names2
; 8 array assignments -> 8 arraycopies:
array = [8,7,6]
array = array2
flarray = [99.9, 88.8, 77.7]
flarray = flarray2
warray = [4444,5555,6666]
warray = warray2
names = ["x1", "x2", "x3"]
names = names2
}
}"""
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
val result = compileText(C64Target(), false, src, writeAssembly = true)!!
val x = result.codegenAst!!.entrypoint()!!
(x.children[12] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[13] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[14] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[15] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[16] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[17] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[18] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[19] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
}
})