diff --git a/pom.xml b/pom.xml index a27c2d3..2171fd7 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ UTF-8 - 1.2.70 + 1.2.71 5.2.0 false diff --git a/src/main/kotlin/function/UserDefinedFunction.kt b/src/main/kotlin/function/UserDefinedFunction.kt index f35e290..828fdab 100644 --- a/src/main/kotlin/function/UserDefinedFunction.kt +++ b/src/main/kotlin/function/UserDefinedFunction.kt @@ -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 diff --git a/src/main/kotlin/function/builtin/APPLY.java b/src/main/kotlin/function/builtin/APPLY.java index ecf0aeb..c2b35a8 100644 --- a/src/main/kotlin/function/builtin/APPLY.java +++ b/src/main/kotlin/function/builtin/APPLY.java @@ -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 { diff --git a/src/main/kotlin/function/builtin/BackquoteEvaluator.java b/src/main/kotlin/function/builtin/BackquoteEvaluator.java index 0903b70..16bfa09 100644 --- a/src/main/kotlin/function/builtin/BackquoteEvaluator.java +++ b/src/main/kotlin/function/builtin/BackquoteEvaluator.java @@ -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 { diff --git a/src/main/kotlin/function/builtin/EVAL.java b/src/main/kotlin/function/builtin/EVAL.java deleted file mode 100644 index 7c5813a..0000000 --- a/src/main/kotlin/function/builtin/EVAL.java +++ /dev/null @@ -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"; - } - } -} diff --git a/src/main/kotlin/function/builtin/Eval.kt b/src/main/kotlin/function/builtin/Eval.kt new file mode 100644 index 0000000..fe5506d --- /dev/null +++ b/src/main/kotlin/function/builtin/Eval.kt @@ -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" + } +} diff --git a/src/main/kotlin/function/builtin/LOAD.java b/src/main/kotlin/function/builtin/LOAD.java index 8c93b50..031cc0a 100644 --- a/src/main/kotlin/function/builtin/LOAD.java +++ b/src/main/kotlin/function/builtin/LOAD.java @@ -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" }) diff --git a/src/main/kotlin/function/builtin/special/AND.java b/src/main/kotlin/function/builtin/special/AND.java index 93ccb8f..7f299f5 100644 --- a/src/main/kotlin/function/builtin/special/AND.java +++ b/src/main/kotlin/function/builtin/special/AND.java @@ -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; diff --git a/src/main/kotlin/function/builtin/special/CASE.java b/src/main/kotlin/function/builtin/special/CASE.java index b1c3de4..df994d2 100644 --- a/src/main/kotlin/function/builtin/special/CASE.java +++ b/src/main/kotlin/function/builtin/special/CASE.java @@ -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; diff --git a/src/main/kotlin/function/builtin/special/COND.java b/src/main/kotlin/function/builtin/special/COND.java index 12166ab..5194bd4 100644 --- a/src/main/kotlin/function/builtin/special/COND.java +++ b/src/main/kotlin/function/builtin/special/COND.java @@ -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; diff --git a/src/main/kotlin/function/builtin/special/IF.java b/src/main/kotlin/function/builtin/special/IF.java index 09f29a5..3970908 100644 --- a/src/main/kotlin/function/builtin/special/IF.java +++ b/src/main/kotlin/function/builtin/special/IF.java @@ -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 { diff --git a/src/main/kotlin/function/builtin/special/LET.java b/src/main/kotlin/function/builtin/special/LET.java index a2c3c9e..6dd55ba 100644 --- a/src/main/kotlin/function/builtin/special/LET.java +++ b/src/main/kotlin/function/builtin/special/LET.java @@ -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 { diff --git a/src/main/kotlin/function/builtin/special/OR.java b/src/main/kotlin/function/builtin/special/OR.java index c18772e..ba310bc 100644 --- a/src/main/kotlin/function/builtin/special/OR.java +++ b/src/main/kotlin/function/builtin/special/OR.java @@ -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; diff --git a/src/main/kotlin/function/builtin/special/PROGN.java b/src/main/kotlin/function/builtin/special/PROGN.java index e8719b2..0b99c07 100644 --- a/src/main/kotlin/function/builtin/special/PROGN.java +++ b/src/main/kotlin/function/builtin/special/PROGN.java @@ -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; diff --git a/src/main/kotlin/function/builtin/special/RECUR.java b/src/main/kotlin/function/builtin/special/RECUR.java index faef519..6a362a0 100644 --- a/src/main/kotlin/function/builtin/special/RECUR.java +++ b/src/main/kotlin/function/builtin/special/RECUR.java @@ -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 { diff --git a/src/main/kotlin/function/builtin/special/SETQ.java b/src/main/kotlin/function/builtin/special/SETQ.java index b38340c..8d3f3a6 100644 --- a/src/main/kotlin/function/builtin/special/SETQ.java +++ b/src/main/kotlin/function/builtin/special/SETQ.java @@ -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; diff --git a/src/main/kotlin/interpreter/LispInterpreter.kt b/src/main/kotlin/interpreter/LispInterpreter.kt index f5fd5e8..7abb22d 100644 --- a/src/main/kotlin/interpreter/LispInterpreter.kt +++ b/src/main/kotlin/interpreter/LispInterpreter.kt @@ -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 diff --git a/src/main/kotlin/table/FunctionTable.kt b/src/main/kotlin/table/FunctionTable.kt index 6dc7cfa..403b5db 100644 --- a/src/main/kotlin/table/FunctionTable.kt +++ b/src/main/kotlin/table/FunctionTable.kt @@ -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, diff --git a/src/test/kotlin/function/builtin/APPLYTest.java b/src/test/kotlin/function/builtin/APPLYTest.java index 2087c00..561c7b4 100644 --- a/src/test/kotlin/function/builtin/APPLYTest.java +++ b/src/test/kotlin/function/builtin/APPLYTest.java @@ -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; diff --git a/src/test/kotlin/function/builtin/EVALTest.java b/src/test/kotlin/function/builtin/EVALTest.java deleted file mode 100644 index 40c07ac..0000000 --- a/src/test/kotlin/function/builtin/EVALTest.java +++ /dev/null @@ -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")); - } -} diff --git a/src/test/kotlin/function/builtin/EvalTest.kt b/src/test/kotlin/function/builtin/EvalTest.kt new file mode 100644 index 0000000..a1bd465 --- /dev/null +++ b/src/test/kotlin/function/builtin/EvalTest.kt @@ -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")) + } +} diff --git a/src/test/kotlin/function/builtin/SETTest.java b/src/test/kotlin/function/builtin/SETTest.java index 7f4c5de..f8a462e 100644 --- a/src/test/kotlin/function/builtin/SETTest.java +++ b/src/test/kotlin/function/builtin/SETTest.java @@ -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; diff --git a/src/test/kotlin/function/builtin/special/ANDTest.java b/src/test/kotlin/function/builtin/special/ANDTest.java index 9d02cc8..0ec6c14 100644 --- a/src/test/kotlin/function/builtin/special/ANDTest.java +++ b/src/test/kotlin/function/builtin/special/ANDTest.java @@ -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; diff --git a/src/test/kotlin/function/builtin/special/IFTest.java b/src/test/kotlin/function/builtin/special/IFTest.java index 6216362..fe0db53 100644 --- a/src/test/kotlin/function/builtin/special/IFTest.java +++ b/src/test/kotlin/function/builtin/special/IFTest.java @@ -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; diff --git a/src/test/kotlin/function/builtin/special/LETTest.java b/src/test/kotlin/function/builtin/special/LETTest.java index cc5ec18..4187300 100644 --- a/src/test/kotlin/function/builtin/special/LETTest.java +++ b/src/test/kotlin/function/builtin/special/LETTest.java @@ -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; diff --git a/src/test/kotlin/function/builtin/special/LET_STARTest.java b/src/test/kotlin/function/builtin/special/LET_STARTest.java index 4ea20d2..ba6ad70 100644 --- a/src/test/kotlin/function/builtin/special/LET_STARTest.java +++ b/src/test/kotlin/function/builtin/special/LET_STARTest.java @@ -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; diff --git a/src/test/kotlin/function/builtin/special/LambdaTest.kt b/src/test/kotlin/function/builtin/special/LambdaTest.kt index 808d44b..6409dbe 100644 --- a/src/test/kotlin/function/builtin/special/LambdaTest.kt +++ b/src/test/kotlin/function/builtin/special/LambdaTest.kt @@ -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 diff --git a/src/test/kotlin/function/builtin/special/ORTest.java b/src/test/kotlin/function/builtin/special/ORTest.java index 3c19a19..cc9006d 100644 --- a/src/test/kotlin/function/builtin/special/ORTest.java +++ b/src/test/kotlin/function/builtin/special/ORTest.java @@ -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; diff --git a/src/test/kotlin/function/builtin/special/SETQTest.java b/src/test/kotlin/function/builtin/special/SETQTest.java index 366ef5a..3ba3be6 100644 --- a/src/test/kotlin/function/builtin/special/SETQTest.java +++ b/src/test/kotlin/function/builtin/special/SETQTest.java @@ -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; diff --git a/src/test/kotlin/testutil/SymbolAndFunctionCleaner.kt b/src/test/kotlin/testutil/SymbolAndFunctionCleaner.kt index ab18a68..8e8bb55 100644 --- a/src/test/kotlin/testutil/SymbolAndFunctionCleaner.kt +++ b/src/test/kotlin/testutil/SymbolAndFunctionCleaner.kt @@ -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() } diff --git a/src/test/kotlin/testutil/TestUtilities.kt b/src/test/kotlin/testutil/TestUtilities.kt index 3d36e5c..6e75a3c 100644 --- a/src/test/kotlin/testutil/TestUtilities.kt +++ b/src/test/kotlin/testutil/TestUtilities.kt @@ -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