Introduced global abstractions of input, output, and termination
This commit is contained in:
parent
217c215efe
commit
a4cb521c7d
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue