diff --git a/src/function/LispFunction.java b/src/function/LispFunction.java index ecf80e0..2b998f6 100644 --- a/src/function/LispFunction.java +++ b/src/function/LispFunction.java @@ -9,7 +9,7 @@ public abstract class LispFunction { public boolean isArgumentListEvaluated() { return true; } - + public boolean isMacro() { return false; } diff --git a/src/function/LispMacro.java b/src/function/LispMacro.java deleted file mode 100644 index a90d5bf..0000000 --- a/src/function/LispMacro.java +++ /dev/null @@ -1,10 +0,0 @@ -package function; - -public abstract class LispMacro extends LispSpecialFunction { - - @Override - public boolean isMacro() { - return true; - } - -} diff --git a/src/function/builtin/BackTickEvaluator.java b/src/function/builtin/BackTickEvaluator.java new file mode 100644 index 0000000..1459728 --- /dev/null +++ b/src/function/builtin/BackTickEvaluator.java @@ -0,0 +1,181 @@ +package function.builtin; + +import static function.builtin.EVAL.eval; +import static sexpression.Nil.NIL; + +import error.LispException; +import sexpression.*; + +class BackTickEvaluator { + + private BackTickExpression backTick; + private Cons resolvedList; + private Cons leader; + private Cons follower; + + public BackTickEvaluator(BackTickExpression backTick) { + this.backTick = backTick; + this.resolvedList = new Cons(NIL, NIL); + this.leader = resolvedList; + this.follower = resolvedList; + } + + public SExpression evaluate() { + SExpression expression = backTick.getExpression(); + + if (expression.isCons()) + expression = resolveList((Cons) expression); + else if (expression.isComma()) + expression = eval(((CommaExpression) expression).getExpression()); + else if (expression.isAtSign()) + throw new AtSignNotInCommaException(); + + return expression; + } + + public SExpression resolveList(Cons list) { + createResolvedList(list); + + return resolvedList; + } + + private void createResolvedList(Cons list) { + for (; list.isCons(); list = (Cons) list.getRest()) + resolveExpression(list.getFirst()); + + follower.setRest(NIL); + } + + private void resolveExpression(SExpression expression) { + if (expression.isAtSign()) + throw new AtSignNotInCommaException(); + else if (expression.isComma()) + resolveCommaExpression(expression); + else + addResolvedExpression(expression); + } + + private void resolveCommaExpression(SExpression expression) { + CommaEvaluationResult result = evaluateComma((CommaExpression) expression); + + if (result.isAtSign()) + unpackResolvedList((Cons) result.getResult()); + else + addResolvedExpression(result.getResult()); + } + + private CommaEvaluationResult evaluateComma(CommaExpression comma) { + SExpression expression = comma.getExpression(); + validateCommaExpression(expression); + + if (expression.isAtSign()) + return new CommaEvaluationAtSignResult(evaluateAtSign((AtSignExpression) expression)); + else + return new CommaEvaluationResult(eval(expression)); + } + + private void validateCommaExpression(SExpression expression) { + if (expression.isComma()) + throw new NestedCommaException(); + } + + private Cons evaluateAtSign(AtSignExpression atSign) { + SExpression expression = atSign.getExpression(); + validateAtSignExpression(expression); + SExpression result = eval(expression); + + if (!result.isList()) + throw new AtSignNotListException(); + + return (Cons) result; + } + + private void validateAtSignExpression(SExpression expression) { + if (expression.isComma()) + throw new NestedCommaException(); + else if (expression.isAtSign()) + throw new NestedAtSignException(); + } + + private void unpackResolvedList(Cons list) { + for (; list.isCons(); list = (Cons) list.getRest()) + addResolvedExpression(list.getFirst()); + } + + private void addResolvedExpression(SExpression expression) { + leader.setFirst(expression); + leader.setRest(new Cons(NIL, NIL)); + follower = leader; + leader = (Cons) leader.getRest(); + } + + private static class CommaEvaluationResult { + + private SExpression result; + + public CommaEvaluationResult(SExpression result) { + this.result = result; + } + + public SExpression getResult() { + return result; + } + + public boolean isAtSign() { + return false; + } + } + + private static class CommaEvaluationAtSignResult extends CommaEvaluationResult { + + public CommaEvaluationAtSignResult(SExpression result) { + super(result); + } + + @Override + public boolean isAtSign() { + return true; + } + } + + public static class NestedCommaException extends LispException { + + private static final long serialVersionUID = 1L; + + @Override + public String getMessage() { + return "nested comma"; + } + } + + public static class NestedAtSignException extends LispException { + + private static final long serialVersionUID = 1L; + + @Override + public String getMessage() { + return "nested at sign"; + } + } + + public static class AtSignNotInCommaException extends LispException { + + private static final long serialVersionUID = 1L; + + @Override + public String getMessage() { + return "at sign not in comma"; + } + } + + public static class AtSignNotListException extends LispException { + + private static final long serialVersionUID = 1L; + + @Override + public String getMessage() { + return "at sign did not evaluate to a list"; + } + } + +} diff --git a/src/function/builtin/EVAL.java b/src/function/builtin/EVAL.java index ecde727..e24098a 100644 --- a/src/function/builtin/EVAL.java +++ b/src/function/builtin/EVAL.java @@ -68,9 +68,14 @@ public class EVAL extends LispFunction { private SExpression evaluateExpression(SExpression argument) { if (argument.isList()) return evaluateList(argument); - - if (argument.isSymbol()) + else if (argument.isSymbol()) return evaluateSymbol(argument); + else if (argument.isBackTick()) + return evaluateBackTick(argument); + else if (argument.isComma()) + throw new UnmatchedCommaException(); + else if (argument.isAtSign()) + throw new UnmatchedAtSignException(); return argument; // NUMBER or STRING } @@ -127,6 +132,12 @@ public class EVAL extends LispFunction { throw new UndefinedSymbolException(argument); } + private SExpression evaluateBackTick(SExpression argument) { + BackTickEvaluator evaluator = new BackTickEvaluator((BackTickExpression) argument); + + return evaluator.evaluate(); + } + public static class UndefinedFunctionException extends LispException { private static final long serialVersionUID = 1L; @@ -157,4 +168,24 @@ public class EVAL extends LispFunction { } } + public static class UnmatchedCommaException extends LispException { + + private static final long serialVersionUID = 1L; + + @Override + public String getMessage() { + return "unmatched comma"; + } + } + + public static class UnmatchedAtSignException extends LispException { + + private static final long serialVersionUID = 1L; + + @Override + public String getMessage() { + return "unmatched at sign"; + } + } + } diff --git a/src/sexpression/AtSignExpression.java b/src/sexpression/AtSignExpression.java new file mode 100644 index 0000000..ff3e894 --- /dev/null +++ b/src/sexpression/AtSignExpression.java @@ -0,0 +1,23 @@ +package sexpression; + +public class AtSignExpression extends SExpression { + + private SExpression expression; + + public AtSignExpression(SExpression expression) { + this.expression = expression; + } + + public SExpression getExpression() { + return expression; + } + + public boolean isAtSign() { + return true; + } + + public String toString() { + return "@" + expression; + } + +} diff --git a/src/sexpression/BackTickExpression.java b/src/sexpression/BackTickExpression.java new file mode 100644 index 0000000..f76aad0 --- /dev/null +++ b/src/sexpression/BackTickExpression.java @@ -0,0 +1,23 @@ +package sexpression; + +public class BackTickExpression extends SExpression { + + private SExpression expression; + + public BackTickExpression(SExpression expression) { + this.expression = expression; + } + + public SExpression getExpression() { + return expression; + } + + public boolean isBackTick() { + return true; + } + + public String toString() { + return "`" + expression; + } + +} diff --git a/src/sexpression/CommaExpression.java b/src/sexpression/CommaExpression.java new file mode 100644 index 0000000..4738004 --- /dev/null +++ b/src/sexpression/CommaExpression.java @@ -0,0 +1,23 @@ +package sexpression; + +public class CommaExpression extends SExpression { + + private SExpression expression; + + public CommaExpression(SExpression expression) { + this.expression = expression; + } + + public SExpression getExpression() { + return expression; + } + + public boolean isComma() { + return true; + } + + public String toString() { + return "," + expression; + } + +} diff --git a/src/sexpression/SExpression.java b/src/sexpression/SExpression.java index cc7d8c4..ff8bf76 100644 --- a/src/sexpression/SExpression.java +++ b/src/sexpression/SExpression.java @@ -35,4 +35,16 @@ public abstract class SExpression { return false; } + public boolean isBackTick() { + return false; + } + + public boolean isComma() { + return false; + } + + public boolean isAtSign() { + return false; + } + } diff --git a/src/token/AtSign.java b/src/token/AtSign.java new file mode 100644 index 0000000..e733e50 --- /dev/null +++ b/src/token/AtSign.java @@ -0,0 +1,22 @@ +package token; + +import java.util.function.Supplier; + +import file.FilePosition; +import sexpression.*; + +public class AtSign extends Token { + + public AtSign(String text, FilePosition position) { + super(text, position); + } + + @Override + public SExpression parseSExpression(Supplier getNextToken) { + Token nextToken = getNextToken.get(); + SExpression argument = nextToken.parseSExpression(getNextToken); + + return new AtSignExpression(argument); + } + +} diff --git a/src/token/BackTick.java b/src/token/BackTick.java new file mode 100644 index 0000000..91648f7 --- /dev/null +++ b/src/token/BackTick.java @@ -0,0 +1,22 @@ +package token; + +import java.util.function.Supplier; + +import file.FilePosition; +import sexpression.*; + +public class BackTick extends Token { + + public BackTick(String text, FilePosition position) { + super(text, position); + } + + @Override + public SExpression parseSExpression(Supplier getNextToken) { + Token nextToken = getNextToken.get(); + SExpression argument = nextToken.parseSExpression(getNextToken); + + return new BackTickExpression(argument); + } + +} diff --git a/src/token/Comma.java b/src/token/Comma.java new file mode 100644 index 0000000..c661bef --- /dev/null +++ b/src/token/Comma.java @@ -0,0 +1,22 @@ +package token; + +import java.util.function.Supplier; + +import file.FilePosition; +import sexpression.*; + +public class Comma extends Token { + + public Comma(String text, FilePosition position) { + super(text, position); + } + + @Override + public SExpression parseSExpression(Supplier getNextToken) { + Token nextToken = getNextToken.get(); + SExpression argument = nextToken.parseSExpression(getNextToken); + + return new CommaExpression(argument); + } + +} diff --git a/src/token/TokenFactoryImpl.java b/src/token/TokenFactoryImpl.java index d9e57d2..d1db477 100644 --- a/src/token/TokenFactoryImpl.java +++ b/src/token/TokenFactoryImpl.java @@ -22,6 +22,12 @@ public class TokenFactoryImpl implements TokenFactory { return new QuoteMark(text, position); case DOUBLE_QUOTE: return new QuotedString(text, position); + case BACK_TICK: + return new BackTick(text, position); + case AT_SIGN: + return new AtSign(text, position); + case COMMA: + return new Comma(text, position); default: if (isNumeric(firstCharacter, text)) { return new Number(text, position); diff --git a/src/util/Characters.java b/src/util/Characters.java index df61bc0..2720d8e 100644 --- a/src/util/Characters.java +++ b/src/util/Characters.java @@ -8,7 +8,10 @@ public final class Characters { public static final int EOF = -1; + public static final char AT_SIGN = '@'; public static final char BACKSLASH = '\\'; + public static final char BACK_TICK = '`'; + public static final char COMMA = ','; public static final char DASH = '-'; public static final char DOUBLE_QUOTE = '\"'; public static final char HASH = '#'; @@ -21,22 +24,23 @@ public final class Characters { public static final char RIGHT_SQUARE_BRACKET = ']'; public static final char SEMICOLON = ';'; public static final char SINGLE_QUOTE = '\''; - public static final char TICK_MARK = '`'; public static final Set illegalIdentifierCharacters = new HashSet<>(); static { - illegalIdentifierCharacters.add(DOUBLE_QUOTE); - illegalIdentifierCharacters.add(SINGLE_QUOTE); + illegalIdentifierCharacters.add(AT_SIGN); illegalIdentifierCharacters.add(BACKSLASH); - illegalIdentifierCharacters.add(TICK_MARK); - illegalIdentifierCharacters.add(LEFT_PARENTHESIS); - illegalIdentifierCharacters.add(RIGHT_PARENTHESIS); - illegalIdentifierCharacters.add(LEFT_SQUARE_BRACKET); - illegalIdentifierCharacters.add(RIGHT_SQUARE_BRACKET); + illegalIdentifierCharacters.add(BACK_TICK); + illegalIdentifierCharacters.add(COMMA); + illegalIdentifierCharacters.add(DOUBLE_QUOTE); illegalIdentifierCharacters.add(HASH); + illegalIdentifierCharacters.add(LEFT_PARENTHESIS); + illegalIdentifierCharacters.add(LEFT_SQUARE_BRACKET); illegalIdentifierCharacters.add(PERIOD); + illegalIdentifierCharacters.add(RIGHT_PARENTHESIS); + illegalIdentifierCharacters.add(RIGHT_SQUARE_BRACKET); illegalIdentifierCharacters.add(SEMICOLON); + illegalIdentifierCharacters.add(SINGLE_QUOTE); } public static boolean isLegalIdentifierCharacter(char c) { diff --git a/test/function/builtin/BackTickEvaluatorTester.java b/test/function/builtin/BackTickEvaluatorTester.java new file mode 100644 index 0000000..629729e --- /dev/null +++ b/test/function/builtin/BackTickEvaluatorTester.java @@ -0,0 +1,135 @@ +package function.builtin; + +import static sexpression.Nil.NIL; +import static testutil.TestUtilities.*; + +import org.junit.Test; + +import function.builtin.BackTickEvaluator.*; +import sexpression.*; + +public class BackTickEvaluatorTester { + + private BackTickEvaluator createBackTickEvaluator(SExpression expression) { + return new BackTickEvaluator(new BackTickExpression(expression)); + } + + @Test + public void evaluateNil() { + BackTickEvaluator evaluator = createBackTickEvaluator(NIL); + + assertSExpressionsMatch(NIL, evaluator.evaluate()); + } + + @Test + public void evaluateNumber() { + SExpression input = new LispNumber("99"); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + assertSExpressionsMatch(input, evaluator.evaluate()); + } + + @Test + public void evaluateList() { + SExpression input = makeList(new LispNumber("1"), new LispNumber("99")); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + assertSExpressionsMatch(input, evaluator.evaluate()); + } + + @Test + public void evaluateComma() { + SExpression input = new CommaExpression(makeList(new Symbol("+"), new LispNumber("1"), new LispNumber("9"))); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + assertSExpressionsMatch(new LispNumber("10"), evaluator.evaluate()); + } + + @Test + public void evaluateListWithComma() { + SExpression input = makeList(new CommaExpression(makeList(new Symbol("+"), new LispNumber("1"), + new LispNumber("9")))); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + assertSExpressionsMatch(makeList(new LispNumber("10")), evaluator.evaluate()); + } + + @Test(expected = NestedCommaException.class) + public void evaluateListWithNestedComma() { + SExpression input = makeList(new CommaExpression(new CommaExpression(new Symbol("+")))); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + evaluator.evaluate(); + } + + @Test(expected = AtSignNotInCommaException.class) + public void evaluateListWithNoCommaPrecedingAtSign() { + SExpression input = makeList(new AtSignExpression(makeList(new Symbol("+")))); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + evaluator.evaluate(); + } + + @Test(expected = AtSignNotInCommaException.class) + public void evaluateAtSign() { + SExpression input = new AtSignExpression(makeList(new Symbol("+"))); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + evaluator.evaluate(); + } + + @Test(expected = NestedAtSignException.class) + public void evaluateListWithNestedAtSigns() { + SExpression input = makeList(new CommaExpression(new AtSignExpression(new AtSignExpression(makeList(new Symbol("+")))))); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + evaluator.evaluate(); + } + + @Test(expected = NestedCommaException.class) + public void evaluateListWithCommaAfterAtSign() { + SExpression input = makeList(new CommaExpression(new AtSignExpression(new CommaExpression(makeList(new Symbol("+")))))); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + evaluator.evaluate(); + } + + @Test + public void evaluateListWithAtSign() { + SExpression input = makeList(new CommaExpression(new AtSignExpression(makeList(new Symbol("LIST"), + new LispNumber("1"), + new LispNumber("9"))))); + SExpression expected = makeList(new LispNumber("1"), new LispNumber("9")); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + assertSExpressionsMatch(expected, evaluator.evaluate()); + } + + @Test(expected = AtSignNotListException.class) + public void atSignDoesNotEvaluateToList() { + SExpression input = makeList(new CommaExpression(new AtSignExpression(new LispNumber("1")))); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + evaluator.evaluate(); + } + + @Test + public void evaluateListWithCommasAndAtSign() { + SExpression input = makeList(new LispNumber("78"), + new CommaExpression(new AtSignExpression(makeList(new Symbol("LIST"), + new LispNumber("1"), + new LispNumber("9")))), + new CommaExpression(makeList(new Symbol("+"), new LispNumber("20"), + new LispNumber("5"))), + new CommaExpression(makeList(new Symbol("LIST"), new LispNumber("7"), + new LispNumber("6"))), + new LispString("\"sky\"")); + SExpression expected = makeList(new LispNumber("78"), new LispNumber("1"), new LispNumber("9"), + new LispNumber("25"), makeList(new LispNumber("7"), new LispNumber("6")), + new LispString("\"sky\"")); + BackTickEvaluator evaluator = createBackTickEvaluator(input); + + assertSExpressionsMatch(expected, evaluator.evaluate()); + } + +} diff --git a/test/function/builtin/EVALTester.java b/test/function/builtin/EVALTester.java index f01f8e9..75c7274 100644 --- a/test/function/builtin/EVALTester.java +++ b/test/function/builtin/EVALTester.java @@ -9,6 +9,7 @@ import static testutil.TestUtilities.*; import org.junit.Test; import function.ArgumentValidator.*; +import function.builtin.BackTickEvaluator.AtSignNotInCommaException; import function.builtin.EVAL.*; public class EVALTester { @@ -99,4 +100,46 @@ public class EVALTester { assertTrue(e.getMessage().length() > 0); } + @Test(expected = UnmatchedCommaException.class) + public void evalComma() { + String input = ",a"; + + evaluateString(input); + } + + @Test(expected = UnmatchedAtSignException.class) + public void evalAtSign() { + String input = "@a"; + + evaluateString(input); + } + + @Test + public void evalBackTick() { + String input = "`(a b c)"; + + assertSExpressionsMatch(parseString("(a b c)"), evaluateString(input)); + } + + @Test + public void evalBackTickWithCommasAndAtSigns() { + String input = "(let ((x '(1 2 3)) (y '(4 5 6)) (z 'apple)) `(start ,x ,@y ,z end))"; + + assertSExpressionsMatch(parseString("(start (1 2 3) 4 5 6 apple end)"), evaluateString(input)); + } + + @Test + public void evalBackTickOnComma() { + String input = "`,9"; + + assertSExpressionsMatch(parseString("9"), evaluateString(input)); + } + + @Test(expected = AtSignNotInCommaException.class) + public void evalBackTickOnAtSign() { + String input = "`@9"; + + assertSExpressionsMatch(parseString("9"), evaluateString(input)); + } + } diff --git a/test/parser/LispParserTester.java b/test/parser/LispParserTester.java index 92e76f6..ce2cab5 100644 --- a/test/parser/LispParserTester.java +++ b/test/parser/LispParserTester.java @@ -310,4 +310,61 @@ public class LispParserTester { parser.getNextSExpression(); } + @Test + public void givenBackTickExpression_CreatesCorrectSExpression() { + String input = "`(list ,a ,@b)"; + LispParser parser = createLispParser(input); + + assertBackTickExpression(parser.getNextSExpression()); + assertTrue(parser.isEof()); + } + + @Test + public void givenComma_CreatesCorrectSExpression() { + String input = ",a"; + LispParser parser = createLispParser(input); + + assertCommaExpression(parser.getNextSExpression()); + assertTrue(parser.isEof()); + } + + @Test + public void givenAtSignExpression_CreatesCorrectSExpression() { + String input = "@b"; + LispParser parser = createLispParser(input); + + assertAtSignExpression(parser.getNextSExpression()); + assertTrue(parser.isEof()); + } + + @Test + public void backTickIsNotPartOfIdentifier() { + String input = "id`ab"; + LispParser parser = createLispParser(input); + + assertSymbol(parser.getNextSExpression()); + assertBackTickExpression(parser.getNextSExpression()); + assertTrue(parser.isEof()); + } + + @Test + public void commaIsNotPartOfIdentifier() { + String input = "id,ab"; + LispParser parser = createLispParser(input); + + assertSymbol(parser.getNextSExpression()); + assertCommaExpression(parser.getNextSExpression()); + assertTrue(parser.isEof()); + } + + @Test + public void atSignIsNotPartOfIdentifier() { + String input = "id@ab"; + LispParser parser = createLispParser(input); + + assertSymbol(parser.getNextSExpression()); + assertAtSignExpression(parser.getNextSExpression()); + assertTrue(parser.isEof()); + } + } diff --git a/test/scanner/LispScannerTextTester.java b/test/scanner/LispScannerTextTester.java index 56faa94..601a639 100644 --- a/test/scanner/LispScannerTextTester.java +++ b/test/scanner/LispScannerTextTester.java @@ -107,4 +107,12 @@ public class LispScannerTextTester { assertTokenTextMatches(input, expected); } + @Test + public void givenBackTickExpression_RecordsCorrectText() { + String input = "`(list ,a ,@b)"; + String[] expected = { "`", "(", "list", ",", "a", ",", "@", "b", ")" }; + + assertTokenTextMatches(input, expected); + } + } diff --git a/test/scanner/LispScannerTypeTester.java b/test/scanner/LispScannerTypeTester.java index d6fb649..423b40d 100644 --- a/test/scanner/LispScannerTypeTester.java +++ b/test/scanner/LispScannerTypeTester.java @@ -278,4 +278,20 @@ public class LispScannerTypeTester { assertTokenTypesMatch(input); } + @Test + public void givenBackTickExpression_ReturnsCorrectTypes() { + String input = "`(list ,a ,@b)"; + expectedTypes.add(BackTick.class); + expectedTypes.add(LeftParenthesis.class); + expectedTypes.add(Identifier.class); + expectedTypes.add(Comma.class); + expectedTypes.add(Identifier.class); + expectedTypes.add(Comma.class); + expectedTypes.add(AtSign.class); + expectedTypes.add(Identifier.class); + expectedTypes.add(RightParenthesis.class); + + assertTokenTypesMatch(input); + } + } diff --git a/test/sexpression/SExpressionTester.java b/test/sexpression/SExpressionTester.java index 2359be8..d679e9f 100644 --- a/test/sexpression/SExpressionTester.java +++ b/test/sexpression/SExpressionTester.java @@ -3,6 +3,7 @@ package sexpression; import static error.ErrorManager.Severity.ERROR; import static org.junit.Assert.*; import static sexpression.Nil.NIL; +import static testutil.TestUtilities.*; import java.math.BigInteger; @@ -21,59 +22,58 @@ public class SExpressionTester { public void setUp() throws Exception {} @Test - public void nilToString() { + public void nil_ToString() { String input = "NIL"; assertSExpressionMatchesString(input, NIL); } @Test - public void numberToString() { + public void number_ToString() { String input = "12"; assertSExpressionMatchesString(input, new LispNumber(input)); } @Test - public void numberValueToString() { + public void numberValue_ToString() { String expected = "12"; assertSExpressionMatchesString(expected, new LispNumber("12")); } @Test - public void stringToString() { + public void string_ToString() { String input = "\"hi\""; assertSExpressionMatchesString(input, new LispString(input)); } @Test - public void symbolToString() { + public void symbol_ToString() { String input = "symbol"; assertSExpressionMatchesString(input.toUpperCase(), new Symbol(input)); } @Test - public void simpleConsToString() { + public void simpleCons_ToString() { String expected = "(1)"; - Cons cons = new Cons(new LispNumber("1"), NIL); + Cons cons = makeList(new LispNumber("1")); assertSExpressionMatchesString(expected, cons); } @Test - public void complexConsToString() { + public void complexCons_ToString() { String expected = "(1 A \"string\")"; - Cons list = new Cons(new LispNumber("1"), - new Cons(new Symbol("a"), new Cons(new LispString("\"string\""), NIL))); + Cons list = makeList(new LispNumber("1"), new Symbol("a"), new LispString("\"string\"")); assertSExpressionMatchesString(expected, list); } @Test - public void improperListToString() { + public void improperList_ToString() { String expected = "(A . B)"; Cons list = new Cons(new Symbol("A"), new Symbol("B")); @@ -81,26 +81,26 @@ public class SExpressionTester { } @Test - public void lambdaExpressionToString() { + public void lambdaExpression_ToString() { String expected = "(LAMBDA)"; - LambdaExpression lambda = new LambdaExpression(new Cons(new Symbol("lambda"), NIL), null); + LambdaExpression lambda = new LambdaExpression(makeList(new Symbol("lambda")), null); assertSExpressionMatchesString(expected, lambda); } @Test - public void lambdaExpressionGetLambdaExpression() { + public void lambdaExpression_GetLambdaExpression() { String expected = "(LAMBDA)"; - LambdaExpression lambda = new LambdaExpression(new Cons(new Symbol("lambda"), NIL), null); + LambdaExpression lambda = new LambdaExpression(makeList(new Symbol("lambda")), null); assertSExpressionMatchesString(expected, lambda.getLambdaExpression()); } @Test - public void lambdaExpressionGetFunction() { + public void lambdaExpression_GetFunction() { String expected = "(LAMBDA)"; UserDefinedFunction function = new UserDefinedFunction(expected, NIL, NIL); - LambdaExpression lambda = new LambdaExpression(new Cons(new Symbol("lambda"), NIL), function); + LambdaExpression lambda = new LambdaExpression(makeList(new Symbol("lambda")), function); assertEquals(function, lambda.getFunction()); } @@ -163,4 +163,61 @@ public class SExpressionTester { assertEquals(BigInteger.ONE, LispNumber.ONE.getValue()); } + @Test + public void backTickExpression_ToString() { + String expected = "`(TEST)"; + SExpression backTick = new BackTickExpression(makeList(new Symbol("TEST"))); + + assertSExpressionMatchesString(expected, backTick); + } + + @Test + public void commaExpression_ToString() { + String expected = ",A"; + SExpression comma = new CommaExpression(new Symbol("A")); + + assertSExpressionMatchesString(expected, comma); + } + + @Test + public void atSignExpression_ToString() { + String expected = "@A"; + SExpression atSign = new AtSignExpression(new Symbol("A")); + + assertSExpressionMatchesString(expected, atSign); + } + + @Test + public void complexBackTickExpression_ToString() { + String expected = "`(LIST ,A ,@B)"; + SExpression backTick = new BackTickExpression(makeList(new Symbol("LIST"), new CommaExpression(new Symbol("A")), + new CommaExpression(new AtSignExpression(new Symbol("B"))))); + + assertSExpressionMatchesString(expected, backTick); + } + + @Test + public void backTickExpression_GetExpression() { + SExpression expression = makeList(new Symbol("TEST")); + BackTickExpression backTick = new BackTickExpression(expression); + + assertSExpressionsMatch(expression, backTick.getExpression()); + } + + @Test + public void commaExpression_GetExpression() { + SExpression expression = new Symbol("A"); + CommaExpression comma = new CommaExpression(expression); + + assertSExpressionsMatch(expression, comma.getExpression()); + } + + @Test + public void atSignExpression_GetExpression() { + SExpression expression = new Symbol("A"); + AtSignExpression atSign = new AtSignExpression(expression); + + assertSExpressionsMatch(expression, atSign.getExpression()); + } + } diff --git a/test/testutil/TestUtilities.java b/test/testutil/TestUtilities.java index d598880..5760a00 100644 --- a/test/testutil/TestUtilities.java +++ b/test/testutil/TestUtilities.java @@ -2,11 +2,13 @@ package testutil; import static function.builtin.EVAL.eval; import static org.junit.Assert.*; +import static sexpression.Nil.NIL; import java.io.*; +import java.util.Arrays; import parser.LispParser; -import sexpression.SExpression; +import sexpression.*; public final class TestUtilities { @@ -41,4 +43,13 @@ public final class TestUtilities { assertNotEquals(one.toString(), two.toString()); } + public static Cons makeList(SExpression... expressionList) { + if (expressionList.length == 0) + return NIL; + + Cons rest = makeList(Arrays.copyOfRange(expressionList, 1, expressionList.length)); + + return new Cons(expressionList[0], rest); + } + } diff --git a/test/testutil/TypeAssertions.java b/test/testutil/TypeAssertions.java index cb78f56..959c904 100644 --- a/test/testutil/TypeAssertions.java +++ b/test/testutil/TypeAssertions.java @@ -17,6 +17,9 @@ public final class TypeAssertions { assertFalse(sExpression.isNumber()); assertFalse(sExpression.isString()); assertFalse(sExpression.isSymbol()); + assertFalse(sExpression.isBackTick()); + assertFalse(sExpression.isComma()); + assertFalse(sExpression.isAtSign()); } public static void assertNil(SExpression sExpression) { @@ -30,7 +33,9 @@ public final class TypeAssertions { assertFalse(sExpression.isNumber()); assertFalse(sExpression.isString()); assertTrue(sExpression.isSymbol()); - + assertFalse(sExpression.isBackTick()); + assertFalse(sExpression.isComma()); + assertFalse(sExpression.isAtSign()); } public static void assertNumber(SExpression sExpression) { @@ -42,6 +47,9 @@ public final class TypeAssertions { assertTrue(sExpression.isNumber()); assertFalse(sExpression.isString()); assertFalse(sExpression.isSymbol()); + assertFalse(sExpression.isBackTick()); + assertFalse(sExpression.isComma()); + assertFalse(sExpression.isAtSign()); } public static void assertString(SExpression sExpression) { @@ -53,6 +61,9 @@ public final class TypeAssertions { assertFalse(sExpression.isNumber()); assertTrue(sExpression.isString()); assertFalse(sExpression.isSymbol()); + assertFalse(sExpression.isBackTick()); + assertFalse(sExpression.isComma()); + assertFalse(sExpression.isAtSign()); } public static void assertSymbol(SExpression sExpression) { @@ -64,10 +75,55 @@ public final class TypeAssertions { assertFalse(sExpression.isNumber()); assertFalse(sExpression.isString()); assertTrue(sExpression.isSymbol()); + assertFalse(sExpression.isBackTick()); + assertFalse(sExpression.isComma()); + assertFalse(sExpression.isAtSign()); } public static void assertT(SExpression sExpression) { assertEquals(T, sExpression); } + public static void assertBackTickExpression(SExpression sExpression) { + assertFalse(sExpression.isAtom()); + assertFalse(sExpression.isCons()); + assertFalse(sExpression.isFunction()); + assertFalse(sExpression.isList()); + assertFalse(sExpression.isNull()); + assertFalse(sExpression.isNumber()); + assertFalse(sExpression.isString()); + assertFalse(sExpression.isSymbol()); + assertTrue(sExpression.isBackTick()); + assertFalse(sExpression.isComma()); + assertFalse(sExpression.isAtSign()); + } + + public static void assertCommaExpression(SExpression sExpression) { + assertFalse(sExpression.isAtom()); + assertFalse(sExpression.isCons()); + assertFalse(sExpression.isFunction()); + assertFalse(sExpression.isList()); + assertFalse(sExpression.isNull()); + assertFalse(sExpression.isNumber()); + assertFalse(sExpression.isString()); + assertFalse(sExpression.isSymbol()); + assertFalse(sExpression.isBackTick()); + assertTrue(sExpression.isComma()); + assertFalse(sExpression.isAtSign()); + } + + public static void assertAtSignExpression(SExpression sExpression) { + assertFalse(sExpression.isAtom()); + assertFalse(sExpression.isCons()); + assertFalse(sExpression.isFunction()); + assertFalse(sExpression.isList()); + assertFalse(sExpression.isNull()); + assertFalse(sExpression.isNumber()); + assertFalse(sExpression.isString()); + assertFalse(sExpression.isSymbol()); + assertFalse(sExpression.isBackTick()); + assertFalse(sExpression.isComma()); + assertTrue(sExpression.isAtSign()); + } + } diff --git a/test/token/TokenFactoryTester.java b/test/token/TokenFactoryTester.java index f374656..dbc50c5 100644 --- a/test/token/TokenFactoryTester.java +++ b/test/token/TokenFactoryTester.java @@ -84,22 +84,14 @@ public class TokenFactoryTester { } @Test - public void emptyTokenTextException_ContainsCorrectSeverity() { - try { - createToken(""); - } catch (EmptyTokenTextException e) { - assertEquals(CRITICAL, e.getSeverity()); - } - } - - @Test - public void emptyTokenTextException_ContainsMessage() { + public void emptyTokenTextException_ContainsCorrectAttributes() { try { createToken(""); } catch (EmptyTokenTextException e) { String message = e.getMessage(); assertNotNull(message); assertTrue(message.length() > 0); + assertEquals(CRITICAL, e.getSeverity()); } } @@ -108,4 +100,22 @@ public class TokenFactoryTester { createToken("[abc]"); } + @Test + public void backTickCreation() { + String text = "`"; + assertTrue(createToken(text) instanceof BackTick); + } + + @Test + public void commaCreation() { + String text = ","; + assertTrue(createToken(text) instanceof Comma); + } + + @Test + public void atSignCreation() { + String text = "@"; + assertTrue(createToken(text) instanceof AtSign); + } + }