Convert streams to kotlin

This commit is contained in:
Mike Cifelli 2018-06-03 09:31:19 -04:00
parent c5fbab6813
commit 0f34d25e12
12 changed files with 288 additions and 364 deletions

View File

@ -1,78 +0,0 @@
package scanner;
import stream.SafeInputStream;
import util.Characters;
import java.io.InputStream;
/**
* Removes Lisp comments from an input stream.
*/
public class LispCommentRemovingInputStream implements LispInputStream {
private SafeInputStream underlyingInputStream;
private boolean isInQuotedString;
private boolean rereadLastCharacter;
private int previousCharacter;
private int currentCharacter;
public LispCommentRemovingInputStream(InputStream underlyingInputStream) {
this.underlyingInputStream = new SafeInputStream(underlyingInputStream);
this.isInQuotedString = false;
this.rereadLastCharacter = false;
this.previousCharacter = 0;
this.currentCharacter = 0;
}
@Override
public int read() {
if (!rereadLastCharacter)
return readFromUnderlyingInputStream();
rereadLastCharacter = false;
return currentCharacter;
}
private int readFromUnderlyingInputStream() {
readNextCharacter();
if (haveEnteredComment())
consumeAllBytesInComment();
return currentCharacter;
}
private void readNextCharacter() {
previousCharacter = currentCharacter;
currentCharacter = underlyingInputStream.read();
if (haveEncounteredStringBoundary())
isInQuotedString = !isInQuotedString;
}
private boolean haveEncounteredStringBoundary() {
return (previousCharacter != Characters.BACKSLASH) && (currentCharacter == Characters.DOUBLE_QUOTE);
}
private boolean haveEnteredComment() {
return (currentCharacter == Characters.SEMICOLON) && (!isInQuotedString);
}
private void consumeAllBytesInComment() {
while (stillInComment())
currentCharacter = underlyingInputStream.read();
}
private boolean stillInComment() {
return (currentCharacter != Characters.NEWLINE) && (currentCharacter != Characters.EOF);
}
@Override
public void unreadLastCharacter() {
if (rereadLastCharacter)
throw new MaximumUnreadsExceededException();
this.rereadLastCharacter = true;
}
}

View File

@ -0,0 +1,66 @@
package scanner
import stream.SafeInputStream
import util.Characters.BACKSLASH
import util.Characters.DOUBLE_QUOTE
import util.Characters.EOF
import util.Characters.NEWLINE
import util.Characters.SEMICOLON
import java.io.InputStream
/**
* Removes Lisp comments from an input stream.
*/
class LispCommentRemovingInputStream(underlyingInputStream: InputStream) : LispInputStream {
private val underlyingInputStream = SafeInputStream(underlyingInputStream)
private var isInQuotedString = false
private var rereadLastCharacter = false
private var previousCharacter = 0
private var currentCharacter = 0
override fun read(): Int {
if (!rereadLastCharacter)
return readFromUnderlyingInputStream()
rereadLastCharacter = false
return currentCharacter
}
override fun unreadLastCharacter() {
if (rereadLastCharacter)
throw LispInputStream.MaximumUnreadsExceededException()
this.rereadLastCharacter = true
}
private fun readFromUnderlyingInputStream(): Int {
readNextCharacter()
if (haveEnteredComment())
consumeAllBytesInComment()
return currentCharacter
}
private fun readNextCharacter() {
previousCharacter = currentCharacter
currentCharacter = underlyingInputStream.read()
if (haveEncounteredStringBoundary())
isInQuotedString = !isInQuotedString
}
private fun haveEncounteredStringBoundary() =
previousCharacter != BACKSLASH.toInt() && currentCharacter == DOUBLE_QUOTE.toInt()
private fun haveEnteredComment() = currentCharacter == SEMICOLON.toInt() && !isInQuotedString
private fun consumeAllBytesInComment() {
while (stillInComment())
currentCharacter = underlyingInputStream.read()
}
private fun stillInComment() = currentCharacter != NEWLINE.toInt() && currentCharacter != EOF
}

View File

@ -1,15 +0,0 @@
package scanner;
import error.CriticalLispException;
public interface LispInputStream {
int read();
void unreadLastCharacter();
public static class MaximumUnreadsExceededException extends CriticalLispException {
private static final long serialVersionUID = 1L;
}
}

View File

@ -0,0 +1,11 @@
package scanner
import error.CriticalLispException
interface LispInputStream {
fun read(): Int
fun unreadLastCharacter()
class MaximumUnreadsExceededException : CriticalLispException()
}

View File

@ -1,181 +0,0 @@
package scanner;
import error.LineColumnException;
import file.FilePosition;
import file.FilePositionTracker;
import token.Token;
import token.TokenFactory;
import token.TokenFactoryImpl;
import util.Characters;
import java.io.InputStream;
import java.util.function.Function;
import static java.lang.Character.isDigit;
import static java.lang.Character.isWhitespace;
/**
* Converts a stream of bytes into a stream of Lisp tokens.
*/
public class LispScanner {
private LispInputStream inputStream;
private FilePositionTracker positionTracker;
private TokenFactory tokenFactory;
public LispScanner(InputStream inputStream, String fileName) {
this.inputStream = new LispCommentRemovingInputStream(inputStream);
this.positionTracker = new FilePositionTracker(fileName);
this.tokenFactory = new TokenFactoryImpl();
}
public Token getNextToken() {
for (int c = inputStream.read(); c != Characters.EOF; c = inputStream.read()) {
char currentCharacter = (char) c;
positionTracker.incrementColumn();
if (!isWhitespace(currentCharacter))
return createTokenFromCharacter(currentCharacter);
else if (currentCharacter == Characters.NEWLINE)
positionTracker.incrementLine();
}
return tokenFactory.createEofToken(positionTracker.currentPosition());
}
private Token createTokenFromCharacter(char c) {
FilePosition currentPosition = positionTracker.currentPosition();
String tokenText = retrieveTokenText(c);
return tokenFactory.createToken(tokenText, currentPosition);
}
private String retrieveTokenText(char firstCharacter) {
String tokenText = "" + firstCharacter;
if (firstCharacter == Characters.DOUBLE_QUOTE)
tokenText = retrieveStringTokenText(firstCharacter);
else if (Characters.INSTANCE.isNumberPrefix(firstCharacter))
tokenText = retrieveNumberOrIdentifierTokenText(firstCharacter);
else if (isDigit(firstCharacter))
tokenText = retrieveNumberTokenText(firstCharacter);
else if (Characters.INSTANCE.isLegalIdentifierCharacter(firstCharacter))
tokenText = retrieveIdentifierTokenText(firstCharacter);
return tokenText;
}
private String retrieveStringTokenText(char firstDoubleQuote) {
ComplexTokenTextRetriever retriever = new ComplexTokenTextRetriever(firstDoubleQuote,
Characters.INSTANCE::isLegalStringCharacter);
return retriever.retrieveToken();
}
private String retrieveNumberOrIdentifierTokenText(char firstCharacter) {
char nextCharacter = (char) inputStream.read();
inputStream.unreadLastCharacter();
if (isDigit(nextCharacter))
return retrieveNumberTokenText(firstCharacter);
return retrieveIdentifierTokenText(firstCharacter);
}
private String retrieveNumberTokenText(char firstCharacter) {
ComplexTokenTextRetriever retriever = new ComplexTokenTextRetriever(firstCharacter, Character::isDigit);
return retriever.retrieveToken();
}
private String retrieveIdentifierTokenText(char firstCharacter) {
ComplexTokenTextRetriever retriever = new ComplexTokenTextRetriever(firstCharacter,
Characters.INSTANCE::isLegalIdentifierCharacter);
return retriever.retrieveToken();
}
private class ComplexTokenTextRetriever {
Function<Character, Boolean> isPartOfToken;
StringBuilder text;
FilePosition position;
char firstCharacter;
char currentCharacter;
char previousCharacter;
public ComplexTokenTextRetriever(char firstCharacter, Function<Character, Boolean> isPartOfToken) {
this.isPartOfToken = isPartOfToken;
this.text = new StringBuilder();
this.position = positionTracker.currentPosition();
this.firstCharacter = firstCharacter;
this.currentCharacter = firstCharacter;
this.previousCharacter = firstCharacter;
}
public String retrieveToken() {
text.append(firstCharacter);
for (int c = inputStream.read(); c != Characters.EOF; c = inputStream.read()) {
currentCharacter = (char) c;
if (!isPartOfToken.apply(currentCharacter)) {
inputStream.unreadLastCharacter();
return text.toString();
}
addCharacterToToken();
if (isTerminatingCharacter())
return text.toString();
previousCharacter = currentCharacter;
}
return terminateTokenWithEof();
}
private void addCharacterToToken() {
text.append(currentCharacter);
positionTracker.incrementColumn();
if (currentCharacter == Characters.NEWLINE)
positionTracker.incrementLine();
}
private boolean isTerminatingCharacter() {
return isStringToken() && isTerminatingDoubleQuote();
}
private boolean isStringToken() {
return firstCharacter == Characters.DOUBLE_QUOTE;
}
private boolean isTerminatingDoubleQuote() {
return (currentCharacter == Characters.DOUBLE_QUOTE) &&
(previousCharacter != Characters.BACKSLASH);
}
private String terminateTokenWithEof() {
if (isStringToken())
throw new UnterminatedStringException(position);
return text.toString();
}
}
public static class UnterminatedStringException extends LineColumnException {
private static final long serialVersionUID = 1L;
public UnterminatedStringException(FilePosition position) {
super(position);
}
@Override
public String getMessagePrefix() {
return "unterminated quoted string";
}
}
}

View File

@ -0,0 +1,141 @@
package scanner
import error.LineColumnException
import file.FilePosition
import file.FilePositionTracker
import token.Token
import token.TokenFactoryImpl
import util.Characters
import util.Characters.BACKSLASH
import util.Characters.DOUBLE_QUOTE
import util.Characters.EOF
import util.Characters.NEWLINE
import java.io.InputStream
import java.lang.Character.isDigit
import java.lang.Character.isWhitespace
/**
* Converts a stream of bytes into a stream of Lisp tokens.
*/
class LispScanner(inputStream: InputStream, fileName: String) {
private val inputStream = LispCommentRemovingInputStream(inputStream)
private val positionTracker = FilePositionTracker(fileName)
private val tokenFactory = TokenFactoryImpl()
val nextToken: Token
get() {
var c = inputStream.read()
while (c != EOF) {
val currentCharacter = c.toChar()
positionTracker.incrementColumn()
if (!isWhitespace(currentCharacter))
return createTokenFromCharacter(currentCharacter)
else if (currentCharacter == NEWLINE)
positionTracker.incrementLine()
c = inputStream.read()
}
return tokenFactory.createEofToken(positionTracker.currentPosition())
}
private fun createTokenFromCharacter(c: Char): Token {
val currentPosition = positionTracker.currentPosition()
val tokenText = retrieveTokenText(c)
return tokenFactory.createToken(tokenText, currentPosition)
}
private fun retrieveTokenText(firstCharacter: Char): String {
var tokenText = "" + firstCharacter
if (firstCharacter == DOUBLE_QUOTE)
tokenText = retrieveStringTokenText(firstCharacter)
else if (Characters.isNumberPrefix(firstCharacter))
tokenText = retrieveNumberOrIdentifierTokenText(firstCharacter)
else if (isDigit(firstCharacter))
tokenText = retrieveNumberTokenText(firstCharacter)
else if (Characters.isLegalIdentifierCharacter(firstCharacter))
tokenText = retrieveIdentifierTokenText(firstCharacter)
return tokenText
}
private fun retrieveStringTokenText(firstDoubleQuote: Char) =
ComplexTokenTextRetriever(firstDoubleQuote, { Characters.isLegalStringCharacter(it) }).retrieveToken()
private fun retrieveNumberOrIdentifierTokenText(firstCharacter: Char): String {
val nextCharacter = inputStream.read().toChar()
inputStream.unreadLastCharacter()
return if (isDigit(nextCharacter))
retrieveNumberTokenText(firstCharacter)
else
retrieveIdentifierTokenText(firstCharacter)
}
private fun retrieveNumberTokenText(firstCharacter: Char) =
ComplexTokenTextRetriever(firstCharacter, { Character.isDigit(it) }).retrieveToken()
private fun retrieveIdentifierTokenText(firstCharacter: Char) =
ComplexTokenTextRetriever(firstCharacter, { Characters.isLegalIdentifierCharacter(it) }).retrieveToken()
private inner class ComplexTokenTextRetriever(internal var firstCharacter: Char,
internal var isPartOfToken: (Char) -> Boolean) {
internal var text = StringBuilder()
internal var position = positionTracker.currentPosition()
internal var currentCharacter = firstCharacter
internal var previousCharacter = firstCharacter
private fun isTerminatingCharacter() = isStringToken() && isTerminatingDoubleQuote()
private fun isStringToken() = firstCharacter == DOUBLE_QUOTE
private fun isTerminatingDoubleQuote() = currentCharacter == DOUBLE_QUOTE && previousCharacter != BACKSLASH
fun retrieveToken(): String {
text.append(firstCharacter)
var c = inputStream.read()
while (c != EOF) {
currentCharacter = c.toChar()
if (!isPartOfToken(currentCharacter)) {
inputStream.unreadLastCharacter()
return text.toString()
}
addCharacterToToken()
if (isTerminatingCharacter())
return text.toString()
previousCharacter = currentCharacter
c = inputStream.read()
}
return terminateTokenWithEof()
}
private fun addCharacterToToken() {
text.append(currentCharacter)
positionTracker.incrementColumn()
if (currentCharacter == NEWLINE)
positionTracker.incrementLine()
}
private fun terminateTokenWithEof(): String {
if (isStringToken())
throw UnterminatedStringException(position)
return text.toString()
}
}
class UnterminatedStringException(position: FilePosition) : LineColumnException(position) {
override val messagePrefix: String
get() = "unterminated quoted string"
}
}

View File

@ -1,32 +0,0 @@
package stream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import static java.nio.charset.StandardCharsets.UTF_8;
public class SafeInputStream {
private InputStreamReader underlyingStream;
public SafeInputStream(InputStream underlyingStream) {
this.underlyingStream = new InputStreamReader(underlyingStream, UTF_8);
}
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,28 @@
package stream
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets.UTF_8
class SafeInputStream(underlyingStream: InputStream) {
private val underlyingStream: InputStreamReader = InputStreamReader(underlyingStream, UTF_8)
fun read(): Int {
try {
return underlyingStream.read()
} catch (e: IOException) {
throw UncheckedIOException(e)
}
}
fun close() {
try {
underlyingStream.close()
} catch (e: IOException) {
throw UncheckedIOException(e)
}
}
}

View File

@ -1,38 +0,0 @@
package stream;
import java.io.IOException;
import java.io.OutputStream;
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,31 @@
package stream
import java.io.IOException
import java.io.OutputStream
class SafeOutputStream(private val underlyingStream: OutputStream) {
fun write(b: ByteArray) {
try {
underlyingStream.write(b)
} catch (e: IOException) {
throw UncheckedIOException(e)
}
}
fun flush() {
try {
underlyingStream.flush()
} catch (e: IOException) {
throw UncheckedIOException(e)
}
}
fun close() {
try {
underlyingStream.close()
} catch (e: IOException) {
throw UncheckedIOException(e)
}
}
}

View File

@ -1,20 +0,0 @@
package stream;
import error.CriticalLispException;
import java.io.IOException;
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

@ -0,0 +1,11 @@
package stream
import error.CriticalLispException
import java.io.IOException
class UncheckedIOException(private val exception: IOException) : CriticalLispException() {
override val message: String
get() = exception.message ?: ""
}