Refactor stream code and add unit tests
This commit is contained in:
parent
b298e118e3
commit
228b4b1793
|
@ -8,26 +8,26 @@ import java.util.function.Function;
|
|||
import com.googlecode.lanterna.terminal.*;
|
||||
|
||||
import interpreter.*;
|
||||
import stream.SafeOutputStream;
|
||||
import terminal.LispTerminal;
|
||||
import terminal.SafeStream.SafeOutputStream;
|
||||
import terminal.SafeStream.UncheckedIOException;
|
||||
|
||||
public class LispMain {
|
||||
|
||||
private static final String GREETING = "Transcendental Lisp - Version 1.0.1";
|
||||
|
||||
private static final String ANSI_RESET = "\u001B[0m";
|
||||
private static final String ANSI_RED = "\u001B[31m";
|
||||
private static final String ANSI_GREEN = "\u001B[32m";
|
||||
private static final String ANSI_YELLOW = "\u001B[33m";
|
||||
private static final String ANSI_PURPLE = "\u001B[35m";
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
public static void main(String[] arguments) {
|
||||
LispMain lispMain = new LispMain();
|
||||
|
||||
if (args.length == 0)
|
||||
if (arguments.length == 0)
|
||||
lispMain.runInteractive();
|
||||
else
|
||||
lispMain.runWithFile(args[0]);
|
||||
lispMain.runWithFile(arguments[0]);
|
||||
}
|
||||
|
||||
private PipedInputStream inputReader;
|
||||
|
@ -98,6 +98,16 @@ public class LispMain {
|
|||
safeOutputWriter.close();
|
||||
}
|
||||
|
||||
private Function<String, String> makeColorDecorator(String color) {
|
||||
return new Function<String, String>() {
|
||||
|
||||
@Override
|
||||
public String apply(String s) {
|
||||
return color + s + ANSI_RESET;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void runWithFile(String fileName) {
|
||||
buildFileInterpreter(fileName).interpret();
|
||||
}
|
||||
|
@ -116,15 +126,4 @@ public class LispMain {
|
|||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static Function<String, String> makeColorDecorator(String color) {
|
||||
return new Function<String, String>() {
|
||||
|
||||
@Override
|
||||
public String apply(String s) {
|
||||
return color + s + ANSI_RESET;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,21 +2,23 @@ package scanner;
|
|||
|
||||
import static util.Characters.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.InputStream;
|
||||
|
||||
import stream.SafeInputStream;
|
||||
|
||||
/**
|
||||
* Removes Lisp comments from an input stream.
|
||||
*/
|
||||
public class LispCommentRemovingInputStream implements LispInputStream {
|
||||
|
||||
private InputStream underlyingInputStream;
|
||||
private SafeInputStream underlyingInputStream;
|
||||
private boolean isInQuotedString;
|
||||
private boolean rereadLastCharacter;
|
||||
private int previousCharacter;
|
||||
private int currentCharacter;
|
||||
|
||||
public LispCommentRemovingInputStream(InputStream underlyingInputStream) {
|
||||
this.underlyingInputStream = underlyingInputStream;
|
||||
this.underlyingInputStream = new SafeInputStream(underlyingInputStream);
|
||||
this.isInQuotedString = false;
|
||||
this.rereadLastCharacter = false;
|
||||
this.previousCharacter = 0;
|
||||
|
@ -25,14 +27,6 @@ public class LispCommentRemovingInputStream implements LispInputStream {
|
|||
|
||||
@Override
|
||||
public int read() {
|
||||
try {
|
||||
return readWithIOException();
|
||||
} catch (IOException ioException) {
|
||||
throw new UncheckedIOException(ioException);
|
||||
}
|
||||
}
|
||||
|
||||
private int readWithIOException() throws IOException {
|
||||
if (!rereadLastCharacter)
|
||||
return readFromUnderlyingInputStream();
|
||||
|
||||
|
@ -41,17 +35,16 @@ public class LispCommentRemovingInputStream implements LispInputStream {
|
|||
return currentCharacter;
|
||||
}
|
||||
|
||||
private int readFromUnderlyingInputStream() throws IOException {
|
||||
private int readFromUnderlyingInputStream() {
|
||||
readNextCharacter();
|
||||
|
||||
if (haveEnteredComment())
|
||||
consumeAllBytesInComment();
|
||||
|
||||
return currentCharacter;
|
||||
|
||||
}
|
||||
|
||||
private void readNextCharacter() throws IOException {
|
||||
private void readNextCharacter() {
|
||||
previousCharacter = currentCharacter;
|
||||
currentCharacter = underlyingInputStream.read();
|
||||
|
||||
|
@ -67,7 +60,7 @@ public class LispCommentRemovingInputStream implements LispInputStream {
|
|||
return (currentCharacter == SEMICOLON) && (!isInQuotedString);
|
||||
}
|
||||
|
||||
private void consumeAllBytesInComment() throws IOException {
|
||||
private void consumeAllBytesInComment() {
|
||||
while (stillInComment())
|
||||
currentCharacter = underlyingInputStream.read();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package scanner;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import error.CriticalLispException;
|
||||
|
||||
public interface LispInputStream {
|
||||
|
@ -15,19 +13,4 @@ public interface LispInputStream {
|
|||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
|
||||
public static class UncheckedIOException extends CriticalLispException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private IOException ioException;
|
||||
|
||||
public UncheckedIOException(IOException ioException) {
|
||||
this.ioException = ioException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return ioException.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package stream;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class SafeInputStream {
|
||||
|
||||
private InputStream underlyingStream;
|
||||
|
||||
public SafeInputStream(InputStream underlyingStream) {
|
||||
this.underlyingStream = underlyingStream;
|
||||
}
|
||||
|
||||
public int read() {
|
||||
try {
|
||||
return underlyingStream.read();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
underlyingStream.close();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package stream;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class SafeOutputStream {
|
||||
|
||||
private OutputStream underlyingStream;
|
||||
|
||||
public SafeOutputStream(OutputStream underlyingStream) {
|
||||
this.underlyingStream = underlyingStream;
|
||||
}
|
||||
|
||||
public void write(byte[] b) {
|
||||
try {
|
||||
underlyingStream.write(b);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
|
||||
try {
|
||||
underlyingStream.flush();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
underlyingStream.close();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package stream;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import error.CriticalLispException;
|
||||
|
||||
public class UncheckedIOException extends CriticalLispException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private IOException exception;
|
||||
|
||||
public UncheckedIOException(IOException exception) {
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return exception.getMessage();
|
||||
}
|
||||
|
||||
}
|
|
@ -4,7 +4,7 @@ import static java.lang.Character.isDigit;
|
|||
import static terminal.ControlSequenceLookup.lookupControlSequence;
|
||||
import static util.Characters.*;
|
||||
|
||||
import terminal.SafeStream.SafeInputStream;
|
||||
import stream.SafeInputStream;
|
||||
|
||||
class ControlSequenceHandler {
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import com.googlecode.lanterna.*;
|
|||
import com.googlecode.lanterna.input.*;
|
||||
import com.googlecode.lanterna.terminal.IOSafeTerminal;
|
||||
|
||||
import terminal.SafeStream.*;
|
||||
import stream.*;
|
||||
|
||||
public class LispTerminal {
|
||||
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
package terminal;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public interface SafeStream {
|
||||
|
||||
public static class SafeInputStream {
|
||||
|
||||
private InputStream underlyingStream;
|
||||
|
||||
public SafeInputStream(InputStream underlyingStream) {
|
||||
this.underlyingStream = underlyingStream;
|
||||
}
|
||||
|
||||
public int read() {
|
||||
try {
|
||||
return underlyingStream.read();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
underlyingStream.close();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class SafeOutputStream {
|
||||
|
||||
private OutputStream underlyingStream;
|
||||
|
||||
public SafeOutputStream(OutputStream underlyingStream) {
|
||||
this.underlyingStream = underlyingStream;
|
||||
}
|
||||
|
||||
public void write(byte[] b) {
|
||||
try {
|
||||
underlyingStream.write(b);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
|
||||
try {
|
||||
underlyingStream.flush();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
underlyingStream.close();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class UncheckedIOException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private IOException exception;
|
||||
|
||||
public UncheckedIOException(IOException exception) {
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return exception.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -4,36 +4,63 @@ import static error.ErrorManager.Severity.CRITICAL;
|
|||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import environment.RuntimeEnvironment;
|
||||
import interpreter.LispInterpreterBuilderImpl.InterpreterAlreadyBuiltException;
|
||||
|
||||
public class LispInterpreterBuilderTest {
|
||||
|
||||
private LispInterpreterBuilder builder = new LispInterpreterBuilderImpl() {
|
||||
private static final String TERMINATED = "terminated";
|
||||
|
||||
private Set<String> indicatorSet;
|
||||
private ByteArrayOutputStream outputStream;
|
||||
private ByteArrayOutputStream errorOutputStream;
|
||||
private RuntimeEnvironment environment;
|
||||
private LispInterpreterBuilder builder;
|
||||
|
||||
public LispInterpreterBuilderTest() {
|
||||
this.environment = RuntimeEnvironment.getInstance();
|
||||
|
||||
this.builder = new LispInterpreterBuilderImpl() {
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.isBuilt = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void setCommonFeatures() {
|
||||
builder.setOutput(new PrintStream(new ByteArrayOutputStream()));
|
||||
builder.setErrorOutput(new PrintStream(new ByteArrayOutputStream()));
|
||||
builder.setOutput(new PrintStream(outputStream));
|
||||
builder.setErrorOutput(new PrintStream(errorOutputStream));
|
||||
builder.setTerminationFunction(() -> {});
|
||||
builder.setErrorTerminationFunction(() -> {});
|
||||
builder.setErrorTerminationFunction(() -> indicatorSet.add(TERMINATED));
|
||||
}
|
||||
|
||||
private void assertTerminated() {
|
||||
assertTrue(indicatorSet.contains(TERMINATED));
|
||||
}
|
||||
|
||||
private void assertErrorMessageWritten() {
|
||||
assertTrue(errorOutputStream.toByteArray().length > 0);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
indicatorSet = new HashSet<>();
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
errorOutputStream = new ByteArrayOutputStream();
|
||||
builder.reset();
|
||||
environment.reset();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
builder.reset();
|
||||
environment.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -41,7 +68,6 @@ public class LispInterpreterBuilderTest {
|
|||
setCommonFeatures();
|
||||
builder.setInput(System.in, "stdin");
|
||||
LispInterpreter interpreter = builder.build();
|
||||
|
||||
assertTrue(interpreter instanceof InteractiveLispInterpreter);
|
||||
}
|
||||
|
||||
|
@ -51,8 +77,8 @@ public class LispInterpreterBuilderTest {
|
|||
builder.setInput(System.in, "stdin");
|
||||
builder.setNotInteractive();
|
||||
LispInterpreter interpreter = builder.build();
|
||||
|
||||
assertFalse(interpreter instanceof InteractiveLispInterpreter);
|
||||
assertFalse(interpreter instanceof FileLispInterpreter);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -60,8 +86,7 @@ public class LispInterpreterBuilderTest {
|
|||
setCommonFeatures();
|
||||
builder.useFile("test/interpreter/test-files/file.lisp");
|
||||
LispInterpreter interpreter = builder.build();
|
||||
|
||||
assertFalse(interpreter instanceof InteractiveLispInterpreter);
|
||||
assertTrue(interpreter instanceof FileLispInterpreter);
|
||||
}
|
||||
|
||||
@Test(expected = InterpreterAlreadyBuiltException.class)
|
||||
|
@ -73,7 +98,6 @@ public class LispInterpreterBuilderTest {
|
|||
@Test
|
||||
public void interpreterAlreadyBuiltException_HasCorrectAttributes() {
|
||||
InterpreterAlreadyBuiltException e = new InterpreterAlreadyBuiltException();
|
||||
|
||||
assertEquals(CRITICAL, e.getSeverity());
|
||||
assertNotNull(e.getMessage());
|
||||
assertTrue(e.getMessage().length() > 0);
|
||||
|
@ -88,4 +112,38 @@ public class LispInterpreterBuilderTest {
|
|||
builder.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void attemptToBuildInterpreterOnBadFile() {
|
||||
setCommonFeatures();
|
||||
builder.useFile("test/interpreter/test-files/does-not-exist.lisp");
|
||||
builder.build();
|
||||
assertErrorMessageWritten();
|
||||
assertTerminated();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void makeSureDecoratorsAreInitializedWithDefaults() {
|
||||
builder.build();
|
||||
assertEquals("", environment.decoratePrompt(""));
|
||||
assertEquals("", environment.decorateValueOutput(""));
|
||||
assertEquals("", environment.decorateWarningOutput(""));
|
||||
assertEquals("", environment.decorateErrorOutput(""));
|
||||
assertEquals("", environment.decorateCriticalOutput(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void makeSureDecoratorsAreSetCorrectly() {
|
||||
builder.setPromptDecorator(s -> "#" + s + "#");
|
||||
builder.setValueOutputDecorator(s -> "@" + s + "@");
|
||||
builder.setWarningOutputDecorator(s -> "%" + s + "%");
|
||||
builder.setErrorOutputDecorator(s -> "*" + s + "*");
|
||||
builder.setCriticalOutputDecorator(s -> "$" + s + "$");
|
||||
builder.build();
|
||||
assertEquals("#x#", environment.decoratePrompt("x"));
|
||||
assertEquals("@x@", environment.decorateValueOutput("x"));
|
||||
assertEquals("%x%", environment.decorateWarningOutput("x"));
|
||||
assertEquals("*x*", environment.decorateErrorOutput("x"));
|
||||
assertEquals("$x$", environment.decorateCriticalOutput("x"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ import java.io.InputStream;
|
|||
import org.junit.Test;
|
||||
|
||||
import error.LispException;
|
||||
import scanner.LispInputStream.UncheckedIOException;
|
||||
import scanner.LispScanner.UnterminatedStringException;
|
||||
import stream.UncheckedIOException;
|
||||
import token.Eof.EofEncounteredException;
|
||||
import token.RightParenthesis.StartsWithRightParenthesisException;
|
||||
import token.TokenFactory.BadCharacterException;
|
||||
|
|
|
@ -8,7 +8,8 @@ import java.io.InputStream;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import scanner.LispInputStream.*;
|
||||
import scanner.LispInputStream.MaximumUnreadsExceededException;
|
||||
import stream.UncheckedIOException;
|
||||
|
||||
public class LispCommentRemovingInputStreamTest {
|
||||
|
||||
|
@ -177,7 +178,7 @@ public class LispCommentRemovingInputStreamTest {
|
|||
lispInputStream.unreadLastCharacter();
|
||||
}
|
||||
|
||||
@Test()
|
||||
@Test
|
||||
public void callUnreadMultipleTimes_ExceptionHasCorrectSeverity() {
|
||||
String input = "abc";
|
||||
LispInputStream lispInputStream = createLispInputStream(input);
|
||||
|
@ -200,20 +201,8 @@ public class LispCommentRemovingInputStreamTest {
|
|||
lispInputStream.read();
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void underlyingInputStreamThrowsIOException_ExceptionHasCorrectSeverity() {
|
||||
InputStream ioExceptionThrowingInputStream = createIOExceptionThrowingInputStream();
|
||||
LispInputStream lispInputStream = new LispCommentRemovingInputStream(ioExceptionThrowingInputStream);
|
||||
|
||||
try {
|
||||
lispInputStream.read();
|
||||
} catch (UncheckedIOException e) {
|
||||
assertEquals(CRITICAL, e.getSeverity());
|
||||
}
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void underlyingInputStreamThrowsIOException_ExceptionHasErrorMessage() {
|
||||
@Test
|
||||
public void underlyingInputStreamThrowsIOException_ExceptionHasCorrectAttributes() {
|
||||
InputStream ioExceptionThrowingInputStream = createIOExceptionThrowingInputStream();
|
||||
LispInputStream lispInputStream = new LispCommentRemovingInputStream(ioExceptionThrowingInputStream);
|
||||
|
||||
|
@ -223,6 +212,7 @@ public class LispCommentRemovingInputStreamTest {
|
|||
String message = e.getMessage();
|
||||
assertNotNull(message);
|
||||
assertTrue(message.length() >= 0);
|
||||
assertEquals(CRITICAL, e.getSeverity());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ import static terminal.SelectGraphicRendition.*;
|
|||
|
||||
import org.junit.*;
|
||||
|
||||
import stream.SafeInputStream;
|
||||
import terminal.ControlSequence.NullControlSequence;
|
||||
import terminal.SafeStream.SafeInputStream;
|
||||
import testutil.TestUtilities;
|
||||
import util.Characters;
|
||||
|
||||
|
|
Loading…
Reference in New Issue