From: Andrew Burton
Subject: Lisp Parsing Documents
Date: 
Message-ID: <20040307171925.20400.00001025@mb-m04.aol.com>
I'm attempting to create a small implementation of Lisp inside of the virtual
world known as Second Life (http://www.secondlife.com/) and was wondering if
someone could point me at some decent documents on proper Lisp parsing.

Allow me to explain.  SL is a MMOG/pervasive world that it more of a 3D
development environment than it is a game.  You're encouraged to socialize and
build little gizmos or houses, as opposed to the common theme of wasting
monsters.  In fact there are no monsters.  But I'm getting sidetracked.

In SL, we have a mini-language called LSL, it's a C/Java-like language that
lets us script our builds.  We can make doors open, make cars drivable, and
even parse strings.  It's not a powerful language, compiles to a byte-code, and
gets really slow if you try to parse too many characters.  There are ways
around this, ways I've used to create a small Lisp parser, but my parser needs
help.

I've not Googled yet, thought I fully plan to sniff about on my own.  I'm
asking this as an initial step, because I'm hoping the collective group of
comp.lang.lisp can not only point me at docs, but fill in my gaps of Lisp
know-how by way of experience and opinion.  I've gotten a rudimentary parser
working, but my outside-in-method(1) doesn't seem to work well when I want to
add abilities like conditionals, loops, and recursion.

So, if anyone has any preferred documents, I would be ever so thankful for
links and suggestions. :)

---

1. My parser works like the following.

(+ 1 (+ 2 (+ 3 4)))
(+ 1 (+ 2 7))
(+ 1 9)
10

I keep thinking that may be backwards from the way Lisp parsers really work.

Andrew Burton - tuglyraisin at aol dot com
Felecia Station on Harvestgain - http://www.darkbeast.com/
(setq line "I will not tie up my muse.")
(defun punish (x) (if (> x 0) (progn (format t "~A~%" line) (punish (- x 1)))))
(punish 500)

From: Peter Seibel
Subject: Re: Lisp Parsing Documents
Date: 
Message-ID: <m34qt0yzk1.fsf@javamonkey.com>
···········@aol.commcast (Andrew Burton) writes:

> I'm attempting to create a small implementation of Lisp inside of
> the virtual world known as Second Life (http://www.secondlife.com/)
> and was wondering if someone could point me at some decent documents
> on proper Lisp parsing.

Read Chapter 23 of the Common Lisp Hyperspec[1] until it all makes
sense. The beauty of Lisp syntax is it's so darn easy to parse.

> Allow me to explain. SL is a MMOG/pervasive world that it more of a
> 3D development environment than it is a game. You're encouraged to
> socialize and build little gizmos or houses, as opposed to the
> common theme of wasting monsters. In fact there are no monsters. But
> I'm getting sidetracked.
>
> In SL, we have a mini-language called LSL, it's a C/Java-like
> language that lets us script our builds. We can make doors open,
> make cars drivable, and even parse strings. It's not a powerful
> language, compiles to a byte-code, and gets really slow if you try
> to parse too many characters. There are ways around this, ways I've
> used to create a small Lisp parser, but my parser needs help.

So you're writing your Lisp implementation in LSL? Depending on how
you are planning to use your Lisp-inside the world, you might also be
able to write a compiler in Common Lisp that emits bytecodes for
whatever VM SL runs on. If you really wanted to go to town you could
ues Common Lisp to write a Lispy language that runs on the SL VM and
then write a compiler for an even simpler Lisp in that language (which
you need if you want folks in SL to be able to enter bits of Lisp code
and have it run.)

> I've not Googled yet, thought I fully plan to sniff about on my own.
> I'm asking this as an initial step, because I'm hoping the
> collective group of comp.lang.lisp can not only point me at docs,
> but fill in my gaps of Lisp know-how by way of experience and
> opinion. I've gotten a rudimentary parser working, but my
> outside-in-method(1) doesn't seem to work well when I want to add
> abilities like conditionals, loops, and recursion.

You probably don't want to be dealing with high-level constructs like
conditionals, etc. at the same time as you're parsing. Consider that
all the Lisp reader does is turn a stream of characters into Lisp
objects, i.e. lists, numbers, symbols and a few others (which in
Common Lisp are all handled by reader macros.)

Once you've got a list out of the reader *then* you start applying
meaning to the elements of the list. For ideas about how to go about
that you'll want to read section 3.1.2.1 of the Hyperspec[2]


> So, if anyone has any preferred documents, I would be ever so
> thankful for links and suggestions. :)

> 1. My parser works like the following.
>
> (+ 1 (+ 2 (+ 3 4)))
> (+ 1 (+ 2 7))
> (+ 1 9)
> 10

You're combining parsing with evaluation. Which, as you observed is
not going to work so well when you don't necessarily want to evaluate
all the sub-elements. Read the stuff in the Hypespec I pointted you to
and meditate on it for a while. I suspect it'll help.

-Peter

[1] <http://www.lispworks.com/reference/HyperSpec/Body/23_.htm>
[2] <http://www.lispworks.com/reference/HyperSpec/Body/03_aba.htm>

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Andrew Burton
Subject: Re: Lisp Parsing Documents
Date: 
Message-ID: <20040307214140.22628.00001061@mb-m10.aol.com>
>Read Chapter 23 of the Common Lisp 
> Hyperspec[1] until it all makes
> sense. The beauty of Lisp syntax is it's 
> so darn easy to parse.

I will read that, thank you.  And, yes, Lisp is very easy to parse!  :)

>Depending on how you are planning to 
> use your Lisp-inside the world, you might 
> also be able to write a compiler in 
> Common Lisp that emits bytecodes for
> whatever VM SL runs on.

We don't have access to either the byte code spec or to a way of uploading byte
code.  Anything compiled, so far, has to be compiled in-world from LSL.  As
clunky as it may be, for now, my best plan was for a kind of Lisp interpreter.

> You probably don't want to be dealing 
> with high-level constructs like > conditionals, etc. at the same time as 
> you're parsing. 

I'm quickly figuring that out.  My knowledge of language writing is rather
limited.  The Hyperspec should, and I imagine will, clear up my ignorance
speedily.  Thank you for the link.

Thanks.



Andrew Burton - tuglyraisin at aol dot com
Felecia Station on Harvestgain - http://www.darkbeast.com/
(setq line "I will not tie up my muse.")
(defun punish (x) (if (> x 0) (progn (format t "~A~%" line) (punish (- x 1)))))
(punish 500)
From: Peter Seibel
Subject: Re: Lisp Parsing Documents
Date: 
Message-ID: <m3znaryhae.fsf@javamonkey.com>
···········@aol.commcast (Andrew Burton) writes:

>>Read Chapter 23 of the Common Lisp 
>> Hyperspec[1] until it all makes
>> sense. The beauty of Lisp syntax is it's 
>> so darn easy to parse.
>
> I will read that, thank you.  And, yes, Lisp is very easy to parse!  :)
>
>>Depending on how you are planning to 
>> use your Lisp-inside the world, you might 
>> also be able to write a compiler in 
>> Common Lisp that emits bytecodes for
>> whatever VM SL runs on.
>
> We don't have access to either the byte code spec or to a way of
> uploading byte code. Anything compiled, so far, has to be compiled
> in-world from LSL. As clunky as it may be, for now, my best plan was
> for a kind of Lisp interpreter.

I see. So here's another hint (though you should be able to pick this
up from the Hyperspec after sufficient meditation): You're definitely
going to want to figure out how to implement a cons cell in LSL. If
you can do that and can implement READ-CHAR you're well on your way to
having all the infrastructure you need for writing a primitive Lisp
interpreter.

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Lars Brinkhoff
Subject: Re: Lisp Parsing Documents
Date: 
Message-ID: <85oer7n4no.fsf@junk.nocrew.org>
Peter Seibel <·····@javamonkey.com> writes:
> ···········@aol.commcast (Andrew Burton) writes:
> > I'm attempting to create a small implementation of Lisp [...]  and
> > was wondering if someone could point me at some decent documents
> > on proper Lisp parsing.
> Read Chapter 23 of the Common Lisp Hyperspec until it all makes
> sense.

There's also Chapter 2, and, in particular, the reader algorithm:
  http://clhs.lisp.se/Body/02_b.htm

-- 
Lars Brinkhoff,         Services for Unix, Linux, GCC, HTTP
Brinkhoff Consulting    http://www.brinkhoff.se/
From: Peter Seibel
Subject: Re: Lisp Parsing Documents
Date: 
Message-ID: <m3u10zxntr.fsf@javamonkey.com>
Lars Brinkhoff <·········@nocrew.org> writes:

> Peter Seibel <·····@javamonkey.com> writes:
>> ···········@aol.commcast (Andrew Burton) writes:
>> > I'm attempting to create a small implementation of Lisp [...]  and
>> > was wondering if someone could point me at some decent documents
>> > on proper Lisp parsing.
>> Read Chapter 23 of the Common Lisp Hyperspec until it all makes
>> sense.
>
> There's also Chapter 2, and, in particular, the reader algorithm:
>   http://clhs.lisp.se/Body/02_b.htm

Yes indeed. You should read that before Chapter 23.

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Will Hartung
Subject: Re: Lisp Parsing Documents
Date: 
Message-ID: <c2itrj$1se0c7$1@ID-197644.news.uni-berlin.de>
"Andrew Burton" <···········@aol.commcast> wrote in message
··································@mb-m04.aol.com...
> I've not Googled yet, thought I fully plan to sniff about on my own.  I'm
> asking this as an initial step, because I'm hoping the collective group of
> comp.lang.lisp can not only point me at docs, but fill in my gaps of Lisp
> know-how by way of experience and opinion.  I've gotten a rudimentary
parser
> working, but my outside-in-method(1) doesn't seem to work well when I want
to
> add abilities like conditionals, loops, and recursion.

As was mentioned, you're confusing parsing and evaluation.

Recall that there are three major steps in computer languages.

1) Lexing which converts the text stream into higher level constructs
typically known as tokens.

2) Parsing with throws context into the mix on top of the tokens, i.e. make
sure that we're getting a token stream that makes sense.

3) Evaluation, which takes a parsed token stream and performs Real Work.

In Lisp like languages, the Lexing part is more dominant than the Parsing
part. At least, that's what it appears like at the beginning. The detail is
that unlike languages like C where the lexing and parsing are strictly, and
statically, defined, Lisp makes both parts pretty dynamic. The Lisp Reader
and the Macro facility hilite these two features.

See, the key is that Lisp evaluates/interprets/compiles S-Expressions.
Lists. The language really has little concept of letters, spaces,
punctionation, etc. Simply put, Lisp itself doesn't care HOW those SExprs
are created, only that they exist, and then it runs from there. The real
elements of a Lisp program are Lists, Symbols, Numbers, and Strings.

So, the Reader and, in fact, Macros, actually lie on the fringe of the
language (though that is not to say they're not vital or important).

In most typical languages, lets say you have the statement:

IF <expr> THEN <statement>

The Lexer will turn a string of text into a list of tokens: IF-keyword
<expr> THEN-keyword <statement>.

A Parsing layer would then try and make sense of that and might create an
internal structure IF-STATEMENT with two elements, EXPR and STATEMENT. But
in order to do that, it needs to know what the IF statement will look like,
and most parsers do in fact know this.

That's why if you erroneously typed in "IF THEN <statement>" you may well
get an error from the Parser, versus the Evaluator, saying that the IF
statement is wrong.

At a high level, the Lisp Parser has very little idea of things like IF
statements. This is why Lisp is so easy to Parse, and why the Reader is
really more of a Lexer who's job is to correctly interpret (IF (> A B)
(PRINT A) (PRINT B)) as a simple 4 element List containing 1 atom and 3
sub-lists. It sure LOOKS like an IF statement to the human eye, but there's
no Lisp Parser to enforce that. In Lisp, its the Evaluators job to make
heads or tails out of a List like that.

So, the first step is writing the routine that converts SExpr formatted
strings into Lists of Numbers, Strings, Symbols and, of course, other Lists,
and I'm guessing you've already done that. Once you've done that, it's time
to move on to the Evaluation phase.

In Lisp rather than an actual Parser, we have this concept of "special
forms". Special forms are those SExprs that break that standard evaluation
rule of looking up the function of the first atom, and passing it a list of
the evaluation of all of the forms following it.

Generic Lisp looks like this:

(FUNCNAME (+ 1 2) (- 3 4) (OTHERFUNC A))

FUNCNAME refers to a function of somekind, and the 3 following forms are
evalutated on their own and the results of those evaluations are passed as
parameters to the routine referenced by FUNCNAME.

Now, IF is an example of what could be considered a Special Form (whether it
technically is in the CL spec is a detail not important here) because in (IF
(> A B) (PRINT A) (PRINT B)), we only want to evaluate the 3rd and 4th forms
based upon the evaluation of the 2nd form. So, standard Lisp evaluation
rules do not apply to IF.

Note that things like +, -, *, etc are not typically Special Forms, but
simply functions (though obviously many compilers "know" about these
functions and give them special consideration).

So, when evaluating Lisp, you have a main routine that looks for Special
Forms so they can be handled appropriately, and if it doesn't see a Special
Form, it simply makes a Function Call.

In Lisp this Function Call is refered to as the APPLY function. APPLY takes
a piece of code and calls it with a list of arguments.

For example, (APPLY #'+ '(2 2)) adds 2 + 2. i.e It calls the + function with
2 parameters.

Here is a very crude "EVAL" function:

(defun evaluate (form)
  (if (consp form) ;; Working on a list?
      (let ((first (car form)) ;; first element of the list
            (args (cdr form))) ;; rest of the list
        (if (consp first) ;; If the first element is a List itself
            (error "Expecting an atom as first element of form: ~a" form)
          (cond
           ((eq first 'IF) ;; We have an IF statment
            (evaluate-if args))
           (t ;; IF is the only special form we have here, so we apply
everything else
              (apply-function first args)))))
    form)) ;; Since the Form isn't a list, it "must" be a Number, String, or
Symbol.
           ;; These are "self evaluating" and simply return themselves

(defun evaluate-if (args)
  ;; (if <expr> <true> <false>?)
  (let ((l (length args)))
    (when (or (< l 0) (> 2 l)) ;; IFs must have 1 or 2 elements
      (error "~A has the wrong number of elements for an IF." args))
    (let ((expr (first args)) ;; This breaks the IF into its 3 component
parts
          (true (second args))
          (false (if (= (length args) 3) (third args) nil)))
      (if (evaluate expr) ;; Note, we simpy recursively call the evaluate
routine
          (evaluate true) ;; and have to do the work on the truth
        (when false ;; and false parts
          (eval false))))))

(defun apply-function (name args)
  (if (not (symbolp name))
      (error "~A is not a symbol." name))
  (let ((func (function name))) ;; We let CL look up our function for us
    (if (not func) ;; No function, no worky
        (error "Could not find a function for ~A" name))
    (let ((evald-arguments (mapcan #'evaluate args))) ;; EVALUATE each
argument in passed list
      (apply func evald-arguments)))) ;; have CL call our function with the
results

This is simple because simple "Lisp in Lisp" IS simple, we steal a LOT of
infrastructure, notably how to call functions, memory management, symbol
table lookup, etc. etc.

But you can see from this that if you want to add your own special forms,
notably things like PROGN and DO, and even LAMBDA, you can "simply" throw
them into the COND structure and expand the language that way. Also note the
that EVALUATE-IF and APPLY-FUNCTION actually check their own parameters
here. In many typical simple languages, a Parser would have found and
complained about these things earlier on.

That is the very simplistic basics of a Lisp evaluator, and with that said,
if it were me doing this, I'd move the Lisp out of your little language and
compile SExprs INTO that language rather than trying to interpret it within
that language. That way you'll get the best performance vs running an
interpreter on top of an interpreter.

Compiling lispy languages into other high level and scripting languages is
actually simpler than writing an interpreter, because the host language does
a lot of the basic infrastructure for you.

Regards,

Will Hartung
(·····@msoft.com)
From: Drew McDermott
Subject: Re: Lisp Parsing Documents
Date: 
Message-ID: <c2l9ht$rmv$1@news.wss.yale.edu>
Andrew Burton wrote:

> I'm attempting to create a small implementation of Lisp inside of the virtual
> world known as Second Life (http://www.secondlife.com/) and was wondering if
> someone could point me at some decent documents on proper Lisp parsing.

Like most Lisp hackers who use Java, I had to implement a large fraction 
of Lisp in Java to order to avoid hysteria.  The implementation includes 
a simple reader (no macro characters, for instance).   It needs an 
auxiliary class called a PeekReader that adds "peek" to PushBackReader:

import java.io.*;

public class PeekReader extends PushbackReader
{
   PeekReader(Reader s)
   {
     super(s);
   }

   int peek() throws IOException
   {
     int ch = read();
     unread(ch);
     return ch;
   }
}

Here's the main file.  It has a reader, a printer, and a bunch of other 
stuff.  I know there are more complete implementations of Lisp in Java 
out there, but perhaps the simplicity of this one will fit your needs 
better.  (The 'main' method is for debugging; you may ignore it, but it 
does happen to read and print expressions from System.in.  It's a 
read-no.eval-print loop.)

    -- Drew McDermott


import java.io.*;
import java.util.*;

public class Lisp
{
   public static void main(String[] argv)
   {
     Object x;
     PrintWriter out = new PrintWriter(System.out);
     PeekReader in = new PeekReader(new InputStreamReader(System.in));
     while (true)
       {
         out.print("> ");
         out.flush();
         //System.out.println("?>");
         try
           {
             x = read(in);
             print(x, out);
             out.println("");
             out.flush();
           }
         catch(IOException e)
           {
             out.println("Exception " + e);
           }
       }
   }

   static class List
   {
     private Object a;
     private List d;

     List(Object a1, List d1)
     {
       a = a1;
       d = d1;
     }

     void printit(PrintWriter srm)
     {
       srm.write('(');
       for (List d = this; !empty(d); d = cdr(d))
         {
           print(car(d), srm);
           if (!empty(cdr(d)))
             srm.write(' ');
         }
       srm.write(')');
     }
   }

   static class Symbol
   {
     String name;
     static Hashtable symtab = new Hashtable();
     Object store;

     private Symbol(String n)
     {
       name = n;
     }

     static Symbol make(String n)
     {
       return make(n, true);
     }

     static Symbol make(String n, boolean in_table)
     {
       Symbol prev = null, newsym;
       if (in_table)
         prev = (Symbol)(symtab.get(n));
       if (prev == null)
         {
           newsym = new Symbol(n);
           if (in_table) symtab.put(n, newsym);
           return newsym;
         }
       else
         return prev;
     }

     static boolean is(Object x)
     {
       return (x instanceof Symbol);
     }

     public String toString()
     {
       return name;
     }

     void printit(PrintWriter srm)
     {
       srm.print(name);
     }
   }

   public static final List nil = null;

   static boolean is_pair(Object x)
   {
     return x instanceof List;
   }

   static boolean is_list(Object x)
   {
     return (x == null || x instanceof List);
   }

   public static boolean is_nil(List l)
   {
     return (l == nil);
   }

   public static boolean empty(List x)
   {
     return x==null;
   }

   public static List cons(Object a, List d)
   {
     return new List(a, d);
   }

   public static Object car(List x)
   {
     return x.a;
   }

   public static void set_car(List x, Object v)
   {
     x.a = v;
   }

   public static List cdr(List x)
   {
     return x.d;
   }

   public static void set_cdr(List x, List v)
   {
     x.d = v;
   }

   public static Object caar(List x)
   {
     return Lisp.car((List)(Lisp.car(x)));
   }

   public static Object cadr(List x)
   {
     return Lisp.car(Lisp.cdr(x));
   }

   public static List cddr(List x)
   {
     return Lisp.cdr(Lisp.cdr(x));
   }

   public static Object caddr(List x)
   {
     return Lisp.car(Lisp.cdr(Lisp.cdr(x)));
   }

   public static List list1(Object x)
   {
     return cons(x, nil);
   }

   public static List list2(Object x, Object y)
   {
     return cons(x, list1(y));
   }

   public static List list3(Object x, Object y, Object z)
   {
     return cons(x, list2(y,z));
   }

   public static List list4(Object x, Object y, Object z, Object w)
   {
     return cons(x, list3(y,z,w));
   }

   public static List append (List l1, List l2)
   {
     if (empty(l1))
       return l2;
     else
       return cons(car(l1),
                   append(cdr(l1), l2));
   }

   public static List append3 (List l1, List l2, List l3)
   {
     return append(l1, append(l2, l3));
   }

   public static List remove(Object x, List l)
   {
     if (empty(l))
       return nil;
     else if (x.equals(car(l)))
       return cdr(l);
     else
       return cons(car(l), remove(x, cdr(l)));
   }

   // Remove 1 occurrence, destructively
   public static List dremove(Object x, List l)
   {
     if (empty(l))
       return nil;
     else if (x.equals(Lisp.car(l)))
       return cdr(l);
     else
       {
         set_cdr(l, dremove(x, cdr(l)));
         return l;
       }
   }

   public static int length(List l)
   {
     int n = 0;
     while (!empty(l))
       {
         l = cdr(l);
         n++;
       }
     return n;
   }

   public static boolean member(Object x, List l)
   {
     while (!is_nil(l))
       {
         if (x.equals(car(l)))
           return true;
         l = cdr(l);
       }
     return false;
   }

   public static List reverse(List l)
   {
     List r = nil;
     while (l != nil)
       {
         r = cons(car(l), r);
         l = cdr(l);
       }
     return r;
   }

   public static List copy(List l)
   {
     if (is_nil(l))
       return nil;
     else
       return cons(car(l), copy(cdr(l)));
   }

   public static Object nth(int n, List l)
   {
     l = nthcdr(n, l);
     return car(l);
   }

   public static List nthcdr(int n, List l)
   {
     while (n > 0 && l != nil)
       {
         n--;
         l = cdr(l);
       }
     return l;
   }

   // The last n elements of l
   public static List back(List l, int n)
   {
     int k = length(l) - n;
     while (k > 0)
       {
         l = cdr(l);
         k--;
       }
     return l;
   }

   // The first n elements of l
   public static List front(List l, int n)
   {
     if (n <= 0)
       return nil;
     else
       return cons(car(l), front(cdr(l), n-1));
   }

   public static boolean equal(Object l1, Object l2)
   {
     if (is_list(l1))
       {
         if (is_list(l2))
           {
             return Lisp.equal(Lisp.car((List)l1),
                               Lisp.car((List)l2))
                    && Lisp.equal(Lisp.cdr((List)l1),
                                  Lisp.cdr((List)l2));
           }
         else
           return false;
       }
     else if (is_list(l2))
       return false;
     else return l1 == l2;
   }

   public static int equal_hash(Object x)
   {
     if (is_list(x))
       {
         int c = 0;
         for (List l = (List)x; !Lisp.is_nil(l); l = Lisp.cdr(l))
           c += Lisp.equal_hash(Lisp.car(l));
         return c;
       }
     else
       return x.hashCode();
   }

   static List from_vec(Vector v)
   {
     List res = nil;
     for (int i = v.size() - 1; i >= 0; i--)
       res = cons(v.elementAt(i), res);
     return res;
   }

   static List merge(List l1, List l2, Comparator cc)
   {
     if (empty(l1))
       return l2;
     else if (empty(l2))
       return l1;
     else if (cc.compare(car(l1), car(l2)) < 0)
       return cons(car(l1), merge(cdr(l1), l2, cc));
     else
       return cons(car(l2), merge(l1, cdr(l2), cc));
   }

   static List sort(List l, Comparator cc)
   {
     if (empty(l) || empty(cdr(l)))
       return l;
     else
       return merge(sort(everyother(l), cc),
                    sort(everyother(cdr(l)), cc),
                    cc);
   }

   static List everyother(List l)
   {
     if (empty(l))
       return nil;
     else
       return cons(car(l),
                   (empty(cdr(l))) ? nil : everyother(cdr(cdr(l))));
   }

   static Object read_from_string(String s) throws IOException
   {
     return read(new PeekReader(new StringReader(s)));
   }

   static Object read(PeekReader srm) throws IOException
   {
     Object ob = read_one(srm);
     if (ob == end_of_file_marker)
       throw new EOFException("End of file during Lisp.read");
     else if (ob == list_end_marker)
       throw new List_syntax_error("Too many close parens");
     else return ob;
   }

   // Called to read a piece of something.  Since we may be in the middle
   // of a list, it doesn't mind seeing ')'.
   static Object read_one(PeekReader srm) throws IOException
   {
     int ch, next;
     // int parenlev = 0;
     do
       {
          ch = srm.read();
       }
     while (ch != -1 && Character.isWhitespace((char)ch));
     if (ch == -1)
       return end_of_file_marker;
     else if (ch == '(')
       return read_list(srm);
     else if (ch == ')')
       return list_end_marker;
     else if (ch == '+' || ch == '-')
       {
         next = srm.peek();
         if (next == -1 || is_delim((char)next))
           return Symbol.make(String.valueOf((char)ch));
         else if (Character.isDigit((char)next))
           return read_num(srm, ch == '+');
         else
           {
             srm.unread(ch);
             return read_sym(srm);
           }
       }
     else if (ch == '"')
       return read_string(srm);
     else
       {
         srm.unread(ch);
         if (Character.isDigit((char)ch))
           return read_num(srm, true);
         else
           return read_sym(srm);
       }
   }

   static class Read_marker
   {
     int id;
     Read_marker(int i)
     {
       id = i;
     }
   }

   static Read_marker list_end_marker = new Read_marker(0);
   static Read_marker end_of_file_marker = new Read_marker(-1);

   static List read_list(PeekReader srm) throws IOException, 
List_syntax_error
   {
     Object r;
     List l = nil;
     //System.out.print("!");
     do
       {
         r = read_one(srm);
         if (r == end_of_file_marker)
           throw new List_syntax_error("Too many open parens");
         else if (r != list_end_marker)
           l = cons(r, l);
         //System.out.print("<" + r + "/" + length(l) + ">");
       }
     while (r != list_end_marker);
     return reverse(l);
   }

   static String read_string(PeekReader srm) throws IOException, 
List_syntax_error
   {
     int ch;
     StringBuffer buf = new StringBuffer();
     do
       {
         ch = srm.read();
         if (ch == '"')
           return buf.toString();
         else if (ch == '\\')
           ch = srm.read();
         if (ch != -1)
           buf.append((char)ch);
       }
     while (ch != -1);
     throw new List_syntax_error("End-of-file in middle of string");
   }

   static Number read_num(PeekReader srm, boolean positive)
          throws IOException, List_syntax_error
   {
     int ch, d;
     int iv = 0; float rv = (float)0.0;
     boolean is_float = false;
     do
       {
         ch = srm.read();
         if (ch == -1 || is_delim((char)ch))
           {
             srm.unread(ch);
             return  is_float ? ((Number)(new Float(positive? rv : -rv)))
                              : ((Number)(new Integer(positive? iv : -iv)));
           }
         else if (Character.isDigit((char)ch))
           {
             d = Character.digit((char)ch, 10);
             if (is_float)
               rv += ((float)d) / 10;
             else
               iv = iv * 10 + d;
           }
         else if (ch == '.')
           {
             if (is_float)
               throw new List_syntax_error("Too many decimal points in 
number");
             rv = (float)iv;
             is_float = true;
           }
         else
           throw new List_syntax_error("Ill-formed number");
       }
     while (true);
   }

   static Symbol read_sym(PeekReader srm)
          throws IOException, List_syntax_error
   {
     int ch;
     String s = "";
     do
       {
         ch = srm.read();
         if (ch == -1 || is_delim((char)ch))
           {
             srm.unread(ch);
             return Symbol.make(s);
           }
         else
           s += String.valueOf((char)ch);
       }
     while (true);
   }

   static boolean is_delim(char ch)
   {
     return Character.isWhitespace(ch)
            || ch == '(' || ch == ')';
   }

   static class List_syntax_error extends IOException
   {
     String desc;
     List_syntax_error(String d)
     {
       desc = d;
     }
   }

   static void print(Object x, PrintWriter srm)
   {
     if (x == null)
       srm.print("()");
     else if (x instanceof Number)
       srm.print(x.toString());
     else if (x instanceof String)
       {
         String s = (String)x;
         char ch;
         srm.print("\"");
         for (int i=0; i < s.length(); i++)
           {
             ch = s.charAt(i);
             if (ch == '\\' || ch == '"')
               srm.print("\\");
             srm.print(ch);
           }
         srm.print("\"");
       }
     else if (x instanceof List)
       ((List)x).printit(srm);
     else if (x instanceof Symbol)
       ((Symbol)x).printit(srm);
     else srm.print("??" + x + "??");
   }
}