diff --git a/src/parser/LispParser.java b/src/parser/LispParser.java
index eea61c7..c94d661 100644
--- a/src/parser/LispParser.java
+++ b/src/parser/LispParser.java
@@ -1,137 +1,61 @@
-/*
- * Name: Mike Cifelli
- * Course: CIS 443 - Programming Languages
- * Assignment: Lisp Parser
- */
-
package parser;
import java.io.InputStream;
+import error.LispException;
+import parser.MalformedSExpressionException.EofEncounteredException;
+import parser.MalformedSExpressionException.StartsWithRightParenthesisException;
+import parser.MalformedSExpressionException.UnrecognizedTokenException;
import scanner.LispScanner;
+import token.Eof;
import token.Token;
/**
- * A LispParser
converts a stream of bytes into internal
- * representations of Lisp S-expressions. When the end of stream has been
- * reached the eof
method of this parser will return true.
+ * Converts a stream of bytes into internal representations of Lisp
+ * S-expressions.
*/
public class LispParser {
private LispScanner scanner;
private Token nextToken;
-
- // A field to store an exception that has been thrown in the 'eof' method
- // as a result of reading in the next token from 'scanner'.
- private Exception delayedException;
-
- // A field to notify us if the next token has already been stored in
- // 'nextToken' by the 'eof' method.
private boolean nextTokenStored;
- /**
- * Create a new LispParser
that produces S-expressions from
- * the specified input stream.
- *
- * @param in
- * the input stream to obtain S-expressions from (must not be
- * null
)
- * @param fileName
- * the name of the file that in
is reading from
- */
- public LispParser(InputStream in, String fileName) {
- scanner = new LispScanner(in, fileName);
+ public LispParser(InputStream inputStream, String fileName) {
+ scanner = new LispScanner(inputStream, fileName);
nextToken = null;
- delayedException = null;
nextTokenStored = false;
}
- /**
- * Determing if this parser has reached the end of its underlying input
- * stream.
- *
- * @return
- * true
if this parser has reached the end of its underlying
- * input stream; false
otherwise
- */
public boolean eof() {
- if (! nextTokenStored) {
- // attempt to read the next token from 'scanner' and store it in
- // 'nextToken'
+ if (!nextTokenStored) {
try {
nextToken = scanner.getNextToken();
nextTokenStored = true;
- } catch (Exception e) {
+ } catch (LispException e) {
// this method should give the illusion of not actually reading
- // a token, so we store any exceptions thrown as a result of
- // reading in the next token from 'scanner' (it will be thrown
+ // a token, so we ignore any exceptions (they will be thrown
// the next time the 'getSExpr' method is called)
- delayedException = e;
-
- if (nextToken == null) {
- // we have not successfully read in any tokens yet, so we
- // could not have read in an end-of-file token
+ if (nextToken == null)
return false;
- }
}
}
- return (nextToken.getType() == Token.Type.EOF);
+ return nextToken instanceof Eof;
}
- /**
- * Returns the next S-expression from this parser.
- *
- * @return
- * the next S-expression from this parser
- * @throws RuntimeException
- * Indicates that an illegal S-expression was encountered in the
- * underlying input stream.
- * @throws IOException
- * Indicates that an I/O error has occurred.
- */
public SExpression getSExpr() {
- if (delayedException != null) {
- // the 'eof' method has stored an exception for us to throw
-
- // determine the type of the stored exception and throw it!
- if (delayedException instanceof RuntimeException) {
- RuntimeException e = (RuntimeException) delayedException;
-
- // remove the exception from 'delayedException'
- delayedException = null;
-
- throw e;
- }
- }
-
- if (! nextTokenStored) {
- // the next token has not been stored in 'nextToken' by the 'eof'
- // method
-
+ if (!nextTokenStored)
nextToken = scanner.getNextToken();
- } else {
- // the 'eof' method has been called and has read in the next token
- // already to determine if we have reached the end-of-file
-
+ else
nextTokenStored = false;
- }
return sExpr();
}
// sExpr ::= NUMBER | IDENTIFIER | STRING | QUOTE_MARK sExpr |
- // LEFT_PAREN sExprTail
- //
- // Returns: an S-expression that matches the rules given above
- // Throws: RuntimeException - Indicates that an illegal S-expression was
- // encountered in the underlying input stream.
- // Throws: IOException - Indicates that an I/O error has occurred.
- // Precondition: 'nextToken' is not null.
+ // LEFT_PAREN sExprTail
private SExpression sExpr() {
- // determine the type of 'nextToken' and create the appropriate
- // S-expression
switch (nextToken.getType()) {
case NUMBER:
return new LispNumber(nextToken.getText());
@@ -143,35 +67,22 @@ public class LispParser {
nextToken = scanner.getNextToken();
SExpression arg = sExpr();
- return new Cons(new Symbol("QUOTE"),
- new Cons(arg, Nil.getUniqueInstance()));
+ return new Cons(new Symbol("QUOTE"), new Cons(arg, Nil.getUniqueInstance()));
case LEFT_PAREN:
return sExprTail();
case RIGHT_PAREN:
- throw new RuntimeException("expression begins with \')\'" +
- " - line " + nextToken.getLine() +
- " column " + nextToken.getColumn());
+ throw new StartsWithRightParenthesisException(nextToken);
case EOF:
- throw new RuntimeException("end-of-file encountered" +
- " - line " + nextToken.getLine() +
- " column " + nextToken.getColumn());
- default: // unrecognized token type
- throw new RuntimeException("unrecognizable token" +
- " - line " + nextToken.getLine() +
- " column " + nextToken.getColumn());
+ throw new EofEncounteredException(nextToken);
+ default:
+ throw new UnrecognizedTokenException(nextToken);
}
}
// sExprTail ::= RIGHT_PAREN | sExpr sExprTail
- //
- // Returns: an S-expression that matches the rules given above
- // Throws: IOException - Indicates that an I/O error has occurred.
- // Precondition: 'scanner' is not null.
private SExpression sExprTail() {
nextToken = scanner.getNextToken();
- // determine the type of 'nextToken' and create the appropriate
- // S-expression
switch (nextToken.getType()) {
case RIGHT_PAREN:
return Nil.getUniqueInstance();
diff --git a/src/parser/MalformedSExpressionException.java b/src/parser/MalformedSExpressionException.java
new file mode 100644
index 0000000..cf28963
--- /dev/null
+++ b/src/parser/MalformedSExpressionException.java
@@ -0,0 +1,69 @@
+package parser;
+
+import java.text.MessageFormat;
+
+import error.LispException;
+import token.Token;
+
+public abstract class MalformedSExpressionException extends LispException {
+
+ private static final long serialVersionUID = 1L;
+ private Token token;
+
+ public MalformedSExpressionException(Token token) {
+ this.token = token;
+ }
+
+ @Override
+ public int getSeverity() {
+ return 0;
+ }
+
+ @Override
+ public String getMessage() {
+ return MessageFormat.format("{0} - line {1}, column {2}", getMessagePrefix(), token.getLine(),
+ token.getColumn());
+ }
+
+ public abstract String getMessagePrefix();
+
+ public static class EofEncounteredException extends MalformedSExpressionException {
+
+ private static final long serialVersionUID = 1L;
+
+ public EofEncounteredException(Token token) {
+ super(token);
+ }
+
+ public String getMessagePrefix() {
+ return "end-of-file encountered";
+ }
+ }
+
+ public static class StartsWithRightParenthesisException extends MalformedSExpressionException {
+
+ private static final long serialVersionUID = 1L;
+
+ public StartsWithRightParenthesisException(Token token) {
+ super(token);
+ }
+
+ public String getMessagePrefix() {
+ return "Expression begins with ')'";
+ }
+ }
+
+ public static class UnrecognizedTokenException extends MalformedSExpressionException {
+
+ private static final long serialVersionUID = 1L;
+
+ public UnrecognizedTokenException(Token token) {
+ super(token);
+ }
+
+ public String getMessagePrefix() {
+ return "Unrecognized token";
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/test/parser/LispParserTester.java b/test/parser/LispParserTester.java
new file mode 100644
index 0000000..1bb0581
--- /dev/null
+++ b/test/parser/LispParserTester.java
@@ -0,0 +1,270 @@
+package parser;
+
+import static org.junit.Assert.*;
+
+import java.io.InputStream;
+
+import org.junit.Test;
+
+import error.LispException;
+import testutils.TestUtilities;
+
+public class LispParserTester {
+
+ private LispParser createLispParser(String input) {
+ InputStream stringInputStream = TestUtilities.createInputStreamFromString(input);
+ return new LispParser(stringInputStream, "testFile");
+ }
+
+ private void assertList(SExpression sExpression) {
+ assertFalse(sExpression.atomp());
+ assertTrue(sExpression.consp());
+ assertFalse(sExpression.functionp());
+ assertTrue(sExpression.listp());
+ assertFalse(sExpression.nullp());
+ assertFalse(sExpression.numberp());
+ assertFalse(sExpression.stringp());
+ assertFalse(sExpression.symbolp());
+ }
+
+ private void assertNil(SExpression sExpression) {
+ assertEquals(sExpression, Nil.getUniqueInstance());
+
+ assertTrue(sExpression.atomp());
+ assertFalse(sExpression.consp());
+ assertFalse(sExpression.functionp());
+ assertTrue(sExpression.listp());
+ assertTrue(sExpression.nullp());
+ assertFalse(sExpression.numberp());
+ assertFalse(sExpression.stringp());
+ assertTrue(sExpression.symbolp());
+
+ }
+
+ private void assertNumber(SExpression sExpression) {
+ assertTrue(sExpression.atomp());
+ assertFalse(sExpression.consp());
+ assertFalse(sExpression.functionp());
+ assertFalse(sExpression.listp());
+ assertFalse(sExpression.nullp());
+ assertTrue(sExpression.numberp());
+ assertFalse(sExpression.stringp());
+ assertFalse(sExpression.symbolp());
+ }
+
+ private void assertString(SExpression sExpression) {
+ assertTrue(sExpression.atomp());
+ assertFalse(sExpression.consp());
+ assertFalse(sExpression.functionp());
+ assertFalse(sExpression.listp());
+ assertFalse(sExpression.nullp());
+ assertFalse(sExpression.numberp());
+ assertTrue(sExpression.stringp());
+ assertFalse(sExpression.symbolp());
+ }
+
+ private void assertSymbol(SExpression sExpression) {
+ assertTrue(sExpression.atomp());
+ assertFalse(sExpression.consp());
+ assertFalse(sExpression.functionp());
+ assertFalse(sExpression.listp());
+ assertFalse(sExpression.nullp());
+ assertFalse(sExpression.numberp());
+ assertFalse(sExpression.stringp());
+ assertTrue(sExpression.symbolp());
+ }
+
+ @Test
+ public void testEofMethod_ReturnsTrueWithNoInput() {
+ String input = "";
+ LispParser parser = createLispParser(input);
+
+ assertTrue(parser.eof());
+ }
+
+ @Test
+ public void testEofMethod_ReturnsFalseWithSomeInput() {
+ String input = "abc";
+ LispParser parser = createLispParser(input);
+
+ assertFalse(parser.eof());
+ }
+
+ @Test
+ public void testEofMethod_ReturnsTrueAfterSomeInput() {
+ String input = "(yyz 9 9 9)";
+ LispParser parser = createLispParser(input);
+ parser.getSExpr();
+
+ assertTrue(parser.eof());
+ }
+
+ @Test
+ public void testEofMethod_ReturnsFalseAfterMultipleExpressions() {
+ String input = "()()()";
+ LispParser parser = createLispParser(input);
+ assertFalse(parser.eof());
+ parser.getSExpr();
+ assertFalse(parser.eof());
+ parser.getSExpr();
+ assertFalse(parser.eof());
+ }
+
+ @Test
+ public void testEofMethod_ReturnsTrueAfterMultipleExpressions() {
+ String input = "()()()";
+ LispParser parser = createLispParser(input);
+ assertFalse(parser.eof());
+ parser.getSExpr();
+ assertFalse(parser.eof());
+ parser.getSExpr();
+ assertFalse(parser.eof());
+ parser.getSExpr();
+ assertTrue(parser.eof());
+ }
+
+ @Test
+ public void givenNil_CreatesCorrectSExpression() {
+ String input = "()";
+ LispParser parser = createLispParser(input);
+
+ assertNil(parser.getSExpr());
+ }
+
+ @Test
+ public void givenNumber_CreatesCorrectSExpression() {
+ String input = "12";
+ LispParser parser = createLispParser(input);
+
+ assertNumber(parser.getSExpr());
+ }
+
+ @Test
+ public void givenIdentifier_CreatesCorrectSExpression() {
+ String input = "identifier1";
+ LispParser parser = createLispParser(input);
+
+ assertSymbol(parser.getSExpr());
+ }
+
+ @Test
+ public void givenString_CreatesCorrectSExpression() {
+ String input = "\"string\"";
+ LispParser parser = createLispParser(input);
+
+ assertString(parser.getSExpr());
+ }
+
+ @Test
+ public void givenList_CreatesCorrectSExpression() {
+ String input = "(1 2)";
+ LispParser parser = createLispParser(input);
+
+ assertList(parser.getSExpr());
+ }
+
+ @Test
+ public void givenQuotedIdentifier_CreatesCorrectSExpression() {
+ String input = "'quoted";
+ LispParser parser = createLispParser(input);
+
+ assertList(parser.getSExpr());
+ assertTrue(parser.eof());
+ }
+
+ @Test
+ public void givenComplexList_CreatesCorrectSExpression() {
+ String input = "(defun f (x) \n (print \n (list \"x is \" x) \n ) \n )";
+ LispParser parser = createLispParser(input);
+
+ assertList(parser.getSExpr());
+ assertTrue(parser.eof());
+ }
+
+ @Test
+ public void givenMultipleExpressions_CreatesCorrectSExpressions() {
+ String input = "(setf x 2) x \"hi\" () 29";
+ LispParser parser = createLispParser(input);
+
+ assertList(parser.getSExpr());
+ assertSymbol(parser.getSExpr());
+ assertString(parser.getSExpr());
+ assertNil(parser.getSExpr());
+ assertNumber(parser.getSExpr());
+ assertTrue(parser.eof());
+ }
+
+ @Test
+ public void givenNil_CreatesCorrectSExpressionAfterEofCalls() {
+ String input = "()";
+ LispParser parser = createLispParser(input);
+
+ parser.eof();
+ parser.eof();
+
+ assertNil(parser.getSExpr());
+ assertTrue(parser.eof());
+ }
+
+ @Test(expected = LispException.class)
+ public void givenBadToken_ThrowsException() {
+ String input = "[";
+ LispParser parser = createLispParser(input);
+
+ parser.getSExpr();
+ }
+
+ @Test(expected = LispException.class)
+ public void givenUnterminatedString_ThrowsException() {
+ String input = "\"string";
+ LispParser parser = createLispParser(input);
+
+ parser.getSExpr();
+ }
+
+ @Test(expected = LispException.class)
+ public void givenUnterminatedList_ThrowsException() {
+ String input = "(bad list";
+ LispParser parser = createLispParser(input);
+
+ parser.getSExpr();
+ }
+
+ @Test(expected = LispException.class)
+ public void givenUnmatchedRightParenthesis_ThrowsException() {
+ String input = ")";
+ LispParser parser = createLispParser(input);
+
+ parser.getSExpr();
+ }
+
+ @Test(expected = LispException.class)
+ public void givenBadCharacter_ThrowsExceptionAfterEofCalled() {
+ String input = "[";
+ LispParser parser = createLispParser(input);
+
+ try {
+ parser.eof();
+ } catch (LispException e) {
+ fail("Exception thrown too early");
+ }
+
+ parser.getSExpr();
+ }
+
+ @Test(expected = LispException.class)
+ public void givenBadCharacterAfterValidToken_ThrowsExceptionAtTheCorrectTime() {
+ String input = "id[]";
+ LispParser parser = createLispParser(input);
+
+ try {
+ parser.getSExpr();
+ parser.eof();
+ } catch (LispException e) {
+ fail("Exception thrown too early");
+ }
+
+ parser.getSExpr();
+ }
+
+}