2003/04/30 04:39:41
authormegacz <megacz@xwt.org>
Fri, 30 Jan 2004 06:59:34 +0000 (06:59 +0000)
committermegacz <megacz@xwt.org>
Fri, 30 Jan 2004 06:59:34 +0000 (06:59 +0000)
darcs-hash:20040130065934-2ba56-b65e30e8f1fc3c0a1efae2ea1bd5f189388fc2b5.gz

src/org/xwt/js/Lexer.java
src/org/xwt/js/Parser.java

index 172c593..93586ed 100644 (file)
@@ -23,6 +23,13 @@ import java.io.*;
 
 class Lexer {
 
+    public static void main(String[] s) throws Exception {
+       Lexer l = new Lexer(new InputStreamReader(System.in));
+       int tok = 0;
+       while((tok = l.getToken()) != -1)
+           System.out.println(codeToString[tok]);
+    }
+
     private SmartReader in;
     private boolean pushedBack = false;
 
@@ -31,97 +38,114 @@ class Lexer {
     public String string;
 
     public Lexer(Reader r) throws IOException { in = new SmartReader(r); }
-    public int peekToken() throws IOException { int ret = peekToken(); pushBackToken(); return ret; }
+    public int peekToken() throws IOException { int ret = getToken(); pushBackToken(); return ret; }
     public void pushBackToken() { if (pushedBack) throw new Error("can't push back twice"); pushedBack = true; }
 
     // Token Constants //////////////////////////////////////////////////////////
 
     public final static int
         EOL          = 1,   // end of line
-        RETURN       = 5,   // return
-        GOTO         = 6,   // goto
-        BITOR        = 11,  // |
-        ASSIGN_BITOR = 211, // |=
-        BITXOR       = 12,  // ^
-        ASSIGN_BITXOR= 212, // ^=
-        BITAND       = 13,  // &
-        ASSIGN_BITAND= 213, // &=
-        EQ           = 14,  // ==
-        NE           = 15,  // !=
-        LT           = 16,  // <
-        LE           = 17,  // <=
-        GT           = 18,  // >
-        GE           = 19,  // >=
-        LSH          = 20,  // <<
-        ASSIGN_LSH   = 220, // <<=
-        RSH          = 21,  // >>
-        ASSIGN_RSH   = 221, // >>=
-        URSH         = 22,  // >>>
-        ASSIGN_URSH  = 222, // >>>=
-        ADD          = 23,  // +
-        ASSIGN_ADD   = 223, // +=
+        RETURN       = 2,   // return
+        GOTO         = 3,   // goto
+        BITOR        = 4,   // |
+        ASSIGN_BITOR = 5,   // |=
+        BITXOR       = 6,   // ^
+        ASSIGN_BITXOR= 7,   // ^=
+        BITAND       = 8,   // &
+        ASSIGN_BITAND= 9,   // &=
+        EQ           = 10,  // ==
+        NE           = 11,  // !=
+        LT           = 12,  // <
+        LE           = 13,  // <=
+        GT           = 14,  // >
+        GE           = 15,  // >=
+        LSH          = 16,  // <<
+        ASSIGN_LSH   = 17, // <<=
+        RSH          = 18,  // >>
+        ASSIGN_RSH   = 19, // >>=
+        URSH         = 20,  // >>>
+        ASSIGN_URSH  = 21, // >>>=
+        ADD          = 22,  // +
+        ASSIGN_ADD   = 23, // +=
         SUB          = 24,  // -
-        ASSIGN_SUB   = 224, // -=
-        MUL          = 25,  // *
-        ASSIGN_MUL   = 225, // *=
-        DIV          = 26,  // /
-        ASSIGN_DIV   = 226, // /=
-        MOD          = 27,  // %
-        ASSIGN_MOD   = 227, // %=
-        BITNOT       = 28,  // ~
-        ASSIGN_BITNOT= 228, // ~=
-        DELPROP      = 31,  // delete
-        TYPEOF       = 32,  // typeof
-        NAME         = 44,  // *** identifiers ***
-        NUMBER       = 45,  // *** numeric literals ***
-        STRING       = 46,  // *** string literals ***
-        NULL         = 49,  // null
-        THIS         = 50,  // this
-        FALSE        = 51,  // false
-        TRUE         = 52,  // true
-        SHEQ         = 53,  // ===
-        SHNE         = 54,  // !==
-        THROW        = 62,  // throw
-        IN           = 63,  // in
-        INSTANCEOF   = 64,  // instanceof
-        TRY          = 75,  // try
-        SEMI         = 89,  // ;
-        LB           = 90,  // [
-        RB           = 91,  // ]
-        LC           = 92,  // {
-        RC           = 93,  // }
-        LP           = 94,  // (
-        RP           = 95,  // )
-        COMMA        = 96,  // ,
-        ASSIGN       = 97,  // =
-        HOOK         = 98,  // ?
-        COLON        = 99,  // :
-        OR           = 100, // ||
-        AND          = 101, // &&
-        INC          = 106, // ++
-        DEC          = 107, // --
-        DOT          = 108, // .
-        FUNCTION     = 110, // function
-       IF          = 113,  // if keyword
-        ELSE         = 114, // else keyword
-        SWITCH       = 115, // switch keyword
-        CASE         = 116, // case keyword
-        DEFAULT      = 117, // default keyword
-        WHILE        = 118, // while keyword
-        DO           = 119, // do keyword
-        FOR          = 120, // for keyword
-        BREAK        = 121, // break keyword
-        CONTINUE     = 122, // continue keyword
-        VAR          = 123, // var keyword
-        WITH         = 124, // with keyword
-        CATCH        = 125, // catch keyword
-        FINALLY      = 126, // finally keyword
-        RESERVED     = 127, // reserved keywords
-        NOP          = 128, // NOP
-        VOID         = 132, // void keyword
-        MOD_ASSIGN   = 133, // %=
-        BANG         = 134, // %=
-        ASSERT       = 150; // assert keyword
+        ASSIGN_SUB   = 25, // -=
+        MUL          = 26,  // *
+        ASSIGN_MUL   = 27, // *=
+        DIV          = 28,  // /
+        ASSIGN_DIV   = 29, // /=
+        MOD          = 30,  // %
+        ASSIGN_MOD   = 31, // %=
+        BITNOT       = 32,  // ~
+        ASSIGN_BITNOT= 33, // ~=
+        DELPROP      = 34,  // delete
+        TYPEOF       = 35,  // typeof
+        NAME         = 36,  // *** identifiers ***
+        NUMBER       = 37,  // *** numeric literals ***
+        STRING       = 38,  // *** string literals ***
+        NULL         = 39,  // null
+        THIS         = 40,  // this
+        FALSE        = 41,  // false
+        TRUE         = 42,  // true
+        SHEQ         = 43,  // ===
+        SHNE         = 44,  // !==
+        THROW        = 45,  // throw
+        IN           = 46,  // in
+        INSTANCEOF   = 47,  // instanceof
+        TRY          = 48,  // try
+        SEMI         = 49,  // ;
+        LB           = 50,  // [
+        RB           = 51,  // ]
+        LC           = 52,  // {
+        RC           = 53,  // }
+        LP           = 54,  // (
+        RP           = 55,  // )
+        COMMA        = 56,  // ,
+        ASSIGN       = 57,  // =
+        HOOK         = 58,  // ?
+        COLON        = 59,  // :
+        OR           = 60, // ||
+        AND          = 61, // &&
+        INC          = 62, // ++
+        DEC          = 63, // --
+        DOT          = 64, // .
+        FUNCTION     = 65, // function
+       IF           = 66,  // if keyword
+        ELSE         = 67, // else keyword
+        SWITCH       = 68, // switch keyword
+        CASE         = 69, // case keyword
+        DEFAULT      = 70, // default keyword
+        WHILE        = 71, // while keyword
+        DO           = 72, // do keyword
+        FOR          = 73, // for keyword
+        BREAK        = 74, // break keyword
+        CONTINUE     = 75, // continue keyword
+        VAR          = 76, // var keyword
+        WITH         = 77, // with keyword
+        CATCH        = 78, // catch keyword
+        FINALLY      = 79, // finally keyword
+        RESERVED     = 80, // reserved keywords
+        NOP          = 81, // NOP
+        VOID         = 82, // void keyword
+        MOD_ASSIGN   = 83, // %=
+        BANG         = 84, // %=
+        ASSERT       = 85; // assert keyword
+
+    public static final int MAX_TOKEN = ASSERT;
+
+    public final static String[] codeToString = new String[] {
+       "0", "EOL", "RETURN", "GOTO", "BITOR", "ASSIGN_BITOR",
+       "BITXOR", "ASSIGN_BITXOR", "BITAND", "ASSIGN_BITAND", "EQ",
+       "NE", "LT", "LE", "GT", "GE", "LSH", "ASSIGN_LSH", "RSH",
+       "ASSIGN_RSH", "URSH", "ASSIGN_URSH", "ADD", "ASSIGN_ADD",
+       "SUB", "ASSIGN_SUB", "MUL", "ASSIGN_MUL", "DIV", "ASSIGN_DIV",
+       "MOD", "ASSIGN_MOD", "BITNOT", "ASSIGN_BITNOT=", "DELPROP",
+       "TYPEOF", "NAME", "NUMBER", "STRING", "NULL", "THIS", "FALSE",
+       "TRUE", "SHEQ", "SHNE", "THROW", "IN", "INSTANCEOF", "TRY",
+       "SEMI", "LB", "RB", "LC", "RC", "LP", "RP", "COMMA", "ASSIGN",
+       "HOOK", "COLON", "OR", "AND", "INC", "DEC", "DOT", "FUNCTION",
+       "IF", "ELSE", "SWITCH", "CASE", "DEFAULT", "WHILE", "DO",
+       "FOR", "BREAK", "CONTINUE", "VAR", "WITH", "CATCH", "FINALLY",
+       "RESERVED", "NOP", "VOID", "MOD_ASSIGN", "BANG", "ASSERT" };
 
 
     // Predicates ///////////////////////////////////////////////////////////////////////
@@ -156,167 +180,98 @@ class Lexer {
     
     // Token Subtype Handlers /////////////////////////////////////////////////////////
 
-    private int getKeyword(String name) throws IOException {
-        final int    
-            Id_break         = BREAK,
-            Id_case          = CASE,
-            Id_continue      = CONTINUE,
-            Id_default       = DEFAULT,
-            Id_delete        = DELPROP,
-            Id_do            = DO,
-            Id_else          = ELSE,
-            Id_false         = FALSE,
-            Id_for           = FOR,
-            Id_function      = FUNCTION,
-            Id_if            = IF,
-            Id_in            = IN,
-            Id_null          = NULL,
-            Id_return        = RETURN,
-            Id_switch        = SWITCH,
-            Id_this          = THIS,
-            Id_true          = TRUE,
-            Id_typeof        = TYPEOF,
-            Id_var           = VAR,
-            Id_void          = VOID,
-            Id_while         = WHILE,
-            Id_with          = WITH,
-
-            // the following are #ifdef RESERVE_JAVA_KEYWORDS in jsscan.c
-            Id_abstract      = RESERVED,
-            Id_boolean       = RESERVED,
-            Id_byte          = RESERVED,
-            Id_catch         = CATCH,
-            Id_char          = RESERVED,
-            Id_class         = RESERVED,
-            Id_const         = RESERVED,
-            Id_debugger      = RESERVED,
-            Id_double        = RESERVED,
-            Id_enum          = RESERVED,
-            Id_extends       = RESERVED,
-            Id_final         = RESERVED,
-            Id_finally       = FINALLY,
-            Id_float         = RESERVED,
-            Id_goto          = RESERVED,
-            Id_implements    = RESERVED,
-            Id_instanceof    = INSTANCEOF,
-            Id_int           = RESERVED,
-            Id_interface     = RESERVED,
-            Id_long          = RESERVED,
-            Id_native        = RESERVED,
-            Id_package       = RESERVED,
-            Id_private       = RESERVED,
-            Id_protected     = RESERVED,
-            Id_public        = RESERVED,
-            Id_assert        = ASSERT,
-            Id_short         = RESERVED,
-            Id_static        = RESERVED,
-            Id_super         = RESERVED,
-            Id_synchronized  = RESERVED,
-            Id_throw         = THROW,
-            Id_throws        = RESERVED,
-            Id_transient     = RESERVED,
-            Id_try           = TRY,
-            Id_volatile      = RESERVED;
-        
-        int id;
-        String s = name;
-        L0: { id = -1; String X = null; int c;
-            L: switch (s.length()) {
+    private int getKeyword(String s) throws IOException {
+       char c;
+       switch (s.length()) {
             case 2: c=s.charAt(1);
-                if (c=='f') { if (s.charAt(0)=='i') {id=Id_if; break L0;} }
-                else if (c=='n') { if (s.charAt(0)=='i') {id=Id_in; break L0;} }
-                else if (c=='o') { if (s.charAt(0)=='d') {id=Id_do; break L0;} }
-                break L;
+                if (c=='f') { if (s.charAt(0)=='i') return IF; }
+                else if (c=='n') { if (s.charAt(0)=='i') return IN; }
+                else if (c=='o') { if (s.charAt(0)=='d') return DO; }
+                break;
             case 3: switch (s.charAt(0)) {
-                case 'f': if (s.charAt(2)=='r' && s.charAt(1)=='o') {id=Id_for; break L0;} break L;
-                case 'i': if (s.charAt(2)=='t' && s.charAt(1)=='n') {id=Id_int; break L0;} break L;
-                case 'n': if (s.charAt(2)=='w' && s.charAt(1)=='e')
-                    throw new IOException("the new keyword is not permitted in XWT scripts");
-                    break L;
-                case 't': if (s.charAt(2)=='y' && s.charAt(1)=='r') {id=Id_try; break L0;} break L;
-                case 'v': if (s.charAt(2)=='r' && s.charAt(1)=='a') {id=Id_var; break L0;} break L;
-                } break L;
+                case 'f': if (s.charAt(2)=='r' && s.charAt(1)=='o') return FOR; break;
+                case 'i': if (s.charAt(2)=='t' && s.charAt(1)=='n') return RESERVED; break;
+                case 'n': if (s.charAt(2)=='w' && s.charAt(1)=='e') throw new IOException("the new keyword is not permitted in XWT scripts");
+                case 't': if (s.charAt(2)=='y' && s.charAt(1)=='r') return TRY; break;
+                case 'v': if (s.charAt(2)=='r' && s.charAt(1)=='a') return VAR; break;
+                } break;
             case 4: switch (s.charAt(0)) {
-                case 'b': X="byte";id=Id_byte; break L;
+                case 'b': return s.equals("byte") ? RESERVED : -1;
                 case 'c': c=s.charAt(3);
-                    if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='a') {id=Id_case; break L0;} }
-                    else if (c=='r') { if (s.charAt(2)=='a' && s.charAt(1)=='h') {id=Id_char; break L0;} }
-                    break L;
+                    if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='a') return CASE; }
+                    else if (c=='r') { if (s.charAt(2)=='a' && s.charAt(1)=='h') return RESERVED; }
+                   return -1;
                 case 'e': c=s.charAt(3);
-                    if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='l') {id=Id_else; break L0;} }
-                    else if (c=='m') { if (s.charAt(2)=='u' && s.charAt(1)=='n') {id=Id_enum; break L0;} }
-                    break L;
-                case 'g': X="goto";id=Id_goto; break L;
-                case 'l': X="long";id=Id_long; break L;
-                case 'n': X="null";id=Id_null; break L;
+                    if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='l') return ELSE; }
+                    else if (c=='m') { if (s.charAt(2)=='u' && s.charAt(1)=='n') return RESERVED; }
+                   return -1;
+                case 'g': return s.equals("goto") ? GOTO : -1;
+               case 'l': return s.equals("long") ? RESERVED : -1;
+                case 'n': return s.equals("null") ? NULL : -1;
                 case 't': c=s.charAt(3);
-                    if (c=='e') { if (s.charAt(2)=='u' && s.charAt(1)=='r') {id=Id_true; break L0;} }
-                    else if (c=='s') { if (s.charAt(2)=='i' && s.charAt(1)=='h') {id=Id_this; break L0;} }
-                    break L;
-                case 'v': X="void";id=Id_void; break L;
-                case 'w': X="with";id=Id_with; break L;
-                } break L;
+                    if (c=='e') { if (s.charAt(2)=='u' && s.charAt(1)=='r') return TRUE; }
+                    else if (c=='s') { if (s.charAt(2)=='i' && s.charAt(1)=='h') return THIS; }
+                   return -1;
+                case 'v': return s.equals("void") ? RESERVED : -1;
+               case 'w': return s.equals("with") ? WITH : -1;
+                } break;
             case 5: switch (s.charAt(2)) {
-                case 'a': X="class";id=Id_class; break L;
-                case 'e': X="break";id=Id_break; break L;
-                case 'i': X="while";id=Id_while; break L;
-                case 'l': X="false";id=Id_false; break L;
+                case 'a': return s.equals("class") ? RESERVED : -1;
+               case 'e': return s.equals("break") ? BREAK : -1;
+                case 'i': return s.equals("while") ? WHILE : -1;
+               case 'l': return s.equals("false") ? FALSE : -1;
                 case 'n': c=s.charAt(0);
-                    if (c=='c') { X="const"; throw new IOException("the const keyword is not permitted in XWT"); }
-                    else if (c=='f') { X="final";id=Id_final; }
-                    break L;
-                case 'o': c=s.charAt(0);
-                    if (c=='f') { X="float";id=Id_float; }
-                    else if (c=='s') { X="short";id=Id_short; }
-                    break L;
-                case 'p': X="super";id=Id_super; break L;
-                case 'r': X="throw";id=Id_throw; break L;
-                case 't': X="catch";id=Id_catch; break L;
-                } break L;
+                   if (s.equals("const")) throw new IOException("the const keyword is not permitted in XWT");
+                    else if (s.equals("final")) return RESERVED;
+                   return -1;
+               case 'o': c=s.charAt(0);
+                   if (c == 'c') return s.equals("float") ? RESERVED : -1;
+                   else if (c=='s') return s.equals("final") ? RESERVED : -1;
+                   break;
+               case 'p': return s.equals("super") ? RESERVED : -1;
+                case 'r': return s.equals("throw") ? THROW : -1;
+                case 't': return s.equals("catch") ? CATCH : -1;
+                } break;
             case 6: switch (s.charAt(1)) {
-                case 'a': X="native";id=Id_native; break L;
+               case 'a': return s.equals("class") ? RESERVED : -1;
                 case 'e': c=s.charAt(0);
-                    if (c=='d') { X="delete"; throw new IOException("the delete keyword is not permitted in XWT scripts"); }
-                    else if (c=='r') { X="return";id=Id_return; }
-                    break L;
-                case 'h': X="throws";id=Id_throws; break L;
-                case 'o': X="double";id=Id_double; break L;
-                case 's': X="assert";id=Id_assert; break L;
-                case 'u': X="public";id=Id_public; break L;
-                case 'w': X="switch";id=Id_switch; break L;
-                case 'y': X="typeof";id=Id_typeof; break L;
-                } break L;
+                    if (s.equals("delete")) throw new IOException("the delete keyword is not permitted in XWT scripts");
+                    else if (c=='r') return s.equals("return") ? RETURN : -1;
+                    break;
+               case 'h': return s.equals("throws") ? RESERVED : -1;
+                case 'o': return s.equals("double") ? RESERVED : -1;
+                case 's': return s.equals("assert") ? ASSERT : -1;
+                case 'u': return s.equals("public") ? RESERVED : -1;
+                case 'w': return s.equals("switch") ? SWITCH : -1;
+                case 'y': return s.equals("typeof") ? TYPEOF : -1;
+                } break;
             case 7: switch (s.charAt(1)) {
-                case 'a': X="package";id=Id_package; break L;
-                case 'e': X="default";id=Id_default; break L;
-                case 'i': X="finally";id=Id_finally; break L;
-                case 'o': X="boolean";id=Id_boolean; break L;
-                case 'r': X="private";id=Id_private; break L;
-                case 'x': X="extends";id=Id_extends; break L;
-                } break L;
+                case 'a': return s.equals("package") ? RESERVED : -1;
+                case 'e': return s.equals("default") ? DEFAULT : -1;
+                case 'i': return s.equals("finally") ? FINALLY : -1;
+                case 'o': return s.equals("boolean") ? RESERVED : -1;
+                case 'r': return s.equals("private") ? RESERVED : -1;
+                case 'x': return s.equals("extends") ? RESERVED : -1;
+                } break;
             case 8: switch (s.charAt(0)) {
-                case 'a': X="abstract";id=Id_abstract; break L;
-                case 'c': X="continue";id=Id_continue; break L;
-                case 'd': X="debugger";id=Id_debugger; break L;
-                case 'f': X="function";id=Id_function; break L;
-                case 'v': X="volatile";id=Id_volatile; break L;
-                } break L;
+                case 'a': return s.equals("abstract") ? RESERVED : -1;
+                case 'c': return s.equals("continue") ? CONTINUE : -1;
+                case 'd': return s.equals("debugger") ? RESERVED : -1;
+                case 'f': return s.equals("function") ? FUNCTION : -1;
+                case 'v': return s.equals("volatile") ? RESERVED : -1;
+                } break;
             case 9: c=s.charAt(0);
-                if (c=='i') { X="interface";id=Id_interface; }
-                else if (c=='p') { X="protected";id=Id_protected; }
-                else if (c=='t') { X="transient";id=Id_transient; }
-                break L;
+                if (c=='i') return s.equals("interface") ? RESERVED : -1;
+                else if (c=='p') return s.equals("protected") ? RESERVED : -1;
+                else if (c=='t') return s.equals("transient") ? RESERVED : -1;
+                break;
             case 10: c=s.charAt(1);
-                if (c=='m') { X="implements";id=Id_implements; }
-                else if (c=='n') { X="instanceof"; throw new IOException("the instanceof keyword is not permitted in XWT scripts"); }
-                break L;
-            case 12: X="synchronized";id=Id_synchronized; break L;
+                if (c=='m') return s.equals("implements") ? RESERVED : -1;
+                else if (c=='n' && s.equals("instanceof")) throw new IOException("the instanceof keyword is not permitted in XWT scripts");
+                break;
+            case 12: return s.equals("synchronized") ? RESERVED : -1;
             }
-            if (X!=null && X!=s && !X.equals(s)) id = -1;
-        }
-        if (id == -1) { return -1; }
-        this.op = id >> 8;
-        return id & 0xff;
+       return -1;
     }
 
     private int getIdentifier(int c) throws IOException {
@@ -418,7 +373,7 @@ class Lexer {
 
     public int getToken() throws IOException {
        if (pushedBack) { pushedBack = false; return op; }
-       return (op = getToken());
+       return (op = _getToken());
     }
 
     public int _getToken() throws IOException {
@@ -480,7 +435,10 @@ class Lexer {
         int lastread = -1;
 
         public SmartReader(Reader r) { reader = new PushbackReader(r); }
-        public void unread() throws IOException { reader.unread(lastread); }
+        public void unread() throws IOException {
+           reader.unread(lastread);
+           if (accumulator != null) accumulator.setLength(accumulator.length() - 1);
+       }
         public boolean match(char c) throws IOException { if (peek() == c) { reader.read(); return true; } else return false; }
         public int peek() throws IOException {
             int peeked = reader.read();
@@ -489,13 +447,16 @@ class Lexer {
         }
         public int read() throws IOException {
             lastread = reader.read();
-            if (accumulator != null) accumulator.append(lastread);
+            if (accumulator != null) accumulator.append((char)lastread);
             return lastread;
         }
 
         // FIXME: could be much more efficient
         StringBuffer accumulator = null;
-        public void startString() { accumulator = new StringBuffer(); }
+        public void startString() {
+           accumulator = new StringBuffer();
+           accumulator.append((char)lastread);
+       }
         public String getString() throws IOException {
             String ret = accumulator.toString();
             accumulator = null;
index 861432c..cd2498b 100644 (file)
@@ -3,9 +3,10 @@ import org.xwt.util.*;
 import java.io.*;
 
 // FIXME: for..in
-// FIXME: delete keyword
 public class Parser extends Lexer {
 
+    public static void main(String[] s) throws Exception { System.out.println(new Parser(new InputStreamReader(System.in)).parseExpr()); }
+
     public Parser(Reader r) throws IOException { super(r); }
     private Parser skipToken() throws IOException { getToken(); return this; }
     
@@ -20,8 +21,26 @@ public class Parser extends Lexer {
        Expr next = null;   // if this expr is part of a list
 
        String string = null;
+       Number number = null;
+
+       public String toString() { return toString(0); }
+
+       public String toString(int indent) {
+           String ret = "";
+           for(int i=0; i<indent; i++) ret += " ";
+           ret += codeToString[code];
+           if (code == STRING) ret += " \"" + string + "\"";
+           else if (code == NUMBER) ret += " " + number;
+           ret += "\n";
+           if (left != null) ret += left.toString(indent + 2);
+           if (right != null) ret += left.toString(indent + 2);
+           if (extra != null) ret += left.toString(indent + 2);
+           // FIXME: next
+           return ret;
+       }
 
-       public Expr(String s) { this.string = s; }  // an identifier or label
+       public Expr(String s) { code = STRING; this.string = s; }  // an identifier or label
+       public Expr(Number n) { code = NUMBER; this.number = n; }  // an identifier or label
        public Expr(int code) { this(code, null, null, null); }
        public Expr(int code, Expr left) { this(code, left, null, null); }
        public Expr(int code, Expr left, Expr right) { this(code, left, right, null); }
@@ -71,29 +90,103 @@ public class Parser extends Lexer {
        return new Expr(LC, head);
     }
 
-    /** Subexpressions come in two flavors: starters and continuers.
-     *  Starters can appear at the start of an expression or after a
-     *  continuer, and continuers, which can appear after a starter.
-     */
-    public Expr parseExpr() throws IOException {
-       Expr e = parseStarter();
-       while(true) {
-           Expr e2 = parseContinuer(e);
-           if (e2 == null) return e;
-           e = e2;
-       }
+    static byte[] precedence = new byte[MAX_TOKEN + 1];
+    static {
+       precedence[COMMA] = 1;
+       precedence[ASSIGN] = 2;
+       precedence[GT] = precedence[GE] = 3;
+       precedence[OR] = precedence[AND] = 4;
+       precedence[BITOR] = 5;
+       precedence[BITXOR] = 6;
+       precedence[BITAND] = 7;
+       precedence[EQ] = precedence[NE] = 8;
+       precedence[LT] = precedence[LE] = 9;
+       precedence[SHEQ] = precedence[SHNE] = 10;
+       precedence[LSH] = precedence[RSH] = precedence[URSH] = 11;
+       precedence[ADD] = precedence[SUB] = 12;
+       precedence[MUL] = precedence[DIV] = precedence[MOD] = 13;
+       precedence[BITNOT] = precedence[INSTANCEOF] = 14;
+       precedence[INC] = precedence[DEC] = 15;
     }
 
-    public Expr parseStarter() throws IOException {
+    // called after each parseExpr(); returns null if we can't make the expression any bigger
+    public Expr parseExpr() throws IOException { return parseExpr(null, 0); }
+    public Expr parseExpr(Expr prefix, int minPrecedence) throws IOException {
        Expr e1 = null;     
        Expr e2 = null;     
        Expr e3 = null;     
        Expr head = null;
        Expr tail = null;
+       Expr ret = null;
        int tok = getToken();
-       switch(tok) {
+
+       if (minPrecedence != 0 && tok < precedence.length && precedence[tok] != 0 && precedence[tok] < minPrecedence)
+           return null;
+
+       // these case arms match the precedence of operators; each arm is a precedence level.
+       switch (tok) {
+
+       case COMMA: case ASSIGN: case GT: case GE: case OR: case AND:
+        case BITOR: case BITXOR: case BITAND: case EQ: case NE: case LT:
+        case LE: case SHEQ: case SHNE: case LSH: case RSH: case URSH:
+        case ADD: case SUB: case MUL: case DIV: case MOD:
+           return new Expr(tok, prefix, parseExpr(null, precedence[tok]));
+           
+       case BITNOT: case INSTANCEOF:
+           return new Expr(tok, parseExpr(null, precedence[tok]));
+           
+           // FIXME: this isn't 100% right
+       case INC: case DEC:
+           return new Expr(tok, prefix, (tok == INC || tok == DEC) ? null : parseExpr());
+
+       case LP:
+           while(peekToken() != RP) {
+               if (head == null) head = tail = parseExpr(); else tail = tail.next = parseExpr();
+               tok = getToken();
+               if (tok == RP) break;
+               if (tok != COMMA) throw new Error("expected comma or right paren");
+           }
+           return new Expr(LP, prefix, head);
+
+       case LB:
+           if (prefix != null) {
+               // subscripting
+               e1 = parseExpr();
+               if (getToken() != RB) throw new Error("expected a right brace");
+               return new Expr(LB, prefix, e1);
+           } else {
+               // array ctor
+               tok = getToken();
+               while(true) {
+                   if (tok == RB) return new Expr(LB, prefix, head);
+                   if (head == null) head = tail = parseExpr(); else tail = tail.next = parseExpr();
+                   tok = getToken();
+                   if (tok != COMMA && tok != RP) throw new Error("expected right bracket or comma");
+               }
+           }
+           
+       case LC:
+           if (prefix != null) throw new Error("didn't expect non-null prefix");
+           tok = getToken();
+           while(true) {
+               if (tok == RP) return new Expr(LC, head);
+               if (tok != NAME) throw new Error("expecting name");
+               Expr name = parseExpr();
+               if (tok != COLON) throw new Error("expecting colon");           
+               e1 = new Expr(COLON, name, parseExpr());
+               if (head == null) head = tail = e1; else tail = tail.next = e1;
+               tok = getToken();
+               if (tok != COMMA && tok != RP) throw new Error("expected right curly or comma");
+           }
+           
+       case HOOK:
+           e2 = parseExpr();
+           if (getToken() != COLON) throw new Error("expected colon to close ?: expression");
+           e3 = parseExpr();
+           return new Expr(HOOK, prefix, e2, e3);
            
        case SWITCH: {
+           if (prefix != null) throw new Error("didn't expect non-null prefix");
            if (getToken() != LP) throw new Error("expected left paren");
            Expr switchExpr = parseExpr();
            if (getToken() != RP) throw new Error("expected left paren");
@@ -113,6 +206,7 @@ public class Parser extends Lexer {
        }
            
        case FUNCTION: {
+           if (prefix != null) throw new Error("didn't expect non-null prefix");
            if (getToken() != LP) throw new Error("function keyword must be followed by a left paren");
            Expr formalArgs = null, cur = null;
            tok = getToken();
@@ -128,10 +222,17 @@ public class Parser extends Lexer {
            return new Expr(tok, formalArgs, parseBlock(true));
        }
            
+       case STRING:
+           return new Expr(string);
+
+       case NUMBER:
+           return new Expr(number);
+
        case VAR:
+           if (prefix != null) throw new Error("didn't expect non-null prefix");
            while(true) {
                if (getToken() != NAME) throw new Error("variable declarations must start with a variable name");
-               Expr name = new Expr(string);
+               Expr name = new Expr(NAME, new Expr(string));
                Expr initVal = null;
                tok = peekToken();
                if (tok == ASSIGN) {
@@ -146,39 +247,17 @@ public class Parser extends Lexer {
            }
            return new Expr(VAR, head);
            
-       case LC:
-           tok = getToken();
-           while(true) {
-               if (tok == RP) return new Expr(LC, head);
-               if (tok != NAME) throw new Error("expecting name");
-               Expr name = parseExpr();
-               if (tok != COLON) throw new Error("expecting colon");           
-               e1 = new Expr(COLON, name, parseExpr());
-               if (head == null) head = tail = e1; else tail = tail.next = e1;
-               tok = getToken();
-               if (tok != COMMA && tok != RP) throw new Error("expected right curly or comma");
-           }
-           
-       case LB:
-           tok = getToken();
-           while(true) {
-               if (tok == RB) return new Expr(LB, head);
-               if (head == null) head = tail = parseExpr(); else tail = tail.next = parseExpr();
-               tok = getToken();
-               if (tok != COMMA && tok != RP) throw new Error("expected right bracket or comma");
-           }
-           
        case NAME:
+           if (prefix != null) throw new Error("didn't expect non-null prefix");
            return new Expr(string);
     
-       case INC: case DEC: case TYPEOF:
-           return new Expr(tok, parseExpr());
-           
        case TRUE: case FALSE: case NOP:
+           if (prefix != null) throw new Error("didn't expect non-null prefix");
            return new Expr(tok);
            
        case TRY: {
            // FIXME: we deliberately allow you to omit braces in catch{}/finally{} if they are single statements...
+           if (prefix != null) throw new Error("didn't expect non-null prefix");
            Expr tryBlock = parseBlock(true);
            while ((tok = peekToken()) == CATCH)
                if (head == null) head = tail = parseBlock(false); else tail = tail.next = parseBlock(false);
@@ -187,6 +266,7 @@ public class Parser extends Lexer {
        }
            
        case IF: case WHILE: {
+           if (prefix != null) throw new Error("didn't expect non-null prefix");
            if (getToken() != LP) throw new Error("expected left paren");
            Expr parenExpr = parseExpr();
            if (getToken() != RP) throw new Error("expected right paren");
@@ -197,6 +277,7 @@ public class Parser extends Lexer {
 
        case FOR:
            // FIXME: for..in
+           if (prefix != null) throw new Error("didn't expect non-null prefix");
            if (getToken() != LP) throw new Error("expected left paren");
            e1 = parseStatement();
            e2 = parseStatement();
@@ -206,6 +287,7 @@ public class Parser extends Lexer {
            //return new Expr(FOR, e1, e2, e3, parseBlock(false));
            
        case DO: {
+           if (prefix != null) throw new Error("didn't expect non-null prefix");
            Expr firstBlock = parseBlock(false);
            if (getToken() != WHILE) throw new Error("expecting WHILE");
            if (getToken() != LP) throw new Error("expected left paren");
@@ -221,51 +303,6 @@ public class Parser extends Lexer {
        case WITH:
            throw new Error("WITH not yet implemented"); // FIXME
 
-       default: throw new Error("I wasn't expecting a " + tok);
-       }
-    }
-       
-    // called after each parseExpr(); returns null if we can't make the expression any bigger
-    public Expr parseContinuer(Expr prefix) throws IOException {
-       Expr head = null;
-       Expr tail = null;
-       Expr e1, e2, e3;
-       Expr ret = null;
-       int tok;
-
-       // FIXME: postfix and infix operators -- need to handle precedence
-       switch (tok = getToken()) {
-
-       case BITOR: case BITXOR: case BITAND: case EQ: case NE: case LT: case LE:
-       case GT: case GE: case LSH: case RSH: case URSH: case ADD: case SUB: case MUL:
-       case DIV: case MOD: case BITNOT: case SHEQ: case SHNE: case INSTANCEOF:
-       case OR: case AND: case COMMA: case INC: case DEC:
-           throw new Error("haven't figured out how to handle postfix/infix operators yet");
-           //return new Expr(tok, prefix, (tok == INC || tok == DEC) ? null : parseExpr());
-
-       case ASSIGN:
-           throw new Error("haven't figured out how to handle postfix/infix operators yet");
-
-       case LP:
-           while(peekToken() != RP) {
-               if (head == null) head = tail = parseExpr(); else tail = tail.next = parseExpr();
-               tok = getToken();
-               if (tok == RP) break;
-               if (tok != COMMA) throw new Error("expected comma or right paren");
-           }
-           return new Expr(LP, prefix, head);
-
-       case LB:
-           e1 = parseExpr();
-           if (getToken() != RB) throw new Error("expected a right brace");
-           return new Expr(LB, prefix, e1);
-           
-       case HOOK:
-           e2 = parseExpr();
-           if (getToken() != COLON) throw new Error("expected colon to close ?: expression");
-           e3 = parseExpr();
-           return new Expr(HOOK, prefix, e2, e3);
-           
        default:
            pushBackToken();
            return null;