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>
<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>
<skipTests>false</skipTests>
</properties>

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import sexpression.Cons;
import sexpression.Nil;
import sexpression.SExpression;
import static function.builtin.EVAL.eval;
import static function.builtin.Eval.eval;
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.util.Stack;
import static function.builtin.EVAL.eval;
import static function.builtin.Eval.eval;
import static java.text.MessageFormat.format;
@FunctionNames({ "LOAD" })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import sexpression.Cons;
import sexpression.SExpression;
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.cons.LIST.makeList;

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import function.ArgumentValidator.BadArgumentTypeException;
import function.ArgumentValidator.DottedArgumentListException;
import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException;
import function.builtin.EVAL.UndefinedFunctionException;
import function.builtin.Eval.UndefinedFunctionException;
import org.junit.Test;
import sexpression.Cons;
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.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException;
import function.builtin.EVAL.UndefinedSymbolException;
import function.builtin.Eval.UndefinedSymbolException;
import org.junit.Test;
import sexpression.LispNumber;
import table.SymbolTable;

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import function.ArgumentValidator.BadArgumentTypeException
import function.ArgumentValidator.DottedArgumentListException
import function.ArgumentValidator.TooFewArgumentsException
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.isLambdaExpression
import org.assertj.core.api.Assertions.assertThat

View File

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

View File

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

View File

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

View File

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