mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
362 lines
14 KiB
Kotlin
362 lines
14 KiB
Kotlin
package prog8tests.codegeneration
|
|
|
|
import io.kotest.core.spec.style.FunSpec
|
|
import io.kotest.matchers.shouldBe
|
|
import prog8.code.target.VMTarget
|
|
import prog8.vm.VmRunner
|
|
import prog8tests.helpers.ErrorReporterForTests
|
|
import prog8tests.helpers.compileText
|
|
import java.io.File
|
|
|
|
class TestStructsWithArrays : FunSpec({
|
|
val outputDir = File("build/test/TestStructsWithArrays").toPath()
|
|
|
|
beforeTest {
|
|
if (outputDir.toFile().exists()) outputDir.toFile().deleteRecursively()
|
|
outputDir.toFile().mkdirs()
|
|
}
|
|
|
|
test("array in struct access") {
|
|
val text = """
|
|
%encoding iso
|
|
M {
|
|
struct S {
|
|
ubyte[4] data
|
|
}
|
|
}
|
|
main {
|
|
sub start() {
|
|
^^M.S s = ^^M.S : [ [1, 2, 3, 4] ]
|
|
s.data[0] = 10
|
|
if s.data[0] == 10 and s.data[1] == 2 and s.data[2] == 3 and s.data[3] == 4 {
|
|
; pass
|
|
}
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), true, text, outputDir, errors = errors)
|
|
System.err.println("Errors: ${errors.errors}")
|
|
if (result == null) {
|
|
throw Exception("Compilation failed")
|
|
}
|
|
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
|
VmRunner().runProgram(virtfile.toFile().readText(), false)
|
|
}
|
|
|
|
test("nested array in struct initialization") {
|
|
val text = """
|
|
%encoding iso
|
|
M {
|
|
struct S {
|
|
ubyte id
|
|
ubyte[4] data
|
|
word[2] scores
|
|
}
|
|
}
|
|
main {
|
|
sub start() {
|
|
^^M.S s = ^^M.S : [1, [10, 20, 30, 40], [1000, 2000]]
|
|
if s.id == 1 and s.data[0] == 10 and s.data[1] == 20 and s.data[2] == 30 and s.data[3] == 40 and s.scores[0] == 1000 and s.scores[1] == 2000 {
|
|
; pass
|
|
}
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), true, text, outputDir, errors = errors)
|
|
if (result == null) {
|
|
throw Exception("Compilation failed: ${errors.errors}")
|
|
}
|
|
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
|
VmRunner().runProgram(virtfile.toFile().readText(), false)
|
|
}
|
|
|
|
test("struct with inlined array memory layout") {
|
|
val src = $$"""
|
|
main {
|
|
struct Node {
|
|
ubyte a
|
|
bool flag
|
|
ubyte[5] array
|
|
word number
|
|
}
|
|
|
|
sub start() {
|
|
^^Node k2 = [1, false, [65,66,67,68,0], 9999]
|
|
^^Node k3 = $4000
|
|
k3^^=k2^^
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), true, src, outputDir, errors = errors)
|
|
if (result == null) {
|
|
throw Exception("Compilation failed: ${errors.errors}")
|
|
}
|
|
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
|
VmRunner().runAndTestProgram(virtfile.toFile().readText(), true) { vm ->
|
|
vm.memory.getUB(0x4000u).toInt() shouldBe 1
|
|
vm.memory.getUB(0x4001u).toInt() shouldBe 0
|
|
vm.memory.getUB(0x4002u).toInt() shouldBe 65
|
|
vm.memory.getUB(0x4003u).toInt() shouldBe 66
|
|
vm.memory.getUB(0x4004u).toInt() shouldBe 67
|
|
vm.memory.getUB(0x4005u).toInt() shouldBe 68
|
|
vm.memory.getUB(0x4006u).toInt() shouldBe 0
|
|
vm.memory.getUW(0x4007u).toInt() shouldBe 9999
|
|
}
|
|
}
|
|
|
|
test("string literal initializes ubyte array field with C-style semantics") {
|
|
val src = $$"""
|
|
%encoding iso
|
|
main {
|
|
struct Node {
|
|
ubyte id
|
|
ubyte[4] name
|
|
bool flag
|
|
}
|
|
|
|
sub start() {
|
|
; C semantics: shorter than array -> pad with zeros including implicit terminator
|
|
; "abc" -> 'a','b','c','\0' (97, 98, 99, 0)
|
|
^^Node n = [1, "abc", false]
|
|
n.id = 7
|
|
n.flag = true
|
|
^^Node m = $4000
|
|
m^^ = n^^
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), true, src, outputDir, errors = errors)
|
|
if (result == null) {
|
|
throw Exception("Compilation failed: ${errors.errors}")
|
|
}
|
|
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
|
VmRunner().runAndTestProgram(virtfile.toFile().readText(), true) { vm ->
|
|
vm.memory.getUB(0x4000u).toInt() shouldBe 7
|
|
vm.memory.getUB(0x4001u).toInt() shouldBe 'a'.code
|
|
vm.memory.getUB(0x4002u).toInt() shouldBe 'b'.code
|
|
vm.memory.getUB(0x4003u).toInt() shouldBe 'c'.code
|
|
vm.memory.getUB(0x4004u).toInt() shouldBe 0
|
|
vm.memory.getUB(0x4005u).toInt() shouldBe 1 // flag=true
|
|
}
|
|
}
|
|
|
|
test("string literal exactly fills ubyte array field (no implicit terminator)") {
|
|
val src = $$"""
|
|
%encoding iso
|
|
main {
|
|
sub start() {
|
|
struct Node {
|
|
ubyte[4] name
|
|
}
|
|
; "abcd" (4 bytes) fills the array exactly; no room for the 0 terminator
|
|
^^Node n = ["abcd"]
|
|
^^Node m = $4000
|
|
m^^ = n^^
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), true, src, outputDir, errors = errors)
|
|
if (result == null) {
|
|
throw Exception("Compilation failed: ${errors.errors}")
|
|
}
|
|
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
|
VmRunner().runAndTestProgram(virtfile.toFile().readText(), true) { vm ->
|
|
vm.memory.getUB(0x4000u).toInt() shouldBe 'a'.code
|
|
vm.memory.getUB(0x4001u).toInt() shouldBe 'b'.code
|
|
vm.memory.getUB(0x4002u).toInt() shouldBe 'c'.code
|
|
vm.memory.getUB(0x4003u).toInt() shouldBe 'd'.code
|
|
}
|
|
}
|
|
|
|
test("string literal too long for ubyte array field errors") {
|
|
val src = """
|
|
%encoding iso
|
|
main {
|
|
sub start() {
|
|
struct Node {
|
|
ubyte[3] name
|
|
}
|
|
; "abcd" (4 bytes) is too long for an array of size 3
|
|
^^Node n = ["abcd"]
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), true, src, outputDir, errors = errors)
|
|
result shouldBe null
|
|
errors.errors.any { it.contains("does not fit in ubyte array field 'name'") } shouldBe true
|
|
}
|
|
|
|
test("string literal pads with zeros when shorter than ubyte array field") {
|
|
val src = $$"""
|
|
%encoding iso
|
|
main {
|
|
sub start() {
|
|
struct Node {
|
|
ubyte[5] name
|
|
}
|
|
; "ab" -> 'a','b','\0','\0','\0'
|
|
^^Node n = ["ab"]
|
|
^^Node m = $4000
|
|
m^^ = n^^
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), true, src, outputDir, errors = errors)
|
|
if (result == null) {
|
|
throw Exception("Compilation failed: ${errors.errors}")
|
|
}
|
|
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
|
VmRunner().runAndTestProgram(virtfile.toFile().readText(), true) { vm ->
|
|
vm.memory.getUB(0x4000u).toInt() shouldBe 'a'.code
|
|
vm.memory.getUB(0x4001u).toInt() shouldBe 'b'.code
|
|
vm.memory.getUB(0x4002u).toInt() shouldBe 0
|
|
vm.memory.getUB(0x4003u).toInt() shouldBe 0
|
|
vm.memory.getUB(0x4004u).toInt() shouldBe 0
|
|
}
|
|
}
|
|
|
|
test("string literal initializes signed byte array field") {
|
|
val src = $$"""
|
|
%encoding iso
|
|
main {
|
|
struct Node {
|
|
byte id
|
|
byte[4] name
|
|
bool flag
|
|
}
|
|
|
|
sub start() {
|
|
^^Node n = [1, "abc", false]
|
|
^^Node m = $4000
|
|
m^^ = n^^
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), true, src, outputDir, errors = errors)
|
|
if (result == null) {
|
|
throw Exception("Compilation failed: ${errors.errors}")
|
|
}
|
|
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
|
VmRunner().runAndTestProgram(virtfile.toFile().readText(), true) { vm ->
|
|
vm.memory.getSB(0x4000u).toInt() shouldBe 1
|
|
vm.memory.getSB(0x4001u).toInt() shouldBe 'a'.code
|
|
vm.memory.getSB(0x4002u).toInt() shouldBe 'b'.code
|
|
vm.memory.getSB(0x4003u).toInt() shouldBe 'c'.code
|
|
vm.memory.getSB(0x4004u).toInt() shouldBe 0
|
|
}
|
|
}
|
|
|
|
test("string literal with high bytes errors on signed byte array field") {
|
|
val src = """
|
|
%encoding iso
|
|
main {
|
|
struct Node {
|
|
byte[3] name
|
|
}
|
|
|
|
sub start() {
|
|
; 'ÿ' is 0xFF which doesn't fit in signed byte
|
|
^^Node n = ["\u00ff"]
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), true, src, outputDir, errors = errors)
|
|
result shouldBe null
|
|
errors.errors.any { it.contains("do not fit in the signed byte array") } shouldBe true
|
|
}
|
|
|
|
test("string literal initializes ubyte array field via typed struct initializer") {
|
|
val src = $$"""
|
|
%encoding iso
|
|
main {
|
|
struct Node {
|
|
ubyte id
|
|
ubyte[4] name
|
|
bool flag
|
|
}
|
|
|
|
sub start() {
|
|
^^Node n = ^^Node : [1, "abc", false]
|
|
n.id = 7
|
|
n.flag = true
|
|
^^Node m = $4000
|
|
m^^ = n^^
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), true, src, outputDir, errors = errors)
|
|
if (result == null) {
|
|
throw Exception("Compilation failed: ${errors.errors}")
|
|
}
|
|
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
|
VmRunner().runAndTestProgram(virtfile.toFile().readText(), true) { vm ->
|
|
vm.memory.getUB(0x4000u).toInt() shouldBe 7
|
|
vm.memory.getUB(0x4001u).toInt() shouldBe 'a'.code
|
|
vm.memory.getUB(0x4002u).toInt() shouldBe 'b'.code
|
|
vm.memory.getUB(0x4003u).toInt() shouldBe 'c'.code
|
|
vm.memory.getUB(0x4004u).toInt() shouldBe 0
|
|
vm.memory.getUB(0x4005u).toInt() shouldBe 1 // flag=true
|
|
}
|
|
}
|
|
|
|
test("string literal too long for signed byte array field errors") {
|
|
val src = """
|
|
%encoding iso
|
|
main {
|
|
sub start() {
|
|
struct Node {
|
|
byte[3] name
|
|
}
|
|
; "abcd" (4 bytes) is too long for an array of size 3
|
|
^^Node n = ["abcd"]
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), true, src, outputDir, errors = errors)
|
|
result shouldBe null
|
|
errors.errors.any { it.contains("does not fit in byte array field 'name'") } shouldBe true
|
|
}
|
|
|
|
test("array of union instances behaves correctly") {
|
|
val src = """
|
|
%option no_sysinit
|
|
main {
|
|
union U {
|
|
uword w
|
|
ubyte b
|
|
}
|
|
U[2] arr
|
|
|
|
sub start() {
|
|
arr[0].w = $1234
|
|
arr[1].w = $5678
|
|
cx16.r0 = arr[0].w
|
|
cx16.r1 = arr[1].w
|
|
}
|
|
}"""
|
|
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = true, errors = errors)
|
|
if (result == null) {
|
|
throw Exception("Compilation failed: ${errors.errors}")
|
|
}
|
|
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
|
VmRunner().runAndTestProgram(virtfile.toFile().readText(), false) { vm ->
|
|
vm.memory.getUW(0xff0000u) shouldBe 0x1234u
|
|
vm.memory.getUW(0xff0002u) shouldBe 0x5678u
|
|
}
|
|
}
|
|
})
|