Add the backtick facility

Resolves #20
This commit is contained in:
Mike Cifelli 2017-03-11 15:41:07 -05:00
parent 655f49e612
commit 698305f07f
22 changed files with 802 additions and 50 deletions

View File

@ -1,10 +0,0 @@
package function;
public abstract class LispMacro extends LispSpecialFunction {
@Override
public boolean isMacro() {
return true;
}
}

View File

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

View File

@ -68,9 +68,14 @@ public class EVAL extends LispFunction {
private SExpression evaluateExpression(SExpression argument) { private SExpression evaluateExpression(SExpression argument) {
if (argument.isList()) if (argument.isList())
return evaluateList(argument); return evaluateList(argument);
else if (argument.isSymbol())
if (argument.isSymbol())
return evaluateSymbol(argument); 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 return argument; // NUMBER or STRING
} }
@ -127,6 +132,12 @@ public class EVAL extends LispFunction {
throw new UndefinedSymbolException(argument); throw new UndefinedSymbolException(argument);
} }
private SExpression evaluateBackTick(SExpression argument) {
BackTickEvaluator evaluator = new BackTickEvaluator((BackTickExpression) argument);
return evaluator.evaluate();
}
public static class UndefinedFunctionException extends LispException { public static class UndefinedFunctionException extends LispException {
private static final long serialVersionUID = 1L; 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";
}
}
} }

View File

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

View File

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

View File

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

View File

@ -35,4 +35,16 @@ public abstract class SExpression {
return false; return false;
} }
public boolean isBackTick() {
return false;
}
public boolean isComma() {
return false;
}
public boolean isAtSign() {
return false;
}
} }

22
src/token/AtSign.java Normal file
View File

@ -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<Token> getNextToken) {
Token nextToken = getNextToken.get();
SExpression argument = nextToken.parseSExpression(getNextToken);
return new AtSignExpression(argument);
}
}

22
src/token/BackTick.java Normal file
View File

@ -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<Token> getNextToken) {
Token nextToken = getNextToken.get();
SExpression argument = nextToken.parseSExpression(getNextToken);
return new BackTickExpression(argument);
}
}

22
src/token/Comma.java Normal file
View File

@ -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<Token> getNextToken) {
Token nextToken = getNextToken.get();
SExpression argument = nextToken.parseSExpression(getNextToken);
return new CommaExpression(argument);
}
}

View File

@ -22,6 +22,12 @@ public class TokenFactoryImpl implements TokenFactory {
return new QuoteMark(text, position); return new QuoteMark(text, position);
case DOUBLE_QUOTE: case DOUBLE_QUOTE:
return new QuotedString(text, position); 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: default:
if (isNumeric(firstCharacter, text)) { if (isNumeric(firstCharacter, text)) {
return new Number(text, position); return new Number(text, position);

View File

@ -8,7 +8,10 @@ public final class Characters {
public static final int EOF = -1; public static final int EOF = -1;
public static final char AT_SIGN = '@';
public static final char BACKSLASH = '\\'; 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 DASH = '-';
public static final char DOUBLE_QUOTE = '\"'; public static final char DOUBLE_QUOTE = '\"';
public static final char HASH = '#'; public static final char HASH = '#';
@ -21,22 +24,23 @@ public final class Characters {
public static final char RIGHT_SQUARE_BRACKET = ']'; public static final char RIGHT_SQUARE_BRACKET = ']';
public static final char SEMICOLON = ';'; public static final char SEMICOLON = ';';
public static final char SINGLE_QUOTE = '\''; public static final char SINGLE_QUOTE = '\'';
public static final char TICK_MARK = '`';
public static final Set<Character> illegalIdentifierCharacters = new HashSet<>(); public static final Set<Character> illegalIdentifierCharacters = new HashSet<>();
static { static {
illegalIdentifierCharacters.add(DOUBLE_QUOTE); illegalIdentifierCharacters.add(AT_SIGN);
illegalIdentifierCharacters.add(SINGLE_QUOTE);
illegalIdentifierCharacters.add(BACKSLASH); illegalIdentifierCharacters.add(BACKSLASH);
illegalIdentifierCharacters.add(TICK_MARK); illegalIdentifierCharacters.add(BACK_TICK);
illegalIdentifierCharacters.add(LEFT_PARENTHESIS); illegalIdentifierCharacters.add(COMMA);
illegalIdentifierCharacters.add(RIGHT_PARENTHESIS); illegalIdentifierCharacters.add(DOUBLE_QUOTE);
illegalIdentifierCharacters.add(LEFT_SQUARE_BRACKET);
illegalIdentifierCharacters.add(RIGHT_SQUARE_BRACKET);
illegalIdentifierCharacters.add(HASH); illegalIdentifierCharacters.add(HASH);
illegalIdentifierCharacters.add(LEFT_PARENTHESIS);
illegalIdentifierCharacters.add(LEFT_SQUARE_BRACKET);
illegalIdentifierCharacters.add(PERIOD); illegalIdentifierCharacters.add(PERIOD);
illegalIdentifierCharacters.add(RIGHT_PARENTHESIS);
illegalIdentifierCharacters.add(RIGHT_SQUARE_BRACKET);
illegalIdentifierCharacters.add(SEMICOLON); illegalIdentifierCharacters.add(SEMICOLON);
illegalIdentifierCharacters.add(SINGLE_QUOTE);
} }
public static boolean isLegalIdentifierCharacter(char c) { public static boolean isLegalIdentifierCharacter(char c) {

View File

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

View File

@ -9,6 +9,7 @@ import static testutil.TestUtilities.*;
import org.junit.Test; import org.junit.Test;
import function.ArgumentValidator.*; import function.ArgumentValidator.*;
import function.builtin.BackTickEvaluator.AtSignNotInCommaException;
import function.builtin.EVAL.*; import function.builtin.EVAL.*;
public class EVALTester { public class EVALTester {
@ -99,4 +100,46 @@ public class EVALTester {
assertTrue(e.getMessage().length() > 0); 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));
}
} }

View File

@ -310,4 +310,61 @@ public class LispParserTester {
parser.getNextSExpression(); 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());
}
} }

View File

@ -107,4 +107,12 @@ public class LispScannerTextTester {
assertTokenTextMatches(input, expected); assertTokenTextMatches(input, expected);
} }
@Test
public void givenBackTickExpression_RecordsCorrectText() {
String input = "`(list ,a ,@b)";
String[] expected = { "`", "(", "list", ",", "a", ",", "@", "b", ")" };
assertTokenTextMatches(input, expected);
}
} }

View File

@ -278,4 +278,20 @@ public class LispScannerTypeTester {
assertTokenTypesMatch(input); 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);
}
} }

View File

@ -3,6 +3,7 @@ package sexpression;
import static error.ErrorManager.Severity.ERROR; import static error.ErrorManager.Severity.ERROR;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static sexpression.Nil.NIL; import static sexpression.Nil.NIL;
import static testutil.TestUtilities.*;
import java.math.BigInteger; import java.math.BigInteger;
@ -21,59 +22,58 @@ public class SExpressionTester {
public void setUp() throws Exception {} public void setUp() throws Exception {}
@Test @Test
public void nilToString() { public void nil_ToString() {
String input = "NIL"; String input = "NIL";
assertSExpressionMatchesString(input, NIL); assertSExpressionMatchesString(input, NIL);
} }
@Test @Test
public void numberToString() { public void number_ToString() {
String input = "12"; String input = "12";
assertSExpressionMatchesString(input, new LispNumber(input)); assertSExpressionMatchesString(input, new LispNumber(input));
} }
@Test @Test
public void numberValueToString() { public void numberValue_ToString() {
String expected = "12"; String expected = "12";
assertSExpressionMatchesString(expected, new LispNumber("12")); assertSExpressionMatchesString(expected, new LispNumber("12"));
} }
@Test @Test
public void stringToString() { public void string_ToString() {
String input = "\"hi\""; String input = "\"hi\"";
assertSExpressionMatchesString(input, new LispString(input)); assertSExpressionMatchesString(input, new LispString(input));
} }
@Test @Test
public void symbolToString() { public void symbol_ToString() {
String input = "symbol"; String input = "symbol";
assertSExpressionMatchesString(input.toUpperCase(), new Symbol(input)); assertSExpressionMatchesString(input.toUpperCase(), new Symbol(input));
} }
@Test @Test
public void simpleConsToString() { public void simpleCons_ToString() {
String expected = "(1)"; String expected = "(1)";
Cons cons = new Cons(new LispNumber("1"), NIL); Cons cons = makeList(new LispNumber("1"));
assertSExpressionMatchesString(expected, cons); assertSExpressionMatchesString(expected, cons);
} }
@Test @Test
public void complexConsToString() { public void complexCons_ToString() {
String expected = "(1 A \"string\")"; String expected = "(1 A \"string\")";
Cons list = new Cons(new LispNumber("1"), Cons list = makeList(new LispNumber("1"), new Symbol("a"), new LispString("\"string\""));
new Cons(new Symbol("a"), new Cons(new LispString("\"string\""), NIL)));
assertSExpressionMatchesString(expected, list); assertSExpressionMatchesString(expected, list);
} }
@Test @Test
public void improperListToString() { public void improperList_ToString() {
String expected = "(A . B)"; String expected = "(A . B)";
Cons list = new Cons(new Symbol("A"), new Symbol("B")); Cons list = new Cons(new Symbol("A"), new Symbol("B"));
@ -81,26 +81,26 @@ public class SExpressionTester {
} }
@Test @Test
public void lambdaExpressionToString() { public void lambdaExpression_ToString() {
String expected = "(LAMBDA)"; 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); assertSExpressionMatchesString(expected, lambda);
} }
@Test @Test
public void lambdaExpressionGetLambdaExpression() { public void lambdaExpression_GetLambdaExpression() {
String expected = "(LAMBDA)"; 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()); assertSExpressionMatchesString(expected, lambda.getLambdaExpression());
} }
@Test @Test
public void lambdaExpressionGetFunction() { public void lambdaExpression_GetFunction() {
String expected = "(LAMBDA)"; String expected = "(LAMBDA)";
UserDefinedFunction function = new UserDefinedFunction(expected, NIL, NIL); 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()); assertEquals(function, lambda.getFunction());
} }
@ -163,4 +163,61 @@ public class SExpressionTester {
assertEquals(BigInteger.ONE, LispNumber.ONE.getValue()); 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());
}
} }

View File

@ -2,11 +2,13 @@ package testutil;
import static function.builtin.EVAL.eval; import static function.builtin.EVAL.eval;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static sexpression.Nil.NIL;
import java.io.*; import java.io.*;
import java.util.Arrays;
import parser.LispParser; import parser.LispParser;
import sexpression.SExpression; import sexpression.*;
public final class TestUtilities { public final class TestUtilities {
@ -41,4 +43,13 @@ public final class TestUtilities {
assertNotEquals(one.toString(), two.toString()); 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);
}
} }

View File

@ -17,6 +17,9 @@ public final class TypeAssertions {
assertFalse(sExpression.isNumber()); assertFalse(sExpression.isNumber());
assertFalse(sExpression.isString()); assertFalse(sExpression.isString());
assertFalse(sExpression.isSymbol()); assertFalse(sExpression.isSymbol());
assertFalse(sExpression.isBackTick());
assertFalse(sExpression.isComma());
assertFalse(sExpression.isAtSign());
} }
public static void assertNil(SExpression sExpression) { public static void assertNil(SExpression sExpression) {
@ -30,7 +33,9 @@ public final class TypeAssertions {
assertFalse(sExpression.isNumber()); assertFalse(sExpression.isNumber());
assertFalse(sExpression.isString()); assertFalse(sExpression.isString());
assertTrue(sExpression.isSymbol()); assertTrue(sExpression.isSymbol());
assertFalse(sExpression.isBackTick());
assertFalse(sExpression.isComma());
assertFalse(sExpression.isAtSign());
} }
public static void assertNumber(SExpression sExpression) { public static void assertNumber(SExpression sExpression) {
@ -42,6 +47,9 @@ public final class TypeAssertions {
assertTrue(sExpression.isNumber()); assertTrue(sExpression.isNumber());
assertFalse(sExpression.isString()); assertFalse(sExpression.isString());
assertFalse(sExpression.isSymbol()); assertFalse(sExpression.isSymbol());
assertFalse(sExpression.isBackTick());
assertFalse(sExpression.isComma());
assertFalse(sExpression.isAtSign());
} }
public static void assertString(SExpression sExpression) { public static void assertString(SExpression sExpression) {
@ -53,6 +61,9 @@ public final class TypeAssertions {
assertFalse(sExpression.isNumber()); assertFalse(sExpression.isNumber());
assertTrue(sExpression.isString()); assertTrue(sExpression.isString());
assertFalse(sExpression.isSymbol()); assertFalse(sExpression.isSymbol());
assertFalse(sExpression.isBackTick());
assertFalse(sExpression.isComma());
assertFalse(sExpression.isAtSign());
} }
public static void assertSymbol(SExpression sExpression) { public static void assertSymbol(SExpression sExpression) {
@ -64,10 +75,55 @@ public final class TypeAssertions {
assertFalse(sExpression.isNumber()); assertFalse(sExpression.isNumber());
assertFalse(sExpression.isString()); assertFalse(sExpression.isString());
assertTrue(sExpression.isSymbol()); assertTrue(sExpression.isSymbol());
assertFalse(sExpression.isBackTick());
assertFalse(sExpression.isComma());
assertFalse(sExpression.isAtSign());
} }
public static void assertT(SExpression sExpression) { public static void assertT(SExpression sExpression) {
assertEquals(T, 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());
}
} }

View File

@ -84,22 +84,14 @@ public class TokenFactoryTester {
} }
@Test @Test
public void emptyTokenTextException_ContainsCorrectSeverity() { public void emptyTokenTextException_ContainsCorrectAttributes() {
try {
createToken("");
} catch (EmptyTokenTextException e) {
assertEquals(CRITICAL, e.getSeverity());
}
}
@Test
public void emptyTokenTextException_ContainsMessage() {
try { try {
createToken(""); createToken("");
} catch (EmptyTokenTextException e) { } catch (EmptyTokenTextException e) {
String message = e.getMessage(); String message = e.getMessage();
assertNotNull(message); assertNotNull(message);
assertTrue(message.length() > 0); assertTrue(message.length() > 0);
assertEquals(CRITICAL, e.getSeverity());
} }
} }
@ -108,4 +100,22 @@ public class TokenFactoryTester {
createToken("[abc]"); 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);
}
} }