Introduced global abstractions of input, output, and termination

This commit is contained in:
Mike Cifelli 2017-01-17 13:54:21 -05:00
parent 217c215efe
commit a4cb521c7d
9 changed files with 260 additions and 108 deletions

View File

@ -0,0 +1,56 @@
package environment;
import java.io.*;
public class Environment {
private static final Environment instance = new Environment();
public static Environment getInstance() {
return instance;
}
private InputStream input;
private PrintStream output;
private PrintStream errorOutput;
private Runnable terminate;
private Environment() {}
public void setInput(InputStream input) {
this.input = input;
}
public void setOutput(PrintStream output) {
this.output = output;
}
public void setErrorOutput(PrintStream errorOutput) {
this.errorOutput = errorOutput;
}
public void setTerminate(Runnable terminate) {
this.terminate = terminate;
}
public InputStream getInput() {
return input;
}
public String getInputName() {
return input.toString();
}
public PrintStream getOutput() {
return output;
}
public PrintStream getErrorOutput() {
return errorOutput;
}
public void terminate() {
terminate.run();
}
}

View File

@ -1,7 +1,9 @@
package error;
import java.io.PrintStream;
import java.text.MessageFormat;
import java.util.function.Consumer;
import environment.Environment;
/**
* Prints error messages and potentially terminates the application.
@ -14,25 +16,28 @@ public class ErrorManager {
private static final String ANSI_RED = "\u001B[31m";
private static final String ANSI_PURPLE = "\u001B[35m";
private Runnable systemTerminatingFunction;
private Consumer<String> outputFunction;
private Environment environment;
public ErrorManager(Runnable systemTerminatingFunction, Consumer<String> outputFunction) {
this.systemTerminatingFunction = systemTerminatingFunction;
this.outputFunction = outputFunction;
public ErrorManager() {
this.environment = Environment.getInstance();
}
public void generateError(LispException lispException) {
outputFunction.accept(formatMessage(lispException));
printError(lispException);
if (isCritical(lispException))
systemTerminatingFunction.run();
environment.terminate();
}
private void printError(LispException lispException) {
String formattedMessage = formatMessage(lispException);
environment.getErrorOutput().println(formattedMessage);
}
private String formatMessage(LispException lispException) {
String color = isCritical(lispException) ? ANSI_PURPLE : ANSI_RED;
return MessageFormat.format("{0}error: {1}{2}\n", color, lispException.getMessage(), ANSI_RESET);
return MessageFormat.format("{0}error: {1}{2}", color, lispException.getMessage(), ANSI_RESET);
}
private boolean isCritical(LispException lispException) {

View File

@ -1,35 +1,25 @@
package function.builtin;
import function.LispFunction;
import function.builtin.cons.LENGTH;
import environment.Environment;
import function.*;
import sexpression.*;
/**
* <code>PRINT</code> represents the PRINT function in Lisp.
*/
public class PRINT extends LispFunction {
// The number of arguments that PRINT takes.
private static final int NUM_ARGS = 1;
private ArgumentValidator argumentValidator;
private Environment environment;
public PRINT() {
this.argumentValidator = new ArgumentValidator("PRINT");
this.argumentValidator.setExactNumberOfArguments(1);
this.environment = Environment.getInstance();
}
public SExpression call(Cons argList) {
// retrieve the number of arguments passed to PRINT
int argListLength = LENGTH.getLength(argList);
SExpression argument = argList.getCar();
environment.getOutput().println(argument);
// make sure we have received the proper number of arguments
if (argListLength != NUM_ARGS) {
Cons originalSExpr = new Cons(new Symbol("PRINT"), argList);
String errMsg = "too " + ((argListLength > NUM_ARGS) ? "many" : "few") + " arguments given to PRINT: "
+ originalSExpr;
throw new RuntimeException(errMsg);
}
SExpression arg = argList.getCar();
System.out.println(arg);
return arg;
return argument;
}
}

View File

@ -1,36 +1,32 @@
package interpreter;
import java.io.*;
import error.ErrorManager;
public class InteractiveLispInterpreter extends LispInterpreter {
private static final String GREETING = "SUNY Potsdam Lisp Interpreter - Version 4.4.3";
private static final String PROMPT = "~ ";
public InteractiveLispInterpreter(InputStream inputStream, PrintStream outputStream, ErrorManager errorManager) {
super(inputStream, outputStream, errorManager);
}
@Override
protected void printGreeting() {
outputStream.println(GREETING);
outputStream.println();
environment.getOutput().println(GREETING);
environment.getOutput().println();
}
@Override
protected void displayPrompt() {
outputStream.print(PROMPT);
environment.getOutput().print(PROMPT);
}
@Override
protected void erasePrompt() {
for (int i = 0; i < PROMPT.length(); i++) {
outputStream.print("\b");
environment.getOutput().print("\b");
}
}
@Override
protected void printFarewell() {
outputStream.println();
outputStream.println();
environment.getOutput().println();
environment.getOutput().println();
}
}

View File

@ -3,6 +3,7 @@ package interpreter;
import java.io.*;
import java.text.MessageFormat;
import environment.Environment;
import error.*;
import function.builtin.EVAL;
import parser.LispParser;
@ -15,12 +16,25 @@ public class LispInterpreter {
private LispParser parser;
private ErrorManager errorManager;
protected PrintStream outputStream;
protected Environment environment;
public LispInterpreter(InputStream inputStream, PrintStream outputStream, ErrorManager errorManager) {
this.errorManager = errorManager;
this.parser = new LispParser(inputStream, inputStream.toString());
this.outputStream = outputStream;
public LispInterpreter() {
this.environment = Environment.getInstance();
this.errorManager = new ErrorManager();
this.parser = new LispParser(this.environment.getInput(), this.environment.getInputName());
}
public LispInterpreter(String fileName) {
this.environment = Environment.getInstance();
this.errorManager = new ErrorManager();
try {
this.environment.setInput(new FileInputStream(fileName));
} catch (FileNotFoundException e) {
this.errorManager.generateError(new LispFileNotFoundException(e));
}
this.parser = new LispParser(this.environment.getInput(), this.environment.getInputName());
}
public void interpret() {
@ -53,7 +67,7 @@ public class LispInterpreter {
String result = MessageFormat.format("{0}{1}{2}", ANSI_GREEN, EVAL.eval(sExpression), ANSI_RESET);
erasePrompt();
outputStream.println(result);
environment.getOutput().println(result);
}
protected void erasePrompt() {}
@ -71,7 +85,27 @@ public class LispInterpreter {
}
protected void printFarewell() {
outputStream.println();
environment.getOutput().println();
}
public static class LispFileNotFoundException extends LispException {
private static final long serialVersionUID = 1L;
private String message;
public LispFileNotFoundException(FileNotFoundException e) {
this.message = e.getMessage();
}
@Override
public int getSeverity() {
return ErrorManager.CRITICAL_LEVEL;
}
@Override
public String getMessage() {
return message;
}
}
}

View File

@ -0,0 +1,19 @@
package interpreter;
import java.io.*;
public interface LispInterpreterBuilder {
void setInput(InputStream inputStream);
void setOutput(PrintStream outputStream);
void setErrorOutput(PrintStream errorOutputStream);
void setTerminate(Runnable terminationFunction);
void useFile(String fileName);
LispInterpreter build();
}

View File

@ -0,0 +1,54 @@
package interpreter;
import java.io.*;
import environment.Environment;
public class LispInterpreterBuilderImpl implements LispInterpreterBuilder {
private Environment environment;
private String fileName;
private boolean isInteractive;
public LispInterpreterBuilderImpl() {
this.environment = Environment.getInstance();
this.fileName = "";
this.isInteractive = true;
}
@Override
public void setInput(InputStream inputStream) {
this.environment.setInput(inputStream);
}
@Override
public void setOutput(PrintStream outputStream) {
this.environment.setOutput(outputStream);
}
@Override
public void setErrorOutput(PrintStream errorOutputStream) {
this.environment.setErrorOutput(errorOutputStream);
}
@Override
public void setTerminate(Runnable terminationFunction) {
this.environment.setTerminate(terminationFunction);
}
@Override
public void useFile(String fileName) {
this.fileName = fileName;
this.isInteractive = false;
}
@Override
public LispInterpreter build() {
if (isInteractive)
return new InteractiveLispInterpreter();
else
return new LispInterpreter(fileName);
}
}

View File

@ -1,8 +1,5 @@
package main;
import java.io.*;
import error.*;
import interpreter.*;
public class LispMain {
@ -10,41 +7,21 @@ public class LispMain {
private LispMain() {}
public static void main(String[] args) {
LispInterpreter interpreter = null;
ErrorManager errorManager = new ErrorManager(() -> System.exit(1), System.err::print);
if (args.length > 0) {
String fileName = args[0];
try {
interpreter = new LispInterpreter(new FileInputStream(fileName), System.out, errorManager);
} catch (FileNotFoundException e) {
errorManager.generateError(new LispFileNotFoundException(e));
}
} else
interpreter = new InteractiveLispInterpreter(System.in, System.out, errorManager);
LispInterpreter interpreter = buildInterpreter(args);
interpreter.interpret();
}
public static class LispFileNotFoundException extends LispException {
private static LispInterpreter buildInterpreter(String[] args) {
LispInterpreterBuilder builder = new LispInterpreterBuilderImpl();
builder.setInput(System.in);
builder.setOutput(System.out);
builder.setErrorOutput(System.err);
builder.setTerminate(() -> System.exit(1));
private static final long serialVersionUID = 1L;
private String message;
if (args.length > 0)
builder.useFile(args[0]);
public LispFileNotFoundException(FileNotFoundException e) {
this.message = e.getMessage();
}
@Override
public int getSeverity() {
return ErrorManager.CRITICAL_LEVEL;
}
@Override
public String getMessage() {
return message;
}
return builder.build();
}
}

View File

@ -1,13 +1,13 @@
package error;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.util.HashSet;
import java.util.Set;
import java.io.*;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.*;
import environment.Environment;
public class ErrorManagerTester {
@ -15,9 +15,13 @@ public class ErrorManagerTester {
private static final String MESSAGE = "message";
private Set<String> indicatorSet;
private ByteArrayOutputStream outputStream;
private ErrorManager createErrorManagerWithIndicators() {
return new ErrorManager(() -> indicatorSet.add(TERMINATED), (String) -> indicatorSet.add(MESSAGE));
Environment.getInstance().setTerminate(() -> indicatorSet.add(TERMINATED));
Environment.getInstance().setErrorOutput(new PrintStream(outputStream));
return new ErrorManager();
}
private LispException createLispException(int severity) {
@ -37,9 +41,26 @@ public class ErrorManagerTester {
};
}
private void assertTerminated() {
assertTrue(indicatorSet.contains(TERMINATED));
}
private void assertNotTerminated() {
assertFalse(indicatorSet.contains(TERMINATED));
}
private void assertErrorMessageNotWritten() {
assertTrue(outputStream.toByteArray().length == 0);
}
private void assertErrorMessageWritten() {
assertTrue(outputStream.toByteArray().length > 0);
}
@Before
public void setUp() {
this.indicatorSet = new HashSet<>();
this.outputStream = new ByteArrayOutputStream();
}
@Test
@ -47,7 +68,7 @@ public class ErrorManagerTester {
ErrorManager errorManager = createErrorManagerWithIndicators();
errorManager.generateError(createLispException(ErrorManager.CRITICAL_LEVEL));
assertTrue(indicatorSet.contains(TERMINATED));
assertTerminated();
}
@Test
@ -55,15 +76,7 @@ public class ErrorManagerTester {
ErrorManager errorManager = createErrorManagerWithIndicators();
errorManager.generateError(createLispException(0));
assertFalse(indicatorSet.contains(TERMINATED));
}
@Test
public void noMessageDisplayedBeforeError() {
createErrorManagerWithIndicators();
assertFalse(indicatorSet.contains(TERMINATED));
assertFalse(indicatorSet.contains(MESSAGE));
assertNotTerminated();
}
@Test
@ -71,8 +84,16 @@ public class ErrorManagerTester {
ErrorManager errorManager = createErrorManagerWithIndicators();
errorManager.generateError(createLispException(0));
assertFalse(indicatorSet.contains(TERMINATED));
assertTrue(indicatorSet.contains(MESSAGE));
assertNotTerminated();
assertErrorMessageWritten();
}
@Test
public void noMessageDisplayedBeforeError() {
createErrorManagerWithIndicators();
assertNotTerminated();
assertErrorMessageNotWritten();
}
@Test
@ -80,8 +101,8 @@ public class ErrorManagerTester {
ErrorManager errorManager = createErrorManagerWithIndicators();
errorManager.generateError(createLispException(ErrorManager.CRITICAL_LEVEL));
assertTrue(indicatorSet.contains(TERMINATED));
assertTrue(indicatorSet.contains(MESSAGE));
assertTerminated();
assertErrorMessageWritten();
}
}