Continued refactoring and adding unit tests

This commit is contained in:
Mike Cifelli 2016-12-10 11:57:49 -05:00
parent 1670a825e5
commit 89bb52ab71
14 changed files with 459 additions and 141 deletions

View File

@ -4,7 +4,9 @@
* Assignment: Lisp Interpreter Phase 1 - Lexical Analysis * Assignment: Lisp Interpreter Phase 1 - Lexical Analysis
*/ */
package scanner; package constructs;
import file.FilePosition;
/** /**
* A <code>Token</code> represents a token in Common Lisp. * A <code>Token</code> represents a token in Common Lisp.
@ -59,12 +61,12 @@ public class Token {
* @param column * @param column
* the column number that this token is found on * the column number that this token is found on
*/ */
public Token(Type type, String text, String fName, int line, int column) { public Token(Type type, String text, FilePosition position) {
this.type = type; this.type = type;
this.text = text; this.text = text;
this.fName = fName; this.fName = position.getFileName();
this.line = line; this.line = position.getLineNumber();
this.column = column; this.column = position.getColumnNumber();
} }
/** /**

View File

@ -0,0 +1,11 @@
package constructs;
import file.FilePosition;
public interface TokenFactory {
Token createToken(String text, FilePosition position);
Token createEOFToken(FilePosition position);
}

View File

@ -0,0 +1,37 @@
package constructs;
import static util.Characters.*;
import file.FilePosition;
import util.Characters;
public class TokenFactoryImpl implements TokenFactory {
public Token createToken(String text, FilePosition position) {
char firstCharacter = text.charAt(0);
switch (firstCharacter) {
case LEFT_PARENTHESIS:
return new Token(Token.Type.LEFT_PAREN, text, position);
case RIGHT_PARENTHESIS:
return new Token(Token.Type.RIGHT_PAREN, text, position);
case SINGLE_QUOTE:
return new Token(Token.Type.QUOTE_MARK, text, position);
case DOUBLE_QUOTE:
return new Token(Token.Type.STRING, text, position);
default:
if (Character.isDigit(firstCharacter)) {
return new Token(Token.Type.NUMBER, text, position);
} else if (Characters.isLegalIdentifierCharacter(firstCharacter)) {
return new Token(Token.Type.IDENTIFIER, text, position);
}
}
throw new RuntimeException("oh no!");
}
public Token createEOFToken(FilePosition position) {
return new Token(Token.Type.EOF, "EOF", position);
}
}

View File

@ -1,22 +1,12 @@
/*
* Name: Mike Cifelli
* Course: CIS 443 - Programming Languages
* Assignment: Lisp Interpreter Phase 1 - Lexical Analysis
*/
package error; package error;
import java.text.MessageFormat; import java.text.MessageFormat;
/** /**
* <code>ErrorManager</code> is an error handling class for a Lisp interpreter. * Prints error messages.
*/ */
public class ErrorManager { public class ErrorManager {
/**
* The lowest "criticality" level of an error that will cause the currently
* running program to terminate.
*/
public static final int CRITICAL_LEVEL = 3; public static final int CRITICAL_LEVEL = 3;
public static final String ANSI_RESET = "\u001B[0m"; public static final String ANSI_RESET = "\u001B[0m";
@ -25,16 +15,15 @@ public class ErrorManager {
public static final String ANSI_PURPLE = "\u001B[35m"; public static final String ANSI_PURPLE = "\u001B[35m";
/** /**
* Prints out the specified error message to the console and decides * Prints out the specified error message to the console and decides whether
* whether or not to terminate the currently running program. * or not to terminate the currently running program.
* *
* @param message * @param message
* the error message * the error message
* @param level * @param level
* the "criticality" level of the error * the "criticality" level of the error
* @postcondition * @postcondition If <code>level &gt;= CRITICAL_LEVEL</code> the currently
* If <code>level &gt;= CRITICAL_LEVEL</code> the currently running * running program has been terminated.
* program has been terminated.
*/ */
public static void generateError(String message, int level) { public static void generateError(String message, int level) {
String color = (level >= CRITICAL_LEVEL) ? ANSI_PURPLE : ANSI_RED; String color = (level >= CRITICAL_LEVEL) ? ANSI_PURPLE : ANSI_RED;

View File

@ -0,0 +1,9 @@
package error;
public abstract class LispException extends RuntimeException {
private static final long serialVersionUID = 1L;
public abstract int getSeverity();
}

View File

@ -0,0 +1,39 @@
package file;
import java.util.Objects;
public class FilePosition {
private String fileName;
private int lineNumber;
private int columnNumber;
public FilePosition(String fileName) {
this.fileName = fileName;
}
public String getFileName() {
return fileName;
}
public int getLineNumber() {
return lineNumber;
}
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
public int getColumnNumber() {
return columnNumber;
}
public void setColumnNumber(int columnNumber) {
this.columnNumber = columnNumber;
}
public boolean isEqual(FilePosition otherFilePosition) {
return Objects.equals(this.fileName, otherFilePosition.fileName) && (this.lineNumber == otherFilePosition.lineNumber)
&& (this.columnNumber == otherFilePosition.columnNumber);
}
}

View File

@ -0,0 +1,32 @@
package file;
public class FilePositionTracker {
private String fileName;
private int lineNumber;
private int columnNumber;
public FilePositionTracker(String fileName) {
this.fileName = fileName;
this.lineNumber = 1;
this.columnNumber = 0;
}
public FilePosition getCurrentPosition() {
FilePosition currentPosition = new FilePosition(fileName);
currentPosition.setLineNumber(lineNumber);
currentPosition.setColumnNumber(columnNumber);
return currentPosition;
}
public void incrementColumn() {
columnNumber++;
}
public void incrementLine() {
lineNumber++;
columnNumber = 0;
}
}

View File

@ -9,6 +9,8 @@ package parser;
import scanner.*; import scanner.*;
import java.io.*; import java.io.*;
import constructs.Token;
/** /**
* A <code>LispParser</code> converts a stream of bytes into internal * A <code>LispParser</code> converts a stream of bytes into internal
* representations of Lisp S-expressions. When the end of stream has been * representations of Lisp S-expressions. When the end of stream has been
@ -57,7 +59,7 @@ public class LispParser {
// attempt to read the next token from 'scanner' and store it in // attempt to read the next token from 'scanner' and store it in
// 'nextToken' // 'nextToken'
try { try {
nextToken = scanner.nextToken(); nextToken = scanner.getNextToken();
nextTokenStored = true; nextTokenStored = true;
} catch (Exception e) { } catch (Exception e) {
// this method should give the illusion of not actually reading // this method should give the illusion of not actually reading
@ -115,7 +117,7 @@ public class LispParser {
// the next token has not been stored in 'nextToken' by the 'eof' // the next token has not been stored in 'nextToken' by the 'eof'
// method // method
nextToken = scanner.nextToken(); nextToken = scanner.getNextToken();
} else { } else {
// the 'eof' method has been called and has read in the next token // the 'eof' method has been called and has read in the next token
// already to determine if we have reached the end-of-file // already to determine if we have reached the end-of-file
@ -145,7 +147,7 @@ public class LispParser {
case STRING: case STRING:
return new LispString(nextToken.getText()); return new LispString(nextToken.getText());
case QUOTE_MARK: case QUOTE_MARK:
nextToken = scanner.nextToken(); nextToken = scanner.getNextToken();
SExpression arg = sExpr(); SExpression arg = sExpr();
return new Cons(new Symbol("QUOTE"), return new Cons(new Symbol("QUOTE"),
@ -173,7 +175,7 @@ public class LispParser {
// Throws: IOException - Indicates that an I/O error has occurred. // Throws: IOException - Indicates that an I/O error has occurred.
// Precondition: 'scanner' is not null. // Precondition: 'scanner' is not null.
private SExpression sExprTail() throws IOException { private SExpression sExprTail() throws IOException {
nextToken = scanner.nextToken(); nextToken = scanner.getNextToken();
// determine the type of 'nextToken' and create the appropriate // determine the type of 'nextToken' and create the appropriate
// S-expression // S-expression

View File

@ -1,99 +1,72 @@
package scanner; package scanner;
import static util.Characters.BACKSLASH; import static util.Characters.*;
import static util.Characters.DOUBLE_QUOTE;
import static util.Characters.EOF;
import static util.Characters.HASH;
import static util.Characters.LEFT_PARENTHESIS;
import static util.Characters.LEFT_SQUARE_BRACKET;
import static util.Characters.NEWLINE;
import static util.Characters.PERIOD;
import static util.Characters.RIGHT_PARENTHESIS;
import static util.Characters.RIGHT_SQUARE_BRACKET;
import static util.Characters.SEMICOLON;
import static util.Characters.SINGLE_QUOTE;
import static util.Characters.TICK_MARK;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.HashMap; import java.text.MessageFormat;
import java.util.Map;
import constructs.Token;
import constructs.TokenFactory;
import constructs.TokenFactoryImpl;
import error.LispException;
import file.FilePosition;
import file.FilePositionTracker;
import util.Characters;
/** /**
* Converts a stream of bytes into a stream of Lisp tokens. * Converts a stream of bytes into a stream of Lisp tokens.
*/ */
public class LispScanner { public class LispScanner {
private static Map<Character, Boolean> illegalIdentifierCharacters = new HashMap<>();
static {
illegalIdentifierCharacters.put(DOUBLE_QUOTE, true);
illegalIdentifierCharacters.put(SINGLE_QUOTE, true);
illegalIdentifierCharacters.put(BACKSLASH, true);
illegalIdentifierCharacters.put(TICK_MARK, true);
illegalIdentifierCharacters.put(LEFT_PARENTHESIS, true);
illegalIdentifierCharacters.put(RIGHT_PARENTHESIS, true);
illegalIdentifierCharacters.put(LEFT_SQUARE_BRACKET, true);
illegalIdentifierCharacters.put(RIGHT_SQUARE_BRACKET, true);
illegalIdentifierCharacters.put(HASH, true);
illegalIdentifierCharacters.put(PERIOD, true);
illegalIdentifierCharacters.put(SEMICOLON, true);
}
private InputStream inputStream; private InputStream inputStream;
private String inputStreamName; private FilePositionTracker positionTracker;
private int lineNumber; private TokenFactory tokenFactory;
private int columnNumber;
public LispScanner(InputStream in, String fileName) { public LispScanner(InputStream in, String fileName) {
this.inputStream = new LispFilterInputStream(new BufferedInputStream(in)); this.inputStream = new LispFilterInputStream(new BufferedInputStream(in));
this.inputStreamName = fileName; this.positionTracker = new FilePositionTracker(fileName);
this.lineNumber = 1; this.tokenFactory = new TokenFactoryImpl();
this.columnNumber = 0;
} }
public Token nextToken() throws IOException { public Token getNextToken() throws IOException {
int c; int c;
while ((c = inputStream.read()) != EOF) { while ((c = inputStream.read()) != EOF) {
char nextChar = (char) c; char nextChar = (char) c;
positionTracker.incrementColumn();
++columnNumber; if (Character.isWhitespace(nextChar)) {
if (nextChar == NEWLINE)
positionTracker.incrementLine();
} else {
FilePosition currentPosition = positionTracker.getCurrentPosition();
String tokenText = retrieveTokenText(nextChar);
switch (nextChar) { return tokenFactory.createToken(tokenText, currentPosition);
case NEWLINE:
moveToNewLine();
break;
case LEFT_PARENTHESIS:
return new Token(Token.Type.LEFT_PAREN, "(", inputStreamName, lineNumber, columnNumber);
case RIGHT_PARENTHESIS:
return new Token(Token.Type.RIGHT_PAREN, ")", inputStreamName, lineNumber, columnNumber);
case SINGLE_QUOTE:
return new Token(Token.Type.QUOTE_MARK, "\'", inputStreamName, lineNumber, columnNumber);
case DOUBLE_QUOTE:
return retrieveString(nextChar);
default:
if (Character.isWhitespace(nextChar)) {
continue;
} else if (Character.isDigit(nextChar)) {
return retrieveNumber(nextChar);
} else if (isLegalIdentifierCharacter(nextChar)) {
return retrieveIdentifier(nextChar);
} else {
throw new RuntimeException("illegal character " + "\'" + nextChar + "\'" + " - line " + lineNumber
+ " column " + columnNumber);
}
} }
} }
return new Token(Token.Type.EOF, "EOF", inputStreamName, lineNumber, columnNumber); return tokenFactory.createEOFToken(positionTracker.getCurrentPosition());
} }
private Token retrieveString(char firstDoubleQuote) throws IOException { private String retrieveTokenText(char firstCharacter) throws IOException {
String tokenText = "" + firstCharacter;
if (firstCharacter == DOUBLE_QUOTE)
tokenText = retrieveString(firstCharacter);
else if (Character.isDigit(firstCharacter))
tokenText = retrieveNumber(firstCharacter);
else if (Characters.isLegalIdentifierCharacter(firstCharacter))
tokenText = retrieveIdentifier(firstCharacter);
return tokenText;
}
private String retrieveString(char firstDoubleQuote) throws IOException {
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
int startLine = lineNumber; FilePosition stringPosition = positionTracker.getCurrentPosition();
int startColumn = columnNumber;
char prevChar = firstDoubleQuote; char prevChar = firstDoubleQuote;
text.append(firstDoubleQuote); text.append(firstDoubleQuote);
@ -103,18 +76,18 @@ public class LispScanner {
while ((c = inputStream.read()) != EOF) { while ((c = inputStream.read()) != EOF) {
char nextChar = (char) c; char nextChar = (char) c;
++columnNumber; positionTracker.incrementColumn();
text.append(nextChar); text.append(nextChar);
switch (nextChar) { switch (nextChar) {
case NEWLINE: case NEWLINE:
moveToNewLine(); positionTracker.incrementLine();
break; break;
case DOUBLE_QUOTE: case DOUBLE_QUOTE:
if (prevChar != BACKSLASH) { if (prevChar != BACKSLASH) {
// we have found the terminating double quote // we have found the terminating double quote
return new Token(Token.Type.STRING, text.toString(), inputStreamName, startLine, startColumn); return text.toString();
} }
// this is an escaped double quote // this is an escaped double quote
@ -123,15 +96,11 @@ public class LispScanner {
prevChar = nextChar; prevChar = nextChar;
} }
// the end of 'inStream' was reached before the terminating double throw new UnterminatedStringException(stringPosition);
// quote
throw new RuntimeException("unterminated quoted string" + " - line " + startLine + " column " + startColumn);
} }
private Token retrieveNumber(char firstDigit) throws IOException { private String retrieveNumber(char firstDigit) throws IOException {
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
int startColumn = columnNumber;
text.append(firstDigit); text.append(firstDigit);
inputStream.mark(1); inputStream.mark(1);
@ -145,27 +114,23 @@ public class LispScanner {
// 'nextChar' is a digit in this number // 'nextChar' is a digit in this number
text.append(nextChar); text.append(nextChar);
++columnNumber; positionTracker.incrementColumn();
} else { } else {
// we have reached the end of the number // we have reached the end of the number
inputStream.reset(); // unread the last character inputStream.reset(); // unread the last character
return new Token(Token.Type.NUMBER, text.toString(), inputStreamName, lineNumber, startColumn); return text.toString();
} }
inputStream.mark(1); inputStream.mark(1);
} }
// there are no more bytes to be read from 'inStream' after this number return text.toString();
// token
return new Token(Token.Type.NUMBER, text.toString(), inputStreamName, lineNumber, startColumn);
} }
private Token retrieveIdentifier(char firstChar) throws IOException { private String retrieveIdentifier(char firstChar) throws IOException {
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
int startColumn = columnNumber;
text.append(firstChar); text.append(firstChar);
inputStream.mark(1); inputStream.mark(1);
@ -175,35 +140,45 @@ public class LispScanner {
while ((c = inputStream.read()) != EOF) { while ((c = inputStream.read()) != EOF) {
char nextChar = (char) c; char nextChar = (char) c;
if (isLegalIdentifierCharacter(nextChar)) { if (Characters.isLegalIdentifierCharacter(nextChar)) {
// 'nextChar' is part of the identifier // 'nextChar' is part of the identifier
text.append(nextChar); text.append(nextChar);
++columnNumber; positionTracker.incrementColumn();
} else { } else {
// we have reached the end of this identifier // we have reached the end of this identifier
inputStream.reset(); // unread the last character inputStream.reset(); // unread the last character
return new Token(Token.Type.IDENTIFIER, text.toString(), inputStreamName, lineNumber, startColumn); return text.toString();
} }
inputStream.mark(1); inputStream.mark(1);
} }
// there are no more bytes to be read from 'inStream' after this return text.toString();
// identifier token
return new Token(Token.Type.IDENTIFIER, text.toString(), inputStreamName, lineNumber, startColumn);
} }
private void moveToNewLine() { public static class UnterminatedStringException extends LispException {
lineNumber++;
columnNumber = 0; private static final long serialVersionUID = 1L;
} private FilePosition position;
public UnterminatedStringException(FilePosition position) {
this.position = position;
}
@Override
public int getSeverity() {
return 0;
}
@Override
public String getMessage() {
return MessageFormat.format("unterminated quoted string - line {0}, column {1}", position.getLineNumber(),
position.getColumnNumber());
}
private boolean isLegalIdentifierCharacter(char c) {
return (!Character.isWhitespace(c)) && (illegalIdentifierCharacters.get(c) == null);
} }
} }

View File

@ -1,5 +1,8 @@
package util; package util;
import java.util.HashMap;
import java.util.Map;
public class Characters { public class Characters {
public static final char BACKSLASH = '\\'; public static final char BACKSLASH = '\\';
@ -17,4 +20,24 @@ public class Characters {
public static final int EOF = -1; public static final int EOF = -1;
public static final Map<Character, Boolean> illegalIdentifierCharacters = new HashMap<>();
static {
illegalIdentifierCharacters.put(DOUBLE_QUOTE, true);
illegalIdentifierCharacters.put(SINGLE_QUOTE, true);
illegalIdentifierCharacters.put(BACKSLASH, true);
illegalIdentifierCharacters.put(TICK_MARK, true);
illegalIdentifierCharacters.put(LEFT_PARENTHESIS, true);
illegalIdentifierCharacters.put(RIGHT_PARENTHESIS, true);
illegalIdentifierCharacters.put(LEFT_SQUARE_BRACKET, true);
illegalIdentifierCharacters.put(RIGHT_SQUARE_BRACKET, true);
illegalIdentifierCharacters.put(HASH, true);
illegalIdentifierCharacters.put(PERIOD, true);
illegalIdentifierCharacters.put(SEMICOLON, true);
}
public static boolean isLegalIdentifierCharacter(char c) {
return (! Character.isWhitespace(c)) && (! illegalIdentifierCharacters.containsKey(c));
}
} }

View File

@ -0,0 +1,118 @@
package scanner;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import file.FilePosition;
import file.FilePositionTracker;
public class FilePositionTrackerTester {
public static final String FILE_NAME = "testFileName";
private FilePositionTracker trackerUnderTest;
@Before
public void setUp() throws Exception {
trackerUnderTest = new FilePositionTracker(FILE_NAME);
}
@Test
public void filePositionEquality_CorrectlyReturnsTrue() {
FilePosition positionOne = new FilePosition(FILE_NAME);
positionOne.setLineNumber(5);
positionOne.setColumnNumber(9);
FilePosition positionTwo = new FilePosition(FILE_NAME);
positionTwo.setLineNumber(5);
positionTwo.setColumnNumber(9);
assertTrue(positionOne.isEqual(positionTwo));
}
@Test
public void filePositionEquality_CorrectlyReturnsFalseWithDifferentLine() {
FilePosition positionOne = new FilePosition(FILE_NAME);
positionOne.setLineNumber(5);
positionOne.setColumnNumber(9);
FilePosition positionTwo = new FilePosition(FILE_NAME);
positionTwo.setLineNumber(8);
positionTwo.setColumnNumber(9);
assertFalse(positionOne.isEqual(positionTwo));
}
@Test
public void filePositionEquality_CorrectlyReturnsFalseWithDifferentColumn() {
FilePosition positionOne = new FilePosition(FILE_NAME);
positionOne.setLineNumber(5);
positionOne.setColumnNumber(9);
FilePosition positionTwo = new FilePosition(FILE_NAME);
positionTwo.setLineNumber(5);
positionTwo.setColumnNumber(10);
assertFalse(positionOne.isEqual(positionTwo));
}
@Test
public void filePositionEquality_CorrectlyReturnsFalseWithDifferentFileName() {
FilePosition positionOne = new FilePosition("FileOne");
positionOne.setLineNumber(5);
positionOne.setColumnNumber(9);
FilePosition positionTwo = new FilePosition("FileTwo");
positionTwo.setLineNumber(5);
positionTwo.setColumnNumber(9);
assertFalse(positionOne.isEqual(positionTwo));
}
@Test
public void noMovement_ReturnsInitialPosition() {
FilePosition expectedPosition = new FilePosition(FILE_NAME);
expectedPosition.setLineNumber(1);
expectedPosition.setColumnNumber(0);
assertTrue(expectedPosition.isEqual(trackerUnderTest.getCurrentPosition()));
}
@Test
public void advanceOneColumn_ReturnsCorrectPosition() {
FilePosition expectedPosition = new FilePosition(FILE_NAME);
expectedPosition.setLineNumber(1);
expectedPosition.setColumnNumber(1);
trackerUnderTest.incrementColumn();
assertTrue(expectedPosition.isEqual(trackerUnderTest.getCurrentPosition()));
}
@Test
public void advanceOneLine_ReturnsCorrectPosition() {
FilePosition expectedPosition = new FilePosition(FILE_NAME);
expectedPosition.setLineNumber(2);
expectedPosition.setColumnNumber(0);
trackerUnderTest.incrementLine();
assertTrue(expectedPosition.isEqual(trackerUnderTest.getCurrentPosition()));
}
@Test
public void advanceOneLine_ResetsColumn() {
FilePosition expectedPosition = new FilePosition(FILE_NAME);
expectedPosition.setLineNumber(2);
expectedPosition.setColumnNumber(0);
trackerUnderTest.incrementColumn();
trackerUnderTest.incrementLine();
assertTrue(expectedPosition.isEqual(trackerUnderTest.getCurrentPosition()));
}
}

View File

@ -8,6 +8,7 @@ import java.io.InputStream;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import constructs.Token;
import testutils.TestUtilities; import testutils.TestUtilities;
public class LispScannerLineColumnTester { public class LispScannerLineColumnTester {
@ -32,6 +33,47 @@ public class LispScannerLineColumnTester {
assertTokenLineAndColumnsMatch(input, expectedLinesAndColumns); assertTokenLineAndColumnsMatch(input, expectedLinesAndColumns);
} }
@Test
public void givenStringWithTrailingSpace_RecordsCorrectLocation() throws IOException {
String input = "\"string\" ";
LineColumn[] expectedLinesAndColumns = { LineColumn.create(1, 1) };
assertTokenLineAndColumnsMatch(input, expectedLinesAndColumns);
}
@Test
public void givenIdentifier_RecordsCorrectLocation() throws IOException {
String input = "identifier";
LineColumn[] expectedLinesAndColumns = { LineColumn.create(1, 1) };
assertTokenLineAndColumnsMatch(input, expectedLinesAndColumns);
}
@Test
public void givenIdentifierWithTrailingSpace_RecordsCorrectLocation() throws IOException {
String input = "identifier ";
LineColumn[] expectedLinesAndColumns = { LineColumn.create(1, 1) };
assertTokenLineAndColumnsMatch(input, expectedLinesAndColumns);
}
@Test
public void givenNumber_RecordsCorrectLocation() throws IOException {
String input = "123456789";
LineColumn[] expectedLinesAndColumns = { LineColumn.create(1, 1) };
assertTokenLineAndColumnsMatch(input, expectedLinesAndColumns);
}
@Test
public void givenNumberWithTrailingSpace_RecordsCorrectLocation() throws IOException {
String input = "123456789 ";
LineColumn[] expectedLinesAndColumns = { LineColumn.create(1, 1) };
assertTokenLineAndColumnsMatch(input, expectedLinesAndColumns);
}
@Test @Test
public void givenMultipleStrings_RecordsCorrectLocations() throws IOException { public void givenMultipleStrings_RecordsCorrectLocations() throws IOException {
String input = "\"string1\" \n \"string2 \n with newline\" \n \"string3\""; String input = "\"string1\" \n \"string2 \n with newline\" \n \"string3\"";
@ -68,7 +110,7 @@ public class LispScannerLineColumnTester {
LispScanner lispScanner = new LispScanner(stringInputStream, "stringInputStream"); LispScanner lispScanner = new LispScanner(stringInputStream, "stringInputStream");
for (LineColumn lineColumn : expectedLineColumnList) { for (LineColumn lineColumn : expectedLineColumnList) {
Token nextToken = lispScanner.nextToken(); Token nextToken = lispScanner.getNextToken();
assertTrue(lineColumn.isEqual(nextToken)); assertTrue(lineColumn.isEqual(nextToken));
} }
} }

View File

@ -44,14 +44,14 @@ public class LispScannerTextTester {
InputStream stringInputStream = TestUtilities.createInputStreamFromString(input); InputStream stringInputStream = TestUtilities.createInputStreamFromString(input);
LispScanner lispScanner = new LispScanner(stringInputStream, "stringInputStream"); LispScanner lispScanner = new LispScanner(stringInputStream, "stringInputStream");
assertEquals(expectedText, lispScanner.nextToken().getText()); assertEquals(expectedText, lispScanner.getNextToken().getText());
} }
private void assertInputFileNameMatches(String input, String expectedInputFileName) throws IOException { private void assertInputFileNameMatches(String input, String expectedInputFileName) throws IOException {
InputStream stringInputStream = TestUtilities.createInputStreamFromString(input); InputStream stringInputStream = TestUtilities.createInputStreamFromString(input);
LispScanner lispScanner = new LispScanner(stringInputStream, expectedInputFileName); LispScanner lispScanner = new LispScanner(stringInputStream, expectedInputFileName);
assertEquals(expectedInputFileName, lispScanner.nextToken().getFName()); assertEquals(expectedInputFileName, lispScanner.getNextToken().getFName());
} }
} }

View File

@ -1,19 +1,23 @@
package scanner; package scanner;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import org.junit.Test; import org.junit.Test;
import scanner.Token.Type; import constructs.Token;
import constructs.Token.Type;
import error.ErrorManager;
import testutils.TestUtilities; import testutils.TestUtilities;
public class LispScannerTypeTester { public class LispScannerTypeTester {
@Test @Test
public void givenEmptyFile_returnsCorrectTokenTypes() throws IOException { public void givenEmptyFile_ReturnsCorrectTypes() throws IOException {
String input = ""; String input = "";
Token.Type[] expectedTypes = {}; Token.Type[] expectedTypes = {};
@ -29,7 +33,7 @@ public class LispScannerTypeTester {
} }
@Test @Test
public void givenNil_returnsCorrectTokenTypes() throws IOException { public void givenNil_ReturnsCorrectTypes() throws IOException {
String input = "()"; String input = "()";
Token.Type[] expectedTypes = { Type.LEFT_PAREN, Type.RIGHT_PAREN }; Token.Type[] expectedTypes = { Type.LEFT_PAREN, Type.RIGHT_PAREN };
@ -37,7 +41,7 @@ public class LispScannerTypeTester {
} }
@Test @Test
public void givenListOfNumbers_returnsCorrectTokenTypes() throws IOException { public void givenListOfNumbers_ReturnsCorrectTypes() throws IOException {
String input = "(1 2)"; String input = "(1 2)";
Token.Type[] expectedTypes = { Type.LEFT_PAREN, Type.NUMBER, Type.NUMBER, Type.RIGHT_PAREN }; Token.Type[] expectedTypes = { Type.LEFT_PAREN, Type.NUMBER, Type.NUMBER, Type.RIGHT_PAREN };
@ -45,7 +49,7 @@ public class LispScannerTypeTester {
} }
@Test @Test
public void givenString_returnsCorrectTokenTypes() throws IOException { public void givenString_ReturnsCorrectTypes() throws IOException {
String input = "\"string\""; String input = "\"string\"";
Token.Type[] expectedTypes = { Type.STRING }; Token.Type[] expectedTypes = { Type.STRING };
@ -53,7 +57,7 @@ public class LispScannerTypeTester {
} }
@Test @Test
public void givenStringWithEscapedDoubleQuote_returnsCorrectTokenTypes() throws IOException { public void givenStringWithEscapedDoubleQuote_ReturnsCorrectTypes() throws IOException {
String input = "\"string \n hi \\\" bye\""; String input = "\"string \n hi \\\" bye\"";
Token.Type[] expectedTypes = { Type.STRING }; Token.Type[] expectedTypes = { Type.STRING };
@ -61,14 +65,14 @@ public class LispScannerTypeTester {
} }
@Test @Test
public void givenStringWithEscapedDoubleQuoteAndComment_returnsCorrectTokenTypes() throws IOException { public void givenStringWithEscapedDoubleQuoteAndComment_ReturnsCorrectTypes() throws IOException {
String input = "\"string \n hi \\\" ; bye\""; String input = "\"string \n hi \\\" ; bye\"";
Token.Type[] expectedTypes = { Type.STRING }; Token.Type[] expectedTypes = { Type.STRING };
assertTokenTypesMatch(input, expectedTypes); assertTokenTypesMatch(input, expectedTypes);
} }
@Test(expected = RuntimeException.class) @Test(expected = LispScanner.UnterminatedStringException.class)
public void givenUnterminatedString_ThrowsException() throws IOException { public void givenUnterminatedString_ThrowsException() throws IOException {
String input = "\"oh no!"; String input = "\"oh no!";
Token.Type[] expectedTypes = { Type.STRING }; Token.Type[] expectedTypes = { Type.STRING };
@ -76,8 +80,34 @@ public class LispScannerTypeTester {
assertTokenTypesMatch(input, expectedTypes); assertTokenTypesMatch(input, expectedTypes);
} }
@Test()
public void givenUnterminatedString_ExceptionHasCorrectSeverity() throws IOException {
String input = "\"oh no!";
Token.Type[] expectedTypes = { Type.STRING };
try {
assertTokenTypesMatch(input, expectedTypes);
} catch (LispScanner.UnterminatedStringException e) {
assertTrue(e.getSeverity() < ErrorManager.CRITICAL_LEVEL);
}
}
@Test()
public void givenUnterminatedString_ExceptionContainsMessage() throws IOException {
String input = "\"oh no!";
Token.Type[] expectedTypes = { Type.STRING };
try {
assertTokenTypesMatch(input, expectedTypes);
} catch (LispScanner.UnterminatedStringException e) {
String message = e.getMessage();
assertNotNull(message);
assertTrue(message.length() > 0);
}
}
@Test @Test
public void givenIdentifier_returnsCorrectTokenTypes() throws IOException { public void givenIdentifier_ReturnsCorrectTypes() throws IOException {
String input = "abcdefgHIJKLMNOP1234"; String input = "abcdefgHIJKLMNOP1234";
Token.Type[] expectedTypes = { Type.IDENTIFIER }; Token.Type[] expectedTypes = { Type.IDENTIFIER };
@ -85,7 +115,7 @@ public class LispScannerTypeTester {
} }
@Test @Test
public void givenSingleDigitNumber_returnsCorrectTokenTypes() throws IOException { public void givenSingleDigitNumber_ReturnsCorrectTypes() throws IOException {
String input = "1"; String input = "1";
Token.Type[] expectedTypes = { Type.NUMBER }; Token.Type[] expectedTypes = { Type.NUMBER };
@ -93,7 +123,7 @@ public class LispScannerTypeTester {
} }
@Test @Test
public void givenMultipleDigitNumber_returnsCorrectTokenTypes() throws IOException { public void givenMultipleDigitNumber_ReturnsCorrectTypes() throws IOException {
String input = "1234567890"; String input = "1234567890";
Token.Type[] expectedTypes = { Type.NUMBER }; Token.Type[] expectedTypes = { Type.NUMBER };
@ -101,7 +131,7 @@ public class LispScannerTypeTester {
} }
@Test @Test
public void givenQuote_returnsCorrectTokenTypes() throws IOException { public void givenQuote_ReturnsCorrectTypes() throws IOException {
String input = "'"; String input = "'";
Token.Type[] expectedTypes = { Type.QUOTE_MARK }; Token.Type[] expectedTypes = { Type.QUOTE_MARK };
@ -109,7 +139,16 @@ public class LispScannerTypeTester {
} }
@Test @Test
public void givenFunctionCall_returnsCorrectTokenTypes() throws IOException { public void givenManyTypesWithNoWhitespace_ReturnsCorrectTypes() throws IOException {
String input = "xxx\"hi\"999()'aaa";
Token.Type[] expectedTypes = { Type.IDENTIFIER, Type.STRING, Type.NUMBER, Type.LEFT_PAREN, Type.RIGHT_PAREN,
Type.QUOTE_MARK, Type.IDENTIFIER };
assertTokenTypesMatch(input, expectedTypes);
}
@Test
public void givenFunctionCall_ReturnsCorrectTypes() throws IOException {
String input = "(defun myFunction (x)\n (print x))"; String input = "(defun myFunction (x)\n (print x))";
Token.Type[] expectedTypes = { Type.LEFT_PAREN, Type.IDENTIFIER, Type.IDENTIFIER, Type.LEFT_PAREN, Token.Type[] expectedTypes = { Type.LEFT_PAREN, Type.IDENTIFIER, Type.IDENTIFIER, Type.LEFT_PAREN,
Type.IDENTIFIER, Type.RIGHT_PAREN, Type.LEFT_PAREN, Type.IDENTIFIER, Type.IDENTIFIER, Type.RIGHT_PAREN, Type.LEFT_PAREN, Type.IDENTIFIER,
@ -123,9 +162,9 @@ public class LispScannerTypeTester {
LispScanner lispScanner = new LispScanner(stringInputStream, "stringInputStream"); LispScanner lispScanner = new LispScanner(stringInputStream, "stringInputStream");
for (Token.Type expectedType : expectedTypeList) for (Token.Type expectedType : expectedTypeList)
assertEquals(expectedType, lispScanner.nextToken().getType()); assertEquals(expectedType, lispScanner.getNextToken().getType());
assertEquals(Token.Type.EOF, lispScanner.nextToken().getType()); assertEquals(Token.Type.EOF, lispScanner.getNextToken().getType());
} }
} }