prog8/compiler/test/ast/TestConst.kt

407 lines
16 KiB
Kotlin
Raw Normal View History

package prog8tests.ast
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment
import prog8.ast.statements.Return
import prog8.ast.statements.VarDecl
import prog8.ast.statements.VarDeclType
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
class TestConst: FunSpec({
test("const folding multiple scenarios +/-") {
val source = """
main {
const ubyte boardHeightC = 20
const ubyte boardOffsetC = 3
sub start() {
uword @shared load_location = 12345
word @shared llw = 12345
cx16.r0 = load_location + 8000 + 1000 + 1000
cx16.r2 = 8000 + 1000 + 1000 + load_location
cx16.r4 = load_location + boardOffsetC + boardHeightC - 1
cx16.r5s = llw - 900 - 999
cx16.r7s = llw - 900 + 999
}
}"""
val result = compileText(C64Target(), true, source, writeAssembly = false)!!
// expected:
// uword load_location
// load_location = 12345
// word llw
// llw = 12345
// cx16.r0 = load_location + 10000
// cx16.r2 = load_location + 10000
// cx16.r4 = load_location + 22
// cx16.r5s = llw - 1899
// cx16.r7s = llw + 99
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 9
val addR0value = (stmts[4] as Assignment).value
val binexpr0 = addR0value as BinaryExpression
binexpr0.operator shouldBe "+"
binexpr0.right shouldBe NumericLiteral(DataType.UWORD, 10000.0, Position.DUMMY)
val addR2value = (stmts[5] as Assignment).value
val binexpr2 = addR2value as BinaryExpression
binexpr2.operator shouldBe "+"
binexpr2.right shouldBe NumericLiteral(DataType.UWORD, 10000.0, Position.DUMMY)
val addR4value = (stmts[6] as Assignment).value
val binexpr4 = addR4value as BinaryExpression
binexpr4.operator shouldBe "+"
binexpr4.right shouldBe NumericLiteral(DataType.UWORD, 22.0, Position.DUMMY)
val subR5value = (stmts[7] as Assignment).value
val binexpr5 = subR5value as BinaryExpression
binexpr5.operator shouldBe "-"
binexpr5.right shouldBe NumericLiteral(DataType.WORD, 1899.0, Position.DUMMY)
val subR7value = (stmts[8] as Assignment).value
val binexpr7 = subR7value as BinaryExpression
binexpr7.operator shouldBe "+"
binexpr7.right shouldBe NumericLiteral(DataType.WORD, 99.0, Position.DUMMY)
}
test("const folding multiple scenarios * and / (floats)") {
val source = """
%option enable_floats
main {
sub start() {
float @shared llw = 300.0
float @shared result
result = 9 * 2 * 10 * llw
result++
result = llw * 9 * 2 * 10
result++
result = llw / 30 / 3
result++
result = llw / 2 * 10
result++
result = llw * 90 / 5
}
}"""
val result = compileText(C64Target(), true, source, writeAssembly = false)!!
// expected:
// float llw
// llw = 300.0
// float result
// result = llw * 180.0
// result++
// result = llw * 180.0
// result++
// result = llw / 90.0
// result++
// result = llw * 5.0
// result++
// result = llw * 18.0
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 12
val mulR0Value = (stmts[3] as Assignment).value
val binexpr0 = mulR0Value as BinaryExpression
binexpr0.operator shouldBe "*"
binexpr0.right shouldBe NumericLiteral(DataType.FLOAT, 180.0, Position.DUMMY)
val mulR1Value = (stmts[5] as Assignment).value
val binexpr1 = mulR1Value as BinaryExpression
binexpr1.operator shouldBe "*"
binexpr1.right shouldBe NumericLiteral(DataType.FLOAT, 180.0, Position.DUMMY)
val divR2Value = (stmts[7] as Assignment).value
val binexpr2 = divR2Value as BinaryExpression
binexpr2.operator shouldBe "/"
binexpr2.right shouldBe NumericLiteral(DataType.FLOAT, 90.0, Position.DUMMY)
val mulR3Value = (stmts[9] as Assignment).value
val binexpr3 = mulR3Value as BinaryExpression
binexpr3.operator shouldBe "*"
binexpr3.right shouldBe NumericLiteral(DataType.FLOAT, 5.0, Position.DUMMY)
binexpr3.left shouldBe instanceOf<IdentifierReference>()
val mulR4Value = (stmts[11] as Assignment).value
val binexpr4 = mulR4Value as BinaryExpression
binexpr4.operator shouldBe "*"
binexpr4.right shouldBe NumericLiteral(DataType.FLOAT, 18.0, Position.DUMMY)
binexpr4.left shouldBe instanceOf<IdentifierReference>()
}
test("const folding multiple scenarios * and / (integers)") {
val source = """
main {
sub start() {
word @shared llw = 300
cx16.r0s = 9 * 2 * 10 * llw
cx16.r1s = llw * 9 * 2 * 10
cx16.r2s = llw / 30 / 3
cx16.r3s = llw / 2 * 10
cx16.r4s = llw * 90 / 5 ; not optimized because of loss of integer division precision
}
}"""
val result = compileText(C64Target(), true, source, writeAssembly = false)!!
// expected:
// word llw
// llw = 300
// cx16.r0s = llw * 180
// cx16.r1s = llw * 180
// cx16.r2s = llw / 90
// cx16.r3s = llw /2 *10
// cx16.r4s = llw *90 /5
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 7
val mulR0Value = (stmts[2] as Assignment).value
val binexpr0 = mulR0Value as BinaryExpression
binexpr0.operator shouldBe "*"
binexpr0.right shouldBe NumericLiteral(DataType.WORD, 180.0, Position.DUMMY)
val mulR1Value = (stmts[3] as Assignment).value
val binexpr1 = mulR1Value as BinaryExpression
binexpr1.operator shouldBe "*"
binexpr1.right shouldBe NumericLiteral(DataType.WORD, 180.0, Position.DUMMY)
val divR2Value = (stmts[4] as Assignment).value
val binexpr2 = divR2Value as BinaryExpression
binexpr2.operator shouldBe "/"
binexpr2.right shouldBe NumericLiteral(DataType.WORD, 90.0, Position.DUMMY)
val mulR3Value = (stmts[5] as Assignment).value
val binexpr3 = mulR3Value as BinaryExpression
binexpr3.operator shouldBe "*"
binexpr3.right shouldBe NumericLiteral(DataType.WORD, 10.0, Position.DUMMY)
binexpr3.left shouldBe instanceOf<BinaryExpression>()
val mulR4Value = (stmts[6] as Assignment).value
val binexpr4 = mulR4Value as BinaryExpression
binexpr4.operator shouldBe "/"
binexpr4.right shouldBe NumericLiteral(DataType.WORD, 5.0, Position.DUMMY)
binexpr4.left shouldBe instanceOf<BinaryExpression>()
}
test("const folding and silently typecasted for initializervalues") {
val sourcecode = """
main {
sub start() {
const ubyte TEST = 10
byte @shared x1 = TEST as byte + 1
byte @shared x2 = 1 + TEST as byte
ubyte @shared y1 = TEST + 1 as byte
ubyte @shared y2 = 1 as byte + TEST
}
}
"""
val result = compileText(C64Target(), false, sourcecode)!!
val mainsub = result.compilerAst.entrypoint
mainsub.statements.size shouldBe 10
val declTest = mainsub.statements[0] as VarDecl
val declX1 = mainsub.statements[1] as VarDecl
val initX1 = mainsub.statements[2] as Assignment
val declX2 = mainsub.statements[3] as VarDecl
val initX2 = mainsub.statements[4] as Assignment
val declY1 = mainsub.statements[5] as VarDecl
val initY1 = mainsub.statements[6] as Assignment
val declY2 = mainsub.statements[7] as VarDecl
val initY2 = mainsub.statements[8] as Assignment
mainsub.statements[9] shouldBe instanceOf<Return>()
(declTest.value as NumericLiteral).number shouldBe 10.0
declX1.value shouldBe null
declX2.value shouldBe null
declY1.value shouldBe null
declY2.value shouldBe null
(initX1.value as NumericLiteral).type shouldBe DataType.BYTE
(initX1.value as NumericLiteral).number shouldBe 11.0
(initX2.value as NumericLiteral).type shouldBe DataType.BYTE
(initX2.value as NumericLiteral).number shouldBe 11.0
(initY1.value as NumericLiteral).type shouldBe DataType.UBYTE
(initY1.value as NumericLiteral).number shouldBe 11.0
(initY2.value as NumericLiteral).type shouldBe DataType.UBYTE
(initY2.value as NumericLiteral).number shouldBe 11.0
}
test("const pointer variable indexing works") {
val src="""
main {
sub start() {
const uword pointer=$1000
cx16.r0L = pointer[2]
pointer[2] = cx16.r0L
}
}
"""
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
test("advanced const folding of known library functions") {
val src="""
%import floats
%import math
%import string
main {
sub start() {
float fl = 1.2 ; no other assignments
bool @shared result1 = string.isdigit(math.diff(119, floats.floor(floats.deg(fl)) as ubyte))
bool @shared result2 = string.isletter(math.diff(119, floats.floor(floats.deg(1.2)) as ubyte))
}
}"""
val result = compileText(Cx16Target(), true, src, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 5
(st[0] as VarDecl).type shouldBe VarDeclType.CONST
val assignv1 = (st[2] as Assignment).value
val assignv2 = (st[4] as Assignment).value
(assignv1 as NumericLiteral).number shouldBe 1.0
(assignv2 as NumericLiteral).number shouldBe 0.0
}
test("const address-of memory mapped arrays") {
val src="""
main {
sub start() {
&uword[30] wb = ${'$'}2000
&uword[100] array1 = ${'$'}9e00
&uword[30] array2 = &array1[len(wb)]
cx16.r0 = &array1 ; ${'$'}9e00
cx16.r1 = &array1[len(wb)] ; ${'$'}9e3c
cx16.r2 = &array2 ; ${'$'}9e3c
}
}"""
val result = compileText(Cx16Target(), false, src, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 6
((st[0] as VarDecl).value as NumericLiteral).number shouldBe 0x2000
((st[1] as VarDecl).value as NumericLiteral).number shouldBe 0x9e00
((st[2] as VarDecl).value as NumericLiteral).number shouldBe 0x9e00+2*30
((st[3] as Assignment).value as NumericLiteral).number shouldBe 0x9e00
((st[4] as Assignment).value as NumericLiteral).number shouldBe 0x9e00+2*30
((st[5] as Assignment).value as NumericLiteral).number shouldBe 0x9e00+2*30
}
2024-01-16 19:41:53 +00:00
test("address of a memory mapped variable") {
val src = """
main {
sub start() {
&ubyte mappedvar = 1000
cx16.r0 = &mappedvar
&ubyte[8] array = &mappedvar
cx16.r0 = &array
const uword HIGH_MEMORY_START = 40960
&uword[20] @shared wa = HIGH_MEMORY_START
}
}"""
val result = compileText(Cx16Target(), optimize=false, src, writeAssembly=true)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 7
val arrayDeclV = (st[2] as VarDecl).value
(arrayDeclV as NumericLiteral).number shouldBe 1000.0
val waDeclV = (st[5] as VarDecl).value
(waDeclV as NumericLiteral).number shouldBe 40960.0
}
2024-01-16 19:41:53 +00:00
test("address of a const uword pointer array expression") {
val src="""
main {
sub start() {
const uword buffer = ${'$'}2000
uword @shared addr = &buffer[2]
2024-01-16 19:41:53 +00:00
const ubyte width = 100
ubyte @shared i
ubyte @shared j
uword @shared addr2 = &buffer[i * width + j]
2024-01-16 19:41:53 +00:00
}
}"""
val result = compileText(Cx16Target(), true, src, writeAssembly = true)!!
2024-01-16 19:41:53 +00:00
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 11
val assignAddr = (st[2] as Assignment).value
(assignAddr as NumericLiteral).number shouldBe 8194.0
val assignAddr2 = ((st[9] as Assignment).value as AddressOf)
assignAddr2.identifier.nameInSource shouldBe listOf("buffer")
assignAddr2.arrayIndex!!.indexExpr shouldBe instanceOf<BinaryExpression>()
2024-01-16 19:41:53 +00:00
}
test("out of range const byte and word give correct error") {
var src="""
main {
sub start() {
const byte MIN_BYTE = -129
const word MIN_WORD = -32769
const byte MAX_BYTE = 128
const word MAX_WORD = 32768
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 4
errors.errors[0] shouldContain "out of range"
errors.errors[1] shouldContain "out of range"
errors.errors[2] shouldContain "out of range"
errors.errors[3] shouldContain "out of range"
}
test("out of range var byte and word give correct error") {
var src="""
main {
sub start() {
byte @shared v_MIN_BYTE = -129
word @shared v_MIN_WORD = -32769
byte @shared v_MAX_BYTE = 128
word @shared v_MAX_WORD = 32768
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 8
errors.errors[0] shouldContain "out of range"
errors.errors[2] shouldContain "out of range"
errors.errors[4] shouldContain "out of range"
errors.errors[6] shouldContain "out of range"
}
test("out of range const byte and word no errors with explicit cast if possible") {
var src="""
main {
sub start() {
const byte MIN_BYTE = -129 as byte ; still error
const word MIN_WORD = -32769 as word ; still error
const byte MAX_BYTE = 128 as byte
const word MAX_WORD = 32768 as word
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 4
errors.errors[0] shouldContain(":4:31: const declaration needs a compile-time constant")
errors.errors[1] shouldContain(":4:32: no cast available")
errors.errors[2] shouldContain(":5:31: const declaration needs a compile-time constant")
errors.errors[3] shouldContain(":5:32: no cast available")
}
test("out of range var byte and word no errors with explicit cast if possible") {
var src="""
main {
sub start() {
byte @shared v_min_byte2 = -129 as byte ; still error
word @shared v_min_word2 = -32769 as word ; still error
byte @shared v_min_byte3 = 255 as byte
word @shared v_min_word3 = 50000 as word
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain(":4:37: no cast available")
errors.errors[1] shouldContain(":5:37: no cast available")
}
})