package function.builtin.special 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 org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Test import testutil.LispTestInstance import testutil.SymbolAndFunctionCleaner import testutil.TestUtilities.assertSExpressionsMatch import testutil.TestUtilities.evaluateString import testutil.TestUtilities.parseString import java.io.ByteArrayOutputStream import java.io.PrintStream @LispTestInstance class DefmacroTest : SymbolAndFunctionCleaner() { private var outputStream = ByteArrayOutputStream() private fun assertSomethingPrinted() { assertThat(outputStream.toByteArray()).isNotEmpty() } override fun additionalSetUp() { outputStream.reset() RuntimeEnvironment.reset() RuntimeEnvironment.output = PrintStream(outputStream) RuntimeEnvironment.errorManager = ErrorManager() RuntimeEnvironment.warningOutputDecorator = { it } } override fun additionalTearDown() { RuntimeEnvironment.reset() } @Test fun defmacro() { val input = "(defmacro m () t)" assertSExpressionsMatch(parseString("m"), evaluateString(input)) assertSExpressionsMatch(parseString("t"), evaluateString("(m)")) } @Test fun defmacroWithEmptyBody() { val input = "(defmacro m ())" assertSExpressionsMatch(parseString("m"), evaluateString(input)) assertSExpressionsMatch(parseString("()"), evaluateString("(m)")) } @Test fun defmacroDoesNotEvaluateArguments() { evaluateString("(setq x 'grains)") evaluateString("(defmacro m (x))") evaluateString("(m (setq x 'sprouts))") assertSExpressionsMatch(parseString("grains"), evaluateString("x")) } @Test fun defmacroAdd() { evaluateString("(defmacro m (x) (+ (eval x) 23))") assertSExpressionsMatch(parseString("27"), evaluateString("(m (+ 2 2))")) } @Test fun defmacroSetVariable() { evaluateString("(defmacro m (x) (set x 23))") evaluateString("(m y)") assertSExpressionsMatch(parseString("23"), evaluateString("y")) } @Test fun defmacroVariableCapture() { evaluateString("(setq x 0)") evaluateString("(defmacro m (x) (set x 23))") evaluateString("(m x)") assertSExpressionsMatch(parseString("0"), evaluateString("x")) } @Test fun redefineMacro_DisplaysWarning() { val input = "(defmacro myMacro () nil)" evaluateString(input) evaluateString(input) assertSomethingPrinted() } @Test fun redefineMacro_ActuallyRedefinesSpecialFunction() { evaluateString("(defmacro myMacro () nil)") evaluateString("(defmacro myMacro () T)") assertSomethingPrinted() assertSExpressionsMatch(parseString("t"), evaluateString("(myMacro)")) } @Test fun defmacroWithDottedLambdaList() { assertThrows(DottedArgumentListException::class.java) { evaluateString("(funcall 'defmacro 'm (cons 'a 'b) ())") } } @Test fun defmacroWithNonSymbolName() { assertThrows(BadArgumentTypeException::class.java) { evaluateString("(defmacro 1 () ())") } } @Test fun defmacroWithBadLambdaList() { assertThrows(BadArgumentTypeException::class.java) { evaluateString("(defmacro m a ())") } } @Test fun defmacroWithTooFewArguments() { assertThrows(TooFewArgumentsException::class.java) { evaluateString("(defmacro m)") } } @Test fun defmacroAndCallWithTooFewArguments() { evaluateString("(defmacro m (a b))") assertThrows(TooFewArgumentsException::class.java) { evaluateString("(m a)") } } @Test fun defmacroAndCallWithTooManyArguments() { evaluateString("(defmacro m (a b))") assertThrows(TooManyArgumentsException::class.java) { evaluateString("(m a b c)") } } @Test fun defmacroWithKeywordRestParameter() { evaluateString("(defmacro m (&rest x) (car x))") assertSExpressionsMatch(parseString("1"), evaluateString("(m 1 2 3 4 5)")) } @Test fun 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 fun defmacroWithParametersFollowingKeywordRest() { assertThrows(IllegalKeywordRestPositionException::class.java) { evaluateString("(defmacro m (a &rest b c) (cons a b))") } } @Test fun defmacroWithKeywordRest_CallWithNoArguments() { evaluateString("(defmacro m (&rest a) (car a))") assertSExpressionsMatch(parseString("nil"), evaluateString("(m)")) } @Test fun defmacroWithNormalAndKeywordRest_CallWithNoArguments() { evaluateString("(defmacro m (a &rest b) a)") assertThrows(TooFewArgumentsException::class.java) { evaluateString("(m)") } } @Test fun macroIsEvaluatedAfterExpansion() { evaluateString("(setq x 'grains)") evaluateString("(defmacro m (x) x)") evaluateString("(m (setq x 'sprouts))") assertSExpressionsMatch(parseString("sprouts"), evaluateString("x")) } @Test fun macroIsEvaluatedCorrectly() { evaluateString("(defmacro m (x) `'(+ 2 ,x))") assertSExpressionsMatch(parseString("(+ 2 25)"), evaluateString("(m 25)")) } }