/* Author: Jeff Dalton <J.Dalton@ed.ac.uk>
 * 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;
    }

}
