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 com.googlecode.lanterna.terminal.*;
import interpreter.*; import interpreter.*;
import stream.SafeOutputStream;
import terminal.LispTerminal; import terminal.LispTerminal;
import terminal.SafeStream.SafeOutputStream;
import terminal.SafeStream.UncheckedIOException;
public class LispMain { public class LispMain {
private static final String GREETING = "Transcendental Lisp - Version 1.0.1"; private static final String GREETING = "Transcendental Lisp - Version 1.0.1";
private static final String ANSI_RESET = "\u001B[0m"; private static final String ANSI_RESET = "\u001B[0m";
private static final String ANSI_RED = "\u001B[31m"; private static final String ANSI_RED = "\u001B[31m";
private static final String ANSI_GREEN = "\u001B[32m"; private static final String ANSI_GREEN = "\u001B[32m";
private static final String ANSI_YELLOW = "\u001B[33m"; private static final String ANSI_YELLOW = "\u001B[33m";
private static final String ANSI_PURPLE = "\u001B[35m"; 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(); LispMain lispMain = new LispMain();
if (args.length == 0) if (arguments.length == 0)
lispMain.runInteractive(); lispMain.runInteractive();
else else
lispMain.runWithFile(args[0]); lispMain.runWithFile(arguments[0]);
} }
private PipedInputStream inputReader; private PipedInputStream inputReader;
@ -98,6 +98,16 @@ public class LispMain {
safeOutputWriter.close(); 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) { private void runWithFile(String fileName) {
buildFileInterpreter(fileName).interpret(); buildFileInterpreter(fileName).interpret();
} }
@ -116,15 +126,4 @@ public class LispMain {
return builder.build(); 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 static util.Characters.*;
import java.io.*; import java.io.InputStream;
import stream.SafeInputStream;
/** /**
* Removes Lisp comments from an input stream. * Removes Lisp comments from an input stream.
*/ */
public class LispCommentRemovingInputStream implements LispInputStream { public class LispCommentRemovingInputStream implements LispInputStream {
private InputStream underlyingInputStream; private SafeInputStream underlyingInputStream;
private boolean isInQuotedString; private boolean isInQuotedString;
private boolean rereadLastCharacter; private boolean rereadLastCharacter;
private int previousCharacter; private int previousCharacter;
private int currentCharacter; private int currentCharacter;
public LispCommentRemovingInputStream(InputStream underlyingInputStream) { public LispCommentRemovingInputStream(InputStream underlyingInputStream) {
this.underlyingInputStream = underlyingInputStream; this.underlyingInputStream = new SafeInputStream(underlyingInputStream);
this.isInQuotedString = false; this.isInQuotedString = false;
this.rereadLastCharacter = false; this.rereadLastCharacter = false;
this.previousCharacter = 0; this.previousCharacter = 0;
@ -25,14 +27,6 @@ public class LispCommentRemovingInputStream implements LispInputStream {
@Override @Override
public int read() { public int read() {
try {
return readWithIOException();
} catch (IOException ioException) {
throw new UncheckedIOException(ioException);
}
}
private int readWithIOException() throws IOException {
if (!rereadLastCharacter) if (!rereadLastCharacter)
return readFromUnderlyingInputStream(); return readFromUnderlyingInputStream();
@ -41,17 +35,16 @@ public class LispCommentRemovingInputStream implements LispInputStream {
return currentCharacter; return currentCharacter;
} }
private int readFromUnderlyingInputStream() throws IOException { private int readFromUnderlyingInputStream() {
readNextCharacter(); readNextCharacter();
if (haveEnteredComment()) if (haveEnteredComment())
consumeAllBytesInComment(); consumeAllBytesInComment();
return currentCharacter; return currentCharacter;
} }
private void readNextCharacter() throws IOException { private void readNextCharacter() {
previousCharacter = currentCharacter; previousCharacter = currentCharacter;
currentCharacter = underlyingInputStream.read(); currentCharacter = underlyingInputStream.read();
@ -67,7 +60,7 @@ public class LispCommentRemovingInputStream implements LispInputStream {
return (currentCharacter == SEMICOLON) && (!isInQuotedString); return (currentCharacter == SEMICOLON) && (!isInQuotedString);
} }
private void consumeAllBytesInComment() throws IOException { private void consumeAllBytesInComment() {
while (stillInComment()) while (stillInComment())
currentCharacter = underlyingInputStream.read(); currentCharacter = underlyingInputStream.read();
} }

View File

@ -1,7 +1,5 @@
package scanner; package scanner;
import java.io.IOException;
import error.CriticalLispException; import error.CriticalLispException;
public interface LispInputStream { public interface LispInputStream {
@ -15,19 +13,4 @@ public interface LispInputStream {
private static final long serialVersionUID = 1L; 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 terminal.ControlSequenceLookup.lookupControlSequence;
import static util.Characters.*; import static util.Characters.*;
import terminal.SafeStream.SafeInputStream; import stream.SafeInputStream;
class ControlSequenceHandler { class ControlSequenceHandler {

View File

@ -11,7 +11,7 @@ import com.googlecode.lanterna.*;
import com.googlecode.lanterna.input.*; import com.googlecode.lanterna.input.*;
import com.googlecode.lanterna.terminal.IOSafeTerminal; import com.googlecode.lanterna.terminal.IOSafeTerminal;
import terminal.SafeStream.*; import stream.*;
public class LispTerminal { 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 static org.junit.Assert.*;
import java.io.*; import java.io.*;
import java.util.*;
import org.junit.*; import org.junit.*;
import environment.RuntimeEnvironment;
import interpreter.LispInterpreterBuilderImpl.InterpreterAlreadyBuiltException; import interpreter.LispInterpreterBuilderImpl.InterpreterAlreadyBuiltException;
public class LispInterpreterBuilderTest { 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 @Override
public void reset() { public void reset() {
this.isBuilt = false; this.isBuilt = false;
} }
}; };
}
private void setCommonFeatures() { private void setCommonFeatures() {
builder.setOutput(new PrintStream(new ByteArrayOutputStream())); builder.setOutput(new PrintStream(outputStream));
builder.setErrorOutput(new PrintStream(new ByteArrayOutputStream())); builder.setErrorOutput(new PrintStream(errorOutputStream));
builder.setTerminationFunction(() -> {}); 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 @Before
public void setUp() throws Exception { public void setUp() throws Exception {
indicatorSet = new HashSet<>();
outputStream = new ByteArrayOutputStream();
errorOutputStream = new ByteArrayOutputStream();
builder.reset(); builder.reset();
environment.reset();
} }
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
builder.reset(); builder.reset();
environment.reset();
} }
@Test @Test
@ -41,7 +68,6 @@ public class LispInterpreterBuilderTest {
setCommonFeatures(); setCommonFeatures();
builder.setInput(System.in, "stdin"); builder.setInput(System.in, "stdin");
LispInterpreter interpreter = builder.build(); LispInterpreter interpreter = builder.build();
assertTrue(interpreter instanceof InteractiveLispInterpreter); assertTrue(interpreter instanceof InteractiveLispInterpreter);
} }
@ -51,8 +77,8 @@ public class LispInterpreterBuilderTest {
builder.setInput(System.in, "stdin"); builder.setInput(System.in, "stdin");
builder.setNotInteractive(); builder.setNotInteractive();
LispInterpreter interpreter = builder.build(); LispInterpreter interpreter = builder.build();
assertFalse(interpreter instanceof InteractiveLispInterpreter); assertFalse(interpreter instanceof InteractiveLispInterpreter);
assertFalse(interpreter instanceof FileLispInterpreter);
} }
@Test @Test
@ -60,8 +86,7 @@ public class LispInterpreterBuilderTest {
setCommonFeatures(); setCommonFeatures();
builder.useFile("test/interpreter/test-files/file.lisp"); builder.useFile("test/interpreter/test-files/file.lisp");
LispInterpreter interpreter = builder.build(); LispInterpreter interpreter = builder.build();
assertTrue(interpreter instanceof FileLispInterpreter);
assertFalse(interpreter instanceof InteractiveLispInterpreter);
} }
@Test(expected = InterpreterAlreadyBuiltException.class) @Test(expected = InterpreterAlreadyBuiltException.class)
@ -73,7 +98,6 @@ public class LispInterpreterBuilderTest {
@Test @Test
public void interpreterAlreadyBuiltException_HasCorrectAttributes() { public void interpreterAlreadyBuiltException_HasCorrectAttributes() {
InterpreterAlreadyBuiltException e = new InterpreterAlreadyBuiltException(); InterpreterAlreadyBuiltException e = new InterpreterAlreadyBuiltException();
assertEquals(CRITICAL, e.getSeverity()); assertEquals(CRITICAL, e.getSeverity());
assertNotNull(e.getMessage()); assertNotNull(e.getMessage());
assertTrue(e.getMessage().length() > 0); assertTrue(e.getMessage().length() > 0);
@ -88,4 +112,38 @@ public class LispInterpreterBuilderTest {
builder.build(); 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 org.junit.Test;
import error.LispException; import error.LispException;
import scanner.LispInputStream.UncheckedIOException;
import scanner.LispScanner.UnterminatedStringException; import scanner.LispScanner.UnterminatedStringException;
import stream.UncheckedIOException;
import token.Eof.EofEncounteredException; import token.Eof.EofEncounteredException;
import token.RightParenthesis.StartsWithRightParenthesisException; import token.RightParenthesis.StartsWithRightParenthesisException;
import token.TokenFactory.BadCharacterException; import token.TokenFactory.BadCharacterException;

View File

@ -8,7 +8,8 @@ import java.io.InputStream;
import org.junit.Test; import org.junit.Test;
import scanner.LispInputStream.*; import scanner.LispInputStream.MaximumUnreadsExceededException;
import stream.UncheckedIOException;
public class LispCommentRemovingInputStreamTest { public class LispCommentRemovingInputStreamTest {
@ -177,7 +178,7 @@ public class LispCommentRemovingInputStreamTest {
lispInputStream.unreadLastCharacter(); lispInputStream.unreadLastCharacter();
} }
@Test() @Test
public void callUnreadMultipleTimes_ExceptionHasCorrectSeverity() { public void callUnreadMultipleTimes_ExceptionHasCorrectSeverity() {
String input = "abc"; String input = "abc";
LispInputStream lispInputStream = createLispInputStream(input); LispInputStream lispInputStream = createLispInputStream(input);
@ -200,20 +201,8 @@ public class LispCommentRemovingInputStreamTest {
lispInputStream.read(); lispInputStream.read();
} }
@Test() @Test
public void underlyingInputStreamThrowsIOException_ExceptionHasCorrectSeverity() { public void underlyingInputStreamThrowsIOException_ExceptionHasCorrectAttributes() {
InputStream ioExceptionThrowingInputStream = createIOExceptionThrowingInputStream();
LispInputStream lispInputStream = new LispCommentRemovingInputStream(ioExceptionThrowingInputStream);
try {
lispInputStream.read();
} catch (UncheckedIOException e) {
assertEquals(CRITICAL, e.getSeverity());
}
}
@Test()
public void underlyingInputStreamThrowsIOException_ExceptionHasErrorMessage() {
InputStream ioExceptionThrowingInputStream = createIOExceptionThrowingInputStream(); InputStream ioExceptionThrowingInputStream = createIOExceptionThrowingInputStream();
LispInputStream lispInputStream = new LispCommentRemovingInputStream(ioExceptionThrowingInputStream); LispInputStream lispInputStream = new LispCommentRemovingInputStream(ioExceptionThrowingInputStream);
@ -223,6 +212,7 @@ public class LispCommentRemovingInputStreamTest {
String message = e.getMessage(); String message = e.getMessage();
assertNotNull(message); assertNotNull(message);
assertTrue(message.length() >= 0); assertTrue(message.length() >= 0);
assertEquals(CRITICAL, e.getSeverity());
} }
} }

View File

@ -6,8 +6,8 @@ import static terminal.SelectGraphicRendition.*;
import org.junit.*; import org.junit.*;
import stream.SafeInputStream;
import terminal.ControlSequence.NullControlSequence; import terminal.ControlSequence.NullControlSequence;
import terminal.SafeStream.SafeInputStream;
import testutil.TestUtilities; import testutil.TestUtilities;
import util.Characters; import util.Characters;