Convert lisp interpreter builder to kotlin

This commit is contained in:
Mike Cifelli 2018-03-25 20:16:44 -04:00
parent 1b14306a99
commit 2177d13397
11 changed files with 245 additions and 364 deletions

View File

@ -5,7 +5,6 @@ import com.googlecode.lanterna.terminal.IOSafeTerminal;
import com.googlecode.lanterna.terminal.Terminal;
import interpreter.LispInterpreter;
import interpreter.LispInterpreterBuilder;
import interpreter.LispInterpreterBuilderImpl;
import stream.UncheckedIOException;
import terminal.LispTerminal;
import terminal.TerminalConfiguration;
@ -48,11 +47,11 @@ public class LispMain {
private TerminalConfiguration configuration;
public LispMain() {
this.builder = LispInterpreterBuilderImpl.getInstance();
this.builder = LispInterpreterBuilder.INSTANCE;
}
public LispMain(LispInterpreterBuilder builder, TerminalConfiguration configuration) {
this.builder = builder;
public LispMain(TerminalConfiguration configuration) {
this.builder = LispInterpreterBuilder.INSTANCE;
this.configuration = configuration;
}

View File

@ -1,38 +0,0 @@
package interpreter;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.function.Function;
public interface LispInterpreterBuilder {
void setInput(InputStream inputStream, String inputName);
void setOutput(PrintStream outputStream);
void setErrorOutput(PrintStream errorOutputStream);
void setTerminationFunction(Runnable terminationFunction);
void setErrorTerminationFunction(Runnable errorTerminationFunction);
void setNotInteractive();
void useFile(String fileName);
void setLanguageFileNames(String... languageFiles);
void setPromptDecorator(Function<String, String> decorator);
void setValueOutputDecorator(Function<String, String> decorator);
void setWarningOutputDecorator(Function<String, String> decorator);
void setErrorOutputDecorator(Function<String, String> decorator);
void setCriticalOutputDecorator(Function<String, String> decorator);
LispInterpreter build();
default void reset() {}
}

View File

@ -0,0 +1,166 @@
package interpreter
import environment.RuntimeEnvironment
import error.CriticalLispException
import error.ErrorManager
import interpreter.LispInterpreter.LanguageFile
import util.Path
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.InputStream
import java.io.PrintStream
import java.util.ArrayList
import java.util.function.Function
object LispInterpreterBuilder {
private var inputName = ""
private var isInteractive = true
private var isFileBased = false
private var languageFiles = ArrayList<LanguageFile>()
private var promptDecorator = Function<String, String> { s -> s }
private var valueOutputDecorator = Function<String, String> { s -> s }
private var warningOutputDecorator = Function<String, String> { s -> s }
private var errorOutputDecorator = Function<String, String> { s -> s }
private var criticalOutputDecorator = Function<String, String> { s -> s }
private var inputStream: InputStream? = null
private var outputStream: PrintStream? = null
private var errorOutputStream: PrintStream? = null
private var terminationFunction: Runnable? = null
private var errorTerminationFunction: Runnable? = null
init {
reset()
}
fun reset() {
this.inputName = ""
this.isInteractive = true
this.isFileBased = false
this.languageFiles = ArrayList()
this.promptDecorator = Function { s -> s }
this.valueOutputDecorator = Function { s -> s }
this.warningOutputDecorator = Function { s -> s }
this.errorOutputDecorator = Function { s -> s }
this.criticalOutputDecorator = Function { s -> s }
}
fun setInput(inputStream: InputStream, inputName: String) {
this.inputStream = inputStream
this.inputName = inputName
}
fun setOutput(outputStream: PrintStream) {
this.outputStream = outputStream
}
fun setErrorOutput(errorOutputStream: PrintStream) {
this.errorOutputStream = errorOutputStream
}
fun setTerminationFunction(terminationFunction: Runnable) {
this.terminationFunction = terminationFunction
}
fun setErrorTerminationFunction(errorTerminationFunction: Runnable) {
this.errorTerminationFunction = errorTerminationFunction
}
fun setNotInteractive() {
this.isInteractive = false
}
fun useFile(fileName: String) {
this.isFileBased = true
this.inputName = fileName
this.setNotInteractive()
}
fun setLanguageFileNames(vararg languageFiles: String) {
val classLoader = javaClass.classLoader
this.languageFiles = ArrayList()
for (fileName in languageFiles)
this.languageFiles!!.add(LanguageFile(classLoader.getResourceAsStream(fileName), fileName))
}
fun setPromptDecorator(decorator: Function<String, String>) {
this.promptDecorator = decorator
}
fun setValueOutputDecorator(decorator: Function<String, String>) {
this.valueOutputDecorator = decorator
}
fun setWarningOutputDecorator(decorator: Function<String, String>) {
this.warningOutputDecorator = decorator
}
fun setErrorOutputDecorator(decorator: Function<String, String>) {
this.errorOutputDecorator = decorator
}
fun setCriticalOutputDecorator(decorator: Function<String, String>) {
this.criticalOutputDecorator = decorator
}
fun build(): LispInterpreter {
configureRuntimeEnvironment()
val lispInterpreter = createInterpreter()
lispInterpreter.interpretLanguageFiles(languageFiles)
return lispInterpreter
}
private fun configureRuntimeEnvironment() {
val errorManager = ErrorManager()
RuntimeEnvironment.output = outputStream
RuntimeEnvironment.errorOutput = errorOutputStream
RuntimeEnvironment.errorManager = errorManager
RuntimeEnvironment.terminationFunction = terminationFunction
RuntimeEnvironment.errorTerminationFunction = errorTerminationFunction
RuntimeEnvironment.promptDecorator = promptDecorator
RuntimeEnvironment.valueOutputDecorator = valueOutputDecorator
RuntimeEnvironment.warningOutputDecorator = warningOutputDecorator
RuntimeEnvironment.errorOutputDecorator = errorOutputDecorator
RuntimeEnvironment.criticalOutputDecorator = criticalOutputDecorator
configurePath()
configureInput(errorManager)
}
private fun configurePath() {
if (isFileBased)
RuntimeEnvironment.path = Path.getPathPrefix(inputName!!)
else
RuntimeEnvironment.path = ""
}
private fun configureInput(errorManager: ErrorManager) {
RuntimeEnvironment.inputName = inputName
try {
RuntimeEnvironment.input = getInputStream()
} catch (e: FileNotFoundException) {
errorManager.handle(LispFileNotFoundException(e))
}
}
private fun getInputStream() = if (isFileBased) FileInputStream(inputName) else inputStream
private fun createInterpreter(): LispInterpreter {
if (isFileBased)
return FileLispInterpreter()
else if (isInteractive)
return InteractiveLispInterpreter()
return LispInterpreter()
}
class LispFileNotFoundException(val e: FileNotFoundException) : CriticalLispException() {
override val message: String
get() = e.message ?: ""
}
}

View File

@ -1,216 +0,0 @@
package interpreter;
import environment.RuntimeEnvironment;
import error.CriticalLispException;
import error.ErrorManager;
import interpreter.LispInterpreter.LanguageFile;
import util.Path;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class LispInterpreterBuilderImpl implements LispInterpreterBuilder {
private static LispInterpreterBuilder uniqueInstance = new LispInterpreterBuilderImpl();
public static LispInterpreterBuilder getInstance() {
return uniqueInstance;
}
private String inputName;
private InputStream inputStream;
private PrintStream outputStream;
private PrintStream errorOutputStream;
private Runnable terminationFunction;
private Runnable errorTerminationFunction;
private List<LanguageFile> languageFiles;
private Function<String, String> promptDecorator;
private Function<String, String> valueOutputDecorator;
private Function<String, String> warningOutputDecorator;
private Function<String, String> errorOutputDecorator;
private Function<String, String> criticalOutputDecorator;
private RuntimeEnvironment environment;
private boolean isInteractive;
private boolean isFileBased;
protected boolean isBuilt;
protected LispInterpreterBuilderImpl() {
this.environment = RuntimeEnvironment.INSTANCE;
this.inputName = "";
this.isInteractive = true;
this.isFileBased = false;
this.isBuilt = false;
this.languageFiles = new ArrayList<>();
this.promptDecorator = s -> s;
this.valueOutputDecorator = s -> s;
this.warningOutputDecorator = s -> s;
this.errorOutputDecorator = s -> s;
this.criticalOutputDecorator = s -> s;
}
@Override
public void setInput(InputStream inputStream, String inputName) {
this.inputStream = inputStream;
this.inputName = inputName;
}
@Override
public void setOutput(PrintStream outputStream) {
this.outputStream = outputStream;
}
@Override
public void setErrorOutput(PrintStream errorOutputStream) {
this.errorOutputStream = errorOutputStream;
}
@Override
public void setTerminationFunction(Runnable terminationFunction) {
this.terminationFunction = terminationFunction;
}
@Override
public void setErrorTerminationFunction(Runnable errorTerminationFunction) {
this.errorTerminationFunction = errorTerminationFunction;
}
@Override
public void setNotInteractive() {
this.isInteractive = false;
}
@Override
public void useFile(String fileName) {
this.isFileBased = true;
this.inputName = fileName;
this.setNotInteractive();
}
@Override
public void setLanguageFileNames(String... languageFileNames) {
ClassLoader classLoader = getClass().getClassLoader();
languageFiles = new ArrayList<>();
for (String fileName : languageFileNames)
languageFiles.add(new LanguageFile(classLoader.getResourceAsStream(fileName), fileName));
}
@Override
public void setPromptDecorator(Function<String, String> decorator) {
this.promptDecorator = decorator;
}
@Override
public void setValueOutputDecorator(Function<String, String> decorator) {
this.valueOutputDecorator = decorator;
}
@Override
public void setWarningOutputDecorator(Function<String, String> decorator) {
this.warningOutputDecorator = decorator;
}
@Override
public void setErrorOutputDecorator(Function<String, String> decorator) {
this.errorOutputDecorator = decorator;
}
@Override
public void setCriticalOutputDecorator(Function<String, String> decorator) {
this.criticalOutputDecorator = decorator;
}
@Override
public LispInterpreter build() {
if (!isBuilt)
return buildInterpreter();
else
throw new InterpreterAlreadyBuiltException();
}
private LispInterpreter buildInterpreter() {
configureRuntimeEnvironment();
LispInterpreter lispInterpreter = createInterpreter();
lispInterpreter.interpretLanguageFiles(languageFiles);
isBuilt = true;
return lispInterpreter;
}
private void configureRuntimeEnvironment() {
ErrorManager errorManager = new ErrorManager();
environment.setOutput(outputStream);
environment.setErrorOutput(errorOutputStream);
environment.setErrorManager(errorManager);
environment.setTerminationFunction(terminationFunction);
environment.setErrorTerminationFunction(errorTerminationFunction);
environment.setPromptDecorator(promptDecorator);
environment.setValueOutputDecorator(valueOutputDecorator);
environment.setWarningOutputDecorator(warningOutputDecorator);
environment.setErrorOutputDecorator(errorOutputDecorator);
environment.setCriticalOutputDecorator(criticalOutputDecorator);
configurePath();
configureInput(errorManager);
}
private void configurePath() {
if (isFileBased)
environment.setPath(Path.INSTANCE.getPathPrefix(inputName));
else
environment.setPath("");
}
private void configureInput(ErrorManager errorManager) {
environment.setInputName(inputName);
try {
environment.setInput(getInputStream());
} catch (FileNotFoundException e) {
errorManager.handle(new LispFileNotFoundException(e));
}
}
private InputStream getInputStream() throws FileNotFoundException {
return isFileBased ? new FileInputStream(inputName) : inputStream;
}
private LispInterpreter createInterpreter() {
if (isFileBased)
return new FileLispInterpreter();
else if (isInteractive)
return new InteractiveLispInterpreter();
return new LispInterpreter();
}
public static class InterpreterAlreadyBuiltException extends CriticalLispException {
private static final long serialVersionUID = 1L;
@Override
public String getMessage() {
return "Refusing to build more than one interpreter.";
}
}
public static class LispFileNotFoundException extends CriticalLispException {
private static final long serialVersionUID = 1L;
private String message;
public LispFileNotFoundException(FileNotFoundException e) {
this.message = e.getMessage();
}
@Override
public String getMessage() {
return message;
}
}
}

View File

@ -3,7 +3,6 @@ package acceptance.fixture;
import environment.RuntimeEnvironment;
import interpreter.LispInterpreter;
import interpreter.LispInterpreterBuilder;
import interpreter.LispInterpreterBuilderImpl;
import table.ExecutionContext;
import table.FunctionTable;
import util.Path;
@ -29,21 +28,21 @@ public class LispInterpreterFixture {
}
public static void cleanUp() {
LispInterpreterBuilder.INSTANCE.reset();
FunctionTable.INSTANCE.resetFunctionTable();
executionContext.clearContext();
environment.reset();
}
public static void buildInterpreter() {
LispInterpreterBuilder builder = new LispInterpreterBuilderImpl() {};
builder.setOutput(new PrintStream(outputStream));
builder.setErrorOutput(new PrintStream(outputStream));
builder.setNotInteractive();
builder.setLanguageFileNames(LANGUAGE_FILE_NAMES);
builder.setTerminationFunction(LispInterpreterFixture::terminate);
builder.setErrorTerminationFunction(LispInterpreterFixture::terminateFromError);
LispInterpreterBuilder.INSTANCE.setOutput(new PrintStream(outputStream));
LispInterpreterBuilder.INSTANCE.setErrorOutput(new PrintStream(outputStream));
LispInterpreterBuilder.INSTANCE.setNotInteractive();
LispInterpreterBuilder.INSTANCE.setLanguageFileNames(LANGUAGE_FILE_NAMES);
LispInterpreterBuilder.INSTANCE.setTerminationFunction(LispInterpreterFixture::terminate);
LispInterpreterBuilder.INSTANCE.setErrorTerminationFunction(LispInterpreterFixture::terminateFromError);
interpreter = builder.build();
interpreter = LispInterpreterBuilder.INSTANCE.build();
}
public static void terminate() {}

View File

@ -2,7 +2,9 @@ package application;
import com.googlecode.lanterna.terminal.virtual.DefaultVirtualTerminal;
import environment.RuntimeEnvironment;
import interpreter.LispInterpreterBuilderImpl;
import interpreter.LispInterpreterBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ExpectedSystemExit;
@ -40,7 +42,7 @@ public class MainTest extends SymbolAndFunctionCleaner {
configuration.setInputPair(new PipedOutputStream(), new PipedInputStream());
configuration.setOutputPair(new PipedOutputStream(), new PipedInputStream());
configuration.setTerminal(new DefaultVirtualTerminal());
LispMain main = new LispMain(new LispInterpreterBuilderImpl() {}, configuration);
LispMain main = new LispMain(configuration);
main.runWithFile(fileName);
}
@ -51,7 +53,7 @@ public class MainTest extends SymbolAndFunctionCleaner {
new Thread(() -> {
try {
LispMain main = new LispMain(new LispInterpreterBuilderImpl() {}, terminal.getConfiguration());
LispMain main = new LispMain(terminal.getConfiguration());
main.runInteractive();
} finally {
latch.countDown();
@ -92,11 +94,13 @@ public class MainTest extends SymbolAndFunctionCleaner {
@Override
public void additionalSetUp() {
environment.reset();
LispInterpreterBuilder.INSTANCE.reset();
}
@Override
public void additionalTearDown() {
environment.reset();
LispInterpreterBuilder.INSTANCE.reset();
}
@Test

View File

@ -24,45 +24,45 @@ public class SETTest extends SymbolAndFunctionCleaner {
@Test
public void lookupDefinedSymbol() {
evaluateString("(set 'a 23)");
assertSExpressionsMatch(new LispNumber("23"), executionContext.lookupSymbolValue("A"));
assertSExpressionsMatch(new LispNumber("23"), getExecutionContext().lookupSymbolValue("A"));
}
@Test
public void lookupUndefinedSymbol() {
assertNull(executionContext.lookupSymbolValue("A"));
assertNull(getExecutionContext().lookupSymbolValue("A"));
}
@Test
public void setGlobalVariable() {
evaluateString("(set 'a 23)");
SymbolTable global = executionContext.getScope();
executionContext.setScope(new SymbolTable(global));
SymbolTable global = getExecutionContext().getScope();
getExecutionContext().setScope(new SymbolTable(global));
evaluateString("(set 'a 94)");
executionContext.setScope(global);
getExecutionContext().setScope(global);
assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
}
@Test(expected = UndefinedSymbolException.class)
public void setLocalVariableDefined_DoesNotSetGlobal() {
SymbolTable global = executionContext.getScope();
SymbolTable global = getExecutionContext().getScope();
SymbolTable local = new SymbolTable(global);
local.set("A", new LispNumber("99"));
executionContext.setScope(local);
getExecutionContext().setScope(local);
evaluateString("(set 'a 94)");
executionContext.setScope(global);
getExecutionContext().setScope(global);
evaluateString("a");
}
@Test
public void setLocalVariableUndefined_SetsGlobal() {
SymbolTable global = executionContext.getScope();
SymbolTable global = getExecutionContext().getScope();
SymbolTable local = new SymbolTable(global);
executionContext.setScope(local);
getExecutionContext().setScope(local);
evaluateString("(set 'a 94)");
executionContext.setScope(global);
getExecutionContext().setScope(global);
assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
}

View File

@ -24,31 +24,31 @@ public class SETQTest extends SymbolAndFunctionCleaner {
@Test
public void lookupDefinedSymbol() {
evaluateString("(setq a 23)");
assertSExpressionsMatch(new LispNumber("23"), executionContext.lookupSymbolValue("A"));
assertSExpressionsMatch(new LispNumber("23"), getExecutionContext().lookupSymbolValue("A"));
}
@Test
public void lookupUndefinedSymbol() {
assertNull(executionContext.lookupSymbolValue("A"));
assertNull(getExecutionContext().lookupSymbolValue("A"));
}
@Test
public void setqGlobalVariable() {
evaluateString("(setq a 23)");
SymbolTable global = executionContext.getScope();
executionContext.setScope(new SymbolTable(global));
SymbolTable global = getExecutionContext().getScope();
getExecutionContext().setScope(new SymbolTable(global));
evaluateString("(setq a 94)");
executionContext.setScope(global);
getExecutionContext().setScope(global);
assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
}
@Test
public void setqLocalVariable() {
SymbolTable global = executionContext.getScope();
SymbolTable global = getExecutionContext().getScope();
SymbolTable local = new SymbolTable(global);
local.set("A", new LispNumber("99"));
executionContext.setScope(local);
getExecutionContext().setScope(local);
evaluateString("(setq a 94)");
assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
@ -56,24 +56,24 @@ public class SETQTest extends SymbolAndFunctionCleaner {
@Test(expected = UndefinedSymbolException.class)
public void setqLocalVariableDefined_DoesNotSetGlobal() {
SymbolTable global = executionContext.getScope();
SymbolTable global = getExecutionContext().getScope();
SymbolTable local = new SymbolTable(global);
local.set("A", new LispNumber("99"));
executionContext.setScope(local);
getExecutionContext().setScope(local);
evaluateString("(setq a 94)");
executionContext.setScope(global);
getExecutionContext().setScope(global);
evaluateString("a");
}
@Test
public void setqLocalVariableUndefined_SetsGlobal() {
SymbolTable global = executionContext.getScope();
SymbolTable global = getExecutionContext().getScope();
SymbolTable local = new SymbolTable(global);
executionContext.setScope(local);
getExecutionContext().setScope(local);
evaluateString("(setq a 94)");
executionContext.setScope(global);
getExecutionContext().setScope(global);
assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
}

View File

@ -1,7 +1,6 @@
package interpreter;
import environment.RuntimeEnvironment;
import interpreter.LispInterpreterBuilderImpl.InterpreterAlreadyBuiltException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -11,12 +10,10 @@ import java.io.PrintStream;
import java.util.HashSet;
import java.util.Set;
import static error.Severity.CRITICAL;
import static interpreter.InteractiveLispInterpreter.PROMPT;
import static java.text.MessageFormat.format;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static testutil.TestUtilities.createInputStreamFromString;
@ -33,14 +30,7 @@ public class LispInterpreterTest {
public LispInterpreterTest() {
this.environment = RuntimeEnvironment.INSTANCE;
this.builder = new LispInterpreterBuilderImpl() {
@Override
public void reset() {
this.isBuilt = false;
}
};
this.builder = LispInterpreterBuilder.INSTANCE;
}
private void setCommonFeatures() {
@ -63,14 +53,14 @@ public class LispInterpreterTest {
indicatorSet = new HashSet<>();
outputStream = new ByteArrayOutputStream();
errorOutputStream = new ByteArrayOutputStream();
builder.reset();
environment.reset();
builder.reset();
}
@After
public void tearDown() {
builder.reset();
environment.reset();
builder.reset();
}
@Test
@ -102,30 +92,6 @@ public class LispInterpreterTest {
assertTrue(interpreter instanceof FileLispInterpreter);
}
@Test(expected = InterpreterAlreadyBuiltException.class)
public void cannotBuildMoreThanOneInterpreter() {
builder.build();
builder.build();
}
@Test
public void interpreterAlreadyBuiltException_HasCorrectAttributes() {
InterpreterAlreadyBuiltException e = new InterpreterAlreadyBuiltException();
assertEquals(CRITICAL, e.getSeverity());
assertNotNull(e.getMessage());
assertTrue(e.getMessage().length() > 0);
}
@Test(expected = InterpreterAlreadyBuiltException.class)
public void resetNormallyDoesNothing() {
builder = new LispInterpreterBuilderImpl();
builder.build();
builder.reset();
builder.build();
}
@Test
public void attemptToBuildInterpreterOnBadFile() {
setCommonFeatures();

View File

@ -1,33 +0,0 @@
package testutil;
import org.junit.After;
import org.junit.Before;
import table.ExecutionContext;
import table.FunctionTable;
public abstract class SymbolAndFunctionCleaner {
protected ExecutionContext executionContext;
public SymbolAndFunctionCleaner() {
this.executionContext = ExecutionContext.INSTANCE;
}
@Before
public final void setUp() {
executionContext.clearContext();
FunctionTable.INSTANCE.resetFunctionTable();
additionalSetUp();
}
@After
public final void tearDown() {
executionContext.clearContext();
FunctionTable.INSTANCE.resetFunctionTable();
additionalTearDown();
}
public void additionalSetUp() {}
public void additionalTearDown() {}
}

View File

@ -0,0 +1,34 @@
package testutil
import org.junit.After
import org.junit.Before
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import table.ExecutionContext
import table.FunctionTable
abstract class SymbolAndFunctionCleaner {
// TODO - clean up
protected var executionContext: ExecutionContext = ExecutionContext
@Before
@BeforeEach
fun setUp() {
executionContext.clearContext()
FunctionTable.resetFunctionTable()
additionalSetUp()
}
@After
@AfterEach
fun tearDown() {
executionContext.clearContext()
FunctionTable.resetFunctionTable()
additionalTearDown()
}
open fun additionalSetUp() {}
open fun additionalTearDown() {}
}