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; package error;
import java.io.PrintStream;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.function.Consumer;
import environment.Environment;
/** /**
* Prints error messages and potentially terminates the application. * 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_RED = "\u001B[31m";
private static final String ANSI_PURPLE = "\u001B[35m"; private static final String ANSI_PURPLE = "\u001B[35m";
private Runnable systemTerminatingFunction; private Environment environment;
private Consumer<String> outputFunction;
public ErrorManager(Runnable systemTerminatingFunction, Consumer<String> outputFunction) { public ErrorManager() {
this.systemTerminatingFunction = systemTerminatingFunction; this.environment = Environment.getInstance();
this.outputFunction = outputFunction;
} }
public void generateError(LispException lispException) { public void generateError(LispException lispException) {
outputFunction.accept(formatMessage(lispException)); printError(lispException);
if (isCritical(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) { private String formatMessage(LispException lispException) {
String color = isCritical(lispException) ? ANSI_PURPLE : ANSI_RED; 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) { private boolean isCritical(LispException lispException) {

View File

@ -1,35 +1,25 @@
package function.builtin; package function.builtin;
import function.LispFunction; import environment.Environment;
import function.builtin.cons.LENGTH; import function.*;
import sexpression.*; import sexpression.*;
/**
* <code>PRINT</code> represents the PRINT function in Lisp.
*/
public class PRINT extends LispFunction { public class PRINT extends LispFunction {
// The number of arguments that PRINT takes. private ArgumentValidator argumentValidator;
private static final int NUM_ARGS = 1; private Environment environment;
public PRINT() {
this.argumentValidator = new ArgumentValidator("PRINT");
this.argumentValidator.setExactNumberOfArguments(1);
this.environment = Environment.getInstance();
}
public SExpression call(Cons argList) { public SExpression call(Cons argList) {
// retrieve the number of arguments passed to PRINT SExpression argument = argList.getCar();
int argListLength = LENGTH.getLength(argList); environment.getOutput().println(argument);
// make sure we have received the proper number of arguments return argument;
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;
} }
} }

View File

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

View File

@ -3,6 +3,7 @@ package interpreter;
import java.io.*; import java.io.*;
import java.text.MessageFormat; import java.text.MessageFormat;
import environment.Environment;
import error.*; import error.*;
import function.builtin.EVAL; import function.builtin.EVAL;
import parser.LispParser; import parser.LispParser;
@ -15,12 +16,25 @@ public class LispInterpreter {
private LispParser parser; private LispParser parser;
private ErrorManager errorManager; private ErrorManager errorManager;
protected PrintStream outputStream; protected Environment environment;
public LispInterpreter(InputStream inputStream, PrintStream outputStream, ErrorManager errorManager) { public LispInterpreter() {
this.errorManager = errorManager; this.environment = Environment.getInstance();
this.parser = new LispParser(inputStream, inputStream.toString()); this.errorManager = new ErrorManager();
this.outputStream = outputStream; 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() { public void interpret() {
@ -53,7 +67,7 @@ public class LispInterpreter {
String result = MessageFormat.format("{0}{1}{2}", ANSI_GREEN, EVAL.eval(sExpression), ANSI_RESET); String result = MessageFormat.format("{0}{1}{2}", ANSI_GREEN, EVAL.eval(sExpression), ANSI_RESET);
erasePrompt(); erasePrompt();
outputStream.println(result); environment.getOutput().println(result);
} }
protected void erasePrompt() {} protected void erasePrompt() {}
@ -71,7 +85,27 @@ public class LispInterpreter {
} }
protected void printFarewell() { 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; package main;
import java.io.*;
import error.*;
import interpreter.*; import interpreter.*;
public class LispMain { public class LispMain {
@ -10,41 +7,21 @@ public class LispMain {
private LispMain() {} private LispMain() {}
public static void main(String[] args) { public static void main(String[] args) {
LispInterpreter interpreter = null; LispInterpreter interpreter = buildInterpreter(args);
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);
interpreter.interpret(); 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; if (args.length > 0)
private String message; builder.useFile(args[0]);
public LispFileNotFoundException(FileNotFoundException e) { return builder.build();
this.message = e.getMessage();
}
@Override
public int getSeverity() {
return ErrorManager.CRITICAL_LEVEL;
}
@Override
public String getMessage() {
return message;
}
} }
} }

View File

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