From 10fdbf3b75dee0d0278b0b19247239594a3aad7f Mon Sep 17 00:00:00 2001 From: Mike Cifelli Date: Wed, 14 Dec 2016 12:10:28 -0500 Subject: [PATCH] Added unit tests for the LispParser and started refactoring --- src/parser/LispParser.java | 133 ++------- src/parser/MalformedSExpressionException.java | 69 +++++ test/parser/LispParserTester.java | 270 ++++++++++++++++++ 3 files changed, 361 insertions(+), 111 deletions(-) create mode 100644 src/parser/MalformedSExpressionException.java create mode 100644 test/parser/LispParserTester.java 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(); + } + +}