compilation of builtin functions to opcode

untit tests for stackvm opcodes, value and parser literalvalue
This commit is contained in:
Irmen de Jong 2018-09-24 22:34:12 +02:00
parent 3a0c1c5ada
commit cef0aae927
11 changed files with 539 additions and 91 deletions

View File

@ -23,11 +23,14 @@
_vm_gfx_clearscr(11)
_vm_gfx_text(5, 5, 7, "Calculating Mandelbrot Fractal...")
flt(44)
for pixely in yoffset to yoffset+height-1 {
yy = (pixely-yoffset)/height/3.6+0.4
; yy = (pixely-yoffset)/height/3.6+0.4 ; @todo compiler float error
yy = flt((pixely-yoffset))/height/3.6+0.4
for pixelx in xoffset to xoffset+width-1 {
xx = (pixelx-xoffset)/width/3+0.2
; xx = (pixelx-xoffset)/width/3+0.2 ; @todo compiler float error
xx = flt((pixelx-xoffset))/width/3+0.2
x = 0.0
y = 0.0

View File

@ -1,9 +1,26 @@
%option enable_floats
~ main {
sub start() -> () {
byte i=2
float f
word ww = $55aa
; P_carry(1) @todo function -> assignment
; P_irqd(1) @todo function -> assignment
f=flt(i)
i = msb(ww)
i = lsb(ww)
lsl(i)
lsr(i)
rol(i)
ror(i)
rol2(i)
ror2(i)
while i<10 {
_vm_write_num(i)

View File

@ -1137,14 +1137,8 @@ class FunctionCall(override var target: IdentifierReference,
"all" -> builtinAll(arglist, position, namespace)
"floor" -> builtinFloor(arglist, position, namespace)
"ceil" -> builtinCeil(arglist, position, namespace)
"lsl" -> builtinLsl(arglist, position, namespace)
"lsr" -> builtinLsr(arglist, position, namespace)
"rol" -> throw ExpressionError("builtin function rol can't be used in expressions because it doesn't return a value", position)
"rol2" -> throw ExpressionError("builtin function rol2 can't be used in expressions because it doesn't return a value", position)
"ror" -> throw ExpressionError("builtin function ror can't be used in expressions because it doesn't return a value", position)
"ror2" -> throw ExpressionError("builtin function ror2 can't be used in expressions because it doesn't return a value", position)
"P_carry" -> throw ExpressionError("builtin function P_carry can't be used in expressions because it doesn't return a value", position)
"P_irqd" -> throw ExpressionError("builtin function P_irqd can't be used in expressions because it doesn't return a value", position)
"lsl", "lsr", "rol", "rol2", "ror", "ror2", "P_carry", "P_irqd" ->
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used in expressions because it doesn't return a value", position)
else -> null
}
if(withDatatypeCheck) {

View File

@ -303,9 +303,9 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
expr.arglist.forEach { translate(it) }
val target = expr.target.targetStatement(namespace)
if(target is BuiltinFunctionStatementPlaceholder) {
// call to a builtin function
val funcname = expr.target.nameInSource[0].toUpperCase()
createFunctionCall(funcname) // call builtin function
// call to a builtin function (some will just be an opcode!)
val funcname = expr.target.nameInSource[0]
translateFunctionCall(funcname, expr.arglist)
} else {
when(target) {
is Subroutine -> {
@ -344,6 +344,31 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
}
}
private fun translateFunctionCall(funcname: String, args: List<IExpression>) {
// some functions are implemented as vm opcodes
when (funcname) {
"flt" -> {
// 1 argument, type determines the exact opcode to use
val arg = args.single()
when (arg.resultingDatatype(namespace)) {
DataType.BYTE -> stackvmProg.instr(Opcode.B2FLOAT)
DataType.WORD -> stackvmProg.instr(Opcode.W2FLOAT)
DataType.FLOAT -> stackvmProg.instr(Opcode.NOP)
else -> throw CompilerException("wrong datatype for flt()")
}
}
"msb" -> stackvmProg.instr(Opcode.MSB)
"lsb" -> stackvmProg.instr(Opcode.LSB)
"lsl" -> stackvmProg.instr(Opcode.SHL)
"lsr" -> stackvmProg.instr(Opcode.SHR)
"rol" -> stackvmProg.instr(Opcode.ROL)
"ror" -> stackvmProg.instr(Opcode.ROR)
"rol2" -> stackvmProg.instr(Opcode.ROL2)
"ror2" -> stackvmProg.instr(Opcode.ROR2)
else -> createSyscall(funcname) // call builtin function
}
}
private fun translateBinaryOperator(operator: String) {
val opcode = when(operator) {
"+" -> Opcode.ADD
@ -375,8 +400,8 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
val targetStmt = stmt.target.targetStatement(namespace)!!
if(targetStmt is BuiltinFunctionStatementPlaceholder) {
stmt.arglist.forEach { translate(it) }
val funcname = stmt.target.nameInSource[0].toUpperCase()
createFunctionCall(funcname) // call builtin function
val funcname = stmt.target.nameInSource[0]
translateFunctionCall(funcname, stmt.arglist)
return
}
@ -389,9 +414,9 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
stackvmProg.instr(Opcode.CALL, callLabel = targetname)
}
private fun createFunctionCall(funcname: String) {
private fun createSyscall(funcname: String) {
val function = (
if (funcname.startsWith("_VM_"))
if (funcname.startsWith("_vm_"))
funcname.substring(4)
else
"FUNC_$funcname"
@ -724,7 +749,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
translate(ifstmt)
} else {
// Step is a variable. We can't optimize anything...
TODO("code for non-constant step comparison of LV")
TODO("for loop with non-constant step comparison of LV")
}
translate(body)
@ -760,7 +785,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
val postIncr = PostIncrDecr(makeAssignmentTarget(), "--", range.position)
postIncr.linkParents(range.parent)
translate(postIncr)
TODO("signed numbers and/or special condition still needed for decreasing for loop. Try increasing loop and/or constant loop values instead? At: ${range.position}")
TODO("signed numbers and/or special condition are needed for decreasing for loop. Try an increasing loop and/or constant loop values instead? At: ${range.position}")
}
else -> {
TODO("non-literal-const or other-than-one step increment code At: ${range.position}")

View File

@ -14,7 +14,8 @@ val BuiltinFunctionNames = setOf(
)
val BuiltinFunctionsWithoutSideEffects = BuiltinFunctionNames - setOf("P_carry", "P_irqd",
val BuiltinFunctionsWithoutSideEffects = BuiltinFunctionNames - setOf(
"P_carry", "P_irqd", "lsl", "lsr", "rol", "ror", "rol2", "ror2",
"_vm_write_memchr", "_vm_write_memstr", "_vm_write_num", "_vm_write_char",
"_vm_write_str", "_vm_gfx_clearscr", "_vm_gfx_pixel", "_vm_gfx_text")
@ -232,12 +233,6 @@ fun builtinLsb(args: List<IExpression>, position: Position, namespace:INameScope
fun builtinMsb(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 8 and 255}
fun builtinLsl(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x shl 1 }
fun builtinLsr(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 1 }
fun builtinMin(args: List<IExpression>, position: Position, namespace:INameScope): LiteralValue
= collectionArgOutputNumber(args, position, namespace) { it.min()!! }

View File

@ -161,7 +161,7 @@ enum class Syscall(val callNr: Short) {
FUNC_RNDF(91), // push a random float on the stack (between 0.0 and 1.0)
// note: not all builtin functions of the Prog8 language are present as functions:
// some of them are already opcodes (such as MSB and ROL and FLT)!
// some of them are already opcodes (such as MSB, LSB, LSL, LSR, ROL, ROR, ROL2, ROR2, and FLT)!
}
class Memory {
@ -301,61 +301,130 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=
}
operator fun compareTo(other: Value): Int {
if(stringvalue!=null && other.stringvalue!=null)
return stringvalue.compareTo(other.stringvalue)
return numericValue().toDouble().compareTo(other.numericValue().toDouble())
return when(type) {
DataType.BYTE, DataType.WORD, DataType.FLOAT -> {
when(other.type) {
DataType.BYTE, DataType.WORD, DataType.FLOAT -> {
numericValue().toDouble().compareTo(other.numericValue().toDouble())
}
else -> throw VmExecutionException("comparison can only be done between two numeric values")
}
}
else -> throw VmExecutionException("comparison can only be done between two numeric values")
}
}
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): Value {
if(result.toDouble() < 0 ) {
return when(leftDt) {
DataType.BYTE -> {
// BYTE can become WORD if right operand is WORD, or when value is too large for byte
when(rightDt) {
DataType.BYTE -> Value(DataType.BYTE, result.toInt() and 255)
DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535)
DataType.FLOAT -> throw VmExecutionException("floating point loss of precision")
else -> throw VmExecutionException("$op on non-numeric result type")
}
}
DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("$op on non-numeric type")
}
}
return when(leftDt) {
DataType.BYTE -> {
// BYTE can become WORD if right operand is WORD, or when value is too large for byte
if(result.toDouble() >= 256)
return Value(DataType.WORD, result)
when(rightDt) {
DataType.BYTE -> Value(DataType.BYTE, result)
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> throw VmExecutionException("floating point loss of precision")
else -> throw VmExecutionException("$op on non-numeric result type")
}
}
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("$op on non-numeric type")
}
}
fun add(other: Value): Value {
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() + v2.toDouble()
return Value(type, result)
return arithResult(type, result, other.type, "add")
}
fun sub(other: Value): Value {
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() - v2.toDouble()
return Value(type, result)
return arithResult(type, result, other.type, "sub")
}
fun mul(other: Value): Value {
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() * v2.toDouble()
return Value(type, result)
return arithResult(type, result, other.type, "mul")
}
fun div(other: Value): Value {
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
if(v2.toDouble()==0.0) {
if (type == DataType.BYTE)
return Value(DataType.BYTE, 255)
else if(type == DataType.WORD)
return Value(DataType.WORD, 65535)
}
val result = v1.toDouble() / v2.toDouble()
return Value(DataType.FLOAT, result)
// NOTE: integer division returns integer result!
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, result)
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("div on non-numeric type")
}
}
fun floordiv(other: Value): Value {
if(other.type == DataType.FLOAT && (type!=DataType.FLOAT))
throw VmExecutionException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = (v1.toDouble() / v2.toDouble()).toInt()
return if(this.type==DataType.BYTE)
Value(DataType.BYTE, (result and 255).toShort())
else
Value(DataType.WORD, result and 65535)
val result = floor(v1.toDouble() / v2.toDouble())
// NOTE: integer division returns integer result!
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, result)
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw VmExecutionException("div on non-numeric type")
}
}
fun remainder(other: Value): Value? {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() % v2.toDouble()
return Value(type, result)
return arithResult(type, result, other.type, "remainder")
}
fun pow(other: Value): Value {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble().pow(v2.toDouble())
return Value(type, result) // @todo datatype of pow is now always float, maybe allow byte/word results as well
return arithResult(type, result, other.type,"pow")
}
fun shl(): Value {

View File

@ -344,8 +344,8 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 40)
)
val expected = listOf(
Value(DataType.FLOAT, 3999.0/40.0),
Value(DataType.FLOAT, 42.25/(3999.0/40.0)))
Value(DataType.WORD, 99),
Value(DataType.FLOAT, 42.25/99))
val operator = Opcode.DIV
testBinaryOperator(values, operator, expected)
@ -354,13 +354,13 @@ class TestStackVmOpcodes {
@Test
fun testFloorDiv() {
val values = listOf(
Value(DataType.FLOAT, 42.25),
Value(DataType.FLOAT, 4000.25),
Value(DataType.WORD, 3999),
Value(DataType.BYTE, 40)
)
val expected = listOf(
Value(DataType.WORD, floor(3999.0/40.0)),
Value(DataType.WORD, floor(42.25/floor(3999.0/40.0))))
Value(DataType.WORD, 99),
Value(DataType.FLOAT, 40.0))
val operator = Opcode.FLOORDIV
testBinaryOperator(values, operator, expected)
@ -833,10 +833,6 @@ class TestStackVmOpcodes {
@Test
fun testLess() {
val values = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello"), // 0
Value(DataType.STR, null, stringvalue = "abc"),
Value(DataType.STR, null, stringvalue = "abd"), // 1
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1), // 1
Value(DataType.BYTE, 1),
@ -852,17 +848,21 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 21),
Value(DataType.FLOAT, 21.0001) // 1
)
val expected = listOf(0, 1, 1, 0, 1, 1, 0, 0, 1)
val expected = listOf(1, 0, 1, 1, 0, 0, 1)
testComparisonOperator(values, expected, Opcode.LESS)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESS) // can't compare strings
}
}
@Test
fun testLessEq() {
val values = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello"), // 1
Value(DataType.STR, null, stringvalue = "abc"),
Value(DataType.STR, null, stringvalue = "abd"), // 1
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1), // 1
Value(DataType.BYTE, 1),
@ -878,17 +878,21 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 22),
Value(DataType.FLOAT, 21.999) // 0
)
val expected = listOf(1,1,1,1,0,1,1,1,0)
val expected = listOf(1,1,0,1,1,1,0)
testComparisonOperator(values, expected, Opcode.LESSEQ)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESSEQ) // can't compare strings
}
}
@Test
fun testGreater() {
val values = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello"), // 0
Value(DataType.STR, null, stringvalue = "abd"),
Value(DataType.STR, null, stringvalue = "abc"), // 1
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1), // 0
Value(DataType.BYTE, 1),
@ -904,17 +908,21 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 21),
Value(DataType.FLOAT, 20.9999) // 1
)
val expected = listOf(0, 1, 0, 0, 1, 0, 1, 0, 1)
val expected = listOf(0, 0, 1, 0, 1, 0, 1)
testComparisonOperator(values, expected, Opcode.GREATER)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATER) // can't compare strings
}
}
@Test
fun testGreaterEq() {
val values = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello"), // 1
Value(DataType.STR, null, stringvalue = "abd"),
Value(DataType.STR, null, stringvalue = "abc"), // 1
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1), // 0
Value(DataType.BYTE, 1),
@ -930,17 +938,21 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 22),
Value(DataType.FLOAT, 21.999) // 1
)
val expected = listOf(1,1,0,1,1,0,0,1,1)
val expected = listOf(0,1,1,0,0,1,1)
testComparisonOperator(values, expected, Opcode.GREATEREQ)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATEREQ) // can't compare strings
}
}
@Test
fun testEqual() {
val values = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello"), // 1
Value(DataType.STR, null, stringvalue = "abd"),
Value(DataType.STR, null, stringvalue = "abc"), // 0
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1), // 0
Value(DataType.BYTE, 1),
@ -956,17 +968,21 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 22),
Value(DataType.FLOAT, 21.999) // 0
)
val expected = listOf(1,0,0,1,0,0,1,1,0)
val expected = listOf(0,1,0,0,1,1,0)
testComparisonOperator(values, expected, Opcode.EQUAL)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.EQUAL) // can't compare strings
}
}
@Test
fun testNotEqual() {
val values = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello"), // 0
Value(DataType.STR, null, stringvalue = "abd"),
Value(DataType.STR, null, stringvalue = "abc"), // 1
Value(DataType.BYTE, 0),
Value(DataType.BYTE, 1), // 1
Value(DataType.BYTE, 1),
@ -982,8 +998,16 @@ class TestStackVmOpcodes {
Value(DataType.BYTE, 22),
Value(DataType.FLOAT, 21.999) // 1
)
val expected = listOf(0,1,1,0,1,1,0,0,1)
val expected = listOf(1,0,1,1,0,0,1)
testComparisonOperator(values, expected, Opcode.NOTEQUAL)
val valuesInvalid = listOf(
Value(DataType.STR, null, stringvalue = "hello"),
Value(DataType.STR, null, stringvalue = "hello")
)
assertFailsWith<VmExecutionException> {
testComparisonOperator(valuesInvalid, listOf(0), Opcode.NOTEQUAL) // can't compare strings
}
}
@Test

View File

@ -0,0 +1,294 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.*
import prog8.stackvm.*
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestStackVmValue {
@Test
fun testIdentity() {
val v = Value(DataType.WORD, 12345)
assertEquals(v, v)
assertFalse(v != v)
assertTrue(v<=v)
assertTrue(v>=v)
assertFalse(v<v)
assertFalse(v>v)
assertEquals(Value(DataType.BYTE, 100), Value(DataType.BYTE, 100))
}
@Test
fun testEqualsAndNotEquals() {
assertEquals(Value(DataType.BYTE, 100), Value(DataType.BYTE, 100))
assertEquals(Value(DataType.BYTE, 100), Value(DataType.WORD, 100))
assertEquals(Value(DataType.BYTE, 100), Value(DataType.FLOAT, 100))
assertEquals(Value(DataType.WORD, 254), Value(DataType.BYTE, 254))
assertEquals(Value(DataType.WORD, 12345), Value(DataType.WORD, 12345))
assertEquals(Value(DataType.WORD, 12345), Value(DataType.FLOAT, 12345))
assertEquals(Value(DataType.FLOAT, 100.0), Value(DataType.BYTE, 100))
assertEquals(Value(DataType.FLOAT, 22239.0), Value(DataType.WORD, 22239))
assertEquals(Value(DataType.FLOAT, 9.99), Value(DataType.FLOAT, 9.99))
assertNotEquals(Value(DataType.BYTE, 100), Value(DataType.BYTE, 101))
assertNotEquals(Value(DataType.BYTE, 100), Value(DataType.WORD, 101))
assertNotEquals(Value(DataType.BYTE, 100), Value(DataType.FLOAT, 101))
assertNotEquals(Value(DataType.WORD, 245), Value(DataType.BYTE, 246))
assertNotEquals(Value(DataType.WORD, 12345), Value(DataType.WORD, 12346))
assertNotEquals(Value(DataType.WORD, 12345), Value(DataType.FLOAT, 12346))
assertNotEquals(Value(DataType.FLOAT, 9.99), Value(DataType.BYTE, 9))
assertNotEquals(Value(DataType.FLOAT, 9.99), Value(DataType.WORD, 9))
assertNotEquals(Value(DataType.FLOAT, 9.99), Value(DataType.FLOAT, 9.0))
assertFailsWith<VmExecutionException> {
assertEquals(Value(DataType.STR, null, "hello"), Value(DataType.STR, null, "hello"))
}
assertFailsWith<VmExecutionException> {
assertEquals(Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)))
}
}
@Test
fun testGreaterThan(){
assertTrue(Value(DataType.BYTE, 100) > Value(DataType.BYTE, 99))
assertTrue(Value(DataType.WORD, 254) > Value(DataType.WORD, 253))
assertTrue(Value(DataType.FLOAT, 100.0) > Value(DataType.FLOAT, 99.9))
assertTrue(Value(DataType.BYTE, 100) >= Value(DataType.BYTE, 100))
assertTrue(Value(DataType.WORD, 254) >= Value(DataType.WORD, 254))
assertTrue(Value(DataType.FLOAT, 100.0) >= Value(DataType.FLOAT, 100.0))
assertFalse(Value(DataType.BYTE, 100) > Value(DataType.BYTE, 100))
assertFalse(Value(DataType.WORD, 254) > Value(DataType.WORD, 254))
assertFalse(Value(DataType.FLOAT, 100.0) > Value(DataType.FLOAT, 100.0))
assertFalse(Value(DataType.BYTE, 100) >= Value(DataType.BYTE, 101))
assertFalse(Value(DataType.WORD, 254) >= Value(DataType.WORD, 255))
assertFalse(Value(DataType.FLOAT, 100.0) >= Value(DataType.FLOAT, 100.1))
}
@Test
fun testLessThan() {
assertTrue(Value(DataType.BYTE, 100) < Value(DataType.BYTE, 101))
assertTrue(Value(DataType.WORD, 254) < Value(DataType.WORD, 255))
assertTrue(Value(DataType.FLOAT, 100.0) < Value(DataType.FLOAT, 100.1))
assertTrue(Value(DataType.BYTE, 100) <= Value(DataType.BYTE, 100))
assertTrue(Value(DataType.WORD, 254) <= Value(DataType.WORD, 254))
assertTrue(Value(DataType.FLOAT, 100.0) <= Value(DataType.FLOAT, 100.0))
assertFalse(Value(DataType.BYTE, 100) < Value(DataType.BYTE, 100))
assertFalse(Value(DataType.WORD, 254) < Value(DataType.WORD, 254))
assertFalse(Value(DataType.FLOAT, 100.0) < Value(DataType.FLOAT, 100.0))
assertFalse(Value(DataType.BYTE, 100) <= Value(DataType.BYTE, 99))
assertFalse(Value(DataType.WORD, 254) <= Value(DataType.WORD, 253))
assertFalse(Value(DataType.FLOAT, 100.0) <= Value(DataType.FLOAT, 99.9))
}
@Test
fun testArithmeticByteFirstOperand() {
var r = Value(DataType.BYTE, 100).add(Value(DataType.BYTE, 120))
assertEquals(DataType.BYTE, r.type)
assertEquals(220, r.integerValue())
r = Value(DataType.BYTE, 100).add(Value(DataType.BYTE, 199))
assertEquals(DataType.WORD, r.type)
assertEquals(299, r.integerValue())
r = Value(DataType.BYTE, 100).sub(Value(DataType.BYTE, 88))
assertEquals(DataType.BYTE, r.type)
assertEquals(12, r.integerValue())
r = Value(DataType.BYTE, 100).sub(Value(DataType.BYTE, 188))
assertEquals(DataType.BYTE, r.type)
assertEquals(168, r.integerValue())
r = Value(DataType.BYTE, 5).mul(Value(DataType.BYTE, 33))
assertEquals(DataType.BYTE, r.type)
assertEquals(165, r.integerValue())
r = Value(DataType.BYTE, 22).mul(Value(DataType.BYTE, 33))
assertEquals(DataType.WORD, r.type)
assertEquals(726, r.integerValue())
r = Value(DataType.BYTE, 233).div(Value(DataType.BYTE, 12))
assertEquals(DataType.BYTE, r.type)
assertEquals(19, r.integerValue())
r = Value(DataType.BYTE, 233).div(Value(DataType.BYTE, 0))
assertEquals(DataType.BYTE, r.type)
assertEquals(255, r.integerValue())
r = Value(DataType.BYTE, 233).floordiv(Value(DataType.BYTE, 19))
assertEquals(DataType.BYTE, r.type)
assertEquals(12, r.integerValue())
r = Value(DataType.BYTE, 100).add(Value(DataType.WORD, 120))
assertEquals(DataType.WORD, r.type)
assertEquals(220, r.integerValue())
r = Value(DataType.BYTE, 100).add(Value(DataType.WORD, 199))
assertEquals(DataType.WORD, r.type)
assertEquals(299, r.integerValue())
r = Value(DataType.BYTE, 100).sub(Value(DataType.WORD, 88))
assertEquals(DataType.WORD, r.type)
assertEquals(12, r.integerValue())
r = Value(DataType.BYTE, 100).sub(Value(DataType.WORD, 188))
assertEquals(DataType.WORD, r.type)
assertEquals(65448, r.integerValue())
r = Value(DataType.BYTE, 5).mul(Value(DataType.WORD, 33))
assertEquals(DataType.WORD, r.type)
assertEquals(165, r.integerValue())
r = Value(DataType.BYTE, 22).mul(Value(DataType.WORD, 33))
assertEquals(DataType.WORD, r.type)
assertEquals(726, r.integerValue())
r = Value(DataType.BYTE, 233).div(Value(DataType.WORD, 12))
assertEquals(DataType.BYTE, r.type)
assertEquals(19, r.integerValue())
r = Value(DataType.BYTE, 233).div(Value(DataType.WORD, 0))
assertEquals(DataType.BYTE, r.type)
assertEquals(255, r.integerValue())
r = Value(DataType.BYTE, 233).floordiv(Value(DataType.WORD, 19))
assertEquals(DataType.BYTE, r.type)
assertEquals(12, r.integerValue())
}
@Test
fun testArithmeticWordFirstOperand() {
var r = Value(DataType.WORD, 100).add(Value(DataType.BYTE, 120))
assertEquals(DataType.WORD, r.type)
assertEquals(220, r.integerValue())
r = Value(DataType.WORD, 100).div(Value(DataType.BYTE, 10))
assertEquals(DataType.WORD, r.type)
assertEquals(10, r.integerValue())
r = Value(DataType.WORD, 100).div(Value(DataType.BYTE, 0))
assertEquals(DataType.WORD, r.type)
assertEquals(65535, r.integerValue())
r = Value(DataType.WORD, 100).div(Value(DataType.WORD, 0))
assertEquals(DataType.WORD, r.type)
assertEquals(65535, r.integerValue())
r = Value(DataType.WORD, 33445).floordiv(Value(DataType.WORD, 123))
assertEquals(DataType.WORD, r.type)
assertEquals(271, r.integerValue())
r = Value(DataType.WORD, 33445).floordiv(Value(DataType.WORD, 999))
assertEquals(DataType.WORD, r.type)
assertEquals(33, r.integerValue())
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestParserLiteralValue {
private val dummyPos = Position("test", 0,0,0)
@Test
fun testIdentity() {
val v = LiteralValue(DataType.WORD, wordvalue = 12345, position = dummyPos)
assertEquals(v, v)
assertFalse(v != v)
assertTrue(v <= v)
assertTrue(v >= v)
assertFalse(v < v)
assertFalse(v > v)
assertEquals(LiteralValue(DataType.WORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.WORD, wordvalue = 12345, position = dummyPos))
}
@Test
fun testEqualsAndNotEquals() {
assertEquals(LiteralValue(DataType.BYTE, 100, position=dummyPos), LiteralValue(DataType.BYTE, 100, position=dummyPos))
assertEquals(LiteralValue(DataType.BYTE, 100, position=dummyPos), LiteralValue(DataType.WORD, wordvalue=100, position=dummyPos))
assertEquals(LiteralValue(DataType.BYTE, 100, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos))
assertEquals(LiteralValue(DataType.WORD, wordvalue=254, position=dummyPos), LiteralValue(DataType.BYTE, 254, position=dummyPos))
assertEquals(LiteralValue(DataType.WORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.WORD, wordvalue=12345, position=dummyPos))
assertEquals(LiteralValue(DataType.WORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=12345.0, position=dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos), LiteralValue(DataType.BYTE, 100, position=dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue=22239.0, position=dummyPos), LiteralValue(DataType.WORD,wordvalue=22239, position=dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos))
assertNotEquals(LiteralValue(DataType.BYTE, 100, position=dummyPos), LiteralValue(DataType.BYTE, 101, position=dummyPos))
assertNotEquals(LiteralValue(DataType.BYTE, 100, position=dummyPos), LiteralValue(DataType.WORD, wordvalue=101, position=dummyPos))
assertNotEquals(LiteralValue(DataType.BYTE, 100, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=101.0, position=dummyPos))
assertNotEquals(LiteralValue(DataType.WORD, wordvalue=245, position=dummyPos), LiteralValue(DataType.BYTE, 246, position=dummyPos))
assertNotEquals(LiteralValue(DataType.WORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.WORD, wordvalue=12346, position=dummyPos))
assertNotEquals(LiteralValue(DataType.WORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=12346.0, position=dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.BYTE, 9, position=dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.WORD, wordvalue=9, position=dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=9.0, position=dummyPos))
assertEquals(LiteralValue(DataType.STR, strvalue = "hello", position=dummyPos), LiteralValue(DataType.STR, strvalue="hello", position=dummyPos))
assertNotEquals(LiteralValue(DataType.STR, strvalue = "hello", position=dummyPos), LiteralValue(DataType.STR, strvalue="bye", position=dummyPos))
val lvOne = LiteralValue(DataType.BYTE, 1, position=dummyPos)
val lvTwo = LiteralValue(DataType.BYTE, 2, position=dummyPos)
val lvThree = LiteralValue(DataType.BYTE, 3, position=dummyPos)
val lvOneR = LiteralValue(DataType.BYTE, 1, position=dummyPos)
val lvTwoR = LiteralValue(DataType.BYTE, 2, position=dummyPos)
val lvThreeR = LiteralValue(DataType.BYTE, 3, position=dummyPos)
val lv1 = LiteralValue(DataType.ARRAY, arrayvalue = arrayOf(lvOne, lvTwo, lvThree), position=dummyPos)
val lv2 = LiteralValue(DataType.ARRAY, arrayvalue = arrayOf(lvOneR, lvTwoR, lvThreeR), position=dummyPos)
assertFailsWith<ExpressionError> {
assertEquals(lv1, lv2)
}
}
@Test
fun testGreaterThan(){
assertTrue(LiteralValue(DataType.BYTE, 100, position=dummyPos) > LiteralValue(DataType.BYTE, 99, position=dummyPos))
assertTrue(LiteralValue(DataType.WORD, wordvalue=254, position=dummyPos) > LiteralValue(DataType.WORD, wordvalue=253, position=dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) > LiteralValue(DataType.FLOAT, floatvalue=99.9, position=dummyPos))
assertTrue(LiteralValue(DataType.BYTE, 100, position=dummyPos) >= LiteralValue(DataType.BYTE, 100, position=dummyPos))
assertTrue(LiteralValue(DataType.WORD, wordvalue=254, position=dummyPos) >= LiteralValue(DataType.WORD,wordvalue= 254, position=dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos))
assertFalse(LiteralValue(DataType.BYTE, 100, position=dummyPos) > LiteralValue(DataType.BYTE, 100, position=dummyPos))
assertFalse(LiteralValue(DataType.WORD, wordvalue=254, position=dummyPos) > LiteralValue(DataType.WORD, wordvalue=254, position=dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) > LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos))
assertFalse(LiteralValue(DataType.BYTE, 100, position=dummyPos) >= LiteralValue(DataType.BYTE, 101, position=dummyPos))
assertFalse(LiteralValue(DataType.WORD, wordvalue=254, position=dummyPos) >= LiteralValue(DataType.WORD,wordvalue= 255, position=dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue=100.1, position=dummyPos))
}
@Test
fun testLessThan() {
assertTrue(LiteralValue(DataType.BYTE, 100, position=dummyPos) < LiteralValue(DataType.BYTE, 101, position=dummyPos))
assertTrue(LiteralValue(DataType.WORD, wordvalue=254, position=dummyPos) < LiteralValue(DataType.WORD, wordvalue=255, position=dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) < LiteralValue(DataType.FLOAT, floatvalue=100.1, position=dummyPos))
assertTrue(LiteralValue(DataType.BYTE, 100, position=dummyPos) <= LiteralValue(DataType.BYTE, 100, position=dummyPos))
assertTrue(LiteralValue(DataType.WORD, wordvalue=254, position=dummyPos) <= LiteralValue(DataType.WORD,wordvalue= 254, position=dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos))
assertFalse(LiteralValue(DataType.BYTE, 100, position=dummyPos) < LiteralValue(DataType.BYTE, 100, position=dummyPos))
assertFalse(LiteralValue(DataType.WORD, wordvalue=254, position=dummyPos) < LiteralValue(DataType.WORD, wordvalue=254, position=dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) < LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos))
assertFalse(LiteralValue(DataType.BYTE, 100, position=dummyPos) <= LiteralValue(DataType.BYTE, 99, position=dummyPos))
assertFalse(LiteralValue(DataType.WORD,wordvalue= 254, position=dummyPos) <= LiteralValue(DataType.WORD,wordvalue= 253, position=dummyPos))
assertFalse(LiteralValue(DataType.FLOAT,floatvalue= 100.0, position=dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue=99.9, position=dummyPos))
}
}

View File

@ -264,10 +264,11 @@ The largest 5-byte MFLPT float that can be stored is: **1.7014118345e+38** (ne
Initial values across multiple runs of the program
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The initial values of your variables will be restored automatically when the program is (re)started,
*except for string variables, arrays and matrices*. It is assumed these are left unchanged by the program.
If you do modify them in-place, you should take care yourself that they work as
expected when the program is restarted.
.. todo::
The initial values of your variables will be restored automatically when the program is (re)started,
*except for string variables, arrays and matrices*. It is assumed these are left unchanged by the program.
If you do modify them in-place, you should take care yourself that they work as
expected when the program is restarted.
@ -363,26 +364,45 @@ Assignment statements assign a single value to a target variable or memory locat
Augmented assignments (such as ``A += X``) are also available, but these are just shorthands
for normal assignments (``A = A + X``).
.. attention::
**Data type conversion (in assignments):**
When assigning a value with a 'smaller' datatype to a register or variable with a 'larger' datatype,
the value will be automatically converted to the target datatype: byte --> word --> float.
So assigning a byte to a word variable, or a word to a floating point variable, is fine.
The reverse is *not* true: it is *not* possible to assign a value of a 'larger' datatype to
a variable of a smaller datatype without an explicit conversion. Otherwise you'll get an error telling you
that there is a loss of precision. You can use builtin functions such as ``round`` and ``lsb`` to convert
to a smaller datatype.
Expressions
-----------
In most places where a number or other value is expected, you can use just the number, or a constant expression.
The expression is parsed and evaluated by the compiler itself at compile time, and the (constant) resulting value is used in its place.
If possible, the expression is parsed and evaluated by the compiler itself at compile time, and the (constant) resulting value is used in its place.
Expressions that cannot be compile-time evaluated will result in code that calculates them at runtime.
Expressions can contain procedure and function calls.
There are various built-in functions such as sin(), cos(), min(), max() that can be used in expressions (see :ref:`builtinfunctions`).
You can also reference idendifiers defined elsewhere in your code.
The compiler will evaluate the expression if it is a constant, and just use the resulting value from then on.
Expressions that cannot be compile-time evaluated will result in code that calculates them at runtime.
.. attention::
**Data type conversion (during calculations):**
BYTE values used in arithmetic expressions (calculations) will be automatically converted into WORD values
if the calculation needs that to store the resulting value. Once a WORD value is used, all other results will be WORDs as well
(there's no automatic conversion of WORD into BYTE).
*There is never an automatic conversion into floating point values, and the compiler will NOT issue a warning for this.*
If you require float precision, you'll have to first convert into a floating point explicitly using the ``flt`` builtin function.
For example, this means that if you divide two integer values (say: ``32500 / 99``) the result will be the integer floor
division (328) rather than the floating point result (328.2828282828283). If you need the full precision,
you'll have to write ``flt(32500) / 99`` (or if they're constants, simply ``32500.0 / 99``), to make sure the
first operand is a floating point value.
Arithmetic and Logical expressions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Arithmetic expressions are expressions that calculate a numeric result (integer or floating point).
Many common arithmetic operators can be used and follow the regular precedence rules.
Logical expressions are expressions that calculate a boolean result, true or false
(which in Prog8 will effectively be a 1 or 0 integer value).
Logical expressions are expressions that calculate a boolean result: true or false
(which in reality are just a 1 or 0 integer value).
You can use parentheses to group parts of an expresion to change the precedence.
Usually the normal precedence rules apply (``*`` goes before ``+`` etc.) but subexpressions
@ -516,7 +536,7 @@ msb(x)
flt(x)
Explicitly convert the number x to a floating point number.
Usually this is done automatically but sometimes it may be required to force this.
This is required if you want calculations to have floating point precision when the values aren't float already.
any(x)
1 ('true') if any of the values in the non-scalar (array or matrix) value x is 'true' (not zero), else 0 ('false')
@ -536,12 +556,12 @@ rndf()
lsl(x)
Shift the bits in x (byte or word) one position to the left.
Bit 0 is set to 0 (and the highest bit is shifted into the status register's Carry flag)
Modifies in-place but also returns the new value.
Modifies in-place, doesn't return a value (so can't be used in an expression).
lsr(x)
Shift the bits in x (byte or word) one position to the right.
The highest bit is set to 0 (and bit 0 is shifted into the status register's Carry flag)
Modifies in-place but also returns the new value.
Modifies in-place, doesn't return a value (so can't be used in an expression).
rol(x)
Rotate the bits in x (byte or word) one position to the left.
@ -574,3 +594,6 @@ P_carry(bit)
P_irqd(bit)
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into ``SEI`` or ``CLI`` cpu instruction)
@todo remove P_carry and P_irqd as functions and turn them into assignments instead (allowing only 0 or 1 as value)

View File

@ -319,8 +319,9 @@ If used in the place of a literal value, it expands into the actual array of val
Operators
---------
address-of: ``#``
Takes the address of the symbol following it: ``word address = #somevar``
.. todo::
address-of: ``#``
Takes the address of the symbol following it: ``word address = #somevar``
arithmetic: ``+`` ``-`` ``*`` ``/`` ``//`` ``**`` ``%``

View File

@ -123,6 +123,9 @@ The following 6502 CPU hardware registers are directly usable in program code (a
- ``AX``, ``AY``, ``XY`` surrogate 16-bit registers: LSB-order (lo/hi) combined register pairs
- the status register (P) carry flag and interrupt disable flag can be written via the ``P_carry`` and ``P_irqd`` builtin functions.
@todo remove P_carry and P_irqd as functions and turn them into assignments instead (allowing only 0 or 1 as value)
Subroutine Calling Conventions
------------------------------