diff --git a/.externalToolBuilders/Ant_Builder.launch b/.externalToolBuilders/Ant_Builder.launch
index 382a168..d0e90aa 100644
--- a/.externalToolBuilders/Ant_Builder.launch
+++ b/.externalToolBuilders/Ant_Builder.launch
@@ -1,5 +1,4 @@
-
-
+
@@ -24,4 +23,4 @@
-
+
\ No newline at end of file
diff --git a/src/parser/LispParser.java b/src/parser/LispParser.java
index 5d2e4e6..435f96c 100644
--- a/src/parser/LispParser.java
+++ b/src/parser/LispParser.java
@@ -3,9 +3,8 @@ package parser;
import java.io.InputStream;
import error.LispException;
-import parser.MalformedSExpressionException.*;
import scanner.LispScanner;
-import sexpression.*;
+import sexpression.SExpression;
import token.Eof;
import token.Token;
@@ -17,11 +16,13 @@ public class LispParser {
private LispScanner scanner;
private Token nextToken;
+ private LispException delayedException;
private boolean isNextTokenStored;
public LispParser(InputStream inputStream, String fileName) {
scanner = new LispScanner(inputStream, fileName);
nextToken = null;
+ delayedException = null;
isNextTokenStored = false;
}
@@ -37,59 +38,27 @@ public class LispParser {
nextToken = scanner.getNextToken();
isNextTokenStored = true;
} catch (LispException e) {
- // This method should give the illusion of not actually reading
- // a token, so we ignore any exception. It will be thrown when we
- // return the next sExpression.
+ delayedException = e;
}
}
public SExpression getNextSExpression() {
+ throwDelayedExceptionIfNecessary();
+
if (!isNextTokenStored)
nextToken = scanner.getNextToken();
- else
- isNextTokenStored = false;
- return sExpr();
+ isNextTokenStored = false;
+
+ return nextToken.sExpr(scanner::getNextToken);
}
- // sExpr ::= NUMBER | IDENTIFIER | STRING | QUOTE_MARK sExpr |
- // LEFT_PAREN sExprTail
- private SExpression sExpr() {
- switch (nextToken.getType()) {
- case NUMBER:
- return new LispNumber(nextToken.getText());
- case IDENTIFIER:
- return new Symbol(nextToken.getText());
- case STRING:
- return new LispString(nextToken.getText());
- case QUOTE_MARK:
- nextToken = scanner.getNextToken();
- SExpression arg = sExpr();
+ private void throwDelayedExceptionIfNecessary() {
+ if (delayedException != null) {
+ LispException exceptionToThrow = delayedException;
+ delayedException = null;
- return new Cons(new Symbol("QUOTE"), new Cons(arg, Nil.getUniqueInstance()));
- case LEFT_PAREN:
- return sExprTail();
- case RIGHT_PAREN:
- throw new StartsWithRightParenthesisException(nextToken);
- case EOF:
- throw new EofEncounteredException(nextToken);
- default:
- throw new UnrecognizedTokenException(nextToken);
- }
- }
-
- // sExprTail ::= RIGHT_PAREN | sExpr sExprTail
- private SExpression sExprTail() {
- nextToken = scanner.getNextToken();
-
- switch (nextToken.getType()) {
- case RIGHT_PAREN:
- return Nil.getUniqueInstance();
- default:
- SExpression car = sExpr();
- SExpression cdr = sExprTail();
-
- return new Cons(car, cdr);
+ throw exceptionToThrow;
}
}
diff --git a/src/parser/MalformedSExpressionException.java b/src/sexpression/MalformedSExpressionException.java
similarity index 98%
rename from src/parser/MalformedSExpressionException.java
rename to src/sexpression/MalformedSExpressionException.java
index cf28963..e2966e2 100644
--- a/src/parser/MalformedSExpressionException.java
+++ b/src/sexpression/MalformedSExpressionException.java
@@ -1,4 +1,4 @@
-package parser;
+package sexpression;
import java.text.MessageFormat;
diff --git a/src/token/Eof.java b/src/token/Eof.java
index 743949e..5e7ff95 100644
--- a/src/token/Eof.java
+++ b/src/token/Eof.java
@@ -1,7 +1,10 @@
package token;
-import file.FilePosition;
+import java.util.function.Supplier;
+import file.FilePosition;
+import sexpression.MalformedSExpressionException.EofEncounteredException;
+import sexpression.SExpression;
public class Eof extends Token {
@@ -10,8 +13,8 @@ public class Eof extends Token {
}
@Override
- public Type getType() {
- return Type.EOF;
+ public SExpression sExpr(Supplier getNextToken) {
+ throw new EofEncounteredException(this);
}
}
diff --git a/src/token/Identifier.java b/src/token/Identifier.java
index 3c64cc5..7c6a2e9 100644
--- a/src/token/Identifier.java
+++ b/src/token/Identifier.java
@@ -1,7 +1,10 @@
package token;
-import file.FilePosition;
+import java.util.function.Supplier;
+import file.FilePosition;
+import sexpression.SExpression;
+import sexpression.Symbol;
public class Identifier extends Token {
@@ -10,8 +13,8 @@ public class Identifier extends Token {
}
@Override
- public Type getType() {
- return Type.IDENTIFIER;
+ public SExpression sExpr(Supplier getNextToken) {
+ return new Symbol(getText());
}
}
diff --git a/src/token/LeftParenthesis.java b/src/token/LeftParenthesis.java
index 515a464..758443a 100644
--- a/src/token/LeftParenthesis.java
+++ b/src/token/LeftParenthesis.java
@@ -1,6 +1,9 @@
package token;
+import java.util.function.Supplier;
+
import file.FilePosition;
+import sexpression.SExpression;
public class LeftParenthesis extends Token {
@@ -9,8 +12,10 @@ public class LeftParenthesis extends Token {
}
@Override
- public Type getType() {
- return Type.LEFT_PAREN;
+ public SExpression sExpr(Supplier getNextToken) {
+ Token nextToken = getNextToken.get();
+
+ return nextToken.sExprTail(getNextToken);
}
}
diff --git a/src/token/Number.java b/src/token/Number.java
index 722115b..314c433 100644
--- a/src/token/Number.java
+++ b/src/token/Number.java
@@ -1,7 +1,10 @@
package token;
-import file.FilePosition;
+import java.util.function.Supplier;
+import file.FilePosition;
+import sexpression.LispNumber;
+import sexpression.SExpression;
public class Number extends Token {
@@ -10,8 +13,8 @@ public class Number extends Token {
}
@Override
- public Type getType() {
- return Type.NUMBER;
+ public SExpression sExpr(Supplier getNextToken) {
+ return new LispNumber(getText());
}
}
diff --git a/src/token/QuoteMark.java b/src/token/QuoteMark.java
index bcecd90..d7b12bb 100644
--- a/src/token/QuoteMark.java
+++ b/src/token/QuoteMark.java
@@ -1,7 +1,9 @@
package token;
-import file.FilePosition;
+import java.util.function.Supplier;
+import file.FilePosition;
+import sexpression.*;
public class QuoteMark extends Token {
@@ -10,8 +12,11 @@ public class QuoteMark extends Token {
}
@Override
- public Type getType() {
- return Type.QUOTE_MARK;
+ public SExpression sExpr(Supplier getNextToken) {
+ Token nextToken = getNextToken.get();
+ SExpression argument = nextToken.sExpr(getNextToken);
+
+ return new Cons(new Symbol("QUOTE"), new Cons(argument, Nil.getUniqueInstance()));
}
}
diff --git a/src/token/QuotedString.java b/src/token/QuotedString.java
index 2d2ba4e..a0d9d69 100644
--- a/src/token/QuotedString.java
+++ b/src/token/QuotedString.java
@@ -1,7 +1,10 @@
package token;
-import file.FilePosition;
+import java.util.function.Supplier;
+import file.FilePosition;
+import sexpression.LispString;
+import sexpression.SExpression;
public class QuotedString extends Token {
@@ -10,8 +13,8 @@ public class QuotedString extends Token {
}
@Override
- public Type getType() {
- return Type.STRING;
+ public SExpression sExpr(Supplier getNextToken) {
+ return new LispString(getText());
}
}
diff --git a/src/token/RightParenthesis.java b/src/token/RightParenthesis.java
index 90f3b06..bd329e1 100644
--- a/src/token/RightParenthesis.java
+++ b/src/token/RightParenthesis.java
@@ -1,7 +1,11 @@
package token;
-import file.FilePosition;
+import java.util.function.Supplier;
+import file.FilePosition;
+import sexpression.MalformedSExpressionException.StartsWithRightParenthesisException;
+import sexpression.Nil;
+import sexpression.SExpression;
public class RightParenthesis extends Token {
@@ -10,8 +14,13 @@ public class RightParenthesis extends Token {
}
@Override
- public Type getType() {
- return Type.RIGHT_PAREN;
+ public SExpression sExpr(Supplier getNextToken) {
+ throw new StartsWithRightParenthesisException(this);
+ }
+
+ @Override
+ public SExpression sExprTail(Supplier getNextToken) {
+ return Nil.getUniqueInstance();
}
}
diff --git a/src/token/Token.java b/src/token/Token.java
index 726f239..f8f5480 100644
--- a/src/token/Token.java
+++ b/src/token/Token.java
@@ -1,44 +1,52 @@
package token;
+import java.util.function.Supplier;
+
import file.FilePosition;
+import sexpression.Cons;
+import sexpression.SExpression;
/**
* A token in Lisp.
*/
public abstract class Token {
- public enum Type {
- LEFT_PAREN, RIGHT_PAREN, STRING, QUOTE_MARK, NUMBER, IDENTIFIER, EOF
- }
-
private String text;
- private String fileName;
- private int line;
- private int column;
+ private FilePosition position;
public Token(String text, FilePosition position) {
this.text = text;
- this.fileName = position.getFileName();
- this.line = position.getLineNumber();
- this.column = position.getColumnNumber();
+ this.position = position;
}
- public abstract Type getType();
-
public String getText() {
return text;
}
public String getFileName() {
- return fileName;
+ return position.getFileName();
}
public int getLine() {
- return line;
+ return position.getLineNumber();
}
public int getColumn() {
- return column;
+ return position.getColumnNumber();
+ }
+
+ // sExpr ::= NUMBER | IDENTIFIER | STRING | QUOTE_MARK sExpr |
+ // LEFT_PAREN sExprTail
+ public abstract SExpression sExpr(Supplier getNextToken);
+
+ // sExprTail ::= RIGHT_PAREN | sExpr sExprTail
+ public SExpression sExprTail(Supplier getNextToken) {
+ SExpression car = sExpr(getNextToken);
+
+ Token nextToken = getNextToken.get();
+ SExpression cdr = nextToken.sExprTail(getNextToken);
+
+ return new Cons(car, cdr);
}
}
diff --git a/src/util/Characters.java b/src/util/Characters.java
index ec401e2..4363812 100644
--- a/src/util/Characters.java
+++ b/src/util/Characters.java
@@ -3,7 +3,9 @@ package util;
import java.util.HashSet;
import java.util.Set;
-public class Characters {
+public final class Characters {
+
+ private Characters() {}
public static final int EOF = -1;
@@ -39,7 +41,7 @@ public class Characters {
public static boolean isLegalIdentifierCharacter(char c) {
return (!Character.isWhitespace(c)) && (!illegalIdentifierCharacters.contains(c));
}
-
+
public static boolean isLegalStringCharacter(char c) {
return true;
}
diff --git a/test/parser/LispParserTester.java b/test/parser/LispParserTester.java
index 60dbb1f..16f13ae 100644
--- a/test/parser/LispParserTester.java
+++ b/test/parser/LispParserTester.java
@@ -1,20 +1,31 @@
package parser;
import static org.junit.Assert.*;
+import static testutil.TestUtilities.createIOExceptionThrowingInputStream;
+import static testutil.TestUtilities.createInputStreamFromString;
import java.io.InputStream;
import org.junit.Test;
import error.LispException;
+import scanner.LispInputStream.UncheckedIOException;
+import scanner.LispScanner.UnterminatedStringException;
+import sexpression.MalformedSExpressionException.EofEncounteredException;
+import sexpression.MalformedSExpressionException.StartsWithRightParenthesisException;
import sexpression.Nil;
import sexpression.SExpression;
-import testutils.TestUtilities;
+import token.TokenFactory.BadCharacterException;
public class LispParserTester {
private LispParser createLispParser(String input) {
- InputStream stringInputStream = TestUtilities.createInputStreamFromString(input);
+ InputStream stringInputStream = createInputStreamFromString(input);
+ return new LispParser(stringInputStream, "testFile");
+ }
+
+ private LispParser createIOExceptionThrowingLispParser() {
+ InputStream stringInputStream = createIOExceptionThrowingInputStream();
return new LispParser(stringInputStream, "testFile");
}
@@ -183,6 +194,17 @@ public class LispParserTester {
assertTrue(parser.isEof());
}
+ @Test
+ public void givenMultipleComplexLists_CreatesCorrectSExpressions() {
+ String input = "(defun f (x) \n (print \n (list \"x is \" x) \n ) \n )"
+ + "(defun f (x) \n (print \n (list \"x is \" x) \n ) \n )";
+ LispParser parser = createLispParser(input);
+
+ assertList(parser.getNextSExpression());
+ assertList(parser.getNextSExpression());
+ assertTrue(parser.isEof());
+ }
+
@Test
public void givenMultipleExpressions_CreatesCorrectSExpressions() {
String input = "(setf x 2) x \"hi\" () 29";
@@ -208,7 +230,7 @@ public class LispParserTester {
assertTrue(parser.isEof());
}
- @Test(expected = LispException.class)
+ @Test(expected = BadCharacterException.class)
public void givenBadToken_ThrowsException() {
String input = "[";
LispParser parser = createLispParser(input);
@@ -216,7 +238,7 @@ public class LispParserTester {
parser.getNextSExpression();
}
- @Test(expected = LispException.class)
+ @Test(expected = UnterminatedStringException.class)
public void givenUnterminatedString_ThrowsException() {
String input = "\"string";
LispParser parser = createLispParser(input);
@@ -224,7 +246,7 @@ public class LispParserTester {
parser.getNextSExpression();
}
- @Test(expected = LispException.class)
+ @Test(expected = EofEncounteredException.class)
public void givenUnterminatedList_ThrowsException() {
String input = "(bad list";
LispParser parser = createLispParser(input);
@@ -232,7 +254,7 @@ public class LispParserTester {
parser.getNextSExpression();
}
- @Test(expected = LispException.class)
+ @Test(expected = StartsWithRightParenthesisException.class)
public void givenUnmatchedRightParenthesis_ThrowsException() {
String input = ")";
LispParser parser = createLispParser(input);
@@ -240,7 +262,7 @@ public class LispParserTester {
parser.getNextSExpression();
}
- @Test(expected = LispException.class)
+ @Test(expected = BadCharacterException.class)
public void givenBadCharacter_ThrowsExceptionAfterEofCalled() {
String input = "[";
LispParser parser = createLispParser(input);
@@ -254,7 +276,7 @@ public class LispParserTester {
parser.getNextSExpression();
}
- @Test(expected = LispException.class)
+ @Test(expected = BadCharacterException.class)
public void givenBadCharacterAfterValidToken_ThrowsExceptionAtTheCorrectTime() {
String input = "id[";
LispParser parser = createLispParser(input);
@@ -279,9 +301,22 @@ public class LispParserTester {
try {
parser.getNextSExpression();
fail("Expected LispException");
- } catch (LispException e) {
- }
-
+ } catch (LispException e) {}
+
assertTrue(parser.isEof());
}
+
+ @Test(expected = UncheckedIOException.class)
+ public void handlesIOExceptionCorrectly() {
+ LispParser parser = createIOExceptionThrowingLispParser();
+
+ try {
+ parser.isEof();
+ } catch (LispException e) {
+ fail("Exception thrown too early");
+ }
+
+ parser.getNextSExpression();
+ }
+
}
diff --git a/test/scanner/LispCommentRemovingInputStreamTester.java b/test/scanner/LispCommentRemovingInputStreamTester.java
index 10e6667..f95c0e5 100644
--- a/test/scanner/LispCommentRemovingInputStreamTester.java
+++ b/test/scanner/LispCommentRemovingInputStreamTester.java
@@ -1,8 +1,9 @@
package scanner;
import static org.junit.Assert.*;
+import static testutil.TestUtilities.createIOExceptionThrowingInputStream;
+import static testutil.TestUtilities.createInputStreamFromString;
-import java.io.IOException;
import java.io.InputStream;
import org.junit.Test;
@@ -10,7 +11,6 @@ import org.junit.Test;
import error.ErrorManager;
import scanner.LispInputStream.MaximumUnreadsExceededException;
import scanner.LispInputStream.UncheckedIOException;
-import testutils.TestUtilities;
public class LispCommentRemovingInputStreamTester {
@@ -19,7 +19,7 @@ public class LispCommentRemovingInputStreamTester {
}
private LispInputStream createLispInputStream(String inputString) {
- InputStream stringInputStream = TestUtilities.createInputStreamFromString(inputString);
+ InputStream stringInputStream = createInputStreamFromString(inputString);
return new LispCommentRemovingInputStream(stringInputStream);
}
@@ -32,15 +32,6 @@ public class LispCommentRemovingInputStreamTester {
return charactersRead.toString();
}
- private InputStream createIOExceptionThrowingInputStream() {
- return new InputStream() {
-
- public int read() throws IOException {
- throw new IOException("test IOException");
- }
- };
- }
-
@Test
public void noBytesIn_noBytesOut() {
String input = "";
diff --git a/test/scanner/LispScannerLineColumnTester.java b/test/scanner/LispScannerLineColumnTester.java
index 29d4e81..3d547a9 100644
--- a/test/scanner/LispScannerLineColumnTester.java
+++ b/test/scanner/LispScannerLineColumnTester.java
@@ -1,12 +1,12 @@
package scanner;
import static org.junit.Assert.assertTrue;
+import static testutil.TestUtilities.createInputStreamFromString;
import java.io.InputStream;
import org.junit.Test;
-import testutils.TestUtilities;
import token.Token;
public class LispScannerLineColumnTester {
@@ -30,7 +30,7 @@ public class LispScannerLineColumnTester {
}
private void assertTokenLineAndColumnsMatch(String input, LineColumn[] expectedLineColumnList) {
- InputStream stringInputStream = TestUtilities.createInputStreamFromString(input);
+ InputStream stringInputStream = createInputStreamFromString(input);
LispScanner lispScanner = new LispScanner(stringInputStream, "testFile");
for (LineColumn lineColumn : expectedLineColumnList) {
diff --git a/test/scanner/LispScannerTextTester.java b/test/scanner/LispScannerTextTester.java
index 967a8a9..3f66da2 100644
--- a/test/scanner/LispScannerTextTester.java
+++ b/test/scanner/LispScannerTextTester.java
@@ -1,13 +1,12 @@
package scanner;
import static org.junit.Assert.assertEquals;
+import static testutil.TestUtilities.createInputStreamFromString;
import java.io.InputStream;
import org.junit.Test;
-import testutils.TestUtilities;
-
public class LispScannerTextTester {
private void assertTokenTextMatches(String input, String[] expectedTextList) {
@@ -24,13 +23,13 @@ public class LispScannerTextTester {
}
private LispScanner createLispScanner(String input) {
- InputStream stringInputStream = TestUtilities.createInputStreamFromString(input);
+ InputStream stringInputStream = createInputStreamFromString(input);
return new LispScanner(stringInputStream, "testFile");
}
private void assertInputFileNameMatches(String input, String expectedInputFileName) {
- InputStream stringInputStream = TestUtilities.createInputStreamFromString(input);
+ InputStream stringInputStream = createInputStreamFromString(input);
LispScanner lispScanner = new LispScanner(stringInputStream, expectedInputFileName);
assertEquals(expectedInputFileName, lispScanner.getNextToken().getFileName());
diff --git a/test/scanner/LispScannerTypeTester.java b/test/scanner/LispScannerTypeTester.java
index 3e67681..9539cc1 100644
--- a/test/scanner/LispScannerTypeTester.java
+++ b/test/scanner/LispScannerTypeTester.java
@@ -2,6 +2,7 @@ package scanner;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static testutil.TestUtilities.createInputStreamFromString;
import java.io.InputStream;
import java.util.ArrayList;
@@ -12,15 +13,8 @@ import org.junit.Test;
import error.ErrorManager;
import scanner.LispScanner.UnterminatedStringException;
-import testutils.TestUtilities;
-import token.Eof;
-import token.Identifier;
-import token.LeftParenthesis;
+import token.*;
import token.Number;
-import token.QuoteMark;
-import token.QuotedString;
-import token.RightParenthesis;
-import token.Token;
import token.TokenFactory.BadCharacterException;
public class LispScannerTypeTester {
@@ -28,7 +22,7 @@ public class LispScannerTypeTester {
private List> expectedTypes;
private void assertTokenTypesMatch(String input) {
- InputStream stringInputStream = TestUtilities.createInputStreamFromString(input);
+ InputStream stringInputStream = createInputStreamFromString(input);
LispScanner lispScanner = new LispScanner(stringInputStream, "testFile");
for (Class extends Token> type : expectedTypes)
diff --git a/test/testutil/TestUtilities.java b/test/testutil/TestUtilities.java
new file mode 100644
index 0000000..97e4b27
--- /dev/null
+++ b/test/testutil/TestUtilities.java
@@ -0,0 +1,20 @@
+package testutil;
+
+import java.io.*;
+
+public class TestUtilities {
+
+ public static InputStream createInputStreamFromString(String string) {
+ return new ByteArrayInputStream(string.getBytes());
+ }
+
+ public static InputStream createIOExceptionThrowingInputStream() {
+ return new InputStream() {
+
+ public int read() throws IOException {
+ throw new IOException("test IOException");
+ }
+ };
+ }
+
+}
diff --git a/test/testutils/TestUtilities.java b/test/testutils/TestUtilities.java
deleted file mode 100644
index 6d8f896..0000000
--- a/test/testutils/TestUtilities.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package testutils;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-
-public class TestUtilities {
-
- public static InputStream createInputStreamFromString(String string) {
- return new ByteArrayInputStream(string.getBytes());
- }
-
-}