diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..e96534f
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/kotlin/function/builtin/EVAL.java b/src/main/kotlin/function/builtin/EVAL.java
index 09cb6ac..6f9ebe0 100644
--- a/src/main/kotlin/function/builtin/EVAL.java
+++ b/src/main/kotlin/function/builtin/EVAL.java
@@ -14,7 +14,7 @@ import table.ExecutionContext;
import table.FunctionTable;
import static function.builtin.cons.LIST.makeList;
-import static function.builtin.special.LAMBDA.Lambda;
+import static function.builtin.special.Lambda.Lambda;
import static java.text.MessageFormat.format;
import static sexpression.Nil.NIL;
import static sexpression.Symbol.T;
diff --git a/src/main/kotlin/function/builtin/EXIT.java b/src/main/kotlin/function/builtin/EXIT.java
deleted file mode 100644
index c8136a2..0000000
--- a/src/main/kotlin/function/builtin/EXIT.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package function.builtin;
-
-import environment.RuntimeEnvironment;
-import function.ArgumentValidator;
-import function.FunctionNames;
-import function.LispFunction;
-import sexpression.Cons;
-import sexpression.SExpression;
-
-import static sexpression.Nil.NIL;
-
-@FunctionNames({ "EXIT" })
-public class EXIT extends LispFunction {
-
- private ArgumentValidator argumentValidator;
- private RuntimeEnvironment environment;
-
- public EXIT(String name) {
- this.argumentValidator = new ArgumentValidator(name);
- this.argumentValidator.setMaximumNumberOfArguments(0);
- this.environment = RuntimeEnvironment.INSTANCE;
- }
-
- @Override
- public SExpression call(Cons argumentList) {
- argumentValidator.validate(argumentList);
- environment.terminateSuccessfully();
-
- return NIL;
- }
-}
diff --git a/src/main/kotlin/function/builtin/Exit.kt b/src/main/kotlin/function/builtin/Exit.kt
new file mode 100644
index 0000000..9e008d3
--- /dev/null
+++ b/src/main/kotlin/function/builtin/Exit.kt
@@ -0,0 +1,26 @@
+package function.builtin
+
+import environment.RuntimeEnvironment
+import function.ArgumentValidator
+import function.FunctionNames
+import function.LispFunction
+import sexpression.Cons
+import sexpression.Nil.NIL
+import sexpression.SExpression
+
+@FunctionNames("EXIT")
+class Exit(name: String) : LispFunction() {
+
+ private val argumentValidator: ArgumentValidator = ArgumentValidator(name)
+
+ init {
+ this.argumentValidator.setMaximumNumberOfArguments(0)
+ }
+
+ override fun call(argumentList: Cons): SExpression {
+ argumentValidator.validate(argumentList)
+ RuntimeEnvironment.terminateSuccessfully()
+
+ return NIL
+ }
+}
diff --git a/src/main/kotlin/function/builtin/predicate/NUMERIC_EQUAL.java b/src/main/kotlin/function/builtin/predicate/NUMERIC_EQUAL.java
deleted file mode 100644
index 7e11f8f..0000000
--- a/src/main/kotlin/function/builtin/predicate/NUMERIC_EQUAL.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package function.builtin.predicate;
-
-import function.ArgumentValidator;
-import function.FunctionNames;
-import function.LispFunction;
-import recursion.TailCall;
-import sexpression.Cons;
-import sexpression.LispNumber;
-import sexpression.SExpression;
-
-import static recursion.TailCalls.done;
-import static recursion.TailCalls.tailCall;
-import static sexpression.Nil.NIL;
-import static sexpression.Symbol.T;
-
-@FunctionNames({ "=" })
-public class NUMERIC_EQUAL extends LispFunction {
-
- private ArgumentValidator argumentValidator;
-
- public NUMERIC_EQUAL(String name) {
- this.argumentValidator = new ArgumentValidator(name);
- this.argumentValidator.setMinimumNumberOfArguments(1);
- this.argumentValidator.setEveryArgumentExpectedType(LispNumber.class);
- }
-
- @Override
- public SExpression call(Cons argumentList) {
- argumentValidator.validate(argumentList);
-
- return callTailRecursive(argumentList).invoke();
- }
-
- private TailCall callTailRecursive(Cons argumentList) {
- Cons remainingArguments = (Cons) argumentList.getRest();
-
- if (remainingArguments.isNull())
- return done(T);
-
- SExpression firstArgument = argumentList.getFirst();
- LispNumber number1 = (LispNumber) firstArgument;
- SExpression secondArgument = remainingArguments.getFirst();
- LispNumber number2 = (LispNumber) secondArgument;
-
- if (!isEqual(number1, number2))
- return done(NIL);
-
- return tailCall(() -> callTailRecursive(remainingArguments));
- }
-
- private boolean isEqual(LispNumber number1, LispNumber number2) {
- return number1.getValue().equals(number2.getValue());
- }
-}
diff --git a/src/main/kotlin/function/builtin/predicate/NumericEqual.kt b/src/main/kotlin/function/builtin/predicate/NumericEqual.kt
new file mode 100644
index 0000000..cb9863f
--- /dev/null
+++ b/src/main/kotlin/function/builtin/predicate/NumericEqual.kt
@@ -0,0 +1,39 @@
+package function.builtin.predicate
+
+import function.ArgumentValidator
+import function.FunctionNames
+import function.LispFunction
+import sexpression.Cons
+import sexpression.LispNumber
+import sexpression.Nil.NIL
+import sexpression.SExpression
+import sexpression.Symbol.T
+
+@FunctionNames("=")
+class NumericEqual(name: String) : LispFunction() {
+
+ private val argumentValidator: ArgumentValidator = ArgumentValidator(name)
+
+ init {
+ this.argumentValidator.setMinimumNumberOfArguments(1)
+ this.argumentValidator.setEveryArgumentExpectedType(LispNumber::class.java)
+ }
+
+ override fun call(argumentList: Cons): SExpression {
+ argumentValidator.validate(argumentList)
+
+ return callTailRecursive(argumentList)
+ }
+
+ private tailrec fun callTailRecursive(argumentList: Cons): SExpression {
+ val remainingArguments = argumentList.rest as Cons
+
+ if (remainingArguments.isNull)
+ return T
+
+ val number1 = argumentList.first as LispNumber
+ val number2 = remainingArguments.first as LispNumber
+
+ return if (number1.value != number2.value) NIL else callTailRecursive(remainingArguments)
+ }
+}
diff --git a/src/main/kotlin/function/builtin/special/LAMBDA.kt b/src/main/kotlin/function/builtin/special/Lambda.kt
similarity index 94%
rename from src/main/kotlin/function/builtin/special/LAMBDA.kt
rename to src/main/kotlin/function/builtin/special/Lambda.kt
index 5b625bd..17c51ce 100644
--- a/src/main/kotlin/function/builtin/special/LAMBDA.kt
+++ b/src/main/kotlin/function/builtin/special/Lambda.kt
@@ -11,7 +11,7 @@ import sexpression.SExpression
import sexpression.Symbol
@FunctionNames("LAMBDA", "Λ")
-class LAMBDA(name: String) : LispSpecialFunction() {
+class Lambda(name: String) : LispSpecialFunction() {
private val argumentValidator: ArgumentValidator = ArgumentValidator(name)
private val lambdaListValidator: ArgumentValidator = ArgumentValidator("$name|lambda-list|")
@@ -59,7 +59,7 @@ class LAMBDA(name: String) : LispSpecialFunction() {
lambdaValidator.setEveryArgumentExpectedType(Cons::class.java)
lambdaValidator.validate(makeList(rest))
- val lambda = LAMBDA("LAMBDA").call(rest as Cons)
+ val lambda = Lambda("LAMBDA").call(rest as Cons)
return lambda.function
}
diff --git a/src/main/kotlin/table/FunctionTable.kt b/src/main/kotlin/table/FunctionTable.kt
index f9a00bc..f4d46cd 100644
--- a/src/main/kotlin/table/FunctionTable.kt
+++ b/src/main/kotlin/table/FunctionTable.kt
@@ -5,7 +5,7 @@ import function.FunctionNames
import function.LispFunction
import function.builtin.APPLY
import function.builtin.EVAL
-import function.builtin.EXIT
+import function.builtin.Exit
import function.builtin.FUNCALL
import function.builtin.FUSE
import function.builtin.GENSYM
@@ -32,7 +32,7 @@ import function.builtin.predicate.EQUAL
import function.builtin.predicate.GENSYM_EQUAL
import function.builtin.predicate.LISTP
import function.builtin.predicate.NULL
-import function.builtin.predicate.NUMERIC_EQUAL
+import function.builtin.predicate.NumericEqual
import function.builtin.predicate.NUMERIC_GREATER
import function.builtin.predicate.NUMERIC_LESS
import function.builtin.special.AND
@@ -42,7 +42,7 @@ import function.builtin.special.DEFINE_SPECIAL
import function.builtin.special.DEFMACRO
import function.builtin.special.DEFUN
import function.builtin.special.IF
-import function.builtin.special.LAMBDA
+import function.builtin.special.Lambda
import function.builtin.special.LET
import function.builtin.special.LET_STAR
import function.builtin.special.OR
@@ -72,9 +72,9 @@ object FunctionTable {
allBuiltIns.add(DIVIDE::class.java)
allBuiltIns.add(EQ::class.java)
allBuiltIns.add(EQUAL::class.java)
- allBuiltIns.add(NUMERIC_EQUAL::class.java)
+ allBuiltIns.add(NumericEqual::class.java)
allBuiltIns.add(EVAL::class.java)
- allBuiltIns.add(EXIT::class.java)
+ allBuiltIns.add(Exit::class.java)
allBuiltIns.add(FIRST::class.java)
allBuiltIns.add(FUNCALL::class.java)
allBuiltIns.add(FUSE::class.java)
@@ -82,7 +82,7 @@ object FunctionTable {
allBuiltIns.add(GENSYM_EQUAL::class.java)
allBuiltIns.add(NUMERIC_GREATER::class.java)
allBuiltIns.add(IF::class.java)
- allBuiltIns.add(LAMBDA::class.java)
+ allBuiltIns.add(Lambda::class.java)
allBuiltIns.add(LENGTH::class.java)
allBuiltIns.add(NUMERIC_LESS::class.java)
allBuiltIns.add(LET::class.java)
diff --git a/src/test/kotlin/function/builtin/EXITTest.kt b/src/test/kotlin/function/builtin/EXITTest.kt
deleted file mode 100644
index adf7b8b..0000000
--- a/src/test/kotlin/function/builtin/EXITTest.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package function.builtin
-
-import environment.RuntimeEnvironment
-import function.ArgumentValidator.TooManyArgumentsException
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Test
-import testutil.SymbolAndFunctionCleaner
-import testutil.TestUtilities.evaluateString
-import java.util.HashSet
-
-class EXITTest : SymbolAndFunctionCleaner() {
- private val environment: RuntimeEnvironment
- private var indicatorSet: MutableSet? = null
-
- init {
- this.environment = RuntimeEnvironment
- }
-
- private fun assertTerminated() {
- assertTrue(indicatorSet!!.contains(TERMINATED))
- }
-
- private fun assertNotTerminated() {
- assertFalse(indicatorSet!!.contains(TERMINATED))
- }
-
- override fun additionalSetUp() {
- indicatorSet = HashSet()
- environment.reset()
- environment.terminationFunction = { indicatorSet!!.add(TERMINATED) }
- }
-
- override fun additionalTearDown() {
- environment.reset()
- }
-
- @Test
- fun exitWorks() {
- evaluateString("(exit)")
- assertTerminated()
- }
-
- @Test
- fun exitNotCalled_IndicatorSetIsClean() {
- assertNotTerminated()
- }
-
- @Test(expected = TooManyArgumentsException::class)
- fun exitWithTooManyArguments() {
- evaluateString("(exit 1)")
- }
-
- companion object {
-
- private val TERMINATED = "terminated"
- }
-}
diff --git a/src/test/kotlin/function/builtin/ExitTest.kt b/src/test/kotlin/function/builtin/ExitTest.kt
new file mode 100644
index 0000000..18fab39
--- /dev/null
+++ b/src/test/kotlin/function/builtin/ExitTest.kt
@@ -0,0 +1,53 @@
+package function.builtin
+
+import environment.RuntimeEnvironment
+import function.ArgumentValidator.TooManyArgumentsException
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Test
+import testutil.SymbolAndFunctionCleaner
+import testutil.TestUtilities.evaluateString
+import java.util.HashSet
+
+class ExitTest : SymbolAndFunctionCleaner() {
+
+ companion object {
+ private const val TERMINATED = "terminated"
+ }
+
+ private var indicatorSet = HashSet()
+
+ private fun assertTerminated() {
+ assertThat(indicatorSet.contains(TERMINATED)).isTrue()
+ }
+
+ private fun assertNotTerminated() {
+ assertThat(indicatorSet.contains(TERMINATED)).isFalse()
+ }
+
+ override fun additionalSetUp() {
+ indicatorSet.clear()
+ RuntimeEnvironment.reset()
+ RuntimeEnvironment.terminationFunction = { indicatorSet.add(TERMINATED) }
+ }
+
+ override fun additionalTearDown() {
+ RuntimeEnvironment.reset()
+ }
+
+ @Test
+ fun `exit works`() {
+ evaluateString("(exit)")
+ assertTerminated()
+ }
+
+ @Test
+ fun `no termination when exit not called`() {
+ assertNotTerminated()
+ }
+
+ @Test
+ fun `too many arguments`() {
+ assertThrows(TooManyArgumentsException::class.java) { evaluateString("(exit 1)") }
+ }
+}
diff --git a/src/test/kotlin/function/builtin/predicate/NUMERIC_EQUALTest.java b/src/test/kotlin/function/builtin/predicate/NUMERIC_EQUALTest.java
deleted file mode 100644
index 3a71462..0000000
--- a/src/test/kotlin/function/builtin/predicate/NUMERIC_EQUALTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package function.builtin.predicate;
-
-import function.ArgumentValidator.BadArgumentTypeException;
-import function.ArgumentValidator.TooFewArgumentsException;
-import org.junit.Test;
-import testutil.SymbolAndFunctionCleaner;
-
-import static testutil.TestUtilities.evaluateString;
-import static testutil.TypeAssertions.assertNil;
-import static testutil.TypeAssertions.assertT;
-
-public class NUMERIC_EQUALTest extends SymbolAndFunctionCleaner {
-
- @Test
- public void numericEqualWithOneNumber() {
- String input = "(= 1)";
-
- assertT(evaluateString(input));
- }
-
- @Test
- public void numericEqualWithEqualNumbers() {
- String input = "(= 1 1)";
-
- assertT(evaluateString(input));
- }
-
- @Test
- public void numericEqualWithNonEqualNumbers() {
- String input = "(= 1 2)";
-
- assertNil(evaluateString(input));
- }
-
- @Test
- public void numericEqualWithManyEqualNumbers() {
- String input = "(= 4 4 4 4 4 4 4 4 4 4)";
-
- assertT(evaluateString(input));
- }
-
- @Test
- public void numericEqualWithManyNonEqualNumbers() {
- String input = "(= 4 4 4 4 5 4 4 4 4 4)";
-
- assertNil(evaluateString(input));
- }
-
- @Test(expected = BadArgumentTypeException.class)
- public void numericEqualWithNonNumbers() {
- evaluateString("(= 'x 'x)");
- }
-
- @Test(expected = TooFewArgumentsException.class)
- public void numericEqualWithTooFewArguments() {
- evaluateString("(=)");
- }
-}
diff --git a/src/test/kotlin/function/builtin/predicate/NumericEqualTest.kt b/src/test/kotlin/function/builtin/predicate/NumericEqualTest.kt
new file mode 100644
index 0000000..8a791db
--- /dev/null
+++ b/src/test/kotlin/function/builtin/predicate/NumericEqualTest.kt
@@ -0,0 +1,51 @@
+package function.builtin.predicate
+
+import function.ArgumentValidator.BadArgumentTypeException
+import function.ArgumentValidator.TooFewArgumentsException
+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 testutil.SymbolAndFunctionCleaner
+import testutil.TestUtilities.evaluateString
+import testutil.TypeAssertions.assertNil
+import testutil.TypeAssertions.assertT
+
+@TestInstance(PER_CLASS)
+class NumericEqualTest : SymbolAndFunctionCleaner() {
+
+ @Test
+ fun `one number`() {
+ assertT(evaluateString("(= 1)"))
+ }
+
+ @Test
+ fun `equal numbers`() {
+ assertT(evaluateString("(= 1 1)"))
+ }
+
+ @Test
+ fun `unequal numbers`() {
+ assertNil(evaluateString("(= 1 2)"))
+ }
+
+ @Test
+ fun `many equal numbers`() {
+ assertT(evaluateString("(= 4 4 4 4 4 4 4 4 4 4)"))
+ }
+
+ @Test
+ fun `many unequal numbers`() {
+ assertNil(evaluateString("(= 4 4 4 4 5 4 4 4 4 4)"))
+ }
+
+ @Test
+ fun `bad argument types`() {
+ assertThrows(BadArgumentTypeException::class.java) { evaluateString("(= 'x 'x)") }
+ }
+
+ @Test
+ fun `too few arguments`() {
+ assertThrows(TooFewArgumentsException::class.java) { evaluateString("(=)") }
+ }
+}
diff --git a/src/test/kotlin/function/builtin/special/LAMBDATest.java b/src/test/kotlin/function/builtin/special/LAMBDATest.java
deleted file mode 100644
index 4bd54e8..0000000
--- a/src/test/kotlin/function/builtin/special/LAMBDATest.java
+++ /dev/null
@@ -1,150 +0,0 @@
-package function.builtin.special;
-
-import function.ArgumentValidator.BadArgumentTypeException;
-import function.ArgumentValidator.DottedArgumentListException;
-import function.ArgumentValidator.TooFewArgumentsException;
-import function.ArgumentValidator.TooManyArgumentsException;
-import function.builtin.EVAL.UndefinedFunctionException;
-import org.junit.Test;
-import sexpression.Cons;
-import sexpression.LispNumber;
-import sexpression.Symbol;
-import testutil.SymbolAndFunctionCleaner;
-
-import static function.builtin.special.LAMBDA.Lambda;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static sexpression.LispNumber.ONE;
-import static sexpression.Nil.NIL;
-import static sexpression.Symbol.T;
-import static testutil.TestUtilities.assertSExpressionsMatch;
-import static testutil.TestUtilities.evaluateString;
-import static testutil.TestUtilities.parseString;
-
-public class LAMBDATest extends SymbolAndFunctionCleaner {
-
- @Test
- public void lambda() {
- String input = "(lambda (x) x)";
-
- assertSExpressionsMatch(parseString("(LAMBDA (X) X)"), evaluateString(input));
- }
-
- @Test
- public void lambdaSymbol() {
- String input = "(λ (x) x)";
-
- assertSExpressionsMatch(parseString("(LAMBDA (X) X)"), evaluateString(input));
- }
-
- @Test
- public void lambdaWithNoBody() {
- String input = "(lambda ())";
-
- assertSExpressionsMatch(parseString("(LAMBDA ())"), evaluateString(input));
- }
-
- @Test
- public void lambdaExpressionIsLambdaExpression() {
- Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), new Cons(NIL, new Cons(NIL, NIL)));
-
- assertTrue(Lambda.isLambdaExpression(lambdaExpression));
- }
-
- @Test
- public void somethingElseIsNotLambdaExpression() {
- assertFalse(Lambda.isLambdaExpression(T));
- }
-
- @Test
- public void createLambdaExpression() {
- Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), new Cons(NIL, new Cons(NIL, NIL)));
-
- assertSExpressionsMatch(parseString("(:LAMBDA () ())"),
- Lambda.createFunction(lambdaExpression).getLambdaExpression());
- }
-
- @Test(expected = DottedArgumentListException.class)
- public void lambdaWithDottedArgumentList() {
- String input = "(apply 'lambda (cons '(x) 1))";
-
- evaluateString(input);
- }
-
- @Test(expected = DottedArgumentListException.class)
- public void lambdaWithDottedLambdaList() {
- String input = "(funcall 'lambda (cons 'a 'b) ())";
-
- evaluateString(input);
- }
-
- @Test(expected = DottedArgumentListException.class)
- public void createFunctionWithDottedArgumentList() {
- Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), new Cons(NIL, ONE));
-
- Lambda.createFunction(lambdaExpression);
- }
-
- @Test(expected = BadArgumentTypeException.class)
- public void createFunctionWithNonList() {
- Cons lambdaExpression = new Cons(new Symbol("LAMBDA"), ONE);
-
- Lambda.createFunction(lambdaExpression);
- }
-
- @Test(expected = BadArgumentTypeException.class)
- public void lambdaWithNonSymbolParameter() {
- evaluateString("(lambda (1) ())");
- }
-
- @Test(expected = TooFewArgumentsException.class)
- public void lambdaWithTooFewArguments() {
- evaluateString("(lambda)");
- }
-
- @Test
- public void anonymousLambdaCall() {
- String input = "((lambda (x) x) 203)";
-
- assertSExpressionsMatch(new LispNumber("203"), evaluateString(input));
- }
-
- @Test
- public void anonymousLambdaCallWithMultipleArguments() {
- String input = "((lambda (x y) (+ x y)) 203 2)";
-
- assertSExpressionsMatch(new LispNumber("205"), evaluateString(input));
- }
-
- @Test
- public void anonymousLambdaCallWithSymbol() {
- String input = "((λ (x) (+ x 1)) 3)";
-
- assertSExpressionsMatch(new LispNumber("4"), evaluateString(input));
- }
-
- @Test(expected = TooFewArgumentsException.class)
- public void anonymousLambdaCallWithTooFewArguments() {
- evaluateString("((lambda (x) x))");
- }
-
- @Test(expected = TooManyArgumentsException.class)
- public void anonymousLambdaCallWithTooManyArguments() {
- evaluateString("((lambda (x y) x) 1 2 3)");
- }
-
- @Test(expected = UndefinedFunctionException.class)
- public void badAnonymousFunctionCall() {
- evaluateString("((bad-lambda (x y) x) 1 2 3)");
- }
-
- @Test
- public void lexicalClosure() {
- evaluateString("(setq increment-count (let ((counter 0)) (lambda () (setq counter (+ 1 counter)))))");
-
- assertSExpressionsMatch(parseString("1"), evaluateString("(funcall increment-count)"));
- assertSExpressionsMatch(parseString("2"), evaluateString("(funcall increment-count)"));
- assertSExpressionsMatch(parseString("3"), evaluateString("(funcall increment-count)"));
- assertSExpressionsMatch(parseString("4"), evaluateString("(funcall increment-count)"));
- }
-}
diff --git a/src/test/kotlin/function/builtin/special/LambdaTest.kt b/src/test/kotlin/function/builtin/special/LambdaTest.kt
new file mode 100644
index 0000000..feb8185
--- /dev/null
+++ b/src/test/kotlin/function/builtin/special/LambdaTest.kt
@@ -0,0 +1,149 @@
+package function.builtin.special
+
+import function.ArgumentValidator.BadArgumentTypeException
+import function.ArgumentValidator.DottedArgumentListException
+import function.ArgumentValidator.TooFewArgumentsException
+import function.ArgumentValidator.TooManyArgumentsException
+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
+import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Test
+import sexpression.Cons
+import sexpression.LispNumber
+import sexpression.LispNumber.ONE
+import sexpression.Nil.NIL
+import sexpression.Symbol
+import sexpression.Symbol.T
+import testutil.SymbolAndFunctionCleaner
+import testutil.TestUtilities.assertSExpressionsMatch
+import testutil.TestUtilities.evaluateString
+import testutil.TestUtilities.parseString
+
+class LambdaTest : SymbolAndFunctionCleaner() {
+
+ @Test
+ fun `lambda is evaluated`() {
+ val input = "(lambda (x) x)"
+
+ assertSExpressionsMatch(parseString("(LAMBDA (X) X)"), evaluateString(input))
+ }
+
+ @Test
+ fun `lambda symbol is evaluated`() {
+ val input = "(λ (x) x)"
+
+ assertSExpressionsMatch(parseString("(LAMBDA (X) X)"), evaluateString(input))
+ }
+
+ @Test
+ fun `lambda with no body`() {
+ val input = "(lambda ())"
+
+ assertSExpressionsMatch(parseString("(LAMBDA ())"), evaluateString(input))
+ }
+
+ @Test
+ fun `lambda expression is a lambda expression`() {
+ val lambdaExpression = Cons(Symbol("LAMBDA"), Cons(NIL, Cons(NIL, NIL)))
+
+ assertThat(isLambdaExpression(lambdaExpression)).isTrue()
+ }
+
+ @Test
+ fun `something else is not a lambda expression`() {
+ assertThat(isLambdaExpression(T)).isFalse()
+ }
+
+ @Test
+ fun `create lambda expression`() {
+ val lambdaExpression = Cons(Symbol("LAMBDA"), Cons(NIL, Cons(NIL, NIL)))
+
+ assertSExpressionsMatch(parseString("(:LAMBDA () ())"), createFunction(lambdaExpression).lambdaExpression)
+ }
+
+ @Test
+ fun `dotted argument list`() {
+ val input = "(apply 'lambda (cons '(x) 1))"
+
+ assertThrows(DottedArgumentListException::class.java) { evaluateString(input) }
+ }
+
+ @Test
+ fun `dotted lambda list`() {
+ val input = "(funcall 'lambda (cons 'a 'b) ())"
+
+ assertThrows(DottedArgumentListException::class.java) { evaluateString(input) }
+ }
+
+ @Test
+ fun `create function with dotted argument list`() {
+ val lambdaExpression = Cons(Symbol("LAMBDA"), Cons(NIL, ONE))
+
+ assertThrows(DottedArgumentListException::class.java) { createFunction(lambdaExpression) }
+ }
+
+ @Test
+ fun `create function with non list`() {
+ val lambdaExpression = Cons(Symbol("LAMBDA"), ONE)
+
+ assertThrows(BadArgumentTypeException::class.java) { createFunction(lambdaExpression) }
+ }
+
+ @Test
+ fun `bad argument type`() {
+ assertThrows(BadArgumentTypeException::class.java) { evaluateString("(lambda (1) ())") }
+ }
+
+ @Test
+ fun `too few arguments`() {
+ assertThrows(TooFewArgumentsException::class.java) { evaluateString("(lambda)") }
+ }
+
+ @Test
+ fun `anonymous lambda call`() {
+ val input = "((lambda (x) x) 203)"
+
+ assertSExpressionsMatch(LispNumber("203"), evaluateString(input))
+ }
+
+ @Test
+ fun `anonymous lambda call with multiple arguments`() {
+ val input = "((lambda (x y) (+ x y)) 203 2)"
+
+ assertSExpressionsMatch(LispNumber("205"), evaluateString(input))
+ }
+
+ @Test
+ fun `anonymous lambda call with symbol`() {
+ val input = "((λ (x) (+ x 1)) 3)"
+
+ assertSExpressionsMatch(LispNumber("4"), evaluateString(input))
+ }
+
+ @Test
+ fun `anonymous lambda call with too few arguments`() {
+ assertThrows(TooFewArgumentsException::class.java) { evaluateString("((lambda (x) x))") }
+ }
+
+ @Test
+ fun `anonymous lambda call with too many arguments`() {
+ assertThrows(TooManyArgumentsException::class.java) { evaluateString("((lambda (x y) x) 1 2 3)") }
+ }
+
+ @Test
+ fun `bad anonymous function call`() {
+ assertThrows(UndefinedFunctionException::class.java) { evaluateString("((bad-lambda (x y) x) 1 2 3)") }
+ }
+
+ @Test
+ fun `lexical closure`() {
+ evaluateString("(setq increment-count (let ((counter 0)) (lambda () (setq counter (+ 1 counter)))))")
+
+ assertSExpressionsMatch(parseString("1"), evaluateString("(funcall increment-count)"))
+ assertSExpressionsMatch(parseString("2"), evaluateString("(funcall increment-count)"))
+ assertSExpressionsMatch(parseString("3"), evaluateString("(funcall increment-count)"))
+ assertSExpressionsMatch(parseString("4"), evaluateString("(funcall increment-count)"))
+ }
+}