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