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)")) + } +}