import java.io.*;
import java.math.BigInteger;
/**
* This is the abstract base class for the SchemeObject hierarchy.
**/
public abstract class SchemeObject
{
/**
* Dealing with the static type system of Java is not convenient
* when manipulating SchemeObjects because Scheme itself is
* denamically typed. To make manipulating Scheme objects and
* lists in particular more convenient we make all SchemeObjects
* provide a getCar()
and getCdr()
* method. If the object is not a SchemePair
then
* these methods should through a ClassCastException. As such,
* the following are equivalent:
*
* SchemeObject o = ...; * * System.out.println(o.getCar()); ** and *
* SchemeObject o = ...; * * Scheme.out.println(((SchemePair)o).getCar()); ** @return The
car
of the SchemePair
.
* @throws ClassCastException if this object does not override this method.
* @see SchemePair
**/
public SchemeObject getCar() { throw new ClassCastException("Took car of " + this); }
/**
* Dealing with the static type system of Java is not convenient
* when manipulating SchemeObjects because Scheme itself is
* denamically typed. To make manipulating Scheme objects and
* lists in particular more convenient we make all SchemeObjects
* provide a getCar()
and getCdr()
* method. If the object is not a SchemePair
then
* these methods should through a ClassCastException. As such,
* the following are equivalent:
* * SchemeObject o = ...; * * System.out.println(o.getCdr()); ** and *
* SchemeObject o = ...; * * Scheme.out.println(((SchemePair)o).getCdr()); ** @return The
cdr
of the SchemePair
.
* @throws ClassCastException if this object does not override this method.
* @see SchemePair
**/
public SchemeObject getCdr()
{
throw new RuntimeException("Took cdr of " + this);
}
/**
* This calls getCar()
and/or getCdr()
* @return The car
of the SchemePair
.
* @throws ClassCastException if this object does not override this method.
* @see SchemePair
* @see #getCar()
* @see #getCdr()
**/
public SchemeObject car() { return getCar(); }
/**
* This calls getCar()
and/or getCdr()
* @return The cdr
of the SchemePair
.
* @throws ClassCastException if this object does not override this method.
* @see SchemePair
* @see #getCar()
* @see #getCdr()
**/
public SchemeObject cdr() { return getCdr(); }
/**
* This calls getCar()
and/or getCdr()
* @return The cadr
of the SchemePair
.
* @throws ClassCastException if this object does not override this method.
* @see SchemePair
* @see #getCar()
* @see #getCdr()
**/
public SchemeObject cadr() { return getCdr().getCar(); }
/**
* This calls getCar()
and/or getCdr()
* @return The caddr
of the SchemePair
.
* @throws ClassCastException if this object does not override this method.
* @see SchemePair
* @see #getCar()
* @see #getCdr()
**/
public SchemeObject caddr() { return getCdr().getCdr().getCar(); }
/**
* This calls getCar()
and/or getCdr()
* @return The cddr
of the SchemePair
.
* @throws ClassCastException if this object does not override this method.
* @see SchemePair
* @see #getCar()
* @see #getCdr()
**/
public SchemeObject cddr() { return getCdr().getCdr(); }
/**
* This calls getCar()
and/or getCdr()
* @return The cadddr
of the SchemePair
.
* @throws ClassCastException if this object does not override this method.
* @see SchemePair
* @see #getCar()
* @see #getCdr()
**/
public SchemeObject cadddr() { return getCdr().getCdr().getCdr().getCar(); }
/**
* This calls getCar()
and/or getCdr()
* @return The cdar
of the SchemePair
.
* @throws ClassCastException if this object does not override this method.
* @see SchemePair
* @see #getCar()
* @see #getCdr()
**/
public SchemeObject cdar() { return getCar().getCdr(); }
/**
* This reads in a char from input and throws an IOException on EOF.
* @param in InputStream
to read from.
* @return Character read.
* @throws EOFException on EOF.
* @throws IOException on other IO read error.
* @see InputStream
**/
private static char readChar(InputStream in) throws IOException
{
int ch = in.read();
if (ch < 0)
throw new EOFException();
return ((char) ch);
}
/**
* This reads in a SchemeObject
from the String
.
* @param s String
to read a SchemeObject
from.
* @see #read(InputStream inputStream)
* @see StringInputStream
*/
public static SchemeObject read(String s) throws IOException,EndOfSchemeListException
{
return read(new PushbackInputStream(new StringInputStream(s)));
}
/**
* This reads in a SchemeObject
from the
* InputStream
that is passed in. Subsequent calls
* to read with this InputStream
are only guaranteed
* to work correctly if this InputStream
is in fact
* a PushbackInputStream
. To get a
* PushbackInputStream
* from an InputStream
(for example System.in
)
* use new PushbackInputStream(System.in)
.
*
* @return SchemeObject represting what was read in.
*
* @param inputStream The InputStream
to read the
* SchemeObject
from.
*
* @throws IOException if an there is an error reading from the
* stream.
*
* @throws EndOfSchemeListException if there is an unexpected end
* of a list expression. For example: ")"
.
*
* @see java.io.InputStream
* @see java.io.PushbackInputStream
**/
public static SchemeObject read(InputStream inputStream) throws IOException, EndOfSchemeListException
{
char ch;
// The read() methods below need a PushbackInputStream because
// they need to put characters already read back into the
// InputStream.
PushbackInputStream in;
if (inputStream instanceof PushbackInputStream)
in = (PushbackInputStream)inputStream;
else
in = new PushbackInputStream(inputStream,1);
// skip whitespace.
for(ch = readChar(in); Character.isWhitespace(ch); ch = readChar(in))
;
if (ch == '(') // beginning of a list
return readList(in);
if (ch == '"') // beginning of a string.
return readString(in);
if (ch == '\'') // quoted expression.
{
SchemeObject o = read(in);
return new SchemePair(SchemeSymbol.getSymbol("quote"),new SchemePair(o,SchemeNull.getNull()));
}
if (ch == '#') // beginning of boolean or character
{ // (characters are not supported)
String s = readSymbol(in);
if ("t".equalsIgnoreCase(s))
return SchemeBoolean.getTrue();
if ("f".equalsIgnoreCase(s))
return SchemeBoolean.getFalse();
// if (s.charAt(0) == '\\')
// return SchemeChar.getChar(s.charAt(1));
return SchemeSymbol.getSymbol("#" + s);
}
if (ch == ')') // unexpected ')', error!
throw new EndOfSchemeListException();
if (ch == ';') // comment, ignore rest of line.
{
// skip line
for (ch = (char)readChar(in); ch != '\n'; ch = (char)readChar(in))
;
return read(in);
}
else // some sort of symbol or number.
{
in.unread(ch); // pushback the character read.
String s = readSymbol(in);
/*
* parse symbol as symbol or number. */
if (s != ".")
{
try
{
return new SchemeInteger(new BigInteger(s));
}
catch (NumberFormatException e)
{}
try
{
return new SchemeDouble(Double.parseDouble(s));
}
catch (NumberFormatException e)
{}
}
return SchemeSymbol.getSymbol(s);
}
}
/**
* This reads in a list expression assuming that the initial '('
* has already been read. It returns a SchemeList
* that is either SchemePair
or SchemeNull
.
*
* @return SchemeList representing the partial list that was read in.
*
* @param in The PushbackInputStream
to read the
* scheme list from.
*
* @throws IOException if an there is an error reading from the
* stream.
*
* @throws EndOfSchemeListException if there is an unexpected end
* of a list expression. For example: "1 2 ')"
.
**/
private static SchemeList readList(PushbackInputStream in) throws IOException, EndOfSchemeListException
{
try
{
SchemeObject o = read(in);
SchemeObject p = readList(in);
if (p != SchemeNull.getNull() && SchemeSymbol.getSymbol(".") == p.getCar())
p = p.cadr();
return new SchemePair(o,p);
}
catch(EndOfSchemeListException e)
{
return SchemeNull.getNull();
}
}
/**
* This reads in a string expression assuming that the initial '"'
* has already been read. It returns a SchemeString
.
*
* @return SchemeString representing the string that was read in.
*
* @param in The PushbackInputStream
to read the
* scheme string from.
*
* @throws IOException if an there is an error reading from the
* stream.
**/
private static SchemeString readString(PushbackInputStream in) throws IOException
{
StringBuffer sb = new StringBuffer();
for (char ch = readChar(in); ch != '"'; ch = readChar(in))
{
if (ch == '\\')
{
ch = readChar(in);
switch (ch)
{
case 'n':
ch = '\n';
break;
case 't':
ch = '\t';
break;
case 'r':
ch = '\r';
break;
}
sb.append(ch);
}
else
sb.append(ch);
}
return new SchemeString(sb.toString());
}
/**
* This reads in a symbol expression and returns it as a
* String
.
*
* @return String representing the symbol that was read in. This
* may be a number. If so, it should be converted to a
* SchemeNumber
*
* @param in The PushbackInputStream
to read the
* scheme string from.
*
* @throws IOException if an there is an error reading from the
* stream.
**/
private static String readSymbol(PushbackInputStream in) throws IOException
{
StringBuffer sb = new StringBuffer();
for(char ch = readChar(in); !Character.isWhitespace(ch); ch = readChar(in))
{
if (ch == ')' || ch == '(')
{
in.unread(ch);
break;
}
sb.append(ch);
}
return sb.toString();
}
}