package function.builtin.special; import static org.junit.Assert.assertTrue; import static table.FunctionTable.resetFunctionTable; import static testutil.TestUtilities.*; import java.io.*; import org.junit.*; import environment.RuntimeEnvironment; import error.ErrorManager; import function.ArgumentValidator.*; import function.UserDefinedFunction.IllegalKeywordRestPositionException; public class DEFUNTester { private ByteArrayOutputStream outputStream; private RuntimeEnvironment environment; public DEFUNTester() { this.environment = RuntimeEnvironment.getInstance(); } private void assertSomethingPrinted() { assertTrue(outputStream.toByteArray().length > 0); } @Before public void setUp() { outputStream = new ByteArrayOutputStream(); environment.reset(); environment.setOutput(new PrintStream(outputStream)); environment.setErrorManager(new ErrorManager()); environment.setWarningOutputDecorator(s -> s); resetFunctionTable(); } @After public void tearDown() { environment.reset(); resetFunctionTable(); } @Test public void defun() { String input = "(defun f () t)"; assertSExpressionsMatch(parseString("f"), evaluateString(input)); assertSExpressionsMatch(parseString("t"), evaluateString("(f)")); } @Test public void defunWithEmptyBody() { String input = "(defun f ())"; assertSExpressionsMatch(parseString("f"), evaluateString(input)); assertSExpressionsMatch(parseString("()"), evaluateString("(f)")); } @Test public void defunEvaluatesArguments() { evaluateString("(defun f (x) (car x))"); assertSExpressionsMatch(parseString("1"), evaluateString("(f '(1 2 3))")); } @Test public void defunRecursiveFunction() { evaluateString("(defun fact (x) (if (< x 2) 1 (* x (fact (- x 1)))))"); assertSExpressionsMatch(parseString("120"), evaluateString("(fact 5)")); } @Test public void defunTailRecursiveFunction() { evaluateString("(defun fact-tail (x acc) (if (< x 2) acc (fact-tail (- x 1) (* x acc))))"); assertSExpressionsMatch(parseString("120"), evaluateString("(fact-tail 5 1)")); } @Test public void defunSimpleClass() { evaluateString("(defun counter-class () (let ((counter 0)) (lambda () (setq counter (+ 1 counter)))))"); evaluateString("(setq my-counter (counter-class))"); assertSExpressionsMatch(parseString("1"), evaluateString("(funcall my-counter)")); assertSExpressionsMatch(parseString("2"), evaluateString("(funcall my-counter)")); assertSExpressionsMatch(parseString("3"), evaluateString("(funcall my-counter)")); assertSExpressionsMatch(parseString("4"), evaluateString("(funcall my-counter)")); } @Test public void redefineFunction_DisplaysWarning() { String input = "(defun myFunction () nil)"; evaluateString(input); evaluateString(input); assertSomethingPrinted(); } @Test public void redefineFunction_ActuallyRedefinesFunction() { evaluateString("(defun myFunction2 () nil)"); evaluateString("(defun myFunction2 () T)"); assertSomethingPrinted(); assertSExpressionsMatch(parseString("t"), evaluateString("(myFunction2)")); } @Test(expected = DottedArgumentListException.class) public void defunWithDottedLambdaList() { evaluateString("(funcall 'defun 'x (cons 'a 'b) ())"); } @Test(expected = BadArgumentTypeException.class) public void defunWithNonSymbolName() { evaluateString("(defun 1 () ())"); } @Test(expected = BadArgumentTypeException.class) public void defunWithBadLambdaList() { evaluateString("(defun x a ())"); } @Test(expected = TooFewArgumentsException.class) public void defunWithTooFewArguments() { evaluateString("(defun x)"); } @Test(expected = TooFewArgumentsException.class) public void defunFunctionAndCallWithTooFewArguments() { evaluateString("(defun x (a b))"); evaluateString("(x 'a)"); } @Test(expected = TooManyArgumentsException.class) public void defunFunctionAndCallWithTooManyArguments() { evaluateString("(defun x (a b))"); evaluateString("(x 'a 'b 'c)"); } @Test public void defunWithKeywordRestParameter() { evaluateString("(defun f (&rest x) (car x))"); assertSExpressionsMatch(parseString("1"), evaluateString("(f 1 2 3 4 5)")); } @Test public void defunWithNormalAndKeywordRestParameter() { evaluateString("(defun f (a &rest b) (cons a b))"); assertSExpressionsMatch(parseString("(1 2 3 4 5)"), evaluateString("(f 1 2 3 4 5)")); } @Test(expected = IllegalKeywordRestPositionException.class) public void defunWithParametersFollowingKeywordRest() { evaluateString("(defun f (a &rest b c) (cons a b))"); evaluateString("(f 1 2 3)"); } @Test public void defunWithKeywordRest_CallWithNoArguments() { evaluateString("(defun f (&rest a) (car a))"); assertSExpressionsMatch(parseString("nil"), evaluateString("(f)")); } @Test(expected = TooFewArgumentsException.class) public void defunWithNormalAndKeywordRest_CallWithNoArguments() { evaluateString("(defun f (a &rest b) a)"); evaluateString("(f)"); } }