diff --git a/src/main/java/ru/jengine/virtualmachine/Main.java b/src/main/java/ru/jengine/virtualmachine/Main.java new file mode 100644 index 0000000..75fa29e --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/Main.java @@ -0,0 +1,29 @@ +package ru.jengine.virtualmachine; + +import ru.jengine.virtualmachine.commands.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Main { + + public static void main(String[] args) { + List firstStorage = new ArrayList<>(Arrays.asList(5, 7, 2)); + byte[] firstCode = { + 0b00, + 0, + 0b00, + 1, + 0b100, + 0b00, + 2, + 0b01 + }; + VirtualMachine virtualMachine = new VirtualMachine(List.of(new Add(), new Const(), new Sub(), new Div(), new Mul(), new Jump())); + virtualMachine.registerScript(firstStorage, firstCode); + virtualMachine.executeRegisteredContexts(); + + // TODO: Handle errors? + } +} \ No newline at end of file diff --git a/src/main/java/ru/jengine/virtualmachine/VMStack.java b/src/main/java/ru/jengine/virtualmachine/VMStack.java new file mode 100644 index 0000000..e284320 --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/VMStack.java @@ -0,0 +1,28 @@ +package ru.jengine.virtualmachine; + +import java.util.ArrayDeque; + +public class VMStack { + private final int stackSize; + private final ArrayDeque stack; + + public VMStack(int capacity) { + stackSize = capacity; + stack = new ArrayDeque<>(capacity); + } + + public Object pop() { + return stack.pop(); + } + + public void push(Object element) { + if (stack.size() == stackSize) { + throw new StackOverflowError("VM's stack has overflowed."); + } + stack.push(element); + } + + public Object peek() { + return stack.peek(); + } +} diff --git a/src/main/java/ru/jengine/virtualmachine/VirtualMachine.java b/src/main/java/ru/jengine/virtualmachine/VirtualMachine.java new file mode 100644 index 0000000..d6210fc --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/VirtualMachine.java @@ -0,0 +1,53 @@ +package ru.jengine.virtualmachine; + +import ru.jengine.virtualmachine.commands.*; +import ru.jengine.virtualmachine.context.ScriptContext; +import ru.jengine.virtualmachine.exceptions.VirtualMachineException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class VirtualMachine { + + private final Map commandsMap; + + private final List contexts = new ArrayList<>(); + + public VirtualMachine(List commands) { + commandsMap = commands.stream().collect(Collectors.toMap(Command::getOpCode, Function.identity())); + } + + public void executeRegisteredContexts() { + for (ScriptContext context : this.contexts) { + execute(context); + } + } + + private Command extractNextCommand(ScriptContext context, byte[] code) { + byte opCode = code[context.getInstructionPointer()]; + return commandsMap.get(opCode); + } + + public void registerScript(List initialStorage, byte[] bytecode) { + ScriptContext scriptContext = new ScriptContext(initialStorage, bytecode); + contexts.add(scriptContext); + } + + private void execute(ScriptContext context) { + byte[] code = context.getCode(); + while (context.getInstructionPointer() < code.length) { + Command command = extractNextCommand(context, code); + try { + int offset = command.execute(context); + context.moveInstructionPointer(offset); + } catch (VirtualMachineException e) { + throw e; // TODO: ??? + } + } + + } + +} diff --git a/src/main/java/ru/jengine/virtualmachine/commands/Add.java b/src/main/java/ru/jengine/virtualmachine/commands/Add.java new file mode 100644 index 0000000..ee7f2cf --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/commands/Add.java @@ -0,0 +1,27 @@ +package ru.jengine.virtualmachine.commands; + +import ru.jengine.virtualmachine.context.CommandContext; +import ru.jengine.virtualmachine.exceptions.VirtualMachineException; + +public class Add implements Command { + private static final byte[] opCode = {0b0000_0001}; + private static final int offset = 1; + + @Override + public Byte getOpCode() { + return opCode[0]; + } + + @Override + public int execute(CommandContext context) { + Object firstOp = context.popFromStack(); + Object secondOp = context.popFromStack(); + + if (firstOp instanceof Number && secondOp instanceof Number) { + Number result = BinaryHandler.sumTwoNumbers((Number) firstOp, (Number) secondOp); + context.pushToStack(result); + } else throw new VirtualMachineException("Add operation uses two instances of Number."); + + return offset; + } +} diff --git a/src/main/java/ru/jengine/virtualmachine/commands/BinaryHandler.java b/src/main/java/ru/jengine/virtualmachine/commands/BinaryHandler.java new file mode 100644 index 0000000..b2202a3 --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/commands/BinaryHandler.java @@ -0,0 +1,58 @@ +package ru.jengine.virtualmachine.commands; + + +import ru.jengine.virtualmachine.exceptions.VirtualMachineException; + +public class BinaryHandler { + public static Number sumTwoNumbers(Number firstOp, Number secondOp) { + if (firstOp instanceof Double || secondOp instanceof Double) { + return firstOp.doubleValue() + secondOp.doubleValue(); + } else if (firstOp instanceof Float || secondOp instanceof Float) { + return firstOp.floatValue() + secondOp.floatValue(); + } else if (firstOp instanceof Long || secondOp instanceof Long) { + return firstOp.longValue() + secondOp.longValue(); + } else { + return firstOp.intValue() + secondOp.intValue(); + } + } + + public static Number mulTwoNumbers(Number firstOp, Number secondOp) { + if (firstOp instanceof Double || secondOp instanceof Double) { + return firstOp.doubleValue() * secondOp.doubleValue(); + } else if (firstOp instanceof Float || secondOp instanceof Float) { + return firstOp.floatValue() * secondOp.floatValue(); + } else if (firstOp instanceof Long || secondOp instanceof Long) { + return firstOp.longValue() * secondOp.longValue(); + } else { + return firstOp.intValue() * secondOp.intValue(); + } + } + + public static Number subTwoNumbers(Number firstOp, Number secondOp) { + if (firstOp instanceof Double || secondOp instanceof Double) { + return firstOp.doubleValue() - secondOp.doubleValue(); + } else if (firstOp instanceof Float || secondOp instanceof Float) { + return firstOp.floatValue() - secondOp.floatValue(); + } else if (firstOp instanceof Long || secondOp instanceof Long) { + return firstOp.longValue() - secondOp.longValue(); + } else { + return firstOp.intValue() - secondOp.intValue(); + } + } + + public static Number divTwoNumbers(Number firstOp, Number secondOp) { + if (secondOp.doubleValue() < 1.0E-10) { + throw new VirtualMachineException("Division by zero is restricted."); + } + + if (firstOp instanceof Double || secondOp instanceof Double) { + return firstOp.doubleValue() / secondOp.doubleValue(); + } else if (firstOp instanceof Float || secondOp instanceof Float) { + return firstOp.floatValue() / secondOp.floatValue(); + } else if (firstOp instanceof Long || secondOp instanceof Long) { + return firstOp.doubleValue() / secondOp.longValue(); + } else { + return firstOp.floatValue() / secondOp.intValue(); + } + } +} diff --git a/src/main/java/ru/jengine/virtualmachine/commands/Command.java b/src/main/java/ru/jengine/virtualmachine/commands/Command.java new file mode 100644 index 0000000..bd15977 --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/commands/Command.java @@ -0,0 +1,10 @@ +package ru.jengine.virtualmachine.commands; + +import ru.jengine.virtualmachine.context.CommandContext; + +public interface Command { + Byte getOpCode(); + + int execute(CommandContext context); + +} diff --git a/src/main/java/ru/jengine/virtualmachine/commands/Const.java b/src/main/java/ru/jengine/virtualmachine/commands/Const.java new file mode 100644 index 0000000..241f31d --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/commands/Const.java @@ -0,0 +1,22 @@ +package ru.jengine.virtualmachine.commands; + +import ru.jengine.virtualmachine.context.CommandContext; + +public class Const implements Command { + private static final byte[] opCode = {0b0000_0000}; + private static final int offset = 2; + + @Override + public Byte getOpCode() { + return opCode[0]; // TODO: Byte[] opcode + } + + @Override + public int execute(CommandContext context) { + byte storageIndex = context.readBytes(1)[0]; + Object constValue = context.getElementByIndex(storageIndex); + context.pushToStack(constValue); + return offset; + } + +} diff --git a/src/main/java/ru/jengine/virtualmachine/commands/Div.java b/src/main/java/ru/jengine/virtualmachine/commands/Div.java new file mode 100644 index 0000000..94f33b8 --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/commands/Div.java @@ -0,0 +1,30 @@ +package ru.jengine.virtualmachine.commands; + +import ru.jengine.virtualmachine.context.CommandContext; +import ru.jengine.virtualmachine.exceptions.VirtualMachineException; + +public class Div implements Command { + private static final byte[] opCode = {0b0000_0100}; + private static final int offset = 1; + + @Override + public Byte getOpCode() { + return opCode[0]; + } + + @Override + public int execute(CommandContext context) { + Object firstOp = context.popFromStack(); + Object secondOp = context.popFromStack(); + if (firstOp instanceof Number && secondOp instanceof Number) { + try { + Number result = BinaryHandler.divTwoNumbers((Number) firstOp, (Number) secondOp); + context.pushToStack(result); + } catch (VirtualMachineException e) { + throw e; // TODO: ??? + } + } else throw new VirtualMachineException("Divide operation uses two instances of Number."); + return offset; + } + +} diff --git a/src/main/java/ru/jengine/virtualmachine/commands/Jump.java b/src/main/java/ru/jengine/virtualmachine/commands/Jump.java new file mode 100644 index 0000000..fbd85b0 --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/commands/Jump.java @@ -0,0 +1,21 @@ +package ru.jengine.virtualmachine.commands; + +import ru.jengine.virtualmachine.context.CommandContext; + +public class Jump implements Command { + + private static final byte[] opCode = {0b0111_1111}; + private static final int offset = 1; + + @Override + public Byte getOpCode() { + return opCode[0]; + } + + @Override + public int execute(CommandContext context) { + byte storageIndex = context.readBytes(1)[0]; + Integer offsetValue = (Integer) context.getElementByIndex(storageIndex); + return offsetValue; + } +} diff --git a/src/main/java/ru/jengine/virtualmachine/commands/Mul.java b/src/main/java/ru/jengine/virtualmachine/commands/Mul.java new file mode 100644 index 0000000..7d75adb --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/commands/Mul.java @@ -0,0 +1,25 @@ +package ru.jengine.virtualmachine.commands; + +import ru.jengine.virtualmachine.context.CommandContext; +import ru.jengine.virtualmachine.exceptions.VirtualMachineException; + +public class Mul implements Command { + private static final byte[] opCode = {0b0000_0011}; + private static final int offset = 1; + + @Override + public Byte getOpCode() { + return opCode[0]; + } + + @Override + public int execute(CommandContext context) { + Object firstOp = context.popFromStack(); + Object secondOp = context.popFromStack(); + if (firstOp instanceof Number && secondOp instanceof Number) { + Number result = BinaryHandler.mulTwoNumbers((Number) firstOp, (Number) secondOp); + context.pushToStack(result); + } else throw new VirtualMachineException("Multiply operation uses two instances of Number."); + return offset; + } +} diff --git a/src/main/java/ru/jengine/virtualmachine/commands/Sub.java b/src/main/java/ru/jengine/virtualmachine/commands/Sub.java new file mode 100644 index 0000000..7e596c4 --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/commands/Sub.java @@ -0,0 +1,26 @@ +package ru.jengine.virtualmachine.commands; + +import ru.jengine.virtualmachine.context.CommandContext; +import ru.jengine.virtualmachine.exceptions.VirtualMachineException; + +public class Sub implements Command { + private static final byte[] opCode = {0b0000_0010}; + private static final int offset = 1; + + @Override + public Byte getOpCode() { + return opCode[0]; + } + + @Override + public int execute(CommandContext context) { + Object firstOp = context.popFromStack(); + Object secondOp = context.popFromStack(); + if (firstOp instanceof Number && secondOp instanceof Number) { + Number result = BinaryHandler.subTwoNumbers((Number) firstOp, (Number) secondOp); + context.pushToStack(result); + } else throw new VirtualMachineException("Sub operation uses two instances of Number."); + return offset; + } + +} diff --git a/src/main/java/ru/jengine/virtualmachine/context/CommandContext.java b/src/main/java/ru/jengine/virtualmachine/context/CommandContext.java new file mode 100644 index 0000000..c823f5d --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/context/CommandContext.java @@ -0,0 +1,14 @@ +package ru.jengine.virtualmachine.context; + +public interface CommandContext { + + Object popFromStack(); + + Object peekFromStack(); + + void pushToStack(Object element); + + Object getElementByIndex(Byte index); + + byte[] readBytes(int n); +} diff --git a/src/main/java/ru/jengine/virtualmachine/context/ScriptContext.java b/src/main/java/ru/jengine/virtualmachine/context/ScriptContext.java new file mode 100644 index 0000000..a394e04 --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/context/ScriptContext.java @@ -0,0 +1,59 @@ +package ru.jengine.virtualmachine.context; + +import ru.jengine.virtualmachine.VMStack; + +import java.util.List; + +public class ScriptContext implements CommandContext { + private final VMStack stack = new VMStack(1024); + private final List storage; + private final byte[] code; + private int instructionPointer = 0; + + public ScriptContext(List storage, byte[] code) { + this.storage = storage; + this.code = code; + } + + public int getInstructionPointer() { + return instructionPointer; + } + + public Object popFromStack() { // TODO: check empty + return stack.pop(); + } + + public Object peekFromStack() { + return stack.peek(); + } + + public void pushToStack(Object element) { + try { + stack.push(element); + } catch (StackOverflowError e) { + throw e; // TODO: ??? + } + } + + public Object getElementByIndex(Byte index) { + return storage.get(index.intValue()); + } + + public void moveInstructionPointer(int offset) { + this.instructionPointer += offset; + } + + public byte[] readBytes(int n) { + byte[] slice = new byte[n]; + System.arraycopy(code, instructionPointer + 1, slice, 0, n); + return slice; + } + + public byte[] getCode() { + return code; + } + + public boolean isScriptRunning() { + return instructionPointer < code.length; + } +} diff --git a/src/main/java/ru/jengine/virtualmachine/exceptions/VirtualMachineException.java b/src/main/java/ru/jengine/virtualmachine/exceptions/VirtualMachineException.java new file mode 100644 index 0000000..32a33e5 --- /dev/null +++ b/src/main/java/ru/jengine/virtualmachine/exceptions/VirtualMachineException.java @@ -0,0 +1,7 @@ +package ru.jengine.virtualmachine.exceptions; + +public class VirtualMachineException extends RuntimeException { + public VirtualMachineException(String message) { + super(message); + } +}