Administrator approval is now required for registering new accounts. If you are registering a new account, and are external to the University, please ask the repository owner to contact ServiceLine to request your account be approved. Repository owners must include the newly registered email address, and specific repository in the request for approval.

Verified Commit 601b0f76 authored by Emily Rowlands's avatar Emily Rowlands
Browse files

Wrote an actual BB interpreter

parent 8b2ea6b4
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());
}