diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index 849daaff5..b5c9cba40 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -430,7 +430,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: val to = range.to.constValue(namespace) var step = 1 if(range.step!=null) { - val stepLv = range.step?.constValue(namespace) ?: LiteralValue(DataType.BYTE, 1, position = range.position) + val stepLv = range.step.constValue(namespace) ?: LiteralValue(DataType.BYTE, 1, position = range.position) if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) { err("range step must be an integer != 0") return range diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 50d44aced..f30c88ab1 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -1,6 +1,5 @@ package prog8.compiler -import jdk.nashorn.internal.ir.JumpStatement import prog8.ast.* import prog8.stackvm.* import java.io.PrintStream diff --git a/compiler/src/prog8/optimizing/ConstantFolding.kt b/compiler/src/prog8/optimizing/ConstantFolding.kt index 0fd61158e..d73619efc 100644 --- a/compiler/src/prog8/optimizing/ConstantFolding.kt +++ b/compiler/src/prog8/optimizing/ConstantFolding.kt @@ -195,7 +195,7 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor { override fun process(range: RangeExpr): IExpression { range.from = range.from.process(this) range.to = range.to.process(this) - range.step = range.step?.process(this) + range.step = range.step.process(this) return super.process(range) } diff --git a/compiler/src/prog8/stackvm/ScreenDialog.kt b/compiler/src/prog8/stackvm/ScreenDialog.kt index dc9f54bff..f0c189220 100644 --- a/compiler/src/prog8/stackvm/ScreenDialog.kt +++ b/compiler/src/prog8/stackvm/ScreenDialog.kt @@ -10,12 +10,12 @@ import javax.swing.Timer class BitmapScreenPanel : JPanel() { - private val image = BufferedImage(ScreenWidth, ScreenHeight, BufferedImage.TYPE_INT_ARGB) + private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB) private val g2d = image.graphics as Graphics2D init { - val size = Dimension(image.width * Scaling, image.height * Scaling) + val size = Dimension(image.width * SCALING, image.height * SCALING) minimumSize = size maximumSize = size preferredSize = size @@ -32,7 +32,7 @@ class BitmapScreenPanel : JPanel() { fun clearScreen(color: Int) { g2d.background = palette[color and 15] - g2d.clearRect(0, 0, BitmapScreenPanel.ScreenWidth, BitmapScreenPanel.ScreenHeight) + g2d.clearRect(0, 0, BitmapScreenPanel.SCREENWIDTH, BitmapScreenPanel.SCREENHEIGHT) } fun setPixel(x: Int, y: Int, color: Int) { image.setRGB(x, y, palette[color and 15].rgb) @@ -44,9 +44,9 @@ class BitmapScreenPanel : JPanel() { } companion object { - const val ScreenWidth = 320 - const val ScreenHeight = 256 - const val Scaling = 3 + const val SCREENWIDTH = 320 + const val SCREENHEIGHT = 256 + const val SCALING = 3 val palette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/ Color(0x000000), // 0 = black Color(0xFFFFFF), // 1 = white @@ -93,19 +93,19 @@ class ScreenDialog : JFrame() { // the borders (top, left, right, bottom) val borderTop = JPanel().apply { - preferredSize = Dimension(BitmapScreenPanel.Scaling * (BitmapScreenPanel.ScreenWidth+2*borderWidth), BitmapScreenPanel.Scaling * borderWidth) + preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth) background = BitmapScreenPanel.palette[14] } val borderBottom = JPanel().apply { - preferredSize =Dimension(BitmapScreenPanel.Scaling * (BitmapScreenPanel.ScreenWidth+2*borderWidth), BitmapScreenPanel.Scaling * borderWidth) + preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth) background = BitmapScreenPanel.palette[14] } val borderLeft = JPanel().apply { - preferredSize =Dimension(BitmapScreenPanel.Scaling * borderWidth, BitmapScreenPanel.Scaling * BitmapScreenPanel.ScreenHeight) + preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT) background = BitmapScreenPanel.palette[14] } val borderRight = JPanel().apply { - preferredSize =Dimension(BitmapScreenPanel.Scaling * borderWidth, BitmapScreenPanel.Scaling * BitmapScreenPanel.ScreenHeight) + preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT) background = BitmapScreenPanel.palette[14] } c = GridBagConstraints() diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index b53462698..0cadb37a4 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -117,6 +117,8 @@ enum class Opcode { SWAP, SEC, // set carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations + SEI, // set irq-disable status flag + CLI, // clear irq-disable status flag NOP, BREAKPOINT, // breakpoint TERMINATE, // end the program @@ -134,8 +136,6 @@ enum class Syscall(val callNr: Short) { GFX_CLEARSCR(17), // clear the screen with color pushed on stack GFX_TEXT(18), // write text on screen at (x,y,color,text) pushed on stack in that order - FUNC_P_CARRY(64), - FUNC_P_IRQD(65), FUNC_SIN(66), FUNC_COS(67), FUNC_ABS(68), @@ -162,10 +162,9 @@ enum class Syscall(val callNr: Short) { FUNC_RND(89), // push a random byte on the stack FUNC_RNDW(90), // push a random word on the stack FUNC_RNDF(91), // push a random float on the stack (between 0.0 and 1.0) - FUNC_FLT(92) // note: not all builtin functions of the Prog8 language are present as functions: - // some of them are already opcodes (such as MSB and ROL)! + // some of them are already opcodes (such as MSB and ROL and FLT)! } class Memory { @@ -181,13 +180,13 @@ class Memory { } fun getWord(address: Int): Int { - return 256*mem[address] + mem[address+1] + return mem[address] + 256*mem[address+1] } fun setWord(address: Int, value: Int) { if(value<0 || value>65535) throw VmExecutionException("word value not 0..65535") - mem[address] = (value / 256).toShort() - mem[address+1] = value.and(255).toShort() + mem[address] = value.and(255).toShort() + mem[address+1] = (value / 256).toShort() } fun setFloat(address: Int, value: Double) { @@ -222,6 +221,10 @@ class Memory { } return Petscii.decodePetscii(petscii, true) } + + fun clear() { + for(i in 0..65535) mem[i]=0 + } } @@ -234,18 +237,22 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?= init { when(type) { DataType.BYTE -> { - byteval = (numericvalue!!.toInt() and 255).toShort() + byteval = numericvalue!!.toShort() + if(byteval!! <0 || byteval!! > 255) + throw VmExecutionException("byte value overflow: $byteval") asBooleanValue = byteval != (0.toShort()) } DataType.WORD -> { - wordval = numericvalue!!.toInt() and 65535 + wordval = numericvalue!!.toInt() + if(wordval!! <0 || wordval!! > 65535) + throw VmExecutionException("word value overflow: $wordval") asBooleanValue = wordval != 0 } DataType.FLOAT -> { floatval = numericvalue!!.toDouble() asBooleanValue = floatval != 0.0 } - DataType.ARRAY -> { + DataType.ARRAY, DataType.ARRAY_W -> { asBooleanValue = arrayvalue!!.isNotEmpty() } DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { @@ -262,9 +269,9 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?= DataType.WORD -> "w:%04x".format(wordval) DataType.FLOAT -> "f:$floatval" DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> "\"$stringvalue\"" - DataType.ARRAY -> TODO("array") - DataType.ARRAY_W -> TODO("word array") - DataType.MATRIX -> TODO("matrix") + DataType.ARRAY -> TODO("tostring array") + DataType.ARRAY_W -> TODO("tostring word array") + DataType.MATRIX -> TODO("tostring matrix") } } @@ -286,13 +293,23 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?= } } + override fun hashCode(): Int { + val bh = byteval?.hashCode() ?: 0x10001234 + val wh = wordval?.hashCode() ?: 0x01002345 + val fh = floatval?.hashCode() ?: 0x00103456 + val ah = arrayvalue?.hashCode() ?: 0x11119876 + return bh xor wh xor fh xor ah xor type.hashCode() + } + override fun equals(other: Any?): Boolean { if(other==null || other !is Value) return false - return compareTo(other)==0 + return compareTo(other)==0 // note: datatype doesn't matter } operator fun compareTo(other: Value): Int { + if(stringvalue!=null && other.stringvalue!=null) + return stringvalue.compareTo(other.stringvalue) return numericValue().toDouble().compareTo(other.numericValue().toDouble()) } @@ -446,21 +463,21 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?= fun bitand(other: Value): Value { val v1 = integerValue() val v2 = other.integerValue() - val result = v1.and(v2) + val result = v1 and v2 return Value(type, result) } fun bitor(other: Value): Value { val v1 = integerValue() val v2 = other.integerValue() - val result = v1.or(v2) + val result = v1 or v2 return Value(type, result) } fun bitxor(other: Value): Value { val v1 = integerValue() val v2 = other.integerValue() - val result = v1.xor(v2) + val result = v1 xor v2 return Value(type, result) } @@ -470,16 +487,16 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?= fun inv(): Value { return when(type) { - DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt().inv()) - DataType.WORD -> Value(DataType.WORD, wordval!!.inv()) - else -> throw VmExecutionException("not can only work on byte/word") + DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt().inv() and 255) + DataType.WORD -> Value(DataType.WORD, wordval!!.inv() and 65535) + else -> throw VmExecutionException("inv can only work on byte/word") } } fun inc(): Value { return when(type) { - DataType.BYTE -> Value(DataType.BYTE, (byteval!! + 1).and(255)) - DataType.WORD -> Value(DataType.WORD, (wordval!! + 1).and(65535)) + DataType.BYTE -> Value(DataType.BYTE, (byteval!! + 1) and 255) + DataType.WORD -> Value(DataType.WORD, (wordval!! + 1) and 65535) DataType.FLOAT -> Value(DataType.FLOAT, floatval!! + 1) else -> throw VmExecutionException("inc can only work on byte/word/float") } @@ -487,8 +504,8 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?= fun dec(): Value { return when(type) { - DataType.BYTE -> Value(DataType.BYTE, (byteval!! - 1).and(255)) - DataType.WORD -> Value(DataType.WORD, (wordval!! - 1).and(65535)) + DataType.BYTE -> Value(DataType.BYTE, (byteval!! - 1) and 255) + DataType.WORD -> Value(DataType.WORD, (wordval!! - 1) and 65535) DataType.FLOAT -> Value(DataType.FLOAT, floatval!! - 1) else -> throw VmExecutionException("dec can only work on byte/word/float") } @@ -496,7 +513,7 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?= fun lsb(): Value { return when(type) { - DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt() and 255) + DataType.BYTE -> Value(DataType.BYTE, byteval!!) DataType.WORD -> Value(DataType.WORD, wordval!! and 255) else -> throw VmExecutionException("not can only work on byte/word") } @@ -504,7 +521,7 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?= fun msb(): Value { return when(type) { - DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt() ushr 8 and 255) + DataType.BYTE -> Value(DataType.BYTE, 0) DataType.WORD -> Value(DataType.WORD, wordval!! ushr 8 and 255) else -> throw VmExecutionException("not can only work on byte/word") } @@ -543,13 +560,13 @@ class LabelInstr(val name: String) : Instruction(opcode = Opcode.NOP) { } } -private class VmExecutionException(msg: String?) : Exception(msg) +class VmExecutionException(msg: String?) : Exception(msg) -private class VmTerminationException(msg: String?) : Exception(msg) +class VmTerminationException(msg: String?) : Exception(msg) -private class VmBreakpointException : Exception("breakpoint") +class VmBreakpointException : Exception("breakpoint") -private class MyStack : Stack() { +class MyStack : Stack() { fun peek(amount: Int) : List { return this.toList().subList(max(0, size-amount), size) } @@ -822,48 +839,56 @@ class Program (val name: String, class StackVm(val traceOutputFile: String?) { - private val mem = Memory() - private val evalstack = MyStack() // evaluation stack - private val callstack = MyStack() // subroutine call stack - private var variables = mutableMapOf() // all variables (set of all vars used by all blocks/subroutines) key = their fully scoped name - private var P_carry: Boolean = false - private var P_irqd: Boolean = false + val mem = Memory() + val evalstack = MyStack() // evaluation stack + val callstack = MyStack() // subroutine call stack + var sourceLine = "" // meta info about current line in source file + private set + var P_carry: Boolean = false + private set + var P_irqd: Boolean = false + private set + var variables = mutableMapOf() // all variables (set of all vars used by all blocks/subroutines) key = their fully scoped name + private set private var program = listOf() private var traceOutput = if(traceOutputFile!=null) PrintStream(File(traceOutputFile), "utf-8") else null private lateinit var currentIns: Instruction - private lateinit var canvas: BitmapScreenPanel + private var canvas: BitmapScreenPanel? = null private val rnd = Random() - private var sourceLine = "" - fun load(program: Program, canvas: BitmapScreenPanel) { + fun load(program: Program, canvas: BitmapScreenPanel?) { this.program = program.program this.canvas = canvas - this.variables = program.variables.toMutableMap() + variables = program.variables.toMutableMap() if(this.variables.contains("A") || - this.variables.contains("X") || - this.variables.contains("Y") || - this.variables.contains("XY") || - this.variables.contains("AX") || - this.variables.contains("AY")) + variables.contains("X") || + variables.contains("Y") || + variables.contains("XY") || + variables.contains("AX") || + variables.contains("AY")) throw VmExecutionException("program contains variable(s) for the reserved registers A,X,...") // define the 'registers' - this.variables["A"] = Value(DataType.BYTE, 0) - this.variables["X"] = Value(DataType.BYTE, 0) - this.variables["Y"] = Value(DataType.BYTE, 0) - this.variables["AX"] = Value(DataType.WORD, 0) - this.variables["AY"] = Value(DataType.WORD, 0) - this.variables["XY"] = Value(DataType.WORD, 0) + variables["A"] = Value(DataType.BYTE, 0) + variables["X"] = Value(DataType.BYTE, 0) + variables["Y"] = Value(DataType.BYTE, 0) + variables["AX"] = Value(DataType.WORD, 0) + variables["AY"] = Value(DataType.WORD, 0) + variables["XY"] = Value(DataType.WORD, 0) initMemory(program.memory) + evalstack.clear() + callstack.clear() + P_carry = false + P_irqd = false + sourceLine = "" currentIns = this.program[0] } - fun step() { + fun step(instructionCount: Int = 10000) { // step is invoked every 1/100 sec // we execute 10k instructions in one go so we end up doing 1 million vm instructions per second - val instructionsPerStep = 10000 val start = System.currentTimeMillis() - for(i:Int in 0..instructionsPerStep) { + for(i:Int in 1..instructionCount) { try { currentIns = dispatch(currentIns) @@ -890,6 +915,7 @@ class StackVm(val traceOutputFile: String?) { } private fun initMemory(memory: Map>) { + mem.clear() for (meminit in memory) { var address = meminit.key for (value in meminit.value) { @@ -936,14 +962,19 @@ class StackVm(val traceOutputFile: String?) { Opcode.DUP -> evalstack.push(evalstack.peek()) Opcode.ARRAY -> { val amount = ins.arg!!.integerValue() + if(amount<=0) + throw VmExecutionException("array size must be > 0") val array = mutableListOf() + var arrayDt = DataType.ARRAY for (i in 1..amount) { val value = evalstack.pop() if(value.type!=DataType.BYTE && value.type!=DataType.WORD) throw VmExecutionException("array requires values to be all byte/word") - array.add(value.integerValue()) + if(value.type==DataType.WORD) + arrayDt = DataType.ARRAY_W + array.add(0, value.integerValue()) } - evalstack.push(Value(DataType.ARRAY, null, arrayvalue = array.toIntArray())) + evalstack.push(Value(arrayDt, null, arrayvalue = array.toIntArray())) } Opcode.DISCARD -> evalstack.pop() Opcode.SWAP -> { @@ -1080,17 +1111,17 @@ class StackVm(val traceOutputFile: String?) { // plot pixel at (x, y, color) from stack val color = evalstack.pop() val (y, x) = evalstack.pop2() - canvas.setPixel(x.integerValue(), y.integerValue(), color.integerValue()) + canvas?.setPixel(x.integerValue(), y.integerValue(), color.integerValue()) } Syscall.GFX_CLEARSCR -> { val color = evalstack.pop() - canvas.clearScreen(color.integerValue()) + canvas?.clearScreen(color.integerValue()) } Syscall.GFX_TEXT -> { val text = evalstack.pop() val color = evalstack.pop() val (y, x) = evalstack.pop2() - canvas.writeText(x.integerValue(), y.integerValue(), text.stringvalue!!, color.integerValue()) + canvas?.writeText(x.integerValue(), y.integerValue(), text.stringvalue!!, color.integerValue()) } Syscall.FUNC_RND -> evalstack.push(Value(DataType.BYTE, rnd.nextInt() and 255)) Syscall.FUNC_RNDW -> evalstack.push(Value(DataType.WORD, rnd.nextInt() and 65535)) @@ -1108,8 +1139,6 @@ class StackVm(val traceOutputFile: String?) { Syscall.FUNC_SIN -> evalstack.push(Value(DataType.FLOAT, sin(evalstack.pop().numericValue().toDouble()))) Syscall.FUNC_COS -> evalstack.push(Value(DataType.FLOAT, cos(evalstack.pop().numericValue().toDouble()))) Syscall.FUNC_ROUND -> evalstack.push(Value(DataType.WORD, evalstack.pop().numericValue().toDouble().roundToInt())) - Syscall.FUNC_P_CARRY -> P_carry = evalstack.pop().asBooleanValue - Syscall.FUNC_P_IRQD -> P_irqd = evalstack.pop().asBooleanValue Syscall.FUNC_ABS -> { val value = evalstack.pop() val absValue= @@ -1131,7 +1160,6 @@ class StackVm(val traceOutputFile: String?) { Syscall.FUNC_SQRT -> evalstack.push(Value(DataType.FLOAT, sqrt(evalstack.pop().numericValue().toDouble()))) Syscall.FUNC_RAD -> evalstack.push(Value(DataType.FLOAT, Math.toRadians(evalstack.pop().numericValue().toDouble()))) Syscall.FUNC_DEG -> evalstack.push(Value(DataType.FLOAT, Math.toDegrees(evalstack.pop().numericValue().toDouble()))) - Syscall.FUNC_FLT -> evalstack.push(Value(DataType.FLOAT, evalstack.pop().numericValue().toDouble())) Syscall.FUNC_FLOOR -> { val value = evalstack.pop() val result = @@ -1196,6 +1224,8 @@ class StackVm(val traceOutputFile: String?) { Opcode.SEC -> P_carry = true Opcode.CLC -> P_carry = false + Opcode.SEI -> P_irqd = true + Opcode.CLI -> P_irqd = false Opcode.TERMINATE -> throw VmTerminationException("terminate instruction") Opcode.BREAKPOINT -> throw VmBreakpointException() diff --git a/compiler/test/StackVMOpcodes.kt b/compiler/test/StackVMOpcodes.kt new file mode 100644 index 000000000..c8078947d --- /dev/null +++ b/compiler/test/StackVMOpcodes.kt @@ -0,0 +1,1245 @@ +package prog8tests + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import prog8.ast.* +import prog8.stackvm.* +import kotlin.math.floor +import kotlin.test.* + + +/*** + +@todo opcodes still to be unit-tested: + + SHL, + SHL_MEM, + SHL_MEM_W, + SHL_VAR, + SHR, + SHR_MEM, + SHR_MEM_W, + SHR_VAR, + ROL, + ROL_MEM, + ROL_MEM_W, + ROL_VAR, + ROR, + ROR_MEM, + ROR_MEM_W, + ROR_VAR, + ROL2, + ROL2_MEM, + ROL2_MEM_W, + ROL2_VAR, + ROR2, + ROR2_MEM, + ROR2_MEM_W, + ROR2_VAR, + +**/ + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestStackVmOpcodes { + + private val vm = StackVm(null) + + fun makeProg(ins: MutableList, + vars: Map?=null, + labels: Map?=null, + mem: Map>?=null) : Program { + return Program("test", ins, labels ?: mapOf(), vars ?: mapOf(), mem ?: mapOf()) + } + + @Test + fun testInitAndNop() { + val ins = mutableListOf(Instruction(Opcode.NOP)) + vm.load(makeProg(ins), null) + assertEquals(6, vm.variables.size) + assertTrue(vm.variables.containsKey("XY")) + assertTrue(vm.variables.containsKey("A")) + vm.step(1) + assertThat(vm.callstack, empty()) + assertThat(vm.evalstack, empty()) + assertFailsWith { + vm.step() + } + } + + @Test + fun testBreakpoint() { + val ins = mutableListOf(Instruction(Opcode.BREAKPOINT)) + vm.load(makeProg(ins), null) + assertFailsWith { + vm.step() + } + assertThat(vm.callstack, empty()) + assertThat(vm.evalstack, empty()) + } + + @Test + fun testLine() { + val ins = mutableListOf(Instruction(Opcode.LINE, Value(DataType.STR, null, "line 99"))) + vm.load(makeProg(ins), null) + assertEquals("", vm.sourceLine) + vm.step(1) + assertEquals("line 99", vm.sourceLine) + } + + @Test + fun testSECandSEIandCLCandCLI() { + val ins = mutableListOf(Instruction(Opcode.SEC), Instruction(Opcode.SEI), Instruction(Opcode.CLC), Instruction(Opcode.CLI)) + vm.load(makeProg(ins), null) + assertFalse(vm.P_carry) + assertFalse(vm.P_irqd) + vm.step(1) + assertTrue(vm.P_carry) + assertFalse(vm.P_irqd) + vm.step(1) + assertTrue(vm.P_carry) + assertTrue(vm.P_irqd) + vm.step(1) + assertFalse(vm.P_carry) + assertTrue(vm.P_irqd) + vm.step(1) + assertFalse(vm.P_carry) + assertFalse(vm.P_irqd) + } + + @Test + fun testPush() { + val ins = mutableListOf(Instruction(Opcode.PUSH, Value(DataType.FLOAT, 42.999))) + vm.load(makeProg(ins), null) + assertThat(vm.evalstack, empty()) + vm.step(1) + assertEquals(1, vm.evalstack.size) + assertEquals(Value(DataType.FLOAT, 42.999), vm.evalstack.pop()) + } + + @Test + fun testPushMem() { + val ins = mutableListOf( + Instruction(Opcode.PUSH_MEM, Value(DataType.WORD, 0x2000)), + Instruction(Opcode.PUSH_MEM_W, Value(DataType.WORD, 0x3000)), + Instruction(Opcode.PUSH_MEM_F, Value(DataType.WORD, 0x4000)) + ) + val mem=mapOf(0x2000 to listOf(Value(DataType.WORD, 0x42ea)), + 0x3000 to listOf(Value(DataType.WORD, 0x42ea)), + 0x4000 to listOf(Value(DataType.FLOAT, 42.25))) + vm.load(makeProg(ins, mem=mem), null) + assertEquals(0xea, vm.mem.getByte(0x2000)) + assertEquals(0x42, vm.mem.getByte(0x2001)) + assertEquals(0xea, vm.mem.getByte(0x3000)) + assertEquals(0x42, vm.mem.getByte(0x3001)) + assertEquals(0x42ea, vm.mem.getWord(0x2000)) + assertEquals(0x42ea, vm.mem.getWord(0x3000)) + assertEquals(42.25, vm.mem.getFloat(0x4000)) + assertThat(vm.evalstack, empty()) + vm.step(3) + assertEquals(3, vm.evalstack.size) + assertEquals(Value(DataType.FLOAT, 42.25), vm.evalstack.pop()) + assertEquals(Value(DataType.WORD, 0x42ea), vm.evalstack.pop()) + assertEquals(Value(DataType.BYTE, 0xea), vm.evalstack.pop()) + } + + @Test + fun testPushVar() { + val ins = mutableListOf(Instruction(Opcode.PUSH_VAR, Value(DataType.STR, null, "varname"))) + vm.load(makeProg(ins, mapOf("varname" to Value(DataType.FLOAT, 42.999))), null) + assertEquals(7, vm.variables.size) + assertTrue(vm.variables.containsKey("varname")) + assertTrue(vm.variables.containsKey("XY")) + assertTrue(vm.variables.containsKey("A")) + assertEquals(Value(DataType.FLOAT, 42.999), vm.variables["varname"]) + assertThat(vm.evalstack, empty()) + vm.step(1) + assertEquals(1, vm.evalstack.size) + assertEquals(Value(DataType.FLOAT, 42.999), vm.evalstack.pop()) + assertEquals(Value(DataType.FLOAT, 42.999), vm.variables["varname"]) + } + + @Test + fun testDup() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.FLOAT, 42.999)), + Instruction(Opcode.DUP)) + vm.load(makeProg(ins), null) + assertThat(vm.evalstack, empty()) + vm.step(2) + assertEquals(2, vm.evalstack.size) + assertEquals(Value(DataType.FLOAT, 42.999), vm.evalstack.pop()) + assertEquals(Value(DataType.FLOAT, 42.999), vm.evalstack.pop()) + } + + @Test + fun testSwap() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.BYTE, 123)), + Instruction(Opcode.PUSH, Value(DataType.WORD, 9999)), + Instruction(Opcode.SWAP) + ) + vm.load(makeProg(ins), null) + assertThat(vm.evalstack, empty()) + vm.step(3) + assertEquals(2, vm.evalstack.size) + assertEquals(Value(DataType.BYTE, 123), vm.evalstack.pop()) + assertEquals(Value(DataType.WORD, 9999), vm.evalstack.pop()) + } + + @Test + fun testDiscard() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.FLOAT, 42.999)), + Instruction(Opcode.PUSH, Value(DataType.FLOAT, 3.1415)), + Instruction(Opcode.DISCARD)) + vm.load(makeProg(ins), null) + assertThat(vm.evalstack, empty()) + vm.step(2) + assertEquals(2, vm.evalstack.size) + vm.step(1) + assertEquals(1, vm.evalstack.size) + assertEquals(Value(DataType.FLOAT, 42.999), vm.evalstack.pop()) + } + + @Test + fun testArray() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.WORD, 111)), + Instruction(Opcode.PUSH, Value(DataType.WORD, 222)), + Instruction(Opcode.PUSH, Value(DataType.WORD, 333)), + Instruction(Opcode.ARRAY, Value(DataType.WORD, 2))) + vm.load(makeProg(ins), null) + assertThat(vm.evalstack, empty()) + vm.step(4) + assertEquals(2, vm.evalstack.size) + var array = vm.evalstack.pop() + assertEquals(DataType.ARRAY_W, array.type) + assertThat(array.arrayvalue, equalTo(intArrayOf(222, 333))) + assertEquals(Value(DataType.WORD, 111), vm.evalstack.pop()) + + val ins2 = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.BYTE, 11)), + Instruction(Opcode.PUSH, Value(DataType.BYTE, 22)), + Instruction(Opcode.PUSH, Value(DataType.BYTE, 33)), + Instruction(Opcode.ARRAY, Value(DataType.WORD, 2))) + vm.load(makeProg(ins2), null) + assertThat(vm.evalstack, empty()) + vm.step(4) + assertEquals(2, vm.evalstack.size) + array = vm.evalstack.pop() + assertEquals(DataType.ARRAY, array.type) + assertThat(array.arrayvalue, equalTo(intArrayOf(22, 33))) + assertEquals(Value(DataType.BYTE, 11), vm.evalstack.pop()) + + val ins3 = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.BYTE, 11)), + Instruction(Opcode.PUSH, Value(DataType.WORD, 222)), + Instruction(Opcode.PUSH, Value(DataType.FLOAT, 333.33)), + Instruction(Opcode.ARRAY, Value(DataType.WORD, 2))) + vm.load(makeProg(ins3), null) + assertFailsWith { + vm.step(4) + } + } + + @Test + fun testPopMem() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.FLOAT, 42.25)), + Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)), + Instruction(Opcode.PUSH, Value(DataType.BYTE, 123)), + Instruction(Opcode.POP_MEM, Value(DataType.WORD, 0x2000)), + Instruction(Opcode.POP_MEM, Value(DataType.WORD, 0x3000)), + Instruction(Opcode.POP_MEM, Value(DataType.WORD, 0x4000))) + vm.load(makeProg(ins), null) + assertEquals(0, vm.mem.getWord(0x2000)) + assertEquals(0, vm.mem.getWord(0x3000)) + assertEquals(0.0, vm.mem.getFloat(0x4000)) + assertThat(vm.evalstack, empty()) + vm.step(6) + assertThat(vm.evalstack, empty()) + assertEquals(123, vm.mem.getByte(0x2000)) + assertEquals(0x42ea, vm.mem.getWord(0x3000)) + assertEquals(42.25, vm.mem.getFloat(0x4000)) + } + + + @Test + fun testPopVar() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.FLOAT, 42.25)), + Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)), + Instruction(Opcode.PUSH, Value(DataType.BYTE, 123)), + Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var1")), + Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var2")), + Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var3"))) + val vars = mapOf( + "var1" to Value(DataType.BYTE, 0), + "var2" to Value(DataType.WORD, 0), + "var3" to Value(DataType.FLOAT, 0) + ) + vm.load(makeProg(ins, vars), null) + assertEquals(9, vm.variables.size) + vm.step(6) + assertEquals(Value(DataType.BYTE, 123), vm.variables["var1"]) + assertEquals(Value(DataType.WORD, 0x42ea), vm.variables["var2"]) + assertEquals(Value(DataType.FLOAT, 42.25), vm.variables["var3"]) + + val ins2 = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)), + Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var1"))) + val vars2 = mapOf( + "var1" to Value(DataType.BYTE, 0) + ) + vm.load(makeProg(ins2, vars2), null) + assertEquals(7, vm.variables.size) + assertFailsWith { + vm.step(2) + } + } + + @Test + fun testAdd() { + val values = listOf( + Value(DataType.FLOAT, 42.25), + Value(DataType.WORD, 4000), + Value(DataType.BYTE, 40)) + val expected = listOf( + Value(DataType.WORD, 4000+40), + Value(DataType.FLOAT, 42.25+(4000+40))) + val operator = Opcode.ADD + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testSub() { + val values = listOf( + Value(DataType.FLOAT, 42.25), + Value(DataType.WORD, 4000), + Value(DataType.BYTE, 40)) + val expected = listOf( + Value(DataType.WORD, 4000-40), + Value(DataType.FLOAT, 42.25-(4000-40))) + val operator = Opcode.SUB + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testMul() { + val values = listOf( + Value(DataType.FLOAT, 42.2533), + Value(DataType.WORD, 401), + Value(DataType.BYTE, 4)) + val expected = listOf( + Value(DataType.WORD, 401*4), + Value(DataType.FLOAT, 42.2533*(401*4))) + val operator = Opcode.MUL + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testDiv() { + val values = listOf( + Value(DataType.FLOAT, 42.25), + Value(DataType.WORD, 3999), + Value(DataType.BYTE, 40) + ) + val expected = listOf( + Value(DataType.FLOAT, 3999.0/40.0), + Value(DataType.FLOAT, 42.25/(3999.0/40.0))) + val operator = Opcode.DIV + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testFloorDiv() { + val values = listOf( + Value(DataType.FLOAT, 42.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)))) + val operator = Opcode.FLOORDIV + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testPow() { + val values = listOf( + Value(DataType.FLOAT, 1.1), + Value(DataType.WORD, 3), + Value(DataType.BYTE, 4) + ) + val expected = listOf( + Value(DataType.WORD, 81), + Value(DataType.FLOAT, 2253.2402360440274)) + val operator = Opcode.POW + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testRemainder() { + val values = listOf( + Value(DataType.FLOAT, 2022.5), + Value(DataType.WORD, 500), + Value(DataType.BYTE, 29) + ) + val expected = listOf( + Value(DataType.BYTE, 7), + Value(DataType.FLOAT, 6.5)) + val operator = Opcode.REMAINDER + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testBitand() { + val values = listOf( + Value(DataType.FLOAT, 0x000f), + Value(DataType.WORD, 0b0011001011110001), + Value(DataType.BYTE, 0b10011111), + Value(DataType.BYTE, 0b11111101)) + val expected = listOf( + Value(DataType.BYTE, 0b10011101), + Value(DataType.WORD, 0b0000000010010001), + Value(DataType.FLOAT, 1)) + val operator = Opcode.BITAND + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testBitor() { + val values = listOf( + Value(DataType.FLOAT, 0xfff0), + Value(DataType.WORD, 0b0011001011100000), + Value(DataType.BYTE, 0b00011101), + Value(DataType.BYTE, 0b10010001)) + val expected = listOf( + Value(DataType.BYTE, 0b10011101), + Value(DataType.WORD, 0b0011001011111101), + Value(DataType.FLOAT, 65533.0)) + val operator = Opcode.BITOR + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testBitxor() { + val values = listOf( + Value(DataType.FLOAT, 0xfff0), + Value(DataType.WORD, 0b0011001011100000), + Value(DataType.BYTE, 0b00011101), + Value(DataType.BYTE, 0b10010001)) + val expected = listOf( + Value(DataType.BYTE, 0b10001100), + Value(DataType.WORD, 0b0011001001101100), + Value(DataType.FLOAT, 52636.0)) + val operator = Opcode.BITXOR + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testAnd() { + val values = listOf( + Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), + Value(DataType.BYTE, 0), + Value(DataType.WORD, 0), + Value(DataType.STR, null, ""), + Value(DataType.STR, null, "hello"), + Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), + Value(DataType.FLOAT, 300.33), + Value(DataType.WORD, 5000), + Value(DataType.BYTE, 200), + Value(DataType.BYTE, 1)) + val expected = listOf( + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 0), + Value(DataType.BYTE, 0), + Value(DataType.BYTE, 0), + Value(DataType.BYTE, 0)) + val operator = Opcode.AND + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testOr() { + val values = listOf( + Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), + Value(DataType.BYTE, 0), + Value(DataType.WORD, 0), + Value(DataType.STR, null, ""), + Value(DataType.STR, null, "hello"), + Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), + Value(DataType.FLOAT, 300.33), + Value(DataType.WORD, 5000), + Value(DataType.BYTE, 0), + Value(DataType.BYTE, 0)) + val expected = listOf( + Value(DataType.BYTE, 0), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1)) + val operator = Opcode.OR + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testXor() { + val values = listOf( + Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), + Value(DataType.BYTE, 1), + Value(DataType.WORD, 0), + Value(DataType.STR, null, ""), + Value(DataType.STR, null, "hello"), + Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), + Value(DataType.FLOAT, 300.33), + Value(DataType.WORD, 5000), + Value(DataType.BYTE, 0), + Value(DataType.BYTE, 20)) + val expected = listOf( + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 0), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 0), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 0), + Value(DataType.BYTE, 1)) + val operator = Opcode.XOR + + testBinaryOperator(values, operator, expected) + } + + @Test + fun testInc() { + val values = listOf( + Value(DataType.FLOAT, 2022.5), + Value(DataType.WORD, 65535), + Value(DataType.WORD, 999), + Value(DataType.BYTE, 255), + Value(DataType.BYTE, 99) + ) + val expected = listOf( + Value(DataType.BYTE, 100), + Value(DataType.BYTE, 0), + Value(DataType.WORD, 1000), + Value(DataType.WORD, 0), + Value(DataType.FLOAT, 2023.5) + ) + val operator = Opcode.INC + testUnaryOperator(values, operator, expected) + } + + @Test + fun testDec() { + val values = listOf( + Value(DataType.FLOAT, 0.5), + Value(DataType.FLOAT, 123.456), + Value(DataType.WORD, 1000), + Value(DataType.WORD, 0), + Value(DataType.BYTE, 100), + Value(DataType.BYTE, 0) + ) + val expected = listOf( + Value(DataType.BYTE, 255), + Value(DataType.BYTE, 99), + Value(DataType.WORD, 65535), + Value(DataType.WORD, 999), + Value(DataType.FLOAT, 122.456), + Value(DataType.FLOAT, -0.5) + ) + val operator = Opcode.DEC + testUnaryOperator(values, operator, expected) + } + + @Test + fun testNeg() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.FLOAT, 123.456)), + Instruction(Opcode.NEG), + Instruction(Opcode.NEG) + ) + vm.load(makeProg(ins), null) + assertThat(vm.evalstack, empty()) + vm.step(2) + assertEquals(1, vm.evalstack.size) + assertEquals(Value(DataType.FLOAT, -123.456), vm.evalstack.peek()) + vm.step(1) + assertEquals(1, vm.evalstack.size) + assertEquals(Value(DataType.FLOAT, 123.456), vm.evalstack.peek()) + + val ins2 = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.WORD, 1234)), + Instruction(Opcode.NEG) + ) + vm.load(makeProg(ins2), null) + assertFailsWith { + vm.step(2) + } + val ins3 = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.BYTE, 12)), + Instruction(Opcode.NEG) + ) + vm.load(makeProg(ins3), null) + assertFailsWith { + vm.step(2) + } + } + + @Test + fun testInv() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.BYTE, 123)), + Instruction(Opcode.PUSH, Value(DataType.WORD, 4044)), + Instruction(Opcode.INV), + Instruction(Opcode.INV), + Instruction(Opcode.INV) + ) + vm.load(makeProg(ins), null) + assertThat(vm.evalstack, empty()) + vm.step(3) + assertEquals(2, vm.evalstack.size) + assertEquals(Value(DataType.WORD, 0xf033), vm.evalstack.pop()) + vm.step(1) + assertEquals(1, vm.evalstack.size) + assertEquals(Value(DataType.BYTE, 0x84), vm.evalstack.pop()) + + val ins2 = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.FLOAT, 1234.33)), + Instruction(Opcode.INV) + ) + vm.load(makeProg(ins2), null) + assertFailsWith { + vm.step(2) + } + } + + @Test + fun testLsb() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.FLOAT, 1.23)), + Instruction(Opcode.PUSH, Value(DataType.BYTE, 0x45)), + Instruction(Opcode.PUSH, Value(DataType.WORD, 0xea31)), + Instruction(Opcode.LSB), + Instruction(Opcode.LSB), + Instruction(Opcode.LSB) + ) + vm.load(makeProg(ins), null) + vm.step(4) + assertEquals(Value(DataType.BYTE, 0x31), vm.evalstack.pop()) + vm.step(1) + assertEquals(Value(DataType.BYTE, 0x45), vm.evalstack.pop()) + assertFailsWith { + vm.step(1) + } + } + + @Test + fun testMsb() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.FLOAT, 1.23)), + Instruction(Opcode.PUSH, Value(DataType.BYTE, 0x45)), + Instruction(Opcode.PUSH, Value(DataType.WORD, 0xea31)), + Instruction(Opcode.MSB), + Instruction(Opcode.MSB), + Instruction(Opcode.MSB) + ) + vm.load(makeProg(ins), null) + vm.step(4) + assertEquals(Value(DataType.BYTE, 0xea), vm.evalstack.pop()) + vm.step(1) + assertEquals(Value(DataType.BYTE, 0), vm.evalstack.pop()) + assertFailsWith { + vm.step(1) + } + } + + @Test + fun testB2Word() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.WORD, 0xea31)), + Instruction(Opcode.PUSH, Value(DataType.BYTE, 0x45)), + Instruction(Opcode.B2WORD), + Instruction(Opcode.B2WORD) + ) + vm.load(makeProg(ins), null) + vm.step(3) + assertEquals(Value(DataType.WORD, 0x0045), vm.evalstack.pop()) + assertFailsWith { + vm.step(1) + } + } + + @Test + fun testMSB2Word() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.WORD, 0xea31)), + Instruction(Opcode.PUSH, Value(DataType.BYTE, 0x45)), + Instruction(Opcode.MSB2WORD), + Instruction(Opcode.MSB2WORD) + ) + vm.load(makeProg(ins), null) + vm.step(3) + assertEquals(Value(DataType.WORD, 0x4500), vm.evalstack.pop()) + assertFailsWith { + vm.step(1) + } + } + + @Test + fun testB2Float() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.WORD, 0xea31)), + Instruction(Opcode.PUSH, Value(DataType.BYTE, 123)), + Instruction(Opcode.B2FLOAT), + Instruction(Opcode.B2FLOAT) + ) + vm.load(makeProg(ins), null) + vm.step(3) + assertEquals(Value(DataType.FLOAT, 123.0), vm.evalstack.pop()) + assertFailsWith { + vm.step(1) + } + } + + @Test + fun testW2Float() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.BYTE, 11)), + Instruction(Opcode.PUSH, Value(DataType.WORD, 12345)), + Instruction(Opcode.W2FLOAT), + Instruction(Opcode.W2FLOAT) + ) + vm.load(makeProg(ins), null) + vm.step(3) + assertEquals(Value(DataType.FLOAT, 12345.0), vm.evalstack.pop()) + assertFailsWith { + vm.step(1) + } + } + + @Test + fun testIncMemAndIncMemW() { + val ins = mutableListOf( + Instruction(Opcode.INC_MEM, Value(DataType.WORD, 0x2000)), + Instruction(Opcode.INC_MEM, Value(DataType.WORD, 0x2001)), + Instruction(Opcode.INC_MEM_W, Value(DataType.WORD, 0x3000)), + Instruction(Opcode.INC_MEM_W, Value(DataType.WORD, 0x3002)) + ) + val mem=mapOf(0x2000 to listOf(Value(DataType.BYTE, 100), Value(DataType.BYTE, 255)), + 0x3000 to listOf(Value(DataType.WORD, 0x42ea), Value(DataType.WORD, 0xffff))) + vm.load(makeProg(ins, mem=mem), null) + vm.step(4) + assertEquals(101, vm.mem.getByte(0x2000)) + assertEquals(0, vm.mem.getByte(0x2001)) + assertEquals(0x42eb, vm.mem.getWord(0x3000)) + assertEquals(0, vm.mem.getWord(0x3002)) + } + + @Test + fun testIncVar() { + val ins = mutableListOf( + Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var1")), + Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var2")), + Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var1")), + Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var2"))) + val vars = mapOf("var1" to Value(DataType.WORD, 65534), + "var2" to Value(DataType.BYTE, 254)) + vm.load(makeProg(ins, vars = vars), null) + vm.step(2) + assertEquals(Value(DataType.WORD, 65535), vm.variables["var1"]) + assertEquals(Value(DataType.BYTE, 255), vm.variables["var2"]) + vm.step(2) + assertEquals(Value(DataType.WORD, 0), vm.variables["var1"]) + assertEquals(Value(DataType.BYTE, 0), vm.variables["var2"]) + } + + @Test + fun testDecVar() { + val ins = mutableListOf( + Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var1")), + Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var2")), + Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var1")), + Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var2"))) + val vars = mapOf("var1" to Value(DataType.WORD,1), + "var2" to Value(DataType.BYTE, 1)) + vm.load(makeProg(ins, vars = vars), null) + vm.step(2) + assertEquals(Value(DataType.WORD, 0), vm.variables["var1"]) + assertEquals(Value(DataType.BYTE, 0), vm.variables["var2"]) + vm.step(2) + assertEquals(Value(DataType.WORD, 65535), vm.variables["var1"]) + assertEquals(Value(DataType.BYTE, 255), vm.variables["var2"]) + } + + @Test + fun testDecMemAndDecMemW() { + val ins = mutableListOf( + Instruction(Opcode.DEC_MEM, Value(DataType.WORD, 0x2000)), + Instruction(Opcode.DEC_MEM, Value(DataType.WORD, 0x2001)), + Instruction(Opcode.DEC_MEM_W, Value(DataType.WORD, 0x3000)), + Instruction(Opcode.DEC_MEM_W, Value(DataType.WORD, 0x3002)) + ) + val mem=mapOf(0x2000 to listOf(Value(DataType.BYTE, 100), Value(DataType.BYTE, 0)), + 0x3000 to listOf(Value(DataType.WORD, 0x42ea), Value(DataType.WORD, 0))) + vm.load(makeProg(ins, mem=mem), null) + vm.step(4) + assertEquals(99, vm.mem.getByte(0x2000)) + assertEquals(255, vm.mem.getByte(0x2001)) + assertEquals(0x42e9, vm.mem.getWord(0x3000)) + assertEquals(65535, vm.mem.getWord(0x3002)) + } + + @Test + fun testSyscall() { + val ins = mutableListOf( + Instruction(Opcode.SYSCALL, Value(DataType.BYTE, Syscall.FUNC_RNDF.callNr)), + Instruction(Opcode.SYSCALL, Value(DataType.BYTE, Syscall.FUNC_RNDW.callNr)), + Instruction(Opcode.SYSCALL, Value(DataType.BYTE, Syscall.FUNC_RND.callNr)), + Instruction(Opcode.SYSCALL, Value(DataType.BYTE, Syscall.FUNC_RND.callNr)), + + Instruction(Opcode.PUSH, Value(DataType.WORD, 25544)), + Instruction(Opcode.SYSCALL, Value(DataType.BYTE, Syscall.FUNC_SIN.callNr)) + ) + vm.load(makeProg(ins), null) + vm.step(4) + + val rndb1 = vm.evalstack.pop() + val rndb2 = vm.evalstack.pop() + val rndw = vm.evalstack.pop() + val rndf = vm.evalstack.pop() + assertEquals(DataType.BYTE, rndb1.type) + assertEquals(DataType.BYTE, rndb2.type) + assertEquals(DataType.WORD, rndw.type) + assertEquals(DataType.FLOAT, rndf.type) + assertNotEquals(rndb1.integerValue(), rndb2.integerValue()) + assertTrue(rndf.numericValue().toDouble() > 0.0 && rndf.numericValue().toDouble() < 1.0) + + vm.step(2) + assertEquals(Value(DataType.FLOAT, 0.28582414234140724), vm.evalstack.pop()) + } + + @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), + Value(DataType.BYTE, 1), // 0 + Value(DataType.BYTE, 2), + Value(DataType.WORD, 20), // 1 + Value(DataType.WORD, 20), + Value(DataType.BYTE, 21), // 1 + Value(DataType.WORD, 21), + Value(DataType.BYTE, 21), // 0 + Value(DataType.BYTE, 21), + Value(DataType.FLOAT, 21), // 0 + Value(DataType.BYTE, 21), + Value(DataType.FLOAT, 21.0001) // 1 + ) + val expected = listOf(0, 1, 1, 0, 1, 1, 0, 0, 1) + testComparisonOperator(values, expected, Opcode.LESS) + } + + @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), + Value(DataType.BYTE, 1), // 1 + Value(DataType.BYTE, 21), + Value(DataType.WORD, 20), // 0 + Value(DataType.WORD, 20), + Value(DataType.BYTE, 21), // 1 + Value(DataType.WORD, 21), + Value(DataType.BYTE, 22), // 1 + Value(DataType.BYTE, 21), + Value(DataType.FLOAT, 21), // 1 + Value(DataType.BYTE, 22), + Value(DataType.FLOAT, 21.999) // 0 + ) + val expected = listOf(1,1,1,1,0,1,1,1,0) + testComparisonOperator(values, expected, Opcode.LESSEQ) + } + + @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), + Value(DataType.BYTE, 1), // 0 + Value(DataType.BYTE, 20), + Value(DataType.WORD, 2), // 1 + Value(DataType.WORD, 20), + Value(DataType.BYTE, 21), // 0 + Value(DataType.WORD, 21), + Value(DataType.BYTE, 20), // 1 + Value(DataType.BYTE, 21), + Value(DataType.FLOAT, 21), // 0 + Value(DataType.BYTE, 21), + Value(DataType.FLOAT, 20.9999) // 1 + ) + val expected = listOf(0, 1, 0, 0, 1, 0, 1, 0, 1) + testComparisonOperator(values, expected, Opcode.GREATER) + } + + @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), + Value(DataType.BYTE, 1), // 1 + Value(DataType.BYTE, 21), + Value(DataType.WORD, 20), // 1 + Value(DataType.WORD, 20), + Value(DataType.BYTE, 21), // 0 + Value(DataType.WORD, 21), + Value(DataType.BYTE, 22), // 0 + Value(DataType.BYTE, 21), + Value(DataType.FLOAT, 21), // 1 + Value(DataType.BYTE, 22), + Value(DataType.FLOAT, 21.999) // 1 + ) + val expected = listOf(1,1,0,1,1,0,0,1,1) + testComparisonOperator(values, expected, Opcode.GREATEREQ) + } + + @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), + Value(DataType.BYTE, 1), // 1 + Value(DataType.BYTE, 21), + Value(DataType.WORD, 20), // 0 + Value(DataType.WORD, 20), + Value(DataType.BYTE, 21), // 0 + Value(DataType.WORD, 21), + Value(DataType.BYTE, 21), // 1 + Value(DataType.BYTE, 21), + Value(DataType.FLOAT, 21), // 1 + Value(DataType.BYTE, 22), + Value(DataType.FLOAT, 21.999) // 0 + ) + val expected = listOf(1,0,0,1,0,0,1,1,0) + testComparisonOperator(values, expected, Opcode.EQUAL) + } + + @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), + Value(DataType.BYTE, 1), // 0 + Value(DataType.BYTE, 21), + Value(DataType.WORD, 20), // 1 + Value(DataType.WORD, 20), + Value(DataType.BYTE, 21), // 1 + Value(DataType.WORD, 21), + Value(DataType.BYTE, 21), // 0 + Value(DataType.BYTE, 21), + Value(DataType.FLOAT, 21), // 0 + Value(DataType.BYTE, 22), + Value(DataType.FLOAT, 21.999) // 1 + ) + val expected = listOf(0,1,1,0,1,1,0,0,1) + testComparisonOperator(values, expected, Opcode.NOTEQUAL) + } + + @Test + fun testBCC() { + val ins = mutableListOf( + Instruction(Opcode.SEC), + Instruction(Opcode.BCC, callLabel = "label"), + Instruction(Opcode.CLC), + Instruction(Opcode.BCC, callLabel = "label"), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.TERMINATE), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + val labels = mapOf("label" to ins.last()) // points to the second LINE instruction + vm.load(makeProg(ins, labels=labels), null) + vm.step(2) + assertEquals("", vm.sourceLine) + vm.step(3) + assertEquals("string2", vm.sourceLine) + assertEquals(0, vm.callstack.size) + assertEquals(0, vm.evalstack.size) + } + + @Test + fun testBCS() { + val ins = mutableListOf( + Instruction(Opcode.CLC), + Instruction(Opcode.BCS, callLabel = "label"), + Instruction(Opcode.SEC), + Instruction(Opcode.BCS, callLabel = "label"), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.TERMINATE), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + val labels = mapOf("label" to ins.last()) // points to the second LINE instruction + vm.load(makeProg(ins, labels=labels), null) + assertFalse(vm.P_carry) + vm.step(2) + assertEquals("", vm.sourceLine) + vm.step(3) + assertEquals("string2", vm.sourceLine) + assertEquals(0, vm.callstack.size) + assertEquals(0, vm.evalstack.size) + } + + @Test + fun testBEQ() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.WORD, 1)), + Instruction(Opcode.BEQ, callLabel = "label"), + Instruction(Opcode.PUSH, Value(DataType.WORD, 0)), + Instruction(Opcode.BEQ, callLabel = "label"), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.TERMINATE), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + val labels = mapOf("label" to ins.last()) // points to the second LINE instruction + vm.load(makeProg(ins, labels=labels), null) + vm.step(2) + assertEquals("", vm.sourceLine) + vm.step(3) + assertEquals("string2", vm.sourceLine) + assertEquals(0, vm.callstack.size) + assertEquals(0, vm.evalstack.size) + } + + @Test + fun testBNE() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.WORD, 0)), + Instruction(Opcode.BNE, callLabel = "label"), + Instruction(Opcode.PUSH, Value(DataType.WORD, 1)), + Instruction(Opcode.BNE, callLabel = "label"), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.TERMINATE), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + val labels = mapOf("label" to ins.last()) // points to the second LINE instruction + vm.load(makeProg(ins, labels=labels), null) + vm.step(2) + assertEquals("", vm.sourceLine) + vm.step(3) + assertEquals("string2", vm.sourceLine) + assertEquals(0, vm.callstack.size) + assertEquals(0, vm.evalstack.size) + } + + @Test + fun testBMI() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.WORD, 0)), + Instruction(Opcode.BMI, callLabel = "label"), + Instruction(Opcode.PUSH, Value(DataType.WORD, 1)), + Instruction(Opcode.BMI, callLabel = "label"), + Instruction(Opcode.PUSH, Value(DataType.FLOAT, -99)), + Instruction(Opcode.BMI, callLabel = "label"), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.TERMINATE), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + val labels = mapOf("label" to ins.last()) // points to the second LINE instruction + vm.load(makeProg(ins, labels=labels), null) + vm.step(2) + assertEquals("", vm.sourceLine) + vm.step(2) + assertEquals("", vm.sourceLine) + vm.step(3) + assertEquals("string2", vm.sourceLine) + assertEquals(0, vm.callstack.size) + assertEquals(0, vm.evalstack.size) + } + + @Test + fun testBPL() { + val ins = mutableListOf( + Instruction(Opcode.PUSH, Value(DataType.FLOAT, -99)), + Instruction(Opcode.BPL, callLabel = "label"), + Instruction(Opcode.PUSH, Value(DataType.WORD, 0)), + Instruction(Opcode.BPL, callLabel = "label"), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.TERMINATE), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + val labels = mapOf("label" to ins.last()) // points to the second LINE instruction + vm.load(makeProg(ins, labels=labels), null) + vm.step(2) + assertEquals("", vm.sourceLine) + vm.step(3) + assertEquals("string2", vm.sourceLine) + assertEquals(0, vm.callstack.size) + assertEquals(0, vm.evalstack.size) + } + + @Test + fun testJump() { + val ins = mutableListOf( + Instruction(Opcode.JUMP, callLabel = "label"), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.TERMINATE), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + val labels = mapOf("label" to ins.last()) // points to the second LINE instruction + vm.load(makeProg(ins, labels=labels), null) + vm.step(2) + assertEquals("string2", vm.sourceLine) + assertEquals(0, vm.callstack.size) + assertEquals(0, vm.evalstack.size) + } + + @Test + fun testReturn() { + // @todo only tests return with zero return values + val ins = mutableListOf( + Instruction(Opcode.RETURN), + Instruction(Opcode.TERMINATE), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")) + ) + vm.load(makeProg(ins), null) + assertFailsWith { + vm.step(1) + } + + vm.callstack.add(ins[2]) // set the LINE opcode as return instruction + assertEquals("", vm.sourceLine) + vm.step(2) + assertEquals("string1", vm.sourceLine) + assertEquals(0, vm.callstack.size) + assertEquals(0, vm.evalstack.size) + } + + @Test + fun testCall() { + // @todo only tests call with zero parameters + val ins = mutableListOf( + Instruction(Opcode.CALL, callLabel = "label"), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "returned")), + Instruction(Opcode.TERMINATE), + Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "called")), + Instruction(Opcode.RETURN) + ) + val labels = mapOf("label" to ins[3]) // points to the LINE instruction + vm.load(makeProg(ins, labels = labels), null) + vm.step(1) + assertEquals("", vm.sourceLine) + assertEquals(1, vm.callstack.size) + assertSame(ins[1], vm.callstack.peek()) + vm.step(1) + assertEquals("called", vm.sourceLine) + vm.step(1) + assertEquals(0, vm.callstack.size) + assertEquals("called", vm.sourceLine) + vm.step(1) + assertEquals("returned", vm.sourceLine) + } + + private fun testComparisonOperator(values: List, expected: List, operator: Opcode) { + assertEquals(values.size, expected.size*2) + val ins = mutableListOf() + val vars = values.iterator() + while(vars.hasNext()) { + ins.add(Instruction(Opcode.PUSH, vars.next())) + ins.add(Instruction(Opcode.PUSH, vars.next())) + ins.add(Instruction(operator)) + } + vm.load(makeProg(ins), null) + for(expectedValue in expected) { + vm.step(3) + assertEquals(Value(DataType.BYTE, expectedValue), vm.evalstack.pop()) + } + } + + private fun testBinaryOperator(values: List, operator: Opcode, expected: List) { + assertEquals(values.size, expected.size+1) + val ins = mutableListOf() + for (value in values) + ins.add(Instruction(Opcode.PUSH, value)) + for (i in 1 until values.size) + ins.add(Instruction(operator)) + vm.load(makeProg(ins), null) + vm.step(values.size) + assertEquals(values.size, vm.evalstack.size) + for (expectedVal in expected) { + vm.step(1) + assertEquals(expectedVal, vm.evalstack.peek()) + } + assertFailsWith { + vm.step(1) + } + } + + private fun testUnaryOperator(values: List, operator: Opcode, expected: List) { + assertEquals(values.size, expected.size) + val ins = mutableListOf() + for (value in values) + ins.add(Instruction(Opcode.PUSH, value)) + for (i in 1..values.size) { + ins.add(Instruction(operator)) + ins.add(Instruction(Opcode.DISCARD)) + } + vm.load(makeProg(ins), null) + vm.step(values.size) + assertEquals(values.size, vm.evalstack.size) + for (expectedVal in expected) { + vm.step(1) + assertEquals(expectedVal, vm.evalstack.peek()) + vm.step(1) + } + assertTrue(vm.evalstack.empty()) + assertFailsWith { + vm.step(1) + } + } +}