Skip to content
Snippets Groups Projects
Verified Commit 601b0f76 authored by Emily Rowlands's avatar Emily Rowlands
Browse files

Wrote an actual BB interpreter

parent 8b2ea6b4
No related branches found
No related tags found
No related merge requests found
Showing
with 305 additions and 420 deletions
clear X;
incr X;
incr X;
incr X;
while X not 0 do;
decr X;
end;
clear X;
incr X;
incr X;
clear Y;
incr Y;
incr Y;
incr Y;
clear Z;
while X not 0 do;
clear W;
while Y not 0 do;
incr Z;
incr W;
decr Y;
end;
while W not 0 do;
incr Y;
decr W;
end;
decr X;
end;
package jrr1g18.bb;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Interpreter {
private Map<String, Integer> m_vars = new HashMap<>();
private Map<Integer, String> m_code = new HashMap<>();
private Map<Integer, Integer> whiles = new HashMap<>();
private int m_lineCount = 0;
private int m_whileDepth = 0;
private static final Map<InstructionType, String> INSTRUCTION_NAMES;
static {
Map<InstructionType, String> tempMap = new HashMap<>();
tempMap.put(InstructionType.CLEAR, "clear");
tempMap.put(InstructionType.DECR, "decr");
tempMap.put(InstructionType.INCR, "incr");
tempMap.put(InstructionType.WHILE, "while");
tempMap.put(InstructionType.END, "end;");
INSTRUCTION_NAMES = Collections.unmodifiableMap(tempMap);
}
private enum InstructionType {
INCR, DECR, CLEAR, WHILE, END,
}
@SuppressWarnings("unused")
private String instructionTypeToString(InstructionType it) {
return INSTRUCTION_NAMES.get(it);
}
private InstructionType stringToInstructionType(String instruction) {
for(Entry<InstructionType, String> entry : INSTRUCTION_NAMES
.entrySet()) {
if(Objects.equals(instruction, entry.getValue())) {
return entry.getKey();
}
}
return null;
}
private static String getVarFromLine(String line) {
Matcher m =
Pattern.compile("(?<=incr|clear|decr)(.+)(?=;)").matcher(line);
if(!m.find()) {
System.err.println(line);
System.exit(1);
}
return stripWhitespace(m.group(1));
}
public void run(String fileName) {
readCode(fileName);
}
public String variablesToString() {
return m_vars.toString();
}
private String getLine(int line) {
return m_code.get(line);
}
private String[] splitLine(String line) {
return stripWhitespace(line).split("\\s+");
}
private String[] getSplitLine(int line) {
return splitLine(getLine(line));
}
public static String stripWhitespace(String str) {
return str.replaceAll("(^\\s+|\\s+$)", "");
}
private boolean isVariableNull(String name) {
return m_vars.get(name) == null;
}
private void codeCheck() {
String line;
String[] splitLine;
int whileDepth = 0;
for(int idx = 0; idx < m_lineCount; ++idx) {
line = stripWhitespace(getLine(idx));
splitLine = splitLine(line);
if(line.equals("")) {
continue;
}
if(!line.matches(".*;$")) {
System.err.println("missing ; on line " + (idx + 1));
System.exit(1);
}
if(!splitLine[0].equals("incr") && !splitLine[0].equals("decr")
&& !splitLine[0].equals("end;")
&& !splitLine[0].equals("clear")
&& !splitLine[0].equals("while")) {
System.err.println("Syntax error on line " + (idx + 1));
System.exit(1);
}
InstructionType instruction = stringToInstructionType(splitLine[0]);
if((instruction == InstructionType.CLEAR
|| instruction == InstructionType.DECR
|| instruction == InstructionType.INCR)
&& splitLine.length > 2) {
System.err.println("Too many arguments on line " + (idx + 1));
System.exit(1);
}
switch (instruction) {
case WHILE:
whileDepth++;
splitLine[4] = splitLine[1].replace(
splitLine[1].substring(splitLine[1].length() - 1), "");
if(!splitLine[2].equals("not") || splitLine[4].equals("do")
|| splitLine.length > 5) {
System.err.println("Syntax error on line " + (idx + 1));
System.exit(1);
}
break;
case END:
whileDepth--;
if(splitLine[0].equals("end;") && splitLine.length > 1) {
System.err.println("Syntax error on line " + (idx + 1));
System.exit(1);
}
break;
default:
break;
}
}
if(whileDepth > 0) {
System.err.println(
"There are more while statements than end statements");
} else if(whileDepth < 0) {
System.err.println(
"There are more end statements than while statements");
}
}
private void readCode(String nameOfTheFile) {
try(BufferedReader br =
new BufferedReader(new FileReader(nameOfTheFile))) {
String line;
while((line = br.readLine()) != null) {
m_code.put(m_lineCount, line);
m_lineCount++;
}
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
codeCheck();
exec();
}
private void exec() {
String[] splitLine;
for(int idx = 0; idx < m_lineCount; ++idx) {
splitLine = getSplitLine(idx);
InstructionType instruction = stringToInstructionType(splitLine[0]);
switch (instruction) {
case WHILE:
case END:
idx = loop(idx, splitLine);
continue;
default:
break;
}
String var = getVarFromLine(getLine(idx));
switch (instruction) {
case CLEAR:
clear(var);
break;
case INCR:
incr(var);
break;
case DECR:
decr(var);
break;
default:
break;
}
}
}
private void incr(String name) {
if(isVariableNull(name)) {
System.err.println("Error: Variable " + name + " doesn't exist");
System.exit(1);
} else {
m_vars.put(name, m_vars.get(name) + 1);
}
}
private void decr(String name) {
if(isVariableNull(name)) {
System.err.println("Error: Variable " + name + " doesn't exist");
System.exit(1);
} else if(m_vars.get(name) == 0) {
System.err
.println("Error: Variable " + name + " cannot be negative");
System.exit(1);
} else {
m_vars.put(name, m_vars.get(name) - 1);
}
}
private void clear(String name) {
m_vars.put(name, 0);
}
private int loop(int idx, String[] splitLine) {
String line;
if(splitLine[0].equals("while")) {
m_whileDepth++;
whiles.put(m_whileDepth, idx);
int condition = Integer.parseInt(splitLine[3]);
if(isVariableNull(splitLine[1])
&& m_vars.get(splitLine[1]) == condition) {
while(!splitLine[0].equals("end;")) {
idx++;
line = getLine(idx);
splitLine = splitLine(line);
}
}
} else {
line = m_code.get(whiles.get(m_whileDepth));
splitLine = splitLine(line);
int condition = Integer.parseInt(splitLine[3]);
if(m_vars.get(splitLine[1]) != condition)
idx = whiles.get(m_whileDepth);
else {
m_whileDepth--;
}
}
return idx;
}
}
package jrr1g18.bb;
public class Main {
public static void main(String[] args) {
Interpreter interpreter = new Interpreter();
interpreter.run("multiply.bb");
System.out.println(interpreter.variablesToString());
}
}
package jrr1g18.boring;
import jrr1g18.boring.Token.TokenType;
import jrr1g18.boring.exceptions.IllegalCharacterException;
import jrr1g18.boring.exceptions.IllegalTokenException;
/**
* Interpreter for Boring
*
* @author jrr1g18
*
*/
public class Interpreter {
private Lexer m_lexer;
private Token m_currentToken;
public Interpreter(Lexer lexer) {
m_lexer = lexer;
try {
m_currentToken = m_lexer.getNextToken();
} catch(IllegalCharacterException e) {
e.printStackTrace();
}
}
/**
* Compare current token with tt. If they match, then advance the Lexer.
*
* @param tt The expected TokenType
* @throws IllegalTokenException When the token types do not match
*/
public void eat(TokenType tt) throws IllegalTokenException {
if(m_currentToken.getType() == tt) {
try {
m_currentToken = m_lexer.getNextToken();
} catch(IllegalCharacterException e) {
e.printStackTrace();
}
} else {
throw new IllegalTokenException(
"Expected: " + Lib.tokenTypeToString(tt) + " got: "
+ Lib.tokenTypeToString(m_currentToken.getType()));
}
}
/**
* number : INTEGER
*
* @return An integer token value.
* @throws IllegalTokenException
*/
public int number() throws IllegalTokenException {
Integer value = ((ValueToken) m_currentToken).getValue();
eat(TokenType.INTEGER);
return value;
}
/**
* mulDiv : number ((MUL|DIV) number)*
*
* @return The expression result.
* @throws IllegalTokenException When an illegal token is enountered.
*/
public int mulDiv() throws IllegalTokenException {
int result = number();
while(m_currentToken.getType() == TokenType.MUL
|| m_currentToken.getType() == TokenType.DIV) {
ValueToken token = (ValueToken) m_currentToken;
switch (token.getType()) {
case MUL:
eat(TokenType.MUL);
result *= number();
break;
case DIV:
eat(TokenType.DIV);
result /= number();
break;
default:
break;
}
}
return result;
}
/**
* addSub : number ((ADD|SUB) number)*
*
* @return The expression result.
* @throws IllegalTokenException When an illegal token is enountered.
*/
public int addSub() throws IllegalTokenException {
int result = number();
while(m_currentToken.getType() == TokenType.ADD
|| m_currentToken.getType() == TokenType.SUB) {
ValueToken token = (ValueToken) m_currentToken;
switch (token.getType()) {
case ADD:
eat(TokenType.ADD);
result += number();
break;
case SUB:
eat(TokenType.SUB);
result -= number();
break;
default:
break;
}
}
return result;
}
}
package jrr1g18.boring;
import jrr1g18.boring.Token.TokenType;
import jrr1g18.boring.exceptions.IllegalCharacterException;
public class Lexer {
private String m_text;
private int m_pos;
private String m_currentChar;
public Lexer(String text) {
m_text = text;
}
/**
* Advance m_pos and set m_currentChar
*/
private void nextChar() {
m_pos += 1;
if(m_pos > m_text.length() - 1) {
m_currentChar = null;
} else {
m_currentChar = "" + m_text.charAt(m_pos);
}
}
/**
* Advance until EOF or end of whitespace
*/
public void skipWhitespace() {
while(m_currentChar != null && Lib.isWhitespace(m_currentChar)) {
nextChar();
}
}
/**
* @return A multidigit integer generated from input
*/
public int integer() {
String ret = "";
while(m_currentChar != null && Lib.isInt(m_currentChar)) {
ret += m_currentChar;
nextChar();
}
return Integer.parseInt(ret);
}
/**
* Tokeniser
*
* @return The next token
* @throws IllegalCharacterException When an unknown character is found
*/
public Token getNextToken() throws IllegalCharacterException {
while(m_currentChar != null) {
if(Lib.isWhitespace(m_currentChar)) {
skipWhitespace();
continue;
}
if(Lib.isInt(m_currentChar)) {
return new ValueToken(TokenType.INTEGER, integer());
} else if(m_currentChar
.equals(Lib.tokenTypeToString(TokenType.ADD))) {
nextChar();
return new NamedToken(TokenType.ADD,
Lib.tokenTypeToString(TokenType.ADD));
} else if(m_currentChar
.equals(Lib.tokenTypeToString(TokenType.SUB))) {
nextChar();
return new NamedToken(TokenType.SUB,
Lib.tokenTypeToString(TokenType.SUB));
} else if(m_currentChar
.equals(Lib.tokenTypeToString(TokenType.MUL))) {
nextChar();
return new NamedToken(TokenType.MUL,
Lib.tokenTypeToString(TokenType.MUL));
} else if(m_currentChar
.equals(Lib.tokenTypeToString(TokenType.DIV))) {
nextChar();
return new NamedToken(TokenType.DIV,
Lib.tokenTypeToString(TokenType.DIV));
} else {
throw new IllegalCharacterException(
"Unrecognised thing: " + m_currentChar);
}
}
return new Token(TokenType.EOF);
}
}
package jrr1g18.boring;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import jrr1g18.boring.Token.TokenType;
public abstract class Lib {
/**
* Checks if a string is an integer
*
* @param string String to check
* @return True if an integer, false otherwise
*/
public static boolean isInt(String string) {
return string.matches("\\d+");
}
/**
* Checks if a string is only whitespace
*
* @param string String to check
* @return True if an integer, false otherwise
*/
public static boolean isWhitespace(String string) {
return string.matches("\\s+");
}
private static final Map<TokenType, String> TOKEN_MAP;
static {
HashMap<TokenType, String> tempMap = new HashMap<TokenType, String>();
tempMap.put(TokenType.EOF, "EOF");
tempMap.put(TokenType.INTEGER, "INTEGER");
tempMap.put(TokenType.SEMICOLON, "SEMICOLON");
tempMap.put(TokenType.DIV, "/");
tempMap.put(TokenType.SUB, "-");
tempMap.put(TokenType.ADD, "+");
tempMap.put(TokenType.MUL, "*");
TOKEN_MAP = Collections.unmodifiableMap(tempMap);
}
public static String tokenTypeToString(final TokenType tt) {
return TOKEN_MAP.get(tt);
}
}
package jrr1g18.boring;
public class Main {
public static void main(String[] args) throws Exception {
Lexer lexer = new Lexer("1+2");
Interpreter interpreter = new Interpreter(lexer);
System.out.println(interpreter.addSub());
}
}
package jrr1g18.boring;
public class NamedToken extends Token {
private String m_name;
public NamedToken(TokenType type, String value) {
super(type);
m_name = new String(value);
}
public NamedToken(TokenType type) {
super(type);
m_name = null;
}
@Override
public String toString() {
return "ValueToken(" + Lib.tokenTypeToString(getType()) + ", "
+ getName() + ")";
}
public String getName() {
return m_name;
}
public void setName(final NamedToken value) {
m_name = new String(value.getName());
}
public void setName(final String value) {
m_name = new String(value);
}
}
package jrr1g18.boring;
public class Token {
public enum TokenType {
EOF, INTEGER, SEMICOLON, ADD, SUB, MUL, DIV,
}
private final TokenType m_type;
public Token(TokenType type) {
m_type = type;
}
public TokenType getType() {
return m_type;
}
public String toString() {
return "Token(" + Lib.tokenTypeToString(m_type) + ")";
}
}
\ No newline at end of file
package jrr1g18.boring;
public class ValueToken extends Token {
private Integer m_value;
public ValueToken(TokenType type, final Integer name) {
super(type);
m_value = new Integer(name);
}
public ValueToken(TokenType type, final int name) {
super(type);
m_value = new Integer(name);
}
public ValueToken(TokenType type) {
super(type);
m_value = null;
}
@Override
public String toString() {
return "ValueToken(" + Lib.tokenTypeToString(getType()) + ", "
+ getValue() + ")";
}
public Integer getValue() {
return m_value;
}
public void setValue(ValueToken value) {
m_value = value.getValue();
}
public void setValue(final Integer value) {
m_value = new Integer(value);
}
}
package jrr1g18.boring.exceptions;
public class BoringException extends Exception {
public BoringException(String message) {
super(message);
}
public BoringException(Throwable cause) {
super(cause);
}
public BoringException(String message, Throwable cause) {
super(message, cause);
}
public BoringException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
package jrr1g18.boring.exceptions;
public class IllegalCharacterException extends BoringException {
public IllegalCharacterException(String message) {
super(message);
}
public IllegalCharacterException(Throwable cause) {
super(cause);
}
public IllegalCharacterException(String message, Throwable cause) {
super(message, cause);
}
public IllegalCharacterException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
package jrr1g18.boring.exceptions;
public class IllegalTokenException extends BoringException {
public IllegalTokenException(String message) {
super(message);
}
public IllegalTokenException(Throwable cause) {
super(cause);
}
public IllegalTokenException(String message, Throwable cause) {
super(message, cause);
}
public IllegalTokenException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment