diff --git a/src/main/kotlin/application/LispMain.kt b/src/main/kotlin/application/LispMain.kt index 0d1949f..75347a1 100644 --- a/src/main/kotlin/application/LispMain.kt +++ b/src/main/kotlin/application/LispMain.kt @@ -1,135 +1,98 @@ package application import com.googlecode.lanterna.terminal.DefaultTerminalFactory -import com.googlecode.lanterna.terminal.IOSafeTerminal import com.googlecode.lanterna.terminal.IOSafeTerminalAdapter.createRuntimeExceptionConvertingAdapter -import com.googlecode.lanterna.terminal.Terminal import interpreter.LispInterpreter import interpreter.LispInterpreterBuilder -import stream.UncheckedIOException import terminal.LispTerminal import terminal.LispTerminal.END_OF_SEGMENT import terminal.TerminalConfiguration -import java.io.IOException import java.io.PipedInputStream import java.io.PipedOutputStream import java.io.PrintStream import java.text.MessageFormat.format -class LispMain { +class LispMain @JvmOverloads constructor(var configuration: TerminalConfiguration = TerminalConfiguration()) { - private var builder: LispInterpreterBuilder? = null - private var inputReader: PipedInputStream? = null - private var output: PrintStream? = null - private var lispTerminal: LispTerminal? = null - private var configuration: TerminalConfiguration? = null - - private val version = LispMain::class.java.`package`.implementationVersion - - constructor() { - this.builder = LispInterpreterBuilder + init { + if (configuration.terminal == null) { + configuration.setInputPair(PipedOutputStream(), PipedInputStream()) + configuration.setOutputPair(PipedOutputStream(), PipedInputStream()) + configuration.terminal = createIOSafeTerminal() + } } - constructor(configuration: TerminalConfiguration) { - this.builder = LispInterpreterBuilder - this.configuration = configuration - } + private var inputReader: PipedInputStream = configuration.inputReader + private var output: PrintStream = PrintStream(configuration.outputWriter) + private var lispTerminal: LispTerminal = LispTerminal(configuration) + private val version = javaClass.`package`.implementationVersion fun runInteractive() { - initializeTerminal() printGreeting() - lispTerminal!!.start() + lispTerminal.start() buildInteractiveInterpreter().interpret() shutdownTerminal() } - private fun initializeTerminal() { - createTerminalConfiguration() - inputReader = configuration!!.inputReader - output = PrintStream(configuration!!.outputWriter) - lispTerminal = LispTerminal(configuration!!) - } - - private fun createTerminalConfiguration() { - if (configuration == null) { - configuration = TerminalConfiguration() - configuration!!.setInputPair(PipedOutputStream(), PipedInputStream()) - configuration!!.setOutputPair(PipedOutputStream(), PipedInputStream()) - configuration!!.terminal = createIOSafeTerminal() - } - } - - private fun createIOSafeTerminal(): IOSafeTerminal { - return createRuntimeExceptionConvertingAdapter(createDefaultTerminal()) - } - - private fun createDefaultTerminal(): Terminal { - try { - return DefaultTerminalFactory().createTerminal() - } catch (e: IOException) { - throw UncheckedIOException(e) - } - } - - private fun printGreeting() { - output!!.println(format(GREETING, (version ?: "null"))) - output!!.println() - } - - private fun buildInteractiveInterpreter(): LispInterpreter { - builder!!.setInput(inputReader!!, "terminal") - builder!!.setOutput(output!!) - builder!!.setErrorOutput(output!!) - builder!!.setTerminationFunction { this.shutdownTerminal() } - builder!!.setErrorTerminationFunction { this.shutdownTerminal() } - builder!!.setLanguageFileNames(*LANGUAGE_FILE_NAMES) - builder!!.setPromptDecorator { s -> s + END_OF_SEGMENT } - builder!!.setValueOutputDecorator(makeColorDecorator(ANSI_GREEN)) - builder!!.setWarningOutputDecorator(makeColorDecorator(ANSI_YELLOW)) - builder!!.setErrorOutputDecorator(makeColorDecorator(ANSI_RED)) - builder!!.setCriticalOutputDecorator(makeColorDecorator(ANSI_PURPLE)) - - return builder!!.build() - } - - private fun shutdownTerminal() { - output!!.print(END_OF_SEGMENT) - lispTerminal!!.stop() - output!!.close() - } - - private fun makeColorDecorator(color: String): (String) -> String { - return { color + it + ANSI_RESET } - } - fun runWithFile(fileName: String) { buildFileInterpreter(fileName).interpret() } - private fun buildFileInterpreter(fileName: String): LispInterpreter { - builder!!.useFile(fileName) - builder!!.setOutput(System.out) - builder!!.setErrorOutput(System.err) - builder!!.setTerminationFunction { System.exit(0) } - builder!!.setErrorTerminationFunction { System.exit(1) } - builder!!.setLanguageFileNames(*LANGUAGE_FILE_NAMES) - builder!!.setValueOutputDecorator(makeColorDecorator(ANSI_GREEN)) - builder!!.setWarningOutputDecorator(makeColorDecorator(ANSI_YELLOW)) - builder!!.setErrorOutputDecorator(makeColorDecorator(ANSI_RED)) - builder!!.setCriticalOutputDecorator(makeColorDecorator(ANSI_PURPLE)) + private fun createIOSafeTerminal() = createRuntimeExceptionConvertingAdapter(createDefaultTerminal()) + private fun createDefaultTerminal() = DefaultTerminalFactory().createTerminal() + private fun makeColorDecorator(color: String): (String) -> String = { color + it + ANSI_RESET } - return builder!!.build() + private fun printGreeting() { + output.println(format(GREETING, (version ?: "null"))) + output.println() + } + + private fun buildInteractiveInterpreter(): LispInterpreter { + LispInterpreterBuilder.setInput(inputReader, "terminal") + LispInterpreterBuilder.setOutput(output) + LispInterpreterBuilder.setErrorOutput(output) + LispInterpreterBuilder.setTerminationFunction { this.shutdownTerminal() } + LispInterpreterBuilder.setErrorTerminationFunction { this.shutdownTerminal() } + LispInterpreterBuilder.setLanguageFileNames(*LANGUAGE_FILE_NAMES) + LispInterpreterBuilder.setPromptDecorator { it + END_OF_SEGMENT } + LispInterpreterBuilder.setValueOutputDecorator(makeColorDecorator(ANSI_GREEN)) + LispInterpreterBuilder.setWarningOutputDecorator(makeColorDecorator(ANSI_YELLOW)) + LispInterpreterBuilder.setErrorOutputDecorator(makeColorDecorator(ANSI_RED)) + LispInterpreterBuilder.setCriticalOutputDecorator(makeColorDecorator(ANSI_PURPLE)) + + return LispInterpreterBuilder.build() + } + + private fun buildFileInterpreter(fileName: String): LispInterpreter { + LispInterpreterBuilder.useFile(fileName) + LispInterpreterBuilder.setOutput(System.out) + LispInterpreterBuilder.setErrorOutput(System.err) + LispInterpreterBuilder.setTerminationFunction { System.exit(0) } + LispInterpreterBuilder.setErrorTerminationFunction { System.exit(1) } + LispInterpreterBuilder.setLanguageFileNames(*LANGUAGE_FILE_NAMES) + LispInterpreterBuilder.setValueOutputDecorator(makeColorDecorator(ANSI_GREEN)) + LispInterpreterBuilder.setWarningOutputDecorator(makeColorDecorator(ANSI_YELLOW)) + LispInterpreterBuilder.setErrorOutputDecorator(makeColorDecorator(ANSI_RED)) + LispInterpreterBuilder.setCriticalOutputDecorator(makeColorDecorator(ANSI_PURPLE)) + + return LispInterpreterBuilder.build() + } + + private fun shutdownTerminal() { + output.print(END_OF_SEGMENT) + lispTerminal.stop() + output.close() } companion object { - val GREETING = "Transcendental Lisp - Version {0}" + const val GREETING = "Transcendental Lisp - Version {0}" - val ANSI_RESET = "\u001B[0m" - val ANSI_RED = "\u001B[31m" - val ANSI_GREEN = "\u001B[32m" - val ANSI_YELLOW = "\u001B[33m" - val ANSI_PURPLE = "\u001B[35m" + const val ANSI_RESET = "\u001B[0m" + const val ANSI_RED = "\u001B[31m" + const val ANSI_GREEN = "\u001B[32m" + const val ANSI_YELLOW = "\u001B[33m" + const val ANSI_PURPLE = "\u001B[35m" val LANGUAGE_FILE_NAMES = arrayOf("functions.lisp", "dlambda.lisp") @@ -137,7 +100,7 @@ class LispMain { fun main(arguments: Array) { val lispMain = LispMain() - if (arguments.size == 0) + if (arguments.isEmpty()) lispMain.runInteractive() else lispMain.runWithFile(arguments[0]) diff --git a/src/test/kotlin/application/MainTest.java b/src/test/kotlin/application/MainTest.java deleted file mode 100644 index 668642c..0000000 --- a/src/test/kotlin/application/MainTest.java +++ /dev/null @@ -1,153 +0,0 @@ -package application; - -import com.googlecode.lanterna.terminal.virtual.DefaultVirtualTerminal; -import environment.RuntimeEnvironment; -import interpreter.LispInterpreterBuilder; -import org.junit.Rule; -import org.junit.Test; -import org.junit.contrib.java.lang.system.ExpectedSystemExit; -import org.junit.contrib.java.lang.system.SystemErrRule; -import org.junit.contrib.java.lang.system.SystemOutRule; -import terminal.TerminalConfiguration; -import terminal.VirtualTerminalInteractor; -import testutil.SymbolAndFunctionCleaner; - -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.util.concurrent.CountDownLatch; - -import static com.googlecode.lanterna.input.KeyType.Enter; -import static java.text.MessageFormat.format; -import static org.junit.Assert.assertEquals; - -public class MainTest extends SymbolAndFunctionCleaner { - - private static final String FILE = MainTest.class.getResource("file.lisp").getFile(); - - private RuntimeEnvironment environment; - private CountDownLatch latch; - - public MainTest() { - this.environment = RuntimeEnvironment.INSTANCE; - } - - private void runInterpreterWithFile(String fileName) { - TerminalConfiguration configuration = new TerminalConfiguration(); - configuration.setInputPair(new PipedOutputStream(), new PipedInputStream()); - configuration.setOutputPair(new PipedOutputStream(), new PipedInputStream()); - configuration.setTerminal(new DefaultVirtualTerminal()); - LispMain main = new LispMain(configuration); - - main.runWithFile(fileName); - } - - private VirtualTerminalInteractor runInterpreterAndGetInteractor() { - VirtualTerminalInteractor terminal = new VirtualTerminalInteractor(); - latch = new CountDownLatch(1); - - new Thread(() -> { - try { - LispMain main = new LispMain(terminal.getConfiguration()); - main.runInteractive(); - } finally { - latch.countDown(); - } - }).start(); - - return terminal; - } - - private void waitForInterpreterToShutdown() { - try { - latch.await(); - } catch (InterruptedException ignored) { - } - } - - private String getSystemOutLog() { - return systemOutRule.getLogWithNormalizedLineSeparator(); - } - - private String getSystemErrLog() { - return systemErrRule.getLogWithNormalizedLineSeparator(); - } - - private String getExpectedGreeting() { - return format(LispMain.Companion.getGREETING(), getClass().getPackage().getImplementationVersion()); - } - - @Rule - public ExpectedSystemExit exit = ExpectedSystemExit.none(); - - @Rule - public SystemErrRule systemErrRule = new SystemErrRule().enableLog().mute(); - - @Rule - public SystemOutRule systemOutRule = new SystemOutRule().enableLog().mute(); - - @Override - public void additionalSetUp() { - environment.reset(); - LispInterpreterBuilder.INSTANCE.reset(); - } - - @Override - public void additionalTearDown() { - environment.reset(); - LispInterpreterBuilder.INSTANCE.reset(); - } - - @Test - public void runWithBadFile() { - String expectedMessage = "[critical] bad.lisp (No such file or directory)"; - - exit.expectSystemExitWithStatus(1); - exit.checkAssertionAfterwards(() -> { - assertEquals(format("{0}{1}{2}\n", - LispMain.Companion.getANSI_PURPLE(), - expectedMessage, - LispMain.Companion.getANSI_RESET()), getSystemErrLog()); - assertEquals("", getSystemOutLog()); - }); - - runInterpreterWithFile("bad.lisp"); - } - - @Test - public void runWithFile_PrintsDecoratedLastValueOnly() { - runInterpreterWithFile(FILE); - - assertEquals("", getSystemErrLog()); - assertEquals(format("{0}{1}{2}\n\n", - LispMain.Companion.getANSI_GREEN(), - "RADISH", - LispMain.Companion.getANSI_RESET()), getSystemOutLog()); - } - - @Test - public void runInteractive() { - VirtualTerminalInteractor terminal = runInterpreterAndGetInteractor(); - - terminal.waitForPrompt(); - terminal.enterCharacters("'hi"); - terminal.pressKey(Enter); - terminal.waitForPrompt(); - terminal.assertCursorPosition(2, 5); - terminal.assertScreenText(getExpectedGreeting(), " ", "~ 'hi ", " ", "HI ", "~ "); - terminal.enterControlCharacter('d'); - terminal.assertInputStreamClosed(); - - waitForInterpreterToShutdown(); - } - - @Test - public void runMain() { - LispMain.Companion.main(new String[] { FILE }); - - assertEquals("", getSystemErrLog()); - assertEquals(format("{0}{1}{2}\n\n", - LispMain.Companion.getANSI_GREEN(), - "RADISH", - LispMain.Companion.getANSI_RESET()), getSystemOutLog()); - } -} diff --git a/src/test/kotlin/application/MainTest.kt b/src/test/kotlin/application/MainTest.kt new file mode 100644 index 0000000..b5a063e --- /dev/null +++ b/src/test/kotlin/application/MainTest.kt @@ -0,0 +1,127 @@ +package application + +import application.LispMain.Companion.ANSI_GREEN +import application.LispMain.Companion.ANSI_PURPLE +import application.LispMain.Companion.ANSI_RESET +import com.googlecode.lanterna.input.KeyType.Enter +import com.googlecode.lanterna.terminal.virtual.DefaultVirtualTerminal +import environment.RuntimeEnvironment +import interpreter.LispInterpreterBuilder +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.contrib.java.lang.system.ExpectedSystemExit +import org.junit.contrib.java.lang.system.SystemErrRule +import org.junit.contrib.java.lang.system.SystemOutRule +import terminal.TerminalConfiguration +import terminal.VirtualTerminalInteractor +import testutil.SymbolAndFunctionCleaner +import java.io.PipedInputStream +import java.io.PipedOutputStream +import java.text.MessageFormat.format +import java.util.concurrent.CountDownLatch + +class MainTest : SymbolAndFunctionCleaner() { + + companion object { + private val FILE = MainTest::class.java.getResource("file.lisp").file + } + + private lateinit var latch: CountDownLatch + + private fun systemOutLog() = systemOutRule.logWithNormalizedLineSeparator + private fun systemErrLog() = systemErrRule.logWithNormalizedLineSeparator + private fun expectedGreeting() = format(LispMain.GREETING, javaClass.`package`.implementationVersion) + + @Rule + @JvmField + val exit: ExpectedSystemExit = ExpectedSystemExit.none() + + @Rule + @JvmField + val systemErrRule: SystemErrRule = SystemErrRule().enableLog().mute() + + @Rule + @JvmField + val systemOutRule: SystemOutRule = SystemOutRule().enableLog().mute() + + private fun runInterpreterWithFile(fileName: String) { + val configuration = TerminalConfiguration() + configuration.setInputPair(PipedOutputStream(), PipedInputStream()) + configuration.setOutputPair(PipedOutputStream(), PipedInputStream()) + configuration.terminal = DefaultVirtualTerminal() + val main = LispMain(configuration) + + main.runWithFile(fileName) + } + + private fun runInterpreterAndGetInteractor(): VirtualTerminalInteractor { + val terminal = VirtualTerminalInteractor() + latch = CountDownLatch(1) + + Thread { + try { + val main = LispMain(terminal.configuration) + main.runInteractive() + } finally { + latch.countDown() + } + }.start() + + return terminal + } + + private fun waitForInterpreterToShutdown() { + try { + latch.await() + } catch (ignored: InterruptedException) { + } + } + + override fun additionalSetUp() { + RuntimeEnvironment.reset() + LispInterpreterBuilder.reset() + } + + override fun additionalTearDown() { + RuntimeEnvironment.reset() + LispInterpreterBuilder.reset() + } + + @Test + fun runWithBadFile() { + val expectedMessage = "[critical] bad.lisp (No such file or directory)" + + exit.expectSystemExitWithStatus(1) + exit.checkAssertionAfterwards { + assertEquals(format("{0}{1}{2}\n", ANSI_PURPLE, expectedMessage, ANSI_RESET), systemErrLog()) + assertEquals("", systemOutLog()) + } + + runInterpreterWithFile("bad.lisp") + } + + @Test + fun runWithFile_PrintsDecoratedLastValueOnly() { + runInterpreterWithFile(FILE) + + assertEquals("", systemErrLog()) + assertEquals(format("{0}{1}{2}\n\n", ANSI_GREEN, "RADISH", ANSI_RESET), systemOutLog()) + } + + @Test + fun runInteractive() { + val terminal = runInterpreterAndGetInteractor() + + terminal.waitForPrompt() + terminal.enterCharacters("'hi") + terminal.pressKey(Enter) + terminal.waitForPrompt() + terminal.assertCursorPosition(2, 5) + terminal.assertScreenText(expectedGreeting(), " ", "~ 'hi ", " ", "HI ", "~ ") + terminal.enterControlCharacter('d') + terminal.assertInputStreamClosed() + + waitForInterpreterToShutdown() + } +}