package jscheme;
import java.io.*;
/** InputPort is to Scheme as InputStream is to Java.
* @author Peter Norvig, peter@norvig.com http://www.norvig.com
* Copyright 1998 Peter Norvig, see http://www.norvig.com/license.html **/
public class InputPort extends SchemeUtils {
static String EOF = "#!EOF";
boolean isPushedToken = false;
boolean isPushedChar = false;
Object pushedToken = null;
int pushedChar = -1;
Reader in;
StringBuffer buff = new StringBuffer();
/** Construct an InputPort from an InputStream. **/
public InputPort(InputStream in) { this.in = new InputStreamReader(in);}
/** Construct an InputPort from a Reader. **/
public InputPort(Reader in) { this.in = in;}
/** Read and return a Scheme character or EOF. **/
public Object readChar() {
try {
if (isPushedChar) {
isPushedChar = false;
if (pushedChar == -1) return EOF; else return chr((char)pushedChar);
} else {
int ch = in.read();
if (ch == -1) return EOF; else return chr((char)ch);
}
} catch (IOException e) {
warn("On input, exception: " + e);
return EOF;
}
}
/** Peek at and return the next Scheme character (or EOF).
* However, don't consume the character. **/
public Object peekChar() {
int p = peekCh();
if (p == -1) return EOF; else return chr((char)p);
}
/** Push a character back to be re-used later. **/
int pushChar(int ch) {
isPushedChar = true;
return pushedChar = ch;
}
/** Pop off the previously pushed character. **/
int popChar() {
isPushedChar = false;
return pushedChar;
}
/** Peek at and return the next Scheme character as an int, -1 for EOF.
* However, don't consume the character. **/
public int peekCh() {
try { return isPushedChar ? pushedChar : pushChar(in.read()); }
catch (IOException e) {
warn("On input, exception: " + e);
return -1;
}
}
/** Read and return a Scheme expression, or EOF. **/
public Object read() {
try {
Object token = nextToken();
if (token == "(")
return readTail(false);
else if (token == ")")
{ warn("Extra ) ignored."); return read(); }
else if (token == ".")
{ warn("Extra . ignored."); return read(); }
else if (token == "'")
return list("quote", read());
else if (token == "`")
return list("quasiquote", read());
else if (token == ",")
return list("unquote", read());
else if (token == ",@")
return list("unquote-splicing", read());
else
return token;
} catch (IOException e) {
warn("On input, exception: " + e);
return EOF;
}
}
/** Close the port. Return TRUE if ok. **/
public Object close() {
try { this.in.close(); return TRUE; }
catch (IOException e) { return error("IOException: " + e); }
}
/** Is the argument the EOF object? **/
public static boolean isEOF(Object x) { return x == EOF; }
Object readTail(boolean dotOK) throws IOException {
Object token = nextToken();
if (token == EOF)
return error("EOF during read.");
else if (token == ")")
return null;
else if (token == ".") {
Object result = read();
token = nextToken();
if (token != ")") warn("Where's the ')'? Got " +
token + " after .");
return result;
} else {
isPushedToken = true;
pushedToken = token;
return cons(read(), readTail(true));
}
}
Object nextToken() throws IOException {
int ch;
// See if we should re-use a pushed char or token
if (isPushedToken) {
isPushedToken = false;
return pushedToken;
} else if (isPushedChar) {
ch = popChar();
} else {
ch = in.read();
}
// Skip whitespace
while (Character.isWhitespace((char)ch)) ch = in.read();
// See what kind of non-white character we got
switch(ch) {
case -1: return EOF;
case '(' : return "(";
case ')': return ")";
case '\'': return "'";
case '`': return "`";
case ',':
ch = in.read();
if (ch == '@') return ",@";
else { pushChar(ch); return ","; }
case ';':
// Comment: skip to end of line and then read next token
while(ch != -1 && ch != '\n' && ch != '\r') ch = in.read();
return nextToken();
case '"':
// Strings are represented as char[]
buff.setLength(0);
while ((ch = in.read()) != '"' && ch != -1) {
buff.append((char) ((ch == '\\') ? in.read() : ch));
}
if (ch == -1) warn("EOF inside of a string.");
return buff.toString().toCharArray();
case '#':
switch (ch = in.read()) {
case 't': case 'T': return TRUE;
case 'f': case 'F': return FALSE;
case '(':
pushChar('(');
return listToVector(read());
case '\\':
ch = in.read();
if (ch == 's' || ch == 'S' || ch == 'n' || ch == 'N') {
pushChar(ch);
Object token = nextToken();
if (token == "space") return chr(' ');
else if (token == "newline") return chr('\n');
else {
isPushedToken = true;
pushedToken = token;
return chr((char)ch);
}
} else {
return chr((char)ch);
}
case 'e': case 'i': case 'd': return nextToken();
case 'b': case 'o': case 'x':
warn("#" + ((char)ch) + " not implemented, ignored.");
return nextToken();
default:
warn("#" + ((char)ch) + " not recognized, ignored.");
return nextToken();
}
default:
buff.setLength(0);
int c = ch;
do {
buff.append((char)ch);
ch = in.read();
} while (!Character.isWhitespace((char)ch) && ch != -1 &&
ch != '(' && ch != ')' && ch != '\'' && ch != ';'
&& ch != '"' && ch != ',' && ch != '`');
pushChar(ch);
// Try potential numbers, but catch any format errors.
if (c == '.' || c == '+' || c == '-' || (c >= '0' && c <= '9')) {
try { return new Double(buff.toString()); }
catch (NumberFormatException e) { ; }
}
return buff.toString().toLowerCase().intern();
}
}
}