201 lines
5.9 KiB
Kotlin
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)"))
|
|
}
|
|
}
|