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 2c8e79de authored by Emily Rowlands's avatar Emily Rowlands
Browse files

Added tests and moved tokens

parent cf21a236
......@@ -7,6 +7,6 @@ DIV = /
LPAREN = (
RPAREN = )
factor ::= INTEGER
factor ::= INTEGER | LPAREN expr RPAREN
term ::= factor ((MUL|DIV) factor)*
expr ::= term ((ADD|SUB) term)*
\ No newline at end of file
package jrr1g18.boring;
import jrr1g18.boring.Token.TokenType;
import jrr1g18.boring.exceptions.IllegalCharacterException;
import jrr1g18.boring.exceptions.IllegalTokenException;
import jrr1g18.boring.tokens.Token;
import jrr1g18.boring.tokens.Token.TokenType;
import jrr1g18.boring.tokens.ValueToken;
/**
* Interpreter for Boring
......@@ -15,36 +18,67 @@ public class Interpreter {
public Interpreter(Lexer lexer) {
m_lexer = lexer;
m_currentToken = m_lexer.getNextToken();
try {
m_currentToken = m_lexer.getNextToken();
} catch(IllegalCharacterException e) {
e.printStackTrace();
}
}
private void err(TokenType expected, TokenType actual)
throws IllegalTokenException {
throw new IllegalTokenException(
"Expected: " + Lib.tokenTypeToString(expected) + " got: "
+ Lib.tokenTypeToString(actual));
}
private void err(TokenType actual) throws IllegalTokenException {
throw new IllegalTokenException(
"Illegal Token: " + Lib.tokenTypeToString(actual));
}
/**
* Compare current token with tt. If they match, then advance the Lexer.
*
* @param tt The expected TokenType
* @throws IllegalTokenException
*/
public void eat(TokenType tt) {
try {
if(m_currentToken.getType() == tt) {
public void eat(TokenType tt) throws IllegalTokenException {
if(m_currentToken.getType() == tt) {
try {
m_currentToken = m_lexer.getNextToken();
} else {
throw new IllegalTokenException("Expected: "
+ Lib.tokenTypeToString(tt) + " got: "
+ Lib.tokenTypeToString(m_currentToken.getType()));
} catch(IllegalCharacterException e) {
e.printStackTrace();
}
} catch(IllegalTokenException e) {
e.printStackTrace();
} else {
err(tt, m_currentToken.getType());
}
}
/**
* factor : INTEGER
* factor ::= INTEGER | LPAREN expr RPAREN
*
* @return An integer token value.
* @throws IllegalTokenException
*/
public int factor() {
Integer value = ((ValueToken) m_currentToken).getValue();
eat(TokenType.INTEGER);
public int factor() throws IllegalTokenException {
TokenType tt = m_currentToken.getType();
Integer value = null;
switch (tt) {
case INTEGER:
value = ((ValueToken) m_currentToken).getValue();
eat(TokenType.INTEGER);
break;
case LPAREN:
eat(TokenType.LPAREN);
value = expr();
eat(TokenType.RPAREN);
break;
default:
err(tt);
break;
}
return value;
}
......@@ -52,8 +86,9 @@ public class Interpreter {
* term : factor ((MUL|DIV) factor)*
*
* @return The expression result.
* @throws IllegalTokenException
*/
public int term() {
public int term() throws IllegalTokenException {
int result = factor();
while(m_currentToken.getType() == TokenType.MUL
|| m_currentToken.getType() == TokenType.DIV) {
......@@ -68,6 +103,7 @@ public class Interpreter {
result /= factor();
break;
default:
err(operation);
break;
}
}
......@@ -78,8 +114,9 @@ public class Interpreter {
* expr : term ((ADD|SUB) term)*
*
* @return The expression result.
* @throws IllegalTokenException
*/
public int expr() {
public int expr() throws IllegalTokenException {
int result = term();
while(m_currentToken.getType() == TokenType.ADD
|| m_currentToken.getType() == TokenType.SUB) {
......
package jrr1g18.boring;
import jrr1g18.boring.Token.TokenType;
import java.util.ArrayList;
import java.util.List;
import jrr1g18.boring.exceptions.IllegalCharacterException;
import jrr1g18.boring.tokens.BasicToken;
import jrr1g18.boring.tokens.Token;
import jrr1g18.boring.tokens.Token.TokenType;
import jrr1g18.boring.tokens.ValueToken;
public class Lexer {
private String m_text;
......@@ -11,7 +17,15 @@ public class Lexer {
public Lexer(String text) {
m_text = text;
m_pos = 0;
m_currentChar = "" + m_text.charAt(m_pos);
if(m_text.length() > 0) {
m_currentChar = "" + m_text.charAt(m_pos);
} else {
m_currentChar = null;
}
}
public String getText() {
return m_text;
}
/**
......@@ -29,7 +43,7 @@ public class Lexer {
/**
* Advance until EOF or end of whitespace
*/
public void skipWhitespace() {
private void skipWhitespace() {
while(m_currentChar != null && Lib.isWhitespace(m_currentChar)) {
nextChar();
}
......@@ -38,7 +52,7 @@ public class Lexer {
/**
* @return A multidigit integer generated from input
*/
public int integer() {
private int integer() {
String ret = "";
while(m_currentChar != null && Lib.isInt(m_currentChar)) {
ret += m_currentChar;
......@@ -51,46 +65,62 @@ public class Lexer {
* Tokeniser
*
* @return The next token
* @throws IllegalCharacterException When an unknown character is
* enountered.
*/
public Token getNextToken() {
public Token getNextToken() throws IllegalCharacterException {
while(m_currentChar != null) {
if(Lib.isWhitespace(m_currentChar)) {
skipWhitespace();
continue;
}
try {
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);
}
} catch(IllegalCharacterException e) {
e.printStackTrace();
if(Lib.isInt(m_currentChar)) {
return new ValueToken(TokenType.INTEGER, integer());
} else if(m_currentChar
.equals(Lib.tokenTypeToString(TokenType.ADD))) {
nextChar();
return new BasicToken(TokenType.ADD);
} else if(m_currentChar
.equals(Lib.tokenTypeToString(TokenType.SUB))) {
nextChar();
return new BasicToken(TokenType.SUB);
} else if(m_currentChar
.equals(Lib.tokenTypeToString(TokenType.MUL))) {
nextChar();
return new BasicToken(TokenType.MUL);
} else if(m_currentChar
.equals(Lib.tokenTypeToString(TokenType.DIV))) {
nextChar();
return new BasicToken(TokenType.DIV);
} else if(m_currentChar
.equals(Lib.tokenTypeToString(TokenType.LPAREN))) {
nextChar();
return new BasicToken(TokenType.LPAREN);
} else if(m_currentChar
.equals(Lib.tokenTypeToString(TokenType.RPAREN))) {
nextChar();
return new BasicToken(TokenType.RPAREN);
} else {
throw new IllegalCharacterException(
"Unrecognised thing: " + m_currentChar);
}
}
return new BasicToken(TokenType.EOF);
}
/**
*
* @return All tokens
* @throws IllegalCharacterException When an illegal character is
* encountered
*/
public List<Token> getAllTokens() throws IllegalCharacterException {
List<Token> ret = new ArrayList<Token>();
Token t;
do {
t = getNextToken();
ret.add(t);
} while(t.getType() != TokenType.EOF);
return ret;
}
}
......@@ -7,10 +7,16 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import jrr1g18.boring.Token.TokenType;
import jrr1g18.boring.tokens.Token.TokenType;
public abstract class Lib {
/**
* Get a line from stdin
*
* @param prompt The prompt to display
* @return The inputted string
*/
public static String getLine(String prompt) {
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));
......@@ -50,7 +56,11 @@ public abstract class Lib {
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.LPAREN, "(");
tempMap.put(TokenType.RPAREN, ")");
tempMap.put(TokenType.DIV, "/");
tempMap.put(TokenType.SUB, "-");
tempMap.put(TokenType.ADD, "+");
......
package jrr1g18.boring;
import jrr1g18.boring.exceptions.IllegalTokenException;
public class Main {
public static void main(String[] args) throws Exception {
inputLoop();
// Lexer lexer = new Lexer("1+2*3");
// Interpreter interpreter = new Interpreter(lexer);
// System.out.println(interpreter.expr());
// inputLoop();
Lexer lexer = new Lexer("3*(2+1)");
System.out.println(lexer.getText());
Interpreter interpreter = new Interpreter(lexer);
System.out.println(interpreter.expr());
}
public static void inputLoop() {
......@@ -18,7 +22,11 @@ public class Main {
}
Lexer lexer = new Lexer(input);
Interpreter interpreter = new Interpreter(lexer);
System.out.println(interpreter.expr());
try {
System.out.println(interpreter.expr());
} catch(IllegalTokenException e) {
e.printStackTrace();
}
}
}
......
package jrr1g18.boring;
package jrr1g18.boring.tokens;
import jrr1g18.boring.Lib;
public class BasicToken extends Token {
......@@ -6,4 +8,9 @@ public class BasicToken extends Token {
super(type);
}
@Override
public String toString() {
return "BasicToken(" + Lib.tokenTypeToString(getType()) + ")";
}
}
package jrr1g18.boring;
package jrr1g18.boring.tokens;
import jrr1g18.boring.Lib;
public class NamedToken extends Token {
private String m_name;
......
package jrr1g18.boring;
package jrr1g18.boring.tokens;
public abstract class Token {
public enum TokenType {
EOF, INTEGER, SEMICOLON, ADD, SUB, MUL, DIV,
EOF, INTEGER, SEMICOLON, ADD, SUB, MUL, DIV, LPAREN, RPAREN
}
private final TokenType m_type;
......@@ -16,8 +16,6 @@ public abstract class Token {
return m_type;
}
public String toString() {
return "Token(" + Lib.tokenTypeToString(m_type) + ")";
}
public abstract String toString();
}
\ No newline at end of file
package jrr1g18.boring;
package jrr1g18.boring.tokens;
import jrr1g18.boring.Lib;
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, final int value) {
super(type);
m_value = new Integer(value);
}
public ValueToken(TokenType type) {
super(type);
m_value = null;
super(type);
m_value = null;
}
@Override
public String toString() {
return "ValueToken(" + Lib.tokenTypeToString(getType()) + ", "
+ getValue() + ")";
return "ValueToken(" + Lib.tokenTypeToString(getType()) + ", "
+ getValue() + ")";
}
public Integer getValue() {
return m_value;
return m_value;
}
public void setValue(ValueToken value) {
m_value = value.getValue();
m_value = value.getValue();
}
public void setValue(final Integer value) {
m_value = new Integer(value);
m_value = new Integer(value);
}
}
package jrr1g18.boring.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import jrr1g18.boring.Interpreter;
import jrr1g18.boring.Lexer;
import jrr1g18.boring.exceptions.IllegalTokenException;
class InterpreterTest {
@Test
@DisplayName("Empty Test")
void emptyTest() {
Lexer lexer = new Lexer("");
Interpreter interpreter = new Interpreter(lexer);
Executable closure = () -> interpreter.expr();
assertThrows(IllegalTokenException.class, closure);
}
@Test
@DisplayName("Just whitespace Test")
void justWhitespaceTest() {
Lexer lexer = new Lexer(" ");
Interpreter interpreter = new Interpreter(lexer);
Executable closure = () -> interpreter.expr();
assertThrows(IllegalTokenException.class, closure);
}
@Test
@DisplayName("Basic Integer Test")
void basicIntTest() {
Lexer lexer = new Lexer("2");
Interpreter interpreter = new Interpreter(lexer);
try {
assertEquals(2, interpreter.expr());
} catch(IllegalTokenException e) {
e.printStackTrace();
fail("Exception thrown");
}
}
@Test
@DisplayName("Basic Add Test")
void basicAddTest() {
Lexer lexer = new Lexer("2+3");
Interpreter interpreter = new Interpreter(lexer);
try {
assertEquals(5, interpreter.expr());
} catch(IllegalTokenException e) {
e.printStackTrace();
fail("Exception thrown");
}
}
@Test
@DisplayName("Basic Subtraction Test")
void basicSubTest() {
Lexer lexer = new Lexer("3-2");
Interpreter interpreter = new Interpreter(lexer);
try {
assertEquals(1, interpreter.expr());
} catch(IllegalTokenException e) {
e.printStackTrace();
fail("Exception thrown");
}
}
@Test
@DisplayName("Basic Multiplication Test")
void basicMulTest() {
Lexer lexer = new Lexer("2*3");
Interpreter interpreter = new Interpreter(lexer);
try {
assertEquals(6, interpreter.expr());
} catch(IllegalTokenException e) {
e.printStackTrace();
fail("Exception thrown");
}
}
@Test
@DisplayName("Basic Division Test")
void basicDivTest() {
Lexer lexer = new Lexer("10/2");
Interpreter interpreter = new Interpreter(lexer);
try {
assertEquals(5, interpreter.expr());
} catch(IllegalTokenException e) {
e.printStackTrace();
fail("Exception thrown");
}
}
@Test
@DisplayName("Parentheses Test")
void parenTest() {
Lexer lexer = new Lexer("(2+3)");
Interpreter interpreter = new Interpreter(lexer);
try {
assertEquals(5, interpreter.expr());
} catch(IllegalTokenException e) {
e.printStackTrace();
fail("Exception thrown");
}
}
@Test
@DisplayName("Complex Parentheses Test")
void complexParenTest() {
Lexer lexer = new Lexer(
"7 + 3 * (10 / (12 / (3 + 1) - 1)) / (2 + 3) - 5 - 3 + (8)");
Interpreter interpreter = new Interpreter(lexer);
try {
assertEquals(10, interpreter.expr());
} catch(IllegalTokenException e) {
e.printStackTrace();
fail("Exception thrown");
}
}
}
package jrr1g18.boring.test;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import jrr1g18.boring.Lexer;
import jrr1g18.boring.exceptions.IllegalCharacterException;
import jrr1g18.boring.tokens.BasicToken;
import jrr1g18.boring.tokens.Token;
import jrr1g18.boring.tokens.Token.TokenType;
import jrr1g18.boring.tokens.ValueToken;
class LexerTest {
@Test
@DisplayName("Empty Test")
void emptyTest() {
Lexer lexer = new Lexer("");
List<Token> expected = new ArrayList<Token>();
expected.add(new BasicToken(TokenType.EOF));
List<Token> actual = null;
try {
actual = lexer.getAllTokens();
} catch(IllegalCharacterException e) {
e.printStackTrace();
fail("Exception thrown.");
}
Lib.assertEqualTokenList(expected, actual);
}
@Test
@DisplayName("Just whitespace Test")
void justWhitespaceTest() {
Lexer lexer = new Lexer(" ");
List<Token> expected = new ArrayList<Token>();
expected.add(new BasicToken(TokenType.EOF));
List<Token> actual = null;
try {
actual = lexer.getAllTokens();
} catch(IllegalCharacterException e) {