package interpreter import environment.RuntimeEnvironment import interpreter.InteractiveLispInterpreter.Companion.PROMPT import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testutil.TestUtilities.createInputStreamFromString import java.io.ByteArrayOutputStream import java.io.PrintStream class LispInterpreterTest { companion object { private const val TERMINATED = "terminated" private val FILE = LispInterpreterTest::class.java.getResource("file.lisp").file } private val indicatorSet = mutableSetOf() private val outputStream = ByteArrayOutputStream() private val errorOutputStream = ByteArrayOutputStream() private fun setCommonFeatures() { LispInterpreterBuilder.setOutput(PrintStream(outputStream)) LispInterpreterBuilder.setErrorOutput(PrintStream(errorOutputStream)) LispInterpreterBuilder.setTerminationFunction { } LispInterpreterBuilder.setErrorTerminationFunction { indicatorSet.add(TERMINATED) } } private fun assertTerminated() { assertThat(indicatorSet).contains(TERMINATED) } private fun assertErrorMessageWritten() { assertThat(errorOutputStream.toByteArray()).isNotEmpty() } @BeforeEach() fun setUp() { indicatorSet.clear() outputStream.reset() errorOutputStream.reset() RuntimeEnvironment.reset() LispInterpreterBuilder.reset() } @AfterEach() fun tearDown() { RuntimeEnvironment.reset() LispInterpreterBuilder.reset() } @Test fun `build an interactive interpreter`() { setCommonFeatures() LispInterpreterBuilder.setInput(System.`in`, "stdin") val interpreter = LispInterpreterBuilder.build() assertThat(interpreter is InteractiveLispInterpreter).isTrue() } @Test fun `build a non interactive interpreter`() { setCommonFeatures() LispInterpreterBuilder.setInput(System.`in`, "stdin") LispInterpreterBuilder.setNotInteractive() val interpreter = LispInterpreterBuilder.build() assertThat(interpreter is InteractiveLispInterpreter).isFalse() assertThat(interpreter is FileLispInterpreter).isFalse() } @Test fun `build a file based interpreter`() { setCommonFeatures() LispInterpreterBuilder.useFile(FILE) val interpreter = LispInterpreterBuilder.build() assertThat(interpreter is FileLispInterpreter).isTrue() } @Test fun `building an interpreter on a bad file terminates correctly`() { setCommonFeatures() LispInterpreterBuilder.useFile("does-not-exist.lisp") LispInterpreterBuilder.build() assertErrorMessageWritten() assertTerminated() } @Test fun `the decorators are initialized with the correct defaults`() { LispInterpreterBuilder.build() assertThat(RuntimeEnvironment.decoratePrompt("")).isEmpty() assertThat(RuntimeEnvironment.decorateValueOutput("")).isEmpty() assertThat(RuntimeEnvironment.decorateWarningOutput("")).isEmpty() assertThat(RuntimeEnvironment.decorateErrorOutput("")).isEmpty() assertThat(RuntimeEnvironment.decorateCriticalOutput("")).isEmpty() } @Test fun `the decorators are set correctly`() { LispInterpreterBuilder.setPromptDecorator { s -> "#$s#" } LispInterpreterBuilder.setValueOutputDecorator { s -> "@$s@" } LispInterpreterBuilder.setWarningOutputDecorator { s -> "%$s%" } LispInterpreterBuilder.setErrorOutputDecorator { s -> "*$s*" } LispInterpreterBuilder.setCriticalOutputDecorator { s -> "$$s$" } LispInterpreterBuilder.build() assertThat(RuntimeEnvironment.decoratePrompt("x")).isEqualTo("#x#") assertThat(RuntimeEnvironment.decorateValueOutput("x")).isEqualTo("@x@") assertThat(RuntimeEnvironment.decorateWarningOutput("x")).isEqualTo("%x%") assertThat(RuntimeEnvironment.decorateErrorOutput("x")).isEqualTo("*x*") assertThat(RuntimeEnvironment.decorateCriticalOutput("x")).isEqualTo("\$x$") } @Test fun `the file based interpreter works and prints the last value only`() { setCommonFeatures() LispInterpreterBuilder.useFile(FILE) LispInterpreterBuilder.build().interpret() assertThat(outputStream.toString()).isEqualTo("PICKLE\n\n") assertThat(errorOutputStream.toString()).isEmpty() } @Test fun `the interactive interpreter works`() { setCommonFeatures() LispInterpreterBuilder.setInput(createInputStreamFromString("'pickle"), "input") LispInterpreterBuilder.build().interpret() assertThat(outputStream.toString()).isEqualTo("$PROMPT\nPICKLE\n$PROMPT\n") assertThat(errorOutputStream.toString()).isEmpty() } @Test fun `the interpreter handles bad input`() { setCommonFeatures() LispInterpreterBuilder.setNotInteractive() LispInterpreterBuilder.setInput(createInputStreamFromString("['pickle"), "input") LispInterpreterBuilder.build().interpret() assertThat(outputStream.toString()).isEqualTo("PICKLE\n\n") assertThat(errorOutputStream.toString()).isEqualTo("[error] illegal character >>[<< - line 1, column 1\n") } @Test fun `the interpreter handles an evaluation error`() { setCommonFeatures() LispInterpreterBuilder.setNotInteractive() LispInterpreterBuilder.setInput(createInputStreamFromString("pickle"), "input") LispInterpreterBuilder.build().interpret() assertThat(outputStream.toString()).isEqualTo("\n") assertThat(errorOutputStream.toString()).isEqualTo("[error] symbol PICKLE has no value\n") } }