(*
 * Abstype lispVal is a complete data abstration for Lisp values.  It has a
 * hidden rep, with alternatives for the atomic Lisp types, and a recursive rep
 * component for Lisp lists.  It provides public ops for the Lisp basics,
 * including car, cdr, cons, and more.
 *)

datatype lispVal =
    T |                 (* Lisp t as an ML enumeration literal *)
    NIL |               (* Lisp nil as an ML enumeration literal *)
    N of int |          (* Lisp number as an ML int (who cares about reals)*)
    S of string |       (* Lisp string as an ML string *)
    A of string |       (* Lisp symbolic atom as an ML string *)
    L of lispVal list   (* Lisp list as an ML list *)
(*with*)

    (*
     * One of the following three exceptions may occur during lispVal read.
     *)
    exception MissingDoubleQuote
    exception TooManyRightParens
    exception TooFewRightParens

    (*
     * Lisp nil is called "lnil" to avoid conflict with ML's nil.
     *)
    val lnil = NIL

    (*
     * Basic Lisp ops follow.  I.e., car, cdr, cons, etc.
     *)
    fun car(L(x)) = hd(x) |
	car(x) = lnil

    fun cdr(L(x::y)) = L(y) |
	cdr(x) = lnil

    fun cons(x, L(y)) = L(x::y) |
	cons(x, y) = lnil

    fun caar(x) = car(car(x))
    fun cadr(x) = car(cdr(x))
    fun caddr(x) = car(cdr(cdr(x)))
    fun cadddr(x) = car(cdr(cdr(cdr(x))))
    fun cddr(x) = cdr(cdr(x))
    fun cdddr(x) = cdr(cdr(cdr(x)))
    fun cddddr(x) = cdr(cdr(cdr(cdr(x))))

    fun eq(x:lispVal, y) = x = y

    fun lnull(NIL) = true |	(* as with nil, we prefix to avoid redef *)
	lnull(x) = false

    fun numberp(N(x)) = true |
	numberp(x) = false

    fun stringp(S(x)) = true |
	stringp(x) = false

    fun atom(A(x)) = true |
	atom(x) = false

    fun list(x) = L(x)

    fun assoc(name, alist) =
	if lnull(alist) then
	    lnil
	else if eq(name, caar(alist)) then
	    car(alist)
	else
	    assoc(name, cdr(alist))

    fun plus(N(x), N(y)) = N(x + y) |
	plus(x, y) = NIL

    fun minus(N(x), N(y)) = N(x - y) |
	minus(x, y) = NIL

    fun terpri() = print("\n")

    fun princ1(nil) = print("") |
        princ1(x::nil) = princ(x) |
        princ1(x::xs) = (princ(x); print(" "); princ1(xs))
      and
        princ(T) = print("T") |
        princ(NIL) = print("NIL") |
        princ(N(x)) = print(x) |
        princ(S(x)) = (print("\""); print(x); print("\"")) |
        princ(A(x)) = print(x) |
	princ(L(x)) = (print("("); princ1(x); print(")"))
(*
	princ(L(A("quote")::x)) = (print("'"); princ1(x)) |
	princ(L(A(a)::x)) = (print(a); print(" "); princ1(x)) |
        princ(L(x::nil)) = (print("("); princ(x); print(")")) |
        princ(L(x::y)) = (print("("); princ(x); princ1(y); print(")"))
 *)

    fun lprint(x) =
	(terpri();
	 princ(x))

    fun saneprint(x) =
	(princ(x);
	 terpri())


    (*
     * From here to the end of the abstype is the reader and its aux functions.
     *)

    fun iswhite(c) =
	(c = " ") orelse (c = "\t") orelse (c = "\n")

    fun isbreak(c) =
	iswhite(c) orelse (c = "(") orelse (c = ")")

    fun isdigit(c) =
	let
	    val d = ord(c) - 48
	in
	    (d >= 0) andalso (d <= 9)
	end

    fun moveOverWhitespace(l) =
	if null(l) then
	    nil
	else if iswhite(hd(l)) then
	    moveOverWhitespace(tl(l))
	else
	    l

    fun sreadstring(l, result) =
	if null(l) then
	    raise MissingDoubleQuote
	else if hd(l) = "\"" then
	    (tl(l), result)
	else
	    sreadstring(tl(l), result ^ hd(l))

    fun sreadatom1(l, num, str, isnum) =
        if (null(l)) orelse (isbreak(hd(l))) then
	    (l, num, str, isnum)
	else
	    let
		val char = hd(l)
		val rest = tl(l)
	    in
		if isdigit(char) andalso (str = "") then
		    sreadatom1(rest,
			num * 10 + (ord(char) - 48), str ^ char, true)
		else
		    sreadatom1(rest, 0, str ^ char, false)
	    end

    fun sreadatom(l) =
	let
	    val (l, num, str, isnum) = sreadatom1(l, 0, "", true)
	in
	    if isnum then
		(l, N(num))
	    else
	        if (str = "t") orelse (str = "T") then
		    (l, T)
		else if (str = "nil") orelse (str = "NIL") then
		    (l, NIL)
		else
		    (l, A(str))
	end

    fun sread1(l) =
	let
	    val l = moveOverWhitespace(l)
	in
	    if null(l) then
		(nil, NIL)
	    else if hd(l) = "'" then
		quoteit(tl(l))
	    else if hd(l) = "\"" then
		let
		    val (l, result) = sreadstring(tl(l), "")
		in
		    (l, S(result))
		end
	    else if hd(l) = "(" then
		let
		    val (l, result) = sreadlist(tl(l), nil)
		in
		    (l, L(result))
		end
	    else if hd(l) = ")" then
		raise TooManyRightParens
	    else
		sreadatom(l)
	end

    and quoteit(l) =
	let
	    val (l, result) = sread1(l)
	in
	    (l, L[A("quote"), result])
	end

    and sreadlist(l, result) =
	if null(l) then
	    raise TooFewRightParens
	else if iswhite(hd(l)) then
	    sreadlist(moveOverWhitespace(l), result)
	else if hd(l) = ")" then
	    (tl(l), result)
	else
	    let
		val (l, newresult) = sread1(l)
	    in
		sreadlist(l, result @ [newresult])
	    end

    fun sread0(l, result) =
	if null(l) then
	    (l, result)
	else
	    let
		val (l, newresult) = sread1(l)
	    in
		sread0(l, result @ [newresult])
	    end

    (*
     * Convert a whitespace-separated string of Lisp values into a list of
     * lispVals.  E.g., convert
     *
     *	    "a 10 \"x\" (a b c) t"
     *
     * into
     *
     *	    [ A("a"), N(10), S("x"), L[A("a"), A("b"), A("c")], T ]
     *)
    fun sread(s: string): lispVal list =
	#2(sread0(explode(s), nil))

    (*
     * Read an entire file into a string list, a la sread.
     *)
    fun readlistFromFile(file) =
        if end_of_stream(file) then nil
	else input(file,1) :: readlistFromFile(file)

    fun fread(filename): lispVal list =
        #2(sread0(readlistFromFile(open_in(filename)), nil))

    (*
     * Here's Lisp's actual read, i.e., read a Lisp val from stdin. 
     *)
    fun read() = nil (* LATER *) 


fun tester() =
    let
	val l =
	    hd(sread("( 10 20 x \"hi there\" t y (30 40 50 (60 70)) nil 80 )"))
    in
	(saneprint(car(l));
	saneprint(cdr(l));
	eq(cons(car(l), cdr(l)), l);
	saneprint(car(cdr(car(cdr(cdr(cdr(car(cdr(cdr(cdr(cdr(cdr(cdr(l
								))))))))))))));

	saneprint(hd(tl(sread("''a '(a b ''''c)")))))
    end;

tester();