diff --git a/compiler/examples/mandelbrot.p8 b/compiler/examples/mandelbrot.p8 index b8f585c1c..9cf254fff 100644 --- a/compiler/examples/mandelbrot.p8 +++ b/compiler/examples/mandelbrot.p8 @@ -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 diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index 30933a99a..7bfd78205 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -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) diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index 5eb018c56..6d6ccf6aa 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -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) { diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index b79134bc0..ba6e4e067 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -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) { + // 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}") diff --git a/compiler/src/prog8/functions/BuiltinFunctions.kt b/compiler/src/prog8/functions/BuiltinFunctions.kt index 554e27d1d..35ce9bf5c 100644 --- a/compiler/src/prog8/functions/BuiltinFunctions.kt +++ b/compiler/src/prog8/functions/BuiltinFunctions.kt @@ -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, position: Position, namespace:INameScope fun builtinMsb(args: List, position: Position, namespace:INameScope): LiteralValue = oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 8 and 255} -fun builtinLsl(args: List, position: Position, namespace:INameScope): LiteralValue - = oneIntArgOutputInt(args, position, namespace) { x: Int -> x shl 1 } - -fun builtinLsr(args: List, position: Position, namespace:INameScope): LiteralValue - = oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 1 } - fun builtinMin(args: List, position: Position, namespace:INameScope): LiteralValue = collectionArgOutputNumber(args, position, namespace) { it.min()!! } diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index 9fcb2725d..ef923d0e9 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -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 { diff --git a/compiler/test/StackVMOpcodes.kt b/compiler/test/StackVMOpcodeTests.kt similarity index 97% rename from compiler/test/StackVMOpcodes.kt rename to compiler/test/StackVMOpcodeTests.kt index 1990d06e3..45fd338e1 100644 --- a/compiler/test/StackVMOpcodes.kt +++ b/compiler/test/StackVMOpcodeTests.kt @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + testComparisonOperator(valuesInvalid, listOf(0), Opcode.NOTEQUAL) // can't compare strings + } } @Test diff --git a/compiler/test/ValueOperationsTests.kt b/compiler/test/ValueOperationsTests.kt new file mode 100644 index 000000000..ef0f594ec --- /dev/null +++ b/compiler/test/ValueOperationsTests.kt @@ -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(vv) + + 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 { + assertEquals(Value(DataType.STR, null, "hello"), Value(DataType.STR, null, "hello")) + } + assertFailsWith { + 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 { + 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)) + } + +} + diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 53e3e8bce..9e5fa3fc3 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -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) diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 32784b37b..c62432eea 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -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: ``+`` ``-`` ``*`` ``/`` ``//`` ``**`` ``%`` diff --git a/docs/source/targetsystem.rst b/docs/source/targetsystem.rst index a0e4c09a6..108c1a980 100644 --- a/docs/source/targetsystem.rst +++ b/docs/source/targetsystem.rst @@ -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 ------------------------------