Refactor stream code and add unit tests

This commit is contained in:
Mike Cifelli 2017-03-22 14:08:22 -04:00
parent b298e118e3
commit 228b4b1793
13 changed files with 193 additions and 163 deletions

View File

@ -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;
}
};
}
}

View File

@ -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();
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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();
}
}
}

View File

@ -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"));
}
}

View File

@ -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;

View File

@ -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());
}
}

View File

@ -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;