/* Author: Jeff Dalton * Updated: Wed Jul 11 04:10:02 2001 by Jeff Dalton */ package ix.util.lisp; import java.io.*; import ix.util.*; /** * A LispReader can be used to read Objects using a Lisp-like syntax. * * @see LispFileReader */ // The division of labour between reader and tokenizer is wrong, // because the reader was written for a StreamTokenizer. One of the // resulting problems is that we try to deal with the tokenizer's // problems with numbers here rather than in the tokenizer. /\/ public class LispReader { protected LispTokenizer tk; /* * Constructors */ public LispReader(LispTokenizer tk) { this.tk = tk; } public LispReader(InputStream is) { this.tk = new LispStreamTokenizer(is); } public LispReader(Reader r) { this.tk = new LispStreamTokenizer(r); } // StringBufferInputStream supposedly does not correctly convert // characters to bytes (according to Java in a Nutshell) and in // any case has been deprecated. But nothing is said against // ByteArrayInputStream. public LispReader(String s) { this.tk = new LispStreamTokenizer( new StringBufferInputStream(s)); } /* * "Safe" reader -- avoids throwing RunTimeExceptions. */ public Object safeRead(Object errValue) { try { return readObject(); } catch (RuntimeException e) { // s.b. LispReadException ? Debug.noteException(e); return errValue; } } public Object safeRead() { return safeRead(Lisp.NIL); } /* * The reader */ private static final Object CLOSE = new Object(); // close paren public Object readObject() { Object result = reader(); if (result == CLOSE) throw new LispReadException("Extra close paren"); else return result; } protected Object reader() { int ttype; // Get a token try { ttype = tk.nextToken(); } catch (IOException e) { Debug.noteException(e); throw new LispReadException("Tokenizer error"); } // The tokenizer shouldn't return an EOL, because we don't // have that enabled. Debug.assert(!(ttype == tk.TT_EOL), "EOL token returned"); // Various cases ... if (ttype == tk.TT_EOF) { return Lisp.EOF; } else if (ttype == tk.TT_WORD) { String name = tk.sval; char start = name.charAt(0); if (Character.isDigit(start) || start == '-' || start == '+') return tryAsNumber(name); else if (name.equals("nil")) return Lisp.NIL; else return Symbol.intern(name); } else if (ttype == tk.TT_NUMBER) { return new Double(tk.nval); } else if (ttype == (int)'(') { return listreader(); } else if (ttype == (int)')') { return CLOSE; } else if (ttype == (int)'"') { return tk.sval; // a string } else { // A char of no special meaning. return Symbol.intern(String.valueOf((char)ttype)); } } // We end up with a Double if the first char is '+'. /\/ protected Object tryAsNumber(String name) { try { return Long.valueOf(name); } catch (NumberFormatException e) {} try { return Double.valueOf(name); } catch (NumberFormatException e) {} return Symbol.intern(name); } protected LList listreader() { int ttype; Cons head = new Cons(Lisp.NIL, Lisp.NIL); Cons at = head; Object elt; while (((elt = reader()) != CLOSE) && (elt != Lisp.EOF)) { at.cdr = new Cons(elt, Lisp.NIL); at = (Cons)at.cdr; } if (elt == Lisp.EOF) // throw new LispReadException("EOF in list."); throw new LispReadException("Missing close paren."); else return head.cdr; } }