Convert Eval to kotlin

This commit is contained in:
Mike Cifelli 2018-10-20 09:01:45 -04:00
parent 24343b1543
commit 75f02a89cc
31 changed files with 469 additions and 515 deletions

View File

@ -10,7 +10,7 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>1.2.70</kotlin.version> <kotlin.version>1.2.71</kotlin.version>
<junit5.version>5.2.0</junit5.version> <junit5.version>5.2.0</junit5.version>
<skipTests>false</skipTests> <skipTests>false</skipTests>
</properties> </properties>

View File

@ -1,7 +1,7 @@
package function package function
import error.LispException import error.LispException
import function.builtin.EVAL.eval import function.builtin.Eval.Companion.eval
import sexpression.Cons import sexpression.Cons
import sexpression.Nil import sexpression.Nil
import sexpression.SExpression import sexpression.SExpression

View File

@ -7,8 +7,8 @@ import sexpression.Cons;
import sexpression.SExpression; import sexpression.SExpression;
import table.FunctionTable; import table.FunctionTable;
import static function.builtin.EVAL.applyFunction; import static function.builtin.Eval.applyFunction;
import static function.builtin.EVAL.lookupFunctionOrLambda; import static function.builtin.Eval.lookupFunctionOrLambda;
@FunctionNames({ "APPLY" }) @FunctionNames({ "APPLY" })
public class APPLY extends LispFunction { public class APPLY extends LispFunction {

View File

@ -9,7 +9,7 @@ import sexpression.Cons;
import sexpression.Nil; import sexpression.Nil;
import sexpression.SExpression; import sexpression.SExpression;
import static function.builtin.EVAL.eval; import static function.builtin.Eval.eval;
class BackquoteEvaluator { class BackquoteEvaluator {

View File

@ -1,229 +0,0 @@
package function.builtin;
import error.LispException;
import function.ArgumentValidator;
import function.FunctionNames;
import function.LispFunction;
import function.builtin.special.RECUR.RecurNotInTailPositionException;
import sexpression.BackquoteExpression;
import sexpression.Cons;
import sexpression.LambdaExpression;
import sexpression.Nil;
import sexpression.SExpression;
import sexpression.Symbol;
import table.ExecutionContext;
import table.FunctionTable;
import static function.builtin.cons.LIST.makeList;
import static function.builtin.special.Lambda.Lambda;
import static java.text.MessageFormat.format;
@FunctionNames({ "EVAL" })
public class EVAL extends LispFunction {
private static ExecutionContext executionContext = ExecutionContext.INSTANCE;
public static SExpression eval(SExpression sExpression) {
Cons argumentList = makeList(sExpression);
try {
return lookupEval().call(argumentList);
} catch (LispException e) {
executionContext.restoreGlobalScope();
throw e;
}
}
public static SExpression applyFunction(LispFunction function, Cons argumentList) {
return lookupEval().applyFunctionWithoutEvaluatingArguments(function, argumentList);
}
public static Cons evaluateFunctionArgumentList(Cons argumentList) {
return lookupEval().evaluateArgumentList(argumentList);
}
private static EVAL lookupEval() {
return (EVAL) FunctionTable.INSTANCE.lookupFunction("EVAL");
}
public static LispFunction lookupFunctionOrLambda(SExpression functionExpression) {
LispFunction function = FunctionTable.INSTANCE.lookupFunction(functionExpression.toString());
if (function == null)
function = createLambdaFunction(functionExpression);
return function;
}
private static LispFunction createLambdaFunction(SExpression lambdaExpression) {
if (lambdaExpression.isFunction())
return ((LambdaExpression) lambdaExpression).getFunction();
else if (Lambda.isLambdaExpression(lambdaExpression))
return Lambda.createFunction((Cons) lambdaExpression);
else
throw new UndefinedFunctionException(lambdaExpression);
}
public static SExpression lookupSymbol(String symbolName) {
if (symbolName.equals("NIL"))
return Nil.INSTANCE;
else if (symbolName.equals("T"))
return Symbol.Companion.getT();
else if (symbolName.startsWith(":"))
return new Symbol(symbolName);
return ExecutionContext.INSTANCE.lookupSymbolValue(symbolName);
}
private ArgumentValidator argumentValidator;
public EVAL(String name) {
this.argumentValidator = new ArgumentValidator(name);
this.argumentValidator.setExactNumberOfArguments(1);
}
@Override
public SExpression call(Cons argumentList) {
verifyNotRecurring();
argumentValidator.validate(argumentList);
SExpression argument = argumentList.getFirst();
return evaluateExpression(argument);
}
private void verifyNotRecurring() {
if (executionContext.isRecur()) {
executionContext.clearRecur();
throw new RecurNotInTailPositionException();
}
}
private SExpression evaluateExpression(SExpression argument) {
if (argument.isList())
return evaluateList(argument);
else if (argument.isSymbol())
return evaluateSymbol(argument);
else if (argument.isBackquote())
return evaluateBackTick(argument);
else if (argument.isComma())
throw new UnmatchedCommaException();
else if (argument.isAtSign())
throw new UnmatchedAtSignException();
return argument; // NUMBER or STRING
}
private SExpression evaluateList(SExpression argument) {
if (argument.isCons())
return evaluateFunction((Cons) argument);
return argument; // NIL
}
private SExpression evaluateFunction(Cons list) {
SExpression functionName = list.getFirst();
SExpression arguments = list.getRest();
LispFunction function = lookupFunctionOrLambda(functionName);
validateFunctionList(list, functionName);
return callFunction(function, (Cons) arguments);
}
private void validateFunctionList(Cons list, SExpression functionName) {
ArgumentValidator functionListValidator = new ArgumentValidator(functionName.toString());
functionListValidator.validate(list);
}
private SExpression callFunction(LispFunction function, Cons argumentList) {
if (function.isArgumentListEvaluated())
argumentList = evaluateArgumentList(argumentList);
return applyFunctionWithoutEvaluatingArguments(function, argumentList);
}
private SExpression applyFunctionWithoutEvaluatingArguments(LispFunction function, Cons argumentList) {
verifyNotRecurring();
SExpression result = function.call(argumentList);
if (function.isMacro())
result = eval(result);
return result;
}
private Cons evaluateArgumentList(Cons arguments) {
if (arguments.isNull())
return Nil.INSTANCE;
SExpression first = eval(arguments.getFirst());
SExpression rest = arguments.getRest();
return new Cons(first, evaluateArgumentList((Cons) rest));
}
private SExpression evaluateSymbol(SExpression argument) {
SExpression symbolValue = lookupSymbol(argument.toString());
if (symbolValue != null)
return symbolValue;
throw new UndefinedSymbolException(argument);
}
private SExpression evaluateBackTick(SExpression argument) {
BackquoteEvaluator evaluator = new BackquoteEvaluator((BackquoteExpression) argument);
return evaluator.evaluate();
}
public static class UndefinedFunctionException extends LispException {
private static final long serialVersionUID = 1L;
private SExpression function;
public UndefinedFunctionException(SExpression function) {
this.function = function;
}
@Override
public String getMessage() {
return format("undefined function: {0}", function);
}
}
public static class UndefinedSymbolException extends LispException {
private static final long serialVersionUID = 1L;
private SExpression symbol;
public UndefinedSymbolException(SExpression symbol) {
this.symbol = symbol;
}
@Override
public String getMessage() {
return format("symbol {0} has no value", symbol);
}
}
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,161 @@
package function.builtin
import error.LispException
import function.ArgumentValidator
import function.FunctionNames
import function.LispFunction
import function.builtin.cons.LIST.makeList
import function.builtin.special.Lambda.Lambda.createFunction
import function.builtin.special.Lambda.Lambda.isLambdaExpression
import function.builtin.special.RECUR.RecurNotInTailPositionException
import sexpression.BackquoteExpression
import sexpression.Cons
import sexpression.LambdaExpression
import sexpression.Nil
import sexpression.SExpression
import sexpression.Symbol
import table.ExecutionContext
import table.FunctionTable
@FunctionNames("EVAL")
class Eval(name: String) : LispFunction() {
private val argumentValidator: ArgumentValidator = ArgumentValidator(name).apply {
setExactNumberOfArguments(1)
}
override fun call(argumentList: Cons): SExpression {
verifyNotRecurring()
argumentValidator.validate(argumentList)
return evaluateExpression(argumentList.first)
}
private fun verifyNotRecurring() {
if (ExecutionContext.isRecur) {
ExecutionContext.clearRecur()
throw RecurNotInTailPositionException()
}
}
private fun evaluateExpression(argument: SExpression) = when {
argument.isList -> evaluateList(argument)
argument.isSymbol -> evaluateSymbol(argument)
argument.isBackquote -> evaluateBackTick(argument)
argument.isComma -> throw UnmatchedCommaException()
argument.isAtSign -> throw UnmatchedAtSignException()
else -> argument // NUMBER or STRING
}
private fun evaluateList(argument: SExpression) =
if (argument.isCons)
evaluateFunction(argument as Cons)
else
argument // NIL
private fun evaluateFunction(list: Cons): SExpression {
val functionName = list.first
val arguments = list.rest
val function = lookupFunctionOrLambda(functionName)
validateFunctionList(list, functionName)
return callFunction(function, arguments as Cons)
}
private fun validateFunctionList(list: Cons, functionName: SExpression) {
ArgumentValidator(functionName.toString()).validate(list)
}
private fun callFunction(function: LispFunction, argumentList: Cons) =
if (function.isArgumentListEvaluated)
applyFunctionWithoutEvaluatingArguments(function, evaluateArgumentList(argumentList))
else
applyFunctionWithoutEvaluatingArguments(function, argumentList)
private fun applyFunctionWithoutEvaluatingArguments(function: LispFunction, argumentList: Cons): SExpression {
verifyNotRecurring()
return function.call(argumentList).let {
if (function.isMacro) eval(it) else it
}
}
private fun evaluateArgumentList(arguments: Cons): Cons {
if (arguments.isNull)
return Nil
val first = eval(arguments.first)
val rest = arguments.rest as Cons
return Cons(first, evaluateArgumentList(rest))
}
private fun evaluateSymbol(argument: SExpression) =
lookupSymbol(argument.toString()) ?: throw UndefinedSymbolException(argument)
private fun evaluateBackTick(argument: SExpression) =
BackquoteEvaluator(argument as BackquoteExpression).evaluate()
companion object {
private fun lookupEval() = FunctionTable.lookupFunction("EVAL") as Eval
@JvmStatic
fun eval(sExpression: SExpression): SExpression {
try {
return lookupEval().call(makeList(sExpression))
} catch (e: LispException) {
ExecutionContext.restoreGlobalScope()
throw e
}
}
@JvmStatic
fun applyFunction(function: LispFunction, argumentList: Cons) =
lookupEval().applyFunctionWithoutEvaluatingArguments(function, argumentList)
@JvmStatic
fun evaluateFunctionArgumentList(argumentList: Cons) = lookupEval().evaluateArgumentList(argumentList)
@JvmStatic
fun lookupSymbol(symbolName: String) = when {
symbolName == "NIL" -> Nil
symbolName == "T" -> Symbol.T
symbolName.startsWith(":") -> Symbol(symbolName)
else -> ExecutionContext.lookupSymbolValue(symbolName)
}
@JvmStatic
fun lookupFunctionOrLambda(functionExpression: SExpression): LispFunction {
val function = FunctionTable.lookupFunction(functionExpression.toString())
return function ?: createLambdaFunction(functionExpression)
}
private fun createLambdaFunction(lambdaExpression: SExpression) = when {
lambdaExpression.isFunction -> (lambdaExpression as LambdaExpression).function
isLambdaExpression(lambdaExpression) -> createFunction(lambdaExpression as Cons)
else -> throw UndefinedFunctionException(lambdaExpression)
}
}
class UndefinedFunctionException(function: SExpression) : LispException() {
override val message = "undefined function: $function"
}
class UndefinedSymbolException(symbol: SExpression) : LispException() {
override val message = "symbol $symbol has no value"
}
class UnmatchedCommaException : LispException() {
override val message = "unmatched comma"
}
class UnmatchedAtSignException : LispException() {
override val message = "unmatched at sign"
}
}

View File

@ -18,7 +18,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.util.Stack; import java.util.Stack;
import static function.builtin.EVAL.eval; import static function.builtin.Eval.eval;
import static java.text.MessageFormat.format; import static java.text.MessageFormat.format;
@FunctionNames({ "LOAD" }) @FunctionNames({ "LOAD" })

View File

@ -8,7 +8,7 @@ import sexpression.Cons;
import sexpression.SExpression; import sexpression.SExpression;
import sexpression.Symbol; import sexpression.Symbol;
import static function.builtin.EVAL.eval; import static function.builtin.Eval.eval;
import static recursion.TailCalls.done; import static recursion.TailCalls.done;
import static recursion.TailCalls.tailCall; import static recursion.TailCalls.tailCall;

View File

@ -9,7 +9,7 @@ import sexpression.Nil;
import sexpression.SExpression; import sexpression.SExpression;
import sexpression.Symbol; import sexpression.Symbol;
import static function.builtin.EVAL.eval; import static function.builtin.Eval.eval;
import static function.builtin.predicate.EQUAL.isEqual; import static function.builtin.predicate.EQUAL.isEqual;
import static recursion.TailCalls.done; import static recursion.TailCalls.done;
import static recursion.TailCalls.tailCall; import static recursion.TailCalls.tailCall;

View File

@ -8,7 +8,7 @@ import sexpression.Cons;
import sexpression.Nil; import sexpression.Nil;
import sexpression.SExpression; import sexpression.SExpression;
import static function.builtin.EVAL.eval; import static function.builtin.Eval.eval;
import static recursion.TailCalls.done; import static recursion.TailCalls.done;
import static recursion.TailCalls.tailCall; import static recursion.TailCalls.tailCall;

View File

@ -6,7 +6,7 @@ import function.LispSpecialFunction;
import sexpression.Cons; import sexpression.Cons;
import sexpression.SExpression; import sexpression.SExpression;
import static function.builtin.EVAL.eval; import static function.builtin.Eval.eval;
@FunctionNames({ "IF" }) @FunctionNames({ "IF" })
public class IF extends LispSpecialFunction { public class IF extends LispSpecialFunction {

View File

@ -10,7 +10,7 @@ import sexpression.Symbol;
import table.ExecutionContext; import table.ExecutionContext;
import table.SymbolTable; import table.SymbolTable;
import static function.builtin.EVAL.eval; import static function.builtin.Eval.eval;
@FunctionNames({ "LET" }) @FunctionNames({ "LET" })
public class LET extends LispSpecialFunction { public class LET extends LispSpecialFunction {

View File

@ -7,7 +7,7 @@ import recursion.TailCall;
import sexpression.Cons; import sexpression.Cons;
import sexpression.SExpression; import sexpression.SExpression;
import static function.builtin.EVAL.eval; import static function.builtin.Eval.eval;
import static recursion.TailCalls.done; import static recursion.TailCalls.done;
import static recursion.TailCalls.tailCall; import static recursion.TailCalls.tailCall;

View File

@ -8,7 +8,7 @@ import sexpression.Cons;
import sexpression.Nil; import sexpression.Nil;
import sexpression.SExpression; import sexpression.SExpression;
import static function.builtin.EVAL.eval; import static function.builtin.Eval.eval;
import static recursion.TailCalls.done; import static recursion.TailCalls.done;
import static recursion.TailCalls.tailCall; import static recursion.TailCalls.tailCall;

View File

@ -8,7 +8,7 @@ import sexpression.Cons;
import sexpression.SExpression; import sexpression.SExpression;
import table.ExecutionContext; import table.ExecutionContext;
import static function.builtin.EVAL.evaluateFunctionArgumentList; import static function.builtin.Eval.evaluateFunctionArgumentList;
@FunctionNames({ "RECUR" }) @FunctionNames({ "RECUR" })
public class RECUR extends LispSpecialFunction { public class RECUR extends LispSpecialFunction {

View File

@ -7,7 +7,7 @@ import sexpression.Cons;
import sexpression.SExpression; import sexpression.SExpression;
import sexpression.Symbol; import sexpression.Symbol;
import static function.builtin.EVAL.eval; import static function.builtin.Eval.eval;
import static function.builtin.SET.set; import static function.builtin.SET.set;
import static function.builtin.cons.LIST.makeList; import static function.builtin.cons.LIST.makeList;

View File

@ -6,7 +6,7 @@ import environment.RuntimeEnvironment.input
import environment.RuntimeEnvironment.inputName import environment.RuntimeEnvironment.inputName
import environment.RuntimeEnvironment.output import environment.RuntimeEnvironment.output
import error.LispException import error.LispException
import function.builtin.EVAL.eval import function.builtin.Eval.Companion.eval
import parser.LispParser import parser.LispParser
import sexpression.SExpression import sexpression.SExpression
import java.io.InputStream import java.io.InputStream

View File

@ -4,7 +4,7 @@ import error.CriticalLispException
import function.FunctionNames import function.FunctionNames
import function.LispFunction import function.LispFunction
import function.builtin.APPLY import function.builtin.APPLY
import function.builtin.EVAL import function.builtin.Eval
import function.builtin.Exit import function.builtin.Exit
import function.builtin.FUNCALL import function.builtin.FUNCALL
import function.builtin.FUSE import function.builtin.FUSE
@ -68,7 +68,7 @@ object FunctionTable {
EQ::class.java, EQ::class.java,
EQUAL::class.java, EQUAL::class.java,
NumericEqual::class.java, NumericEqual::class.java,
EVAL::class.java, Eval::class.java,
Exit::class.java, Exit::class.java,
FIRST::class.java, FIRST::class.java,
FUNCALL::class.java, FUNCALL::class.java,

View File

@ -4,7 +4,7 @@ import function.ArgumentValidator.BadArgumentTypeException;
import function.ArgumentValidator.DottedArgumentListException; import function.ArgumentValidator.DottedArgumentListException;
import function.ArgumentValidator.TooFewArgumentsException; import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException; import function.ArgumentValidator.TooManyArgumentsException;
import function.builtin.EVAL.UndefinedFunctionException; import function.builtin.Eval.UndefinedFunctionException;
import org.junit.Test; import org.junit.Test;
import sexpression.Cons; import sexpression.Cons;
import testutil.SymbolAndFunctionCleaner; import testutil.SymbolAndFunctionCleaner;

View File

@ -1,256 +0,0 @@
package function.builtin;
import function.ArgumentValidator.DottedArgumentListException;
import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException;
import function.builtin.BackquoteEvaluator.AtSignNotInCommaException;
import function.builtin.EVAL.UndefinedFunctionException;
import function.builtin.EVAL.UndefinedSymbolException;
import function.builtin.EVAL.UnmatchedAtSignException;
import function.builtin.EVAL.UnmatchedCommaException;
import function.builtin.special.RECUR.RecurNotInTailPositionException;
import org.junit.Test;
import sexpression.Nil;
import testutil.SymbolAndFunctionCleaner;
import static function.builtin.EVAL.lookupSymbol;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static testutil.TestUtilities.assertIsErrorWithMessage;
import static testutil.TestUtilities.assertSExpressionsMatch;
import static testutil.TestUtilities.evaluateString;
import static testutil.TestUtilities.parseString;
public class EVALTest extends SymbolAndFunctionCleaner {
@Test
public void evalNumber() {
String input = "(eval 9)";
assertSExpressionsMatch(parseString("9"), evaluateString(input));
}
@Test
public void evalNil() {
String input = "(eval ())";
assertSExpressionsMatch(parseString("()"), evaluateString(input));
}
@Test
public void evalUsesCurrentLexicalEnvironment() {
String input = "(let ((x 1)) (eval '(+ x 1)))";
assertSExpressionsMatch(parseString("2"), evaluateString(input));
}
@Test
public void lookupKeywordSymbol() {
String symbol = ":symbol";
assertSExpressionsMatch(parseString(symbol), lookupSymbol(symbol));
}
@Test
public void lookupT() {
String symbol = "T";
assertSExpressionsMatch(parseString(symbol), lookupSymbol(symbol));
}
@Test
public void lookupNil() {
String symbol = "NIL";
assertSExpressionsMatch(parseString(symbol), lookupSymbol(symbol));
}
@Test
public void lookupUndefinedSymbol() {
assertNull(EVAL.lookupSymbol("undefined"));
}
@Test(expected = UndefinedFunctionException.class)
public void evalUndefinedFunction() {
String input = "(funcall 'eval '(undefined))";
evaluateString(input);
}
@Test(expected = UndefinedSymbolException.class)
public void evalUndefinedSymbol() {
String input = "(eval undefined)";
evaluateString(input);
}
@Test(expected = DottedArgumentListException.class)
public void evalWithDottedLambdaList() {
String input = "(funcall 'eval (cons '+ 1))";
evaluateString(input);
}
@Test(expected = TooManyArgumentsException.class)
public void evalWithTooManyArguments() {
evaluateString("(eval '1 '2 '3)");
}
@Test(expected = TooFewArgumentsException.class)
public void evalWithTooFewArguments() {
evaluateString("(eval)");
}
@Test
public void undefinedFunctionException_HasCorrectAttributes() {
assertIsErrorWithMessage(new UndefinedFunctionException(Nil.INSTANCE));
}
@Test
public void undefinedSymbolException_HasCorrectAttributes() {
assertIsErrorWithMessage(new UndefinedSymbolException(Nil.INSTANCE));
}
@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() {
evaluateString("`@9");
}
@Test
public void evalNestedBackquotes() {
String input = "`,`,`,`,9";
assertSExpressionsMatch(parseString("9"), evaluateString(input));
}
@Test
public void unmatchedCommaException_HasCorrectAttributes() {
assertIsErrorWithMessage(new UnmatchedCommaException());
}
@Test
public void unmatchedAtSignException_HasCorrectAttributes() {
assertIsErrorWithMessage(new UnmatchedAtSignException());
}
@Test
public void evalQuoteInNestedList() {
String input = "(let ((g 27)) `((,g)))";
assertSExpressionsMatch(parseString("((27))"), evaluateString(input));
}
@Test
public void evalAtSignInNestedList() {
String input = "(let ((g '(1 2 3))) `((,@g)))";
assertSExpressionsMatch(parseString("((1 2 3))"), evaluateString(input));
}
@Test
public void evalNestedBackquotesInList() {
String input = "`(,`(1 ,`2 ,@`(3)))";
assertSExpressionsMatch(parseString("((1 2 3))"), evaluateString(input));
}
@Test
public void scopeRestoredAfterFailure_Let() {
evaluateString("(setq n 100)");
try {
evaluateString("(let ((n 200)) (begin 1 2 3 y))");
fail("expected exception");
} catch (UndefinedSymbolException e) {
}
assertSExpressionsMatch(parseString("100"), evaluateString("n"));
}
@Test
public void scopeRestoredAfterFailure_Defun() {
evaluateString("(setq n 100)");
try {
evaluateString("(defun test (n) (begin 1 2 3 y))");
evaluateString("(test 200)");
fail("expected exception");
} catch (UndefinedSymbolException e) {
}
assertSExpressionsMatch(parseString("100"), evaluateString("n"));
}
@Test
public void scopeRestoredAfterFailure_Lambda() {
evaluateString("(setq n 100)");
try {
evaluateString("((lambda (n) (begin 1 2 3 y)) 200)");
fail("expected exception");
} catch (UndefinedSymbolException e) {
}
assertSExpressionsMatch(parseString("100"), evaluateString("n"));
}
@Test
public void scopeRestoredAfterFailure_Recur() {
evaluateString("(setq n 100)");
try {
evaluateString("(defun tail-recursive (n) (begin (recur) 2))");
evaluateString("(tail-recursive 200)");
fail("expected exception");
} catch (RecurNotInTailPositionException e) {
}
assertSExpressionsMatch(parseString("100"), evaluateString("n"));
}
@Test
public void scopeRestoredAfterFailure_Apply() {
evaluateString("(setq n 100)");
try {
evaluateString("(defun tail-recursive (n) (begin (recur) 2))");
evaluateString("(apply 'tail-recursive '(200))");
fail("expected exception");
} catch (RecurNotInTailPositionException e) {
}
assertSExpressionsMatch(parseString("100"), evaluateString("n"));
}
}

View File

@ -0,0 +1,278 @@
package function.builtin
import function.ArgumentValidator.DottedArgumentListException
import function.ArgumentValidator.TooFewArgumentsException
import function.ArgumentValidator.TooManyArgumentsException
import function.builtin.BackquoteEvaluator.AtSignNotInCommaException
import function.builtin.Eval.Companion.lookupSymbol
import function.builtin.Eval.UndefinedFunctionException
import function.builtin.Eval.UndefinedSymbolException
import function.builtin.Eval.UnmatchedAtSignException
import function.builtin.Eval.UnmatchedCommaException
import function.builtin.special.RECUR.RecurNotInTailPositionException
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
import org.junit.jupiter.api.fail
import sexpression.Nil
import testutil.SymbolAndFunctionCleaner
import testutil.TestUtilities.assertIsErrorWithMessage
import testutil.TestUtilities.assertSExpressionsMatch
import testutil.TestUtilities.evaluateString
import testutil.TestUtilities.parseString
@TestInstance(PER_CLASS)
class EvalTest : SymbolAndFunctionCleaner() {
@Test
fun evalNumber() {
val input = "(eval 9)"
assertSExpressionsMatch(parseString("9"), evaluateString(input))
}
@Test
fun evalNil() {
val input = "(eval ())"
assertSExpressionsMatch(parseString("()"), evaluateString(input))
}
@Test
fun evalUsesCurrentLexicalEnvironment() {
val input = "(let ((x 1)) (eval '(+ x 1)))"
assertSExpressionsMatch(parseString("2"), evaluateString(input))
}
@Test
fun lookupKeywordSymbol() {
val symbol = ":symbol"
assertSExpressionsMatch(parseString(symbol), lookupSymbol(symbol)!!)
}
@Test
fun lookupT() {
val symbol = "T"
assertSExpressionsMatch(parseString(symbol), lookupSymbol(symbol)!!)
}
@Test
fun lookupNil() {
val symbol = "NIL"
assertSExpressionsMatch(parseString(symbol), lookupSymbol(symbol)!!)
}
@Test
fun lookupUndefinedSymbol() {
assertThat(Eval.lookupSymbol("undefined")).isNull()
}
@Test
fun evalUndefinedFunction() {
val input = "(funcall 'eval '(undefined))"
assertThrows(UndefinedFunctionException::class.java) {
evaluateString(input)
}
}
@Test
fun evalUndefinedSymbol() {
val input = "(eval undefined)"
assertThrows(UndefinedSymbolException::class.java) {
evaluateString(input)
}
}
@Test
fun evalWithDottedLambdaList() {
val input = "(funcall 'eval (cons '+ 1))"
assertThrows(DottedArgumentListException::class.java) {
evaluateString(input)
}
}
@Test
fun evalWithTooManyArguments() {
assertThrows(TooManyArgumentsException::class.java) {
evaluateString("(eval '1 '2 '3)")
}
}
@Test
fun evalWithTooFewArguments() {
assertThrows(TooFewArgumentsException::class.java) {
evaluateString("(eval)")
}
}
@Test
fun undefinedFunctionException_HasCorrectAttributes() {
assertIsErrorWithMessage(UndefinedFunctionException(Nil))
}
@Test
fun undefinedSymbolException_HasCorrectAttributes() {
assertIsErrorWithMessage(UndefinedSymbolException(Nil))
}
@Test
fun evalComma() {
val input = ",a"
assertThrows(UnmatchedCommaException::class.java) {
evaluateString(input)
}
}
@Test
fun evalAtSign() {
val input = "@a"
assertThrows(UnmatchedAtSignException::class.java) {
evaluateString(input)
}
}
@Test
fun evalBackTick() {
val input = "`(a b c)"
assertSExpressionsMatch(parseString("(a b c)"), evaluateString(input))
}
@Test
fun evalBackTickWithCommasAndAtSigns() {
val 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
fun evalBackTickOnComma() {
val input = "`,9"
assertSExpressionsMatch(parseString("9"), evaluateString(input))
}
@Test
fun evalBackTickOnAtSign() {
assertThrows(AtSignNotInCommaException::class.java) {
evaluateString("`@9")
}
}
@Test
fun evalNestedBackquotes() {
val input = "`,`,`,`,9"
assertSExpressionsMatch(parseString("9"), evaluateString(input))
}
@Test
fun unmatchedCommaException_HasCorrectAttributes() {
assertIsErrorWithMessage(UnmatchedCommaException())
}
@Test
fun unmatchedAtSignException_HasCorrectAttributes() {
assertIsErrorWithMessage(UnmatchedAtSignException())
}
@Test
fun evalQuoteInNestedList() {
val input = "(let ((g 27)) `((,g)))"
assertSExpressionsMatch(parseString("((27))"), evaluateString(input))
}
@Test
fun evalAtSignInNestedList() {
val input = "(let ((g '(1 2 3))) `((,@g)))"
assertSExpressionsMatch(parseString("((1 2 3))"), evaluateString(input))
}
@Test
fun evalNestedBackquotesInList() {
val input = "`(,`(1 ,`2 ,@`(3)))"
assertSExpressionsMatch(parseString("((1 2 3))"), evaluateString(input))
}
@Test
fun scopeRestoredAfterFailure_Let() {
evaluateString("(setq n 100)")
try {
evaluateString("(let ((n 200)) (begin 1 2 3 y))")
fail("expected exception")
} catch (e: UndefinedSymbolException) {
}
assertSExpressionsMatch(parseString("100"), evaluateString("n"))
}
@Test
fun scopeRestoredAfterFailure_Defun() {
evaluateString("(setq n 100)")
try {
evaluateString("(defun test (n) (begin 1 2 3 y))")
evaluateString("(test 200)")
fail("expected exception")
} catch (e: UndefinedSymbolException) {
}
assertSExpressionsMatch(parseString("100"), evaluateString("n"))
}
@Test
fun scopeRestoredAfterFailure_Lambda() {
evaluateString("(setq n 100)")
try {
evaluateString("((lambda (n) (begin 1 2 3 y)) 200)")
fail("expected exception")
} catch (e: UndefinedSymbolException) {
}
assertSExpressionsMatch(parseString("100"), evaluateString("n"))
}
@Test
fun scopeRestoredAfterFailure_Recur() {
evaluateString("(setq n 100)")
try {
evaluateString("(defun tail-recursive (n) (begin (recur) 2))")
evaluateString("(tail-recursive 200)")
fail("expected exception")
} catch (e: RecurNotInTailPositionException) {
}
assertSExpressionsMatch(parseString("100"), evaluateString("n"))
}
@Test
fun scopeRestoredAfterFailure_Apply() {
evaluateString("(setq n 100)")
try {
evaluateString("(defun tail-recursive (n) (begin (recur) 2))")
evaluateString("(apply 'tail-recursive '(200))")
fail("expected exception")
} catch (e: RecurNotInTailPositionException) {
}
assertSExpressionsMatch(parseString("100"), evaluateString("n"))
}
}

View File

@ -3,7 +3,7 @@ package function.builtin;
import function.ArgumentValidator.BadArgumentTypeException; import function.ArgumentValidator.BadArgumentTypeException;
import function.ArgumentValidator.TooFewArgumentsException; import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException; import function.ArgumentValidator.TooManyArgumentsException;
import function.builtin.EVAL.UndefinedSymbolException; import function.builtin.Eval.UndefinedSymbolException;
import org.junit.Test; import org.junit.Test;
import sexpression.LispNumber; import sexpression.LispNumber;
import table.SymbolTable; import table.SymbolTable;

View File

@ -1,6 +1,6 @@
package function.builtin.special; package function.builtin.special;
import function.builtin.EVAL.UndefinedSymbolException; import function.builtin.Eval.UndefinedSymbolException;
import org.junit.Test; import org.junit.Test;
import sexpression.LispNumber; import sexpression.LispNumber;
import testutil.SymbolAndFunctionCleaner; import testutil.SymbolAndFunctionCleaner;

View File

@ -2,7 +2,7 @@ package function.builtin.special;
import function.ArgumentValidator.TooFewArgumentsException; import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException; import function.ArgumentValidator.TooManyArgumentsException;
import function.builtin.EVAL.UndefinedSymbolException; import function.builtin.Eval.UndefinedSymbolException;
import org.junit.Test; import org.junit.Test;
import testutil.SymbolAndFunctionCleaner; import testutil.SymbolAndFunctionCleaner;

View File

@ -4,7 +4,7 @@ import function.ArgumentValidator.BadArgumentTypeException;
import function.ArgumentValidator.DottedArgumentListException; import function.ArgumentValidator.DottedArgumentListException;
import function.ArgumentValidator.TooFewArgumentsException; import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException; import function.ArgumentValidator.TooManyArgumentsException;
import function.builtin.EVAL.UndefinedSymbolException; import function.builtin.Eval.UndefinedSymbolException;
import org.junit.Test; import org.junit.Test;
import sexpression.Cons; import sexpression.Cons;
import sexpression.LispNumber; import sexpression.LispNumber;

View File

@ -4,7 +4,7 @@ import function.ArgumentValidator.BadArgumentTypeException;
import function.ArgumentValidator.DottedArgumentListException; import function.ArgumentValidator.DottedArgumentListException;
import function.ArgumentValidator.TooFewArgumentsException; import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException; import function.ArgumentValidator.TooManyArgumentsException;
import function.builtin.EVAL.UndefinedSymbolException; import function.builtin.Eval.UndefinedSymbolException;
import org.junit.Test; import org.junit.Test;
import sexpression.Cons; import sexpression.Cons;
import sexpression.LispNumber; import sexpression.LispNumber;

View File

@ -4,7 +4,7 @@ import function.ArgumentValidator.BadArgumentTypeException
import function.ArgumentValidator.DottedArgumentListException import function.ArgumentValidator.DottedArgumentListException
import function.ArgumentValidator.TooFewArgumentsException import function.ArgumentValidator.TooFewArgumentsException
import function.ArgumentValidator.TooManyArgumentsException import function.ArgumentValidator.TooManyArgumentsException
import function.builtin.EVAL.UndefinedFunctionException import function.builtin.Eval.UndefinedFunctionException
import function.builtin.special.Lambda.Lambda.createFunction import function.builtin.special.Lambda.Lambda.createFunction
import function.builtin.special.Lambda.Lambda.isLambdaExpression import function.builtin.special.Lambda.Lambda.isLambdaExpression
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat

View File

@ -1,6 +1,6 @@
package function.builtin.special; package function.builtin.special;
import function.builtin.EVAL.UndefinedSymbolException; import function.builtin.Eval.UndefinedSymbolException;
import org.junit.Test; import org.junit.Test;
import sexpression.LispNumber; import sexpression.LispNumber;
import testutil.SymbolAndFunctionCleaner; import testutil.SymbolAndFunctionCleaner;

View File

@ -3,7 +3,7 @@ package function.builtin.special;
import function.ArgumentValidator.BadArgumentTypeException; import function.ArgumentValidator.BadArgumentTypeException;
import function.ArgumentValidator.TooFewArgumentsException; import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException; import function.ArgumentValidator.TooManyArgumentsException;
import function.builtin.EVAL.UndefinedSymbolException; import function.builtin.Eval.UndefinedSymbolException;
import org.junit.Test; import org.junit.Test;
import sexpression.LispNumber; import sexpression.LispNumber;
import table.SymbolTable; import table.SymbolTable;

View File

@ -15,7 +15,7 @@ abstract class SymbolAndFunctionCleaner {
@Before @Before
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
executionContext.clearContext() ExecutionContext.clearContext()
FunctionTable.resetFunctionTable() FunctionTable.resetFunctionTable()
additionalSetUp() additionalSetUp()
} }
@ -23,7 +23,7 @@ abstract class SymbolAndFunctionCleaner {
@After @After
@AfterEach @AfterEach
fun tearDown() { fun tearDown() {
executionContext.clearContext() ExecutionContext.clearContext()
FunctionTable.resetFunctionTable() FunctionTable.resetFunctionTable()
additionalTearDown() additionalTearDown()
} }

View File

@ -2,7 +2,7 @@ package testutil
import error.LispException import error.LispException
import error.Severity.ERROR import error.Severity.ERROR
import function.builtin.EVAL.eval import function.builtin.Eval.Companion.eval
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import parser.LispParser import parser.LispParser
import sexpression.Cons import sexpression.Cons