diff --git a/src/environment/Environment.java b/src/environment/Environment.java new file mode 100644 index 0000000..993d899 --- /dev/null +++ b/src/environment/Environment.java @@ -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(); + } + +} diff --git a/src/error/ErrorManager.java b/src/error/ErrorManager.java index 72f06b0..241f34e 100644 --- a/src/error/ErrorManager.java +++ b/src/error/ErrorManager.java @@ -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 outputFunction; + private Environment environment; - public ErrorManager(Runnable systemTerminatingFunction, Consumer 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) { diff --git a/src/function/builtin/PRINT.java b/src/function/builtin/PRINT.java index dc36ede..1a49269 100644 --- a/src/function/builtin/PRINT.java +++ b/src/function/builtin/PRINT.java @@ -1,35 +1,25 @@ package function.builtin; -import function.LispFunction; -import function.builtin.cons.LENGTH; +import environment.Environment; +import function.*; import sexpression.*; -/** - * PRINT 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; } } diff --git a/src/interpreter/InteractiveLispInterpreter.java b/src/interpreter/InteractiveLispInterpreter.java index ed23a33..7bc2698 100644 --- a/src/interpreter/InteractiveLispInterpreter.java +++ b/src/interpreter/InteractiveLispInterpreter.java @@ -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(); } } diff --git a/src/interpreter/LispInterpreter.java b/src/interpreter/LispInterpreter.java index da00f73..5833a2f 100644 --- a/src/interpreter/LispInterpreter.java +++ b/src/interpreter/LispInterpreter.java @@ -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; + } } } diff --git a/src/interpreter/LispInterpreterBuilder.java b/src/interpreter/LispInterpreterBuilder.java new file mode 100644 index 0000000..bab884c --- /dev/null +++ b/src/interpreter/LispInterpreterBuilder.java @@ -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(); + +} diff --git a/src/interpreter/LispInterpreterBuilderImpl.java b/src/interpreter/LispInterpreterBuilderImpl.java new file mode 100644 index 0000000..a32eff1 --- /dev/null +++ b/src/interpreter/LispInterpreterBuilderImpl.java @@ -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); + } + +} diff --git a/src/main/LispMain.java b/src/main/LispMain.java index 3f9cc0e..8e58751 100644 --- a/src/main/LispMain.java +++ b/src/main/LispMain.java @@ -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(); } } diff --git a/test/error/ErrorManagerTester.java b/test/error/ErrorManagerTester.java index 8d3c7fb..f181ca8 100644 --- a/test/error/ErrorManagerTester.java +++ b/test/error/ErrorManagerTester.java @@ -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 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(); } }