package function.builtin.special; import static org.junit.Assert.assertTrue; import static testutil.TestUtilities.assertSExpressionsMatch; import static testutil.TestUtilities.evaluateString; import static testutil.TestUtilities.parseString; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import org.junit.Test; import environment.RuntimeEnvironment; import error.ErrorManager; import function.ArgumentValidator.BadArgumentTypeException; import function.ArgumentValidator.DottedArgumentListException; import function.ArgumentValidator.TooFewArgumentsException; import function.ArgumentValidator.TooManyArgumentsException; import function.UserDefinedFunction.IllegalKeywordRestPositionException; import testutil.SymbolAndFunctionCleaner; public class DEFMACROTest extends SymbolAndFunctionCleaner { private ByteArrayOutputStream outputStream; private RuntimeEnvironment environment; public DEFMACROTest() { this.environment = RuntimeEnvironment.getInstance(); } private void assertSomethingPrinted() { assertTrue(outputStream.toByteArray().length > 0); } @Override public void additionalSetUp() { outputStream = new ByteArrayOutputStream(); environment.reset(); environment.setOutput(new PrintStream(outputStream)); environment.setErrorManager(new ErrorManager()); environment.setWarningOutputDecorator(s -> s); } @Override public void additionalTearDown() { environment.reset(); } @Test public void defmacro() { String input = "(defmacro m () t)"; assertSExpressionsMatch(parseString("m"), evaluateString(input)); assertSExpressionsMatch(parseString("t"), evaluateString("(m)")); } @Test public void defmacroWithEmptyBody() { String input = "(defmacro m ())"; assertSExpressionsMatch(parseString("m"), evaluateString(input)); assertSExpressionsMatch(parseString("()"), evaluateString("(m)")); } @Test public void defmacroDoesNotEvaluateArguments() { evaluateString("(setq x 'grains)"); evaluateString("(defmacro m (x))"); evaluateString("(m (setq x 'sprouts))"); assertSExpressionsMatch(parseString("grains"), evaluateString("x")); } @Test public void defmacroAdd() { evaluateString("(defmacro m (x) (+ (eval x) 23))"); assertSExpressionsMatch(parseString("27"), evaluateString("(m (+ 2 2))")); } @Test public void defmacroSetVariable() { evaluateString("(defmacro m (x) (set x 23))"); evaluateString("(m y)"); assertSExpressionsMatch(parseString("23"), evaluateString("y")); } @Test public void defmacroVariableCapture() { evaluateString("(setq x 0)"); evaluateString("(defmacro m (x) (set x 23))"); evaluateString("(m x)"); assertSExpressionsMatch(parseString("0"), evaluateString("x")); } @Test public void redefineMacro_DisplaysWarning() { String input = "(defmacro myMacro () nil)"; evaluateString(input); evaluateString(input); assertSomethingPrinted(); } @Test public void redefineMacro_ActuallyRedefinesSpecialFunction() { evaluateString("(defmacro myMacro () nil)"); evaluateString("(defmacro myMacro () T)"); assertSomethingPrinted(); assertSExpressionsMatch(parseString("t"), evaluateString("(myMacro)")); } @Test(expected = DottedArgumentListException.class) public void defmacroWithDottedLambdaList() { evaluateString("(funcall 'defmacro 'm (cons 'a 'b) ())"); } @Test(expected = BadArgumentTypeException.class) public void defmacroWithNonSymbolName() { evaluateString("(defmacro 1 () ())"); } @Test(expected = BadArgumentTypeException.class) public void defmacroWithBadLambdaList() { evaluateString("(defmacro m a ())"); } @Test(expected = TooFewArgumentsException.class) public void defmacroWithTooFewArguments() { evaluateString("(defmacro m)"); } @Test(expected = TooFewArgumentsException.class) public void defmacroAndCallWithTooFewArguments() { evaluateString("(defmacro m (a b))"); evaluateString("(m a)"); } @Test(expected = TooManyArgumentsException.class) public void defmacroAndCallWithTooManyArguments() { evaluateString("(defmacro m (a b))"); evaluateString("(m a b c)"); } @Test public void defmacroWithKeywordRestParameter() { evaluateString("(defmacro m (&rest x) (car x))"); assertSExpressionsMatch(parseString("1"), evaluateString("(m 1 2 3 4 5)")); } @Test public void defmacroWithNormalAndKeywordRestParameter() { evaluateString("(defmacro m (a &rest b) (list 'cons a (list 'quote b)))"); assertSExpressionsMatch(parseString("(1 2 3 4 5)"), evaluateString("(m 1 2 3 4 5)")); } @Test(expected = IllegalKeywordRestPositionException.class) public void defmacroWithParametersFollowingKeywordRest() { evaluateString("(defmacro m (a &rest b c) (cons a b))"); evaluateString("(m 1 2 3)"); } @Test public void defmacroWithKeywordRest_CallWithNoArguments() { evaluateString("(defmacro m (&rest a) (car a))"); assertSExpressionsMatch(parseString("nil"), evaluateString("(m)")); } @Test(expected = TooFewArgumentsException.class) public void defmacroWithNormalAndKeywordRest_CallWithNoArguments() { evaluateString("(defmacro m (a &rest b) a)"); evaluateString("(m)"); } @Test public void macroIsEvaluatedAfterExpansion() { evaluateString("(setq x 'grains)"); evaluateString("(defmacro m (x) x)"); evaluateString("(m (setq x 'sprouts))"); assertSExpressionsMatch(parseString("sprouts"), evaluateString("x")); } @Test public void macroIsEvaluatedCorrectly() { evaluateString("(defmacro m (x) `'(+ 2 ,x))"); assertSExpressionsMatch(parseString("(+ 2 25)"), evaluateString("(m 25)")); } }