diff --git a/Java/src/main/java/org/ciyam/at/OpCode.java b/Java/src/main/java/org/ciyam/at/OpCode.java
index df3861a..95fb649 100644
--- a/Java/src/main/java/org/ciyam/at/OpCode.java
+++ b/Java/src/main/java/org/ciyam/at/OpCode.java
@@ -876,6 +876,55 @@ public enum OpCode {
state.dataByteBuffer.putLong(address1, functionData.returnValue);
}
+ },
+ /**
+ * ADD VALue
+ * 0x46 addr1 value
+ * @addr1 += value
+ */
+ ADD_VAL(0x46, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) {
+ @Override
+ protected void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+ executeValueOperation(state, (a, b) -> a + b, args);
+ }
+ },
+ /**
+ * SUBtract VALue
+ * 0x07 addr1 value
+ * @addr1 -= value
+ */
+ SUB_VAL(0x47, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) {
+ @Override
+ protected void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+ executeValueOperation(state, (a, b) -> a - b, args);
+ }
+ },
+ /**
+ * MULtiply VALue
+ * 0x08 addr1 value
+ * @addr1 *= value
+ */
+ MUL_VAL(0x48, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) {
+ @Override
+ protected void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+ executeValueOperation(state, (a, b) -> a * b, args);
+ }
+ },
+ /**
+ * DIVide VALue
+ * 0x09 addr1 value
+ * @addr1 /= value
+ * Can also throw IllegealOperationException if divide-by-zero attempted.
+ */
+ DIV_VAL(0x49, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) {
+ @Override
+ protected void executeWithParams(MachineState state, Object... args) throws ExecutionException {
+ try {
+ executeValueOperation(state, (a, b) -> a / b, args);
+ } catch (ArithmeticException e) {
+ throw new IllegalOperationException("Divide by zero", e);
+ }
+ }
};
public final byte value;
@@ -1001,6 +1050,26 @@ public enum OpCode {
state.dataByteBuffer.putLong(address1, newValue);
}
+ /**
+ * Common code for ADD_VAL/SUB_VAL/MUL_VAL/DIV_VAL/MOD_VAL/SHL_VAL/SHR_VAL
+ *
+ * @param codeByteBuffer
+ * @param dataByteBuffer
+ * @param operator
+ * - typically a lambda operating on two long params, e.g. (a, b) -> a + b
+ * @throws ExecutionException
+ */
+ protected void executeValueOperation(MachineState state, TwoValueOperator operator, Object... args) throws ExecutionException {
+ int address1 = (int) args[0];
+
+ long value1 = state.dataByteBuffer.getLong(address1);
+ long value2 = (long) args[1];
+
+ long newValue = operator.apply(value1, value2);
+
+ state.dataByteBuffer.putLong(address1, newValue);
+ }
+
/**
* Common code for BGT/BLT/BGE/BLE/BEQ/BNE
*
diff --git a/Java/src/test/java/org/ciyam/at/DataOpCodeTests.java b/Java/src/test/java/org/ciyam/at/DataOpCodeTests.java
index 6faf1e0..be0d8e8 100644
--- a/Java/src/test/java/org/ciyam/at/DataOpCodeTests.java
+++ b/Java/src/test/java/org/ciyam/at/DataOpCodeTests.java
@@ -9,30 +9,6 @@ import org.junit.Test;
public class DataOpCodeTests extends ExecutableTest {
- @Test
- public void testSET_VAL() throws ExecutionException {
- codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
- codeByteBuffer.put(OpCode.FIN_IMD.value);
-
- execute(true);
-
- assertTrue(state.isFinished());
- assertFalse(state.hadFatalError());
- assertEquals("Data does not match", 2222L, getData(2));
- }
-
- /** Check that trying to use an address outside data segment throws a fatal error. */
- @Test
- public void testSET_VALunbounded() throws ExecutionException {
- codeByteBuffer.put(OpCode.SET_VAL.value).putInt(9999).putLong(2222L);
- codeByteBuffer.put(OpCode.FIN_IMD.value);
-
- execute(true);
-
- assertTrue(state.isFinished());
- assertTrue(state.hadFatalError());
- }
-
@Test
public void testSET_DAT() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
diff --git a/Java/src/test/java/org/ciyam/at/ValueOpCodeTests.java b/Java/src/test/java/org/ciyam/at/ValueOpCodeTests.java
new file mode 100644
index 0000000..fc36a95
--- /dev/null
+++ b/Java/src/test/java/org/ciyam/at/ValueOpCodeTests.java
@@ -0,0 +1,149 @@
+package org.ciyam.at;
+
+import static org.junit.Assert.*;
+
+import org.ciyam.at.test.ExecutableTest;
+import org.junit.Test;
+
+public class ValueOpCodeTests extends ExecutableTest {
+
+ @Test
+ public void testSET_VAL() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ execute(true);
+
+ assertTrue(state.isFinished());
+ assertFalse(state.hadFatalError());
+ assertEquals("Data does not match", 2222L, getData(2));
+ }
+
+ /** Check that trying to use an address outside data segment throws a fatal error. */
+ @Test
+ public void testSET_VALunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(9999).putLong(2222L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ execute(true);
+
+ assertTrue(state.isFinished());
+ assertTrue(state.hadFatalError());
+ }
+
+ @Test
+ public void testADD_VAL() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.ADD_VAL.value).putInt(2).putLong(3333L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ execute(true);
+
+ assertTrue(state.isFinished());
+ assertFalse(state.hadFatalError());
+ assertEquals("Data does not match", 2222L + 3333L, getData(2));
+ }
+
+ /** Check that trying to use an address outside data segment throws a fatal error. */
+ @Test
+ public void testADD_VALunbounded() throws ExecutionException {
+ codeByteBuffer.put(OpCode.ADD_VAL.value).putInt(9999).putLong(3333L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ execute(true);
+
+ assertTrue(state.isFinished());
+ assertTrue(state.hadFatalError());
+ }
+
+ /** Check that adding to an unsigned long value overflows correctly. */
+ @Test
+ public void testADD_VALoverflow() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(0x7fffffffffffffffL);
+ codeByteBuffer.put(OpCode.ADD_VAL.value).putInt(2).putLong(0x8000000000000099L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ execute(true);
+
+ assertTrue(state.isFinished());
+ assertFalse(state.hadFatalError());
+ assertEquals("Data does not match", 0x0000000000000098L, getData(2));
+ }
+
+ @Test
+ public void testSUB_VAL() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.SUB_VAL.value).putInt(3).putLong(2222L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ execute(true);
+
+ assertTrue(state.isFinished());
+ assertFalse(state.hadFatalError());
+ assertEquals("Data does not match", 3333L - 2222L, getData(3));
+ }
+
+ @Test
+ public void testMUL_VAL() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(3333L);
+ codeByteBuffer.put(OpCode.MUL_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ execute(true);
+
+ assertTrue(state.isFinished());
+ assertFalse(state.hadFatalError());
+ assertEquals("Data does not match", (3333L * 2222L), getData(2));
+ }
+
+ @Test
+ public void testDIV_VAL() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(3333L);
+ codeByteBuffer.put(OpCode.DIV_VAL.value).putInt(2).putLong(2222L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ execute(true);
+
+ assertTrue(state.isFinished());
+ assertFalse(state.hadFatalError());
+ assertEquals("Data does not match", (3333L / 2222L), getData(2));
+ }
+
+ /** Check divide-by-zero throws fatal error because error handler not set. */
+ @Test
+ public void testDIV_VALzero() throws ExecutionException {
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.DIV_VAL.value).putInt(3).putLong(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ execute(true);
+
+ assertTrue(state.isFinished());
+ assertTrue(state.hadFatalError());
+ }
+
+ /** Check divide-by-zero is non-fatal because error handler is set. */
+ @Test
+ public void testDIV_DATzeroWithOnError() throws ExecutionException {
+ int errorAddr = 0x20; // adjust this manually
+
+ codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr);
+
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
+ codeByteBuffer.put(OpCode.DIV_VAL.value).putInt(3).putLong(0);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ // errorAddr:
+ assertEquals(errorAddr, codeByteBuffer.position());
+ // Set 1 at address 1 to indicate we handled error OK
+ codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
+ codeByteBuffer.put(OpCode.FIN_IMD.value);
+
+ execute(true);
+
+ assertTrue(state.isFinished());
+ assertFalse(state.hadFatalError());
+ assertEquals("Error flag not set", 1L, getData(1));
+ }
+
+}