Added unit tests for the LispParser and started refactoring

This commit is contained in:
Mike Cifelli 2016-12-14 12:10:28 -05:00
parent be986ea5cf
commit 10fdbf3b75
3 changed files with 361 additions and 111 deletions

View File

@ -1,137 +1,61 @@
/*
* Name: Mike Cifelli
* Course: CIS 443 - Programming Languages
* Assignment: Lisp Parser
*/
package parser; package parser;
import java.io.InputStream; import java.io.InputStream;
import error.LispException;
import parser.MalformedSExpressionException.EofEncounteredException;
import parser.MalformedSExpressionException.StartsWithRightParenthesisException;
import parser.MalformedSExpressionException.UnrecognizedTokenException;
import scanner.LispScanner; import scanner.LispScanner;
import token.Eof;
import token.Token; import token.Token;
/** /**
* A <code>LispParser</code> converts a stream of bytes into internal * Converts a stream of bytes into internal representations of Lisp
* representations of Lisp S-expressions. When the end of stream has been * S-expressions.
* reached the <code>eof</code> method of this parser will return true.
*/ */
public class LispParser { public class LispParser {
private LispScanner scanner; private LispScanner scanner;
private Token nextToken; 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; private boolean nextTokenStored;
/** public LispParser(InputStream inputStream, String fileName) {
* Create a new <code>LispParser</code> that produces S-expressions from scanner = new LispScanner(inputStream, fileName);
* the specified input stream.
*
* @param in
* the input stream to obtain S-expressions from (must not be
* <code>null</code>)
* @param fileName
* the name of the file that <code>in</code> is reading from
*/
public LispParser(InputStream in, String fileName) {
scanner = new LispScanner(in, fileName);
nextToken = null; nextToken = null;
delayedException = null;
nextTokenStored = false; nextTokenStored = false;
} }
/**
* Determing if this parser has reached the end of its underlying input
* stream.
*
* @return
* <code>true</code> if this parser has reached the end of its underlying
* input stream; <code>false</code> otherwise
*/
public boolean eof() { public boolean eof() {
if (! nextTokenStored) { if (!nextTokenStored) {
// attempt to read the next token from 'scanner' and store it in
// 'nextToken'
try { try {
nextToken = scanner.getNextToken(); nextToken = scanner.getNextToken();
nextTokenStored = true; nextTokenStored = true;
} catch (Exception e) { } catch (LispException e) {
// this method should give the illusion of not actually reading // this method should give the illusion of not actually reading
// a token, so we store any exceptions thrown as a result of // a token, so we ignore any exceptions (they will be thrown
// reading in the next token from 'scanner' (it will be thrown
// the next time the 'getSExpr' method is called) // 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 false;
} }
} }
return nextToken instanceof Eof;
} }
return (nextToken.getType() == Token.Type.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() { public SExpression getSExpr() {
if (delayedException != null) { if (!nextTokenStored)
// 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
nextToken = scanner.getNextToken(); nextToken = scanner.getNextToken();
} else { 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
nextTokenStored = false; nextTokenStored = false;
}
return sExpr(); return sExpr();
} }
// sExpr ::= NUMBER | IDENTIFIER | STRING | QUOTE_MARK sExpr | // sExpr ::= NUMBER | IDENTIFIER | STRING | QUOTE_MARK sExpr |
// LEFT_PAREN sExprTail // 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.
private SExpression sExpr() { private SExpression sExpr() {
// determine the type of 'nextToken' and create the appropriate
// S-expression
switch (nextToken.getType()) { switch (nextToken.getType()) {
case NUMBER: case NUMBER:
return new LispNumber(nextToken.getText()); return new LispNumber(nextToken.getText());
@ -143,35 +67,22 @@ public class LispParser {
nextToken = scanner.getNextToken(); nextToken = scanner.getNextToken();
SExpression arg = sExpr(); SExpression arg = sExpr();
return new Cons(new Symbol("QUOTE"), return new Cons(new Symbol("QUOTE"), new Cons(arg, Nil.getUniqueInstance()));
new Cons(arg, Nil.getUniqueInstance()));
case LEFT_PAREN: case LEFT_PAREN:
return sExprTail(); return sExprTail();
case RIGHT_PAREN: case RIGHT_PAREN:
throw new RuntimeException("expression begins with \')\'" + throw new StartsWithRightParenthesisException(nextToken);
" - line " + nextToken.getLine() +
" column " + nextToken.getColumn());
case EOF: case EOF:
throw new RuntimeException("end-of-file encountered" + throw new EofEncounteredException(nextToken);
" - line " + nextToken.getLine() + default:
" column " + nextToken.getColumn()); throw new UnrecognizedTokenException(nextToken);
default: // unrecognized token type
throw new RuntimeException("unrecognizable token" +
" - line " + nextToken.getLine() +
" column " + nextToken.getColumn());
} }
} }
// sExprTail ::= RIGHT_PAREN | sExpr sExprTail // 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() { private SExpression sExprTail() {
nextToken = scanner.getNextToken(); nextToken = scanner.getNextToken();
// determine the type of 'nextToken' and create the appropriate
// S-expression
switch (nextToken.getType()) { switch (nextToken.getType()) {
case RIGHT_PAREN: case RIGHT_PAREN:
return Nil.getUniqueInstance(); return Nil.getUniqueInstance();

View File

@ -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";
}
}
}

View File

@ -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();
}
}