transcendental-lisp/src/test/kotlin/function/builtin/special/DefmacroTest.kt

201 lines
5.9 KiB
Kotlin

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