Commit e7163ddd authored by jm15g21's avatar jm15g21
Browse files

Crazy interpreter mk I

parent 503df360
package Exceptions;
public class InvalidIdentifierException extends Exception {
public InvalidIdentifierException(String errorMessage) {
super(errorMessage);
}
}
package Exceptions;
public class NegativeVariableException extends Exception {
public NegativeVariableException(String errorMessage) {
super(errorMessage);
}
}
package Interpreter;
import java.util.Arrays;
public class Instruction {
public Instruction(int instructionIndex, String lineText) {
this.instructionIndex = instructionIndex;
//Parse the line text and get the identifier and arguments
//Sanitize away bad whitespace, including multiple spaces,
//a random space at the start of a line for no reason and all
//without removing the spaces between arguments
String sanitizedText = lineText.replaceAll("[^\\S ]| +|^ *|;$", "");
if(sanitizedText.contains(" ")) {
identifier = sanitizedText.substring(0, sanitizedText.indexOf(" "));
//Gets all but the first element of the lineText array as arguments
arguments = sanitizedText.substring(sanitizedText.indexOf(" ") + 1).split(" ");
} else {
identifier = sanitizedText;
arguments = new String[]{ };
}
//Debugging mode
System.out.println(instructionIndex + ": " + identifier + "(" + String.join(", ", arguments) + ")");
}
//The index of the instruction in the list of instructions
private int instructionIndex;
//Identifier of the instruction
private String identifier;
//Arguments of the instruction
private String arguments[];
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public String[] getArguments() {
return arguments;
}
public void setArguments(String[] arguments) {
this.arguments = arguments;
}
public int getInstructionIndex() {
return instructionIndex;
}
public void setInstructionIndex(int instructionIndex) {
this.instructionIndex = instructionIndex;
}
}
package Interpreter;
import Exceptions.InvalidIdentifierException;
import Exceptions.NegativeVariableException;
import Managers.IManager;
import Managers.LoopManager;
import Operations.*;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
public class Interpreter {
private final boolean SIMPLIFIED_VIEW = false;
//A store of all the variables
private Hashtable<String, Integer> variableStore = new Hashtable<>();
public Interpreter() {
setupManagers();
setupOperations();
}
//=======================
// Basic Functionality
//=======================
public int executeProgram(int maxIterations) {
//Set the instruction pointer to the first one
instructionPointer = 0;
int remainingIterations = maxIterations;
while(remainingIterations > 0 && !isFinished())
{
try {
executeInstruction();
} catch (Exception e) {
e.printStackTrace();
return 1;
}
//Sanity Check
remainingIterations --;
}
if(remainingIterations <= 0)
{
System.out.println("REACHED MAXIMUM REPETITION DEPTH: 5000");
}
//Successful operation, return 0
return 0;
}
/**
* Executes a single instruction and moves on
* Works kind of like a CPU in some weird and abstract way
* which is pretty cool... I guess
*/
public void executeInstruction() throws NegativeVariableException, InvalidIdentifierException {
//Fetch the current instruction
Instruction currentInstruction = getCurrentInstruction();
//Get the operation from the instruction
IOperation instructionsOperation = fetchOperationFromIdentifier(currentInstruction.getIdentifier());
//Check
if(instructionsOperation == null)
{
System.err.println("Unknown instruction: " + currentInstruction.getIdentifier());
System.exit(1);
}
//Check for altered instruction pointer
int lastInstrPointer = instructionPointer;
//Execute the instruction
instructionsOperation.operate(currentInstruction, this);
//Print the state of the interpreter
if(lastInstrPointer == instructionPointer) {
printState();
}
//Increment the instruction pointer
instructionPointer ++;
}
/**
* @return Returns true if we have completed execution of the current program
*/
public boolean isFinished() {
return instructionPointer >= instructions.size();
}
/**
* Ah finally, after writing the entire interpreter without running a single test on anything
* the ability to read a program finally exists.
* This reads a program from a file and then loads it into the interpreter.
*/
public void readProgram(String fileName) {
//Clear out old instructions
instructions.clear();
//Create the stuff required to read a file
BufferedReader bufferedReader;
//Create the file reader and buffered reader.
try {
FileReader fileReader = new FileReader(fileName);
bufferedReader = new BufferedReader(fileReader);
} catch (FileNotFoundException e) {
System.out.println("File could not be located!");
return;
}
//Begin reading
try {
//Get the first line
String line = bufferedReader.readLine();
//Keep track of the index, it will be important later
int index = 0;
//Keep reading until we reach the end
while (line != null) {
//Process this line, add the new instruction to the instruction list
Instruction instruction = new Instruction(index, line);
instructions.add(instruction);
//Read the next line
line = bufferedReader.readLine();
//Increment the index
index ++;
}
} catch (IOException e) {
e.printStackTrace();
return;
}
System.out.println("====PROGRAM READ====");
}
public Hashtable<String, Integer> getVariableStore() {
return variableStore;
}
//=======================
// Operations
// A unique set of singleton objects that determine how to act
// when a specific operation is called.
// There exists 1 for each operation. (clear sets variable to 0 etc.)
//=======================
//A store of the operations
private Hashtable<String, IOperation> interpreterOperations = new Hashtable<>();
public IOperation fetchOperationFromIdentifier(String identifier) {
return interpreterOperations.get(identifier);
}
private void setupOperations() {
addOperation(new OperationClear());
addOperation(new OperationDecr());
addOperation(new OperationEnd());
addOperation(new OperationIncr());
addOperation(new OperationWhile());
}
private void addOperation(IOperation operation)
{
interpreterOperations.put(operation.getOperationIdentifier(), operation);
}
//=======================
// Instructions
// The list of instructions we are reading, line by line.
//=======================
//Pointer to where the current instruction is
private int instructionPointer;
private ArrayList<Instruction> instructions = new ArrayList<>();
public Instruction getCurrentInstruction() {
return instructions.get(instructionPointer);
}
public Instruction fetchInstruction(int lineNumber) {
return instructions.get(lineNumber);
}
/**
* Jumps to an instruction
* Note that the instruction jumping jumps to BEFORE the instruction, not after it
*/
public void jumpToInstruction(Instruction instruction) {
instructionPointer = instruction.getInstructionIndex();
}
/**
* Jumps to an instruction
* Note that the instruction jumping jumps to BEFORE the instruction, not after it
*/
public void jumpToInstructionIndex(int instructionIndex) {
instructionPointer = instructionIndex;
}
//=======================
// Managers
// what is a manager?
// A manager is a way of storing stuff on the interpreter without putting it directly on
// I don't know why I did it this way tbh seems pointless but clean I guess?
//=======================
//A dictionary of all the managers with their type as the key.
private Hashtable<Class, IManager> managerList;
private void setupManagers() {
managerList = new Hashtable<>();
addManager(new LoopManager());
}
private void addManager(IManager manager) {
managerList.put(manager.getClass(), manager);
}
public IManager getManager(Class managerType) {
return managerList.get(managerType);
}
//========== UTIL
public void printState() {
if(SIMPLIFIED_VIEW)
{
System.out.println("\n" + getCurrentInstruction().getIdentifier() + "(" + String.join(", ", getCurrentInstruction().getArguments()) + ")");
for (String variableName: variableStore.keySet()) {
int value = variableStore.get(variableName);
System.out.print(variableName + ": " + value + " | ");
}
return;
}
System.out.println("===========================");
System.out.println(" CURRENT INTERPRETER STATE ");
System.out.println("===========================");
System.out.println(" STATUS");
System.out.println("===========================");
System.out.println("Instruction Pointer: " + instructionPointer);
System.out.println("Instruction Count: " + instructions.size());
if(instructionPointer < instructions.size()) {
System.out.println("Instruction: " + getCurrentInstruction().getIdentifier());
System.out.println("Arguments: " + String.join(", ", getCurrentInstruction().getArguments()));
}
System.out.println("===========================");
System.out.println(" VARIABLES");
System.out.println("===========================");
for (String variableName: variableStore.keySet()) {
int value = variableStore.get(variableName);
System.out.println(variableName + ": " + value);
}
System.out.println("===========================");
}
}
import Interpreter.Interpreter;
public class Main {
public static void main(String args[]) {
Interpreter interpreter = new Interpreter();
//TODO: Im lazy so we hard code the max iterations and program to run
interpreter.readProgram(args.length == 0 ? "Programs/MultipleTwoNumbers.bb" : args[0]);
interpreter.executeProgram(5000);
}
}
package Managers;
public interface IManager {
}
package Managers;
import Interpreter.Instruction;
import Operations.ILoopOperation;
import java.util.Stack;
public class LoopManager implements IManager {
//The loop stack
private Stack<Instruction> loopStack = new Stack<>();
public void addLoopToStack(Instruction parentInstruction) {
loopStack.push(parentInstruction);
}
public Instruction getParentLoop() {
return loopStack.peek();
}
public void breakLoop() {
loopStack.pop();
}
}
package Operations;
public interface ILoopOperation {
}
package Operations;
import Exceptions.InvalidIdentifierException;
import Exceptions.NegativeVariableException;
import Interpreter.Interpreter;
import Interpreter.Instruction;
public interface IOperation {
/**
* Gets the identifier of the operation, what is used to call it.
* @return a string that represents the ID of the operation
*/
public String getOperationIdentifier();
/**
* Validates that the operation is written in the correct format (It has the right amount of arguments).
* @param args the arguments passed to the operation
* @return True if the operation is valid, False if it is invalid
*/
public boolean validate(String args[]);
/**
* Performs the operation
* @param instruction The line number that the operation was called on
* @param master The master interpreter that called this operation
*/
void operate(Instruction instruction, Interpreter master) throws NegativeVariableException, InvalidIdentifierException;
}
package Operations;
import Interpreter.Interpreter;
import Interpreter.Instruction;
public class OperationClear implements IOperation {
/**
* Gets the identifier of the operation, what is used to call it.
*
* @return a string that represents the ID of the operation
*/
@Override
public String getOperationIdentifier() {
return "clear";
}
/**
* Validates that the operation is written in the correct format (It has the right amount of arguments).
*
* @param args the arguments passed to the operation
* @return True if the operation is valid, False if it is invalid
*/
@Override
public boolean validate(String[] args) {
//Clear operation requires a single argument, the variable being cleared.
return args.length == 1;
}
/**
* Performs the operation
* @param instruction
* @param master The master interpreter that called this operation
*/
@Override
public void operate(Instruction instruction, Interpreter master) {
//Remove the variable from the variable store, if it exists.
master.getVariableStore().remove(instruction.getArguments()[0]);
}
}
package Operations;
import Exceptions.NegativeVariableException;
import Interpreter.Interpreter;
import Interpreter.Instruction;
import java.util.Hashtable;
public class OperationDecr implements IOperation{
/**
* Gets the identifier of the operation, what is used to call it.
*
* @return a string that represents the ID of the operation
*/
@Override
public String getOperationIdentifier() {
return "decr";
}
/**
* Validates that the operation is written in the correct format (It has the right amount of arguments).
*
* @param args the arguments passed to the operation
* @return True if the operation is valid, False if it is invalid
*/
@Override
public boolean validate(String[] args) {
//Decrement takes a single argument, the variable to be decremented.
return args.length == 1;
}
/**
* Performs the operation
* @param instruction
* @param master The master interpreter that called this operation
*/
@Override
public void operate(Instruction instruction, Interpreter master) throws NegativeVariableException {
//Get the variable store, returned as a reference so is the same as the one in the interpreter
//It could be static I guess, but we can have multiple interpreters running at once if we ever
//needed that for some reason.
Hashtable<String, Integer> variableStoreReference = master.getVariableStore();
//Check if the variable already exists in the variable store
if (variableStoreReference.containsKey(instruction.getArguments()[0]))
{
//Decrement the variable with the name passed in as argument by 1.
int integerObjectReference = variableStoreReference.get(instruction.getArguments()[0]);
if(integerObjectReference == 1) {
//Remove the reference to it, since it is now 0
variableStoreReference.remove(instruction.getArguments()[0]);
}
else {
//Put it back in, referencing doesn't work on Integer class?
variableStoreReference.replace(instruction.getArguments()[0], integerObjectReference - 1);
}
}
else
{
throw new NegativeVariableException("Variable " + instruction.getArguments()[0] + " decremented without existing.");
}
}
}
package Operations;
import Exceptions.InvalidIdentifierException;
import Exceptions.NegativeVariableException;
import Interpreter.Interpreter;
import Interpreter.Instruction;
import Managers.LoopManager;
public class OperationEnd implements IOperation {
/**
* Gets the identifier of the operation, what is used to call it.
*
* @return a string that represents the ID of the operation
*/
@Override
public String getOperationIdentifier() {
return "end";
}
/**
* Validates that the operation is written in the correct format (It has the right amount of arguments).
*
* @param args the arguments passed to the operation
* @return True if the operation is valid, False if it is invalid
*/
@Override
public boolean validate(String[] args) {
return false;
}
/**
* Performs the operation
* @param instruction The line number that the operation was called on
* @param master The master interpreter that called this operation
*/
@Override
public void operate(Instruction instruction, Interpreter master) throws NegativeVariableException, InvalidIdentifierException {
//Fetch the loop manager from the master
LoopManager loopManager = (LoopManager) master.getManager(LoopManager.class);
//Peek at our master loop's line
Instruction parentLoopInstruction = loopManager.getParentLoop();
//Get the operation for that line so we can determine validity of loop condition
OperationLoop loopOperation = (OperationLoop) master.fetchOperationFromIdentifier(
parentLoopInstruction.getIdentifier());
//if the condition is satisfied jump the interpreter backwards
//otherwise break out of that loop (by continuing as usual)
if(loopOperation.isSatisfied(master, parentLoopInstruction))
{
//Print the state to show we reached the end
master.printState();
//Jump the interpreter to the line after the parent loop and continue
master.jumpToInstructionIndex(parentLoopInstruction.getInstructionIndex());
}
else
{
//We just broke out of the loop, so indicate this to the loop manager so at the next loop
//we jump back to the correct loop and not the old one
loopManager.breakLoop();
}
}
}
package Operations;
import Interpreter.Interpreter;
import Interpreter.Instruction;
import java.util.Hashtable;
public class OperationIncr implements IOperation{
/**
* Gets the identifier of the operation, what is used to call it.
*
* @return a string that represents the ID of the operation
*/
@Override
public String getOperationIdentifier() {
return "incr";
}
/**
* Validates that the operation is written in the correct format (It has the right amount of arguments).
*
* @param args the arguments passed to the operation
* @return True if the operation is valid, False if it is invalid
*/