2003/06/03 00:53:07
[org.ibex.core.git] / src / org / xwt / js / Parser.java
1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
2 package org.xwt.js;
3
4 import org.xwt.util.*;
5 import java.io.*;
6
7
8 /** parses a stream of lexed tokens into a tree of Expr's */
9 public class Parser extends Lexer {
10
11     // Constructors //////////////////////////////////////////////////////
12
13     public Parser(Reader r, String sourceName, int line) throws IOException {
14         super(r);
15         this.sourceName = sourceName;
16         this.line = line;
17     }
18
19     /** for debugging */
20     public static void main(String[] s) throws Exception {
21         Parser p = new Parser(new InputStreamReader(System.in), "stdin", 0);
22         while(true) {
23             Expr block = p.parseBlock(false);
24             if (block == null) return;
25             System.out.println(block);
26             if (p.peekToken() == -1) return;
27         }
28     }
29
30
31     // Statics ////////////////////////////////////////////////////////////
32
33     static byte[] precedence = new byte[MAX_TOKEN + 1];
34     static boolean[] isRightAssociative = new boolean[MAX_TOKEN + 1];
35     static {
36         precedence[ASSIGN] = 1;
37         isRightAssociative[ASSIGN] = true;
38         precedence[HOOK] = 2;
39         precedence[COMMA] = 3;
40         precedence[OR] = precedence[AND] = 4;
41         precedence[GT] = precedence[GE] = 5;
42         precedence[BITOR] = 6;
43         precedence[BITXOR] = 7;
44         precedence[BITAND] = 8;
45         precedence[EQ] = precedence[NE] = 9;
46         precedence[LT] = precedence[LE] = 10;
47         precedence[SHEQ] = precedence[SHNE] = 11;
48         precedence[LSH] = precedence[RSH] = precedence[URSH] = 12;
49         precedence[ADD] = precedence[SUB] = 13;
50         precedence[MUL] = precedence[DIV] = precedence[MOD] = 14;
51         precedence[BITNOT] = precedence[INSTANCEOF] = 15;
52         precedence[INC] = precedence[DEC] = 16;
53         precedence[LP] = 17;
54         precedence[LB] = 18;
55         precedence[DOT] = 19;
56     }
57
58
59     // Parsing Logic /////////////////////////////////////////////////////////
60
61     public void consume(int code) throws IOException {
62         int got = getToken();
63         if (got != code) throw new ParserException("expected " + codeToString[code] + ", got " + (got == -1 ? "EOL" : codeToString[got]));
64     }
65     public void expect(int code) throws IOException {
66         int got = peekToken();
67         if (got != code) throw new ParserException("expected " + codeToString[code] + ", got " + (got == -1 ? "EOL" : codeToString[got]));
68     }
69
70     /** parses the largest possible expression */
71     public Expr parseMaximalExpr() throws IOException { return parseMaximalExpr(null, -1); }
72     public Expr parseMaximalExpr(Expr prefix, int minPrecedence) throws IOException {
73         while(true) {
74             if (peekToken() == -1) break;
75             Expr save = prefix;
76             prefix = parseSingleExpr(prefix, minPrecedence);
77             if (save == prefix) break;
78             if (prefix == null) throw new ParserException("parseSingleExpr() returned null");
79         }
80         return prefix;
81     }
82
83     public Expr parseSingleExpr(Expr prefix, int minPrecedence) throws IOException {
84         Expr e1 = null, e2 = null, e3 = null, head = null, tail = null, ret = null;
85
86         int tok = peekToken();
87         if (minPrecedence > 0 &&
88             tok < precedence.length &&
89             precedence[tok] != 0 &&
90             (isRightAssociative[tok] ?
91              (precedence[tok] < minPrecedence) :
92              (precedence[tok] <= minPrecedence))) {
93             return prefix;
94              }
95         getToken();
96         int curLine = line;
97
98         // these case arms match the precedence of operators; each arm is a precedence level.
99         switch (tok) {
100
101         case WITH: throw new ParserException("XWT does not allow the WITH keyword");
102         case VOID: case RESERVED: throw new ParserException("reserved word that you shouldn't be using");
103         case NAME: if (prefix != null) { pushBackToken(); return prefix; } else return parseMaximalExpr(new Expr(curLine, NAME, string), minPrecedence);
104         case STRING: if (prefix != null) { pushBackToken(); return prefix; } else return new Expr(curLine, string);
105         case NUMBER: if (prefix != null) { pushBackToken(); return prefix; } else return new Expr(curLine, number);
106         case NULL: case TRUE: case FALSE: case NOP: if (prefix != null) { pushBackToken(); return prefix; } else return new Expr(curLine, tok);
107
108         case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH:
109         case ASSIGN_RSH: case ASSIGN_URSH: case ASSIGN_ADD: case ASSIGN_SUB: case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD:
110             return new Expr(curLine, ASSIGN, prefix, new Expr(curLine, tok - 1, prefix, parseMaximalExpr(null, precedence[ASSIGN])));
111
112         case COMMA: pushBackToken();return prefix;
113
114         case THIS:
115             if (prefix != null) { pushBackToken(); return prefix; } 
116             return new Expr(curLine, THIS);
117
118         case SUB:
119             if (prefix == null && peekToken() == NUMBER) {
120                 getToken();
121                 return new Expr(curLine, new Double(number.doubleValue() * -1));
122             } // else fall through
123         case BITOR: case BITXOR: case BITAND: case SHEQ: case SHNE: case LSH:
124         case RSH: case URSH: case ADD: case MUL: case DIV: case MOD:
125         case ASSIGN: case GT: case GE: case OR: case AND:
126         case EQ: case NE: case LT: case LE:
127             if (prefix == null) throw new ParserException("the " + codeToString[tok] + " token cannot start an expression");
128             return new Expr(curLine, tok, prefix, parseMaximalExpr(null, precedence[tok]));
129
130         case DOT: {
131             //e1 = parseMaximalExpr(null, precedence[DOT]);
132             //if (e1.code == NAME) e1.code = STRING;
133             //else throw new ParserException("argument to DOT must be a NAME; got a " + codeToString[e1.code]);
134             //return new Expr(curLine, DOT, prefix, e1);
135             tok = getToken();
136             if (tok != NAME) throw new ParserException("DOT must be followed by a NAME");
137             return new Expr(curLine, DOT, prefix, new Expr(curLine, STRING, string));
138         }
139
140         case BANG: case BITNOT: case INSTANCEOF: case TYPEOF:
141             if (prefix != null) { pushBackToken(); return prefix; }
142             return new Expr(curLine, tok, parseMaximalExpr(null, precedence[tok]));
143
144         case INC: case DEC:
145             if (prefix == null) {
146                 // prefix
147                 e1 = parseMaximalExpr(null, precedence[tok]);
148                 return new Expr(curLine, ASSIGN, e1, new Expr(curLine, tok == INC ? ADD : SUB, e1, new Expr(curLine, new Integer(1))));
149             } else {
150                 // postfix
151                 return new Expr(curLine, tok, prefix);
152             }
153
154         case LP:
155             if (prefix == null) {  // grouping
156                 Expr r = parseMaximalExpr();
157                 expect(RP); getToken();
158                 return r;
159
160             } else {  // invocation
161                 ExprList list = new ExprList(curLine, LP);
162                 while(peekToken() != RP) {
163                     list.add(parseMaximalExpr());
164                     if (peekToken() == RP) break;
165                     consume(COMMA);
166                 }
167                 getToken();
168                 return new Expr(curLine, LP, prefix, list);
169             }
170
171         case LB:
172             if (prefix == null) {
173                 // array ctor
174                 ExprList list = new ExprList(curLine, LB);
175                 while(true) {
176                     Expr e = parseMaximalExpr();
177                     if (e != null) {
178                         list.add(e);
179                     } else {
180                         if (peekToken() == COMMA) list.add(new Expr(curLine, NULL));
181                     }
182                     if (peekToken() == RB) { consume(RB); return list; }
183                     consume(COMMA);
184                 }
185             } else {
186                 // subscripting
187                 e1 = parseMaximalExpr();
188                 if (getToken() != RB) throw new ParserException("expected a right brace");
189                 return new Expr(curLine, DOT, prefix, e1);
190             }
191             
192         case LC: {
193             // object ctor
194             if (prefix != null) { pushBackToken(); return prefix; }
195             tok = getToken();
196             ExprList list = new ExprList(curLine, RC);
197             if (tok == RC) return list;
198             while(true) {
199                 if (tok != NAME && tok != STRING) throw new ParserException("expecting name, got " + codeToString[tok]);
200                 Expr name = new Expr(curLine, NAME, string);
201                 if (getToken() != COLON) throw new ParserException("expecting colon");          
202                 e1 = new Expr(curLine, COLON, name, parseMaximalExpr());
203                 list.add(e1);
204                 tok = getToken();
205                 if (tok != COMMA && tok != RC) throw new ParserException("expected right curly or comma, got " + codeToString[tok]);
206                 if (tok == RC) return list;
207                 tok = getToken();
208                 if (tok == RC) return list;
209             }
210         }
211             
212         case HOOK:
213             e2 = parseMaximalExpr();
214             if (getToken() != COLON) throw new ParserException("expected colon to close ?: expression");
215             e3 = parseMaximalExpr();
216             return new Expr(curLine, HOOK, prefix, new Expr(curLine, ELSE, e2, e3));
217             
218         case SWITCH: {
219             if (prefix != null) { pushBackToken(); return prefix; }
220             if (getToken() != LP) throw new ParserException("expected left paren");
221             Expr switchExpr = parseMaximalExpr();
222             if (getToken() != RP) throw new ParserException("expected left paren");
223             if (getToken() != LC) throw new ParserException("expected left brace");
224             ExprList toplevel = new ExprList(curLine, LC);
225             while(true) {
226                 tok = getToken();
227                 Expr caseExpr;
228                 if (tok == DEFAULT) caseExpr = null;
229                 else if (tok == CASE) caseExpr = parseMaximalExpr();
230                 else throw new ParserException("expected CASE or DEFAULT");
231                 consume(COLON);
232                 // FIXME: we shouldn't be creating a scope here
233                 ExprList list = new ExprList(curLine, LC);
234                 while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) {
235                     if ((e1 = parseBlock(false)) == null) break;
236                     list.add(e1);
237                 }
238                 toplevel.add(new Expr(curLine, tok, caseExpr, list));
239                 if (peekToken() == RC) { consume(RC); return new Expr(curLine, SWITCH, switchExpr, toplevel); }
240             }
241         }
242             
243         case FUNCTION: {
244             if (prefix != null) { pushBackToken(); return prefix; }
245             consume(LP);
246             ExprList list = new ExprList(curLine, LC);
247             if (peekToken() == RP) consume(RP);
248             else while(true) {
249                 tok = peekToken();
250                 if (tok == COMMA) {
251                     consume(COMMA);
252                     list.add(new Expr(curLine, NULL));
253                 } else {
254                     consume(NAME);
255                     list.add(new Expr(curLine, NAME, string));
256                     if (peekToken() == RP) { consume(RP); break; }
257                     consume(COMMA);
258                 }
259             }
260             return new Expr(curLine, FUNCTION, list, parseBlock(true));
261         }
262             
263         case VAR: {
264             if (prefix != null) { pushBackToken(); return prefix; }
265             ExprList list = new ExprList(curLine, VAR);
266             while(true) {
267                 if (getToken() != NAME) throw new ParserException("variable declarations must start with a variable name");
268                 String name = string;
269                 Expr initVal = null;
270                 tok = peekToken();
271                 Expr e = null;
272                 if (tok == ASSIGN) {
273                     getToken();
274                     initVal = parseMaximalExpr();
275                     tok = peekToken();
276                     e = new Expr(curLine, VAR,
277                                  new Expr(curLine, name),
278                                  new Expr(curLine, ASSIGN,
279                                           new Expr(curLine, name),
280                                           initVal));
281                 } else {
282                     e = new Expr(curLine, VAR, new Expr(curLine, name));
283                 }
284                 list.add(e);
285                 if (tok != COMMA) break;
286                 getToken();
287             }
288             return list;
289         }
290
291         case TRY: {
292             // We deliberately allow you to omit braces in catch{}/finally{} if they are single statements...
293             if (prefix != null) { pushBackToken(); return prefix; }
294             Expr tryBlock = parseBlock(true);
295             
296             tok = peekToken();
297             ExprList list = new ExprList(curLine, TRY);
298             if (tok == CATCH) {
299                 getToken();
300                 if (getToken() != LP) throw new ParserException("expected (");
301                 if (getToken() != NAME) throw new ParserException("expected name");
302                 Expr name = new Expr(curLine, NAME, string);
303                 if (getToken() != RP) throw new ParserException("expected )");
304                 list.add(new Expr(curLine, CATCH, name, parseBlock(false)));
305                 tok = peekToken();
306             }
307             if (tok == FINALLY) {
308                 getToken();
309                 list.add(new Expr(curLine, FINALLY, parseBlock(false)));
310             }
311
312             if (list.size() == 0) throw new ParserException("try without catch or finally");
313             return new Expr(curLine, TRY, tryBlock, list);
314         }
315
316         case IF: case WHILE: {
317             if (prefix != null) { pushBackToken(); return prefix; }
318             if (getToken() != LP) throw new ParserException("expected left paren");
319             Expr parenExpr = parseMaximalExpr();
320             int t;
321             if ((t = getToken()) != RP) throw new ParserException("expected right paren, but got " + codeToString[t]);
322             Expr firstBlock = parseBlock(false);
323             if (tok == IF && peekToken() == ELSE) {
324                 getToken();
325                 return new Expr(curLine, tok, parenExpr, new Expr(curLine, ELSE, firstBlock, parseBlock(false)));
326             } else {
327                 if (tok == IF) return new Expr(curLine, tok, parenExpr, firstBlock);
328                 if (tok == WHILE) {
329                     ExprList list = new ExprList(curLine, WHILE);
330                     list.add(parenExpr);
331                     list.add(firstBlock);
332                     list.add(new Expr(curLine, NULL));
333                     return list;
334                 }
335             }
336         }
337
338         case IN: pushBackToken(); return prefix;
339
340         case FOR:
341             if (prefix != null) { pushBackToken(); return prefix; }
342             if (getToken() != LP) throw new ParserException("expected left paren");
343             e1 = parseMaximalExpr();
344             if (peekToken() == IN) {
345                 getToken();
346                 e2 = parseMaximalExpr();
347                 if (getToken() != RP) throw new ParserException("expected right paren");
348                 return new Expr(curLine, FOR, new Expr(curLine, IN, e1.left, e2), parseBlock(false));
349                 
350             } else {
351                 Expr initExpr = e1;
352                 if (initExpr == null) initExpr = new Expr(curLine, NULL);
353                 expect(SEMI); getToken();
354                 Expr whileExpr = parseMaximalExpr();
355                 expect(SEMI); getToken();
356                 Expr incExpr = parseMaximalExpr();
357                 expect(RP); getToken();
358                 Expr body = parseBlock(false);
359                 Expr loop = new Expr(curLine, WHILE, whileExpr, body);
360                 ExprList list = new ExprList(curLine, LC);
361                 list.add(initExpr);
362                 ExprList list2 = new ExprList(curLine, WHILE);
363                 list.add(list2);
364                 list2.add(whileExpr);
365                 list2.add(body);
366                 list2.add(incExpr);
367                 return list;
368             }
369             
370         case DO: {
371             if (prefix != null) { pushBackToken(); return prefix; }
372             ExprList list = new ExprList(curLine, DO);
373             Expr body = parseBlock(false);
374             consume(WHILE);
375             consume(LP);
376             list.add(parseMaximalExpr());
377             list.add(body);
378             list.add(new Expr(curLine, NULL));
379             consume(RP);
380             consume(SEMI);
381             return list;
382         }
383             
384         default:
385             pushBackToken();
386             return prefix;
387         }
388     }
389     
390     // Expr //////////////////////////////////////////////////////////////////////
391
392     class ExprList extends Expr {
393         Vec v = new Vec();
394         public ExprList(int curLine, int code) { super(curLine, code); }
395         public void add(Expr e) { v.addElement(e); }
396         public int numExprs() { return v.size(); }
397         public int size() { return v.size(); }
398         public Expr elementAt(int i) { return (Expr)v.elementAt(i); }
399         public Object eval(final JS.Scope s) throws ControlTransferException, JS.Exn {
400             switch(code) {
401             case LC: {
402                 // Block
403                 JS.Scope scope = new JS.Scope(s);
404                 for(int i=0; i<v.size(); i++) ((Expr)v.elementAt(i)).eval(scope);
405                 return null;
406             }
407             case Lexer.LB: {
408                 // Array ctor
409                 JS.Array ret = new JS.Array();
410                 for(int i=0; i<numExprs(); i++) ret.addElement(elementAt(i).eval(s));
411                 return ret;
412             }
413             case Lexer.RC: {
414                 // Object ctor
415                 JS.Obj ret = new JS.Obj();
416                 for(int i=0; i<numExprs(); i++) {
417                     Expr e = elementAt(i);
418                     Object key = e.left.string;
419                     Object val = e.right.eval(s);
420                     ret.put(key, val);
421                 }
422                 return ret;
423             }
424             case Lexer.WHILE:
425             case Lexer.DO:
426                 try {
427                     boolean first = true;
428                     Object bogus = null;
429                     ExprList list = this;
430                     Expr whileExpr = list.elementAt(0);
431                     Expr bodyExpr = list.elementAt(1);
432                     Expr incExpr = list.elementAt(2);
433                     for(;(first && code == Lexer.DO) || toBoolean(whileExpr.eval(s)); bogus = incExpr.eval(s)) {
434                         first = false;
435                         try { bodyExpr.eval(s);
436                         } catch (ContinueException c) {
437                             if (c.label == null || c.label.equals(string)) continue;
438                         } catch (BreakException b) {
439                             if (b.label == null || b.label.equals(string)) return null;
440                             throw (BreakException)b.fillInStackTrace();
441                         }
442                     }
443                 } catch (BreakException e) {
444                     if (e.label != null && !e.label.equals(string)) throw e;
445                 }
446                 return null;
447             case Lexer.VAR:
448                 for(int i=0; i<size(); i++) {
449                     Expr e = elementAt(i);
450                     s.declare(e.left.string);
451                     if (e.right != null) e.right.eval(s);
452                 }
453                 return null;
454             default: throw new Error("augh!");
455             }
456         }
457     }
458     
459     /** a block is either a single statement or a list of statements surrounded by curly braces; all expressions are also statements */
460     public Expr parseBlock(boolean requireBraces) throws IOException {
461         Expr smt = null;
462         int tok = peekToken();
463         if (tok == -1) return null;
464         boolean braced = tok == LC;
465         if (requireBraces && !braced) throw new ParserException("expected {, got " + codeToString[tok]);
466         if (braced) getToken();
467         int curLine = line;
468         ExprList block = new ExprList(curLine, LC);
469         while(true) {
470             switch(tok = peekToken()) {
471
472             case -1:
473                 return block;
474
475             case LC:
476                 smt = parseBlock(true); break;
477
478             case THROW: case RETURN: case ASSERT:
479                 getToken();
480                 if (peekToken() == SEMI) {
481                     if (tok == THROW || tok == ASSERT) throw new ParserException(codeToString[tok] + " requires an argument");
482                     consume(SEMI);
483                     smt = new Expr(curLine, tok);
484                 } else {
485                     smt = new Expr(curLine, tok, parseMaximalExpr());
486                     consume(SEMI);
487                 }
488                 break;
489
490             case GOTO: case BREAK: case CONTINUE: {
491                 getToken();
492                 int t = peekToken();
493                 if (t == NAME) {
494                     getToken();
495                     smt = new Expr(curLine, tok, new Expr(curLine, string));
496                 } else if (tok == GOTO) {
497                     throw new ParserException("goto must be followed by a label");
498                 }
499                 smt = new Expr(curLine, tok, new Expr(curLine, string));
500                 consume(SEMI);
501                 break;
502             }
503                 
504             case RC:
505                 if (braced) consume(RC);
506                 return block;
507                 
508             case SEMI:
509                 consume(SEMI);
510                 if (!braced) return block;
511                 continue;
512                 
513 /*  FIXME: LL(2)
514             case NAME: {
515                 String name = string;
516                 getToken();
517                 if (peekToken() == COLON) {
518                     smt = new Expr(curLine, COLON, new Expr(curLine, name), parseBlock(false));
519                     break;
520                 } else {
521                     pushBackToken(tok);
522                     string = name;
523                     // fall through to default case
524                 }
525             }
526 */              
527             default:
528                 smt = parseMaximalExpr();
529                 if (smt == null) {
530                     if (block.numExprs() == 0) return null;
531                     return block;
532                 }
533                 if (peekToken() == SEMI) getToken();
534                 break;
535             }
536
537             if (!braced) return smt;
538             block.add(smt);
539         }
540     }
541     
542
543     /** sorta like gcc trees */
544     class Expr {
545         int code = -1;
546     
547         final Expr left;
548         final Expr right;
549         int line = -1;
550         String sourceName = "unknown";
551     
552         String string = null;
553         Number number = null;
554     
555         public String toString() { return toString(0); }
556         public String toString(int indent) {
557             String ret = "";
558             for(int i=0; i<indent; i++) ret += " ";
559             ret += Lexer.codeToString[code];
560             if (code == Lexer.NUMBER) ret += " " + number;
561             else if (string != null) ret += " \"" + string + "\"";
562             ret += "\n";
563             if (left != null) ret += left.toString(indent + 2);
564             if (right != null) ret += right.toString(indent + 2);
565             return ret;
566         }
567     
568         public Expr(int line, String s) { this(line, Lexer.STRING); this.string = s; }  // an identifier or label
569         public Expr(int line, int code, String s) { this(line, code); this.string = s; }
570         public Expr(int line, Number n) { this(line, Lexer.NUMBER); this.number = n; }  // an identifier or label
571         public Expr(int line, int code) { this(line, code, null, null); }
572         public Expr(int line, int code, Expr left) { this(line, code, left, null); }
573         public Expr(int line, int code, Expr left, Expr right) {
574             this.code = code; this.left = left; this.right = right;
575             this.line = line;
576             this.sourceName = Parser.this.sourceName;
577         }
578
579         public double toDouble(Object o) { return toNumber(o).doubleValue(); }
580         public long toLong(Object o) { return toNumber(o).longValue(); }
581         public boolean toBoolean(Object o) {
582             if (o == null) return false;
583             if (o instanceof Boolean) return ((Boolean)o).booleanValue();
584             if (o instanceof Number) return o.equals(new Integer(0));
585             return true;
586         }
587
588         public Object eval(final JS.Scope s) throws ControlTransferException, JS.Exn {
589             switch(code) {
590
591             case Lexer.BITOR: return new Long(toLong(left.eval(s)) | toLong(right.eval(s)));
592             case Lexer.BITXOR: return new Long(toLong(left.eval(s)) ^ toLong(right.eval(s)));
593             case Lexer.BITAND: return new Long(toLong(left.eval(s)) & toLong(right.eval(s)));
594             case Lexer.BITNOT: return new Long(~toLong(left.eval(s)));
595
596             case Lexer.ADD: {
597                 Object l = left.eval(s);
598                 Object r = right.eval(s);
599                 if (l instanceof String || r instanceof String) {
600                     if (l == null) l = "null";
601                     if (r == null) r = "null";
602                     if (l instanceof Number && ((Number)l).doubleValue() == ((Number)l).longValue())
603                         l = new Long(((Number)l).longValue());
604                     if (r instanceof Number && ((Number)r).doubleValue() == ((Number)r).longValue())
605                         r = new Long(((Number)r).longValue());
606                     return l.toString() + r.toString();
607                 }
608                 return new Double(toDouble(l) + toDouble(r));
609             }
610
611             case Lexer.SUB: return new Double(toDouble(left.eval(s)) - toDouble(right.eval(s)));
612             case Lexer.MUL: return new Double(toDouble(left.eval(s)) * toDouble(right.eval(s)));
613             case Lexer.DIV: return new Double(toDouble(left.eval(s)) / toDouble(right.eval(s)));
614             case Lexer.MOD: return new Double(toDouble(left.eval(s)) % toDouble(right.eval(s)));
615
616             case Lexer.LSH: return new Long(toLong(left.eval(s)) << toLong(right.eval(s)));
617             case Lexer.RSH: return new Long(toLong(left.eval(s)) >> toLong(right.eval(s)));
618             case Lexer.URSH: return new Long(toLong(left.eval(s)) >>> toLong(right.eval(s)));
619
620                 // FIXME: these need to work on strings
621             case Lexer.LT: return toDouble(left.eval(s)) < toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
622             case Lexer.LE: return toDouble(left.eval(s)) <= toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
623             case Lexer.GT: return toDouble(left.eval(s)) > toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
624             case Lexer.GE: return toDouble(left.eval(s)) >= toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
625
626             case Lexer.OR: {
627                 boolean b1 = toBoolean(left.eval(s));
628                 if (b1) return Boolean.TRUE;
629                 return new Boolean(b1 || toBoolean(right.eval(s)));
630             }
631
632             case Lexer.AND: {
633                 boolean b1 = toBoolean(left.eval(s));
634                 if (!b1) return Boolean.FALSE;
635                 return new Boolean(b1 && toBoolean(right.eval(s)));
636             }
637
638             case Lexer.BANG: return new Boolean(!toBoolean(left.eval(s)));
639
640             case Lexer.EQ:
641             case Lexer.NE: {
642                 // FIXME: should use Javascript coercion-equality rules
643                 Object l = left.eval(s);
644                 Object r = right.eval(s);
645                 boolean ret;
646                 if (l == null) { Object t = r; r = l; l = t; }
647                 if (l == null && r == null) ret = true;
648                 else if (l instanceof Boolean) ret = new Boolean(toBoolean(r)).equals(l);
649                 else if (l instanceof Number) ret = toNumber(r).doubleValue() == toNumber(l).doubleValue();
650                 else if (l instanceof String) ret = r != null && l.equals(r.toString());
651                 else ret = l.equals(r);
652                 
653                 return new Boolean(code == Lexer.EQ ? ret : !ret);
654             }
655
656             case Lexer.INC:
657             case Lexer.DEC:
658             case Lexer.ASSIGN: {
659                 Object v = (code == Lexer.ASSIGN) ? right.eval(s) : new Double(toDouble(left.eval(s)) + (code == Lexer.INC ? 1 : -1));
660                 if (left.code == Lexer.DOT) {
661                     Object o = left.left.eval(s);
662                     if (o instanceof String) {
663                         throw new EvaluatorException("can't set properties on a String");
664                     } else if (o instanceof Number) {
665                         throw new EvaluatorException("can't set properties on a Number");
666                     } else if (o instanceof Boolean) {
667                         throw new EvaluatorException("can't set properties on a Boolean");
668                     } else {
669                         JS target = (JS)o;
670                         if (target == null) throw new JS.Exn(new EvaluatorException("attempted to put to the null value"));
671                         target.put(left.right.eval(s), v);
672                         return v;
673                     }
674                 } else {
675                     s.put(left.string, v);
676                     return v;
677                 }
678             }
679
680             case Lexer.TYPEOF: {
681                 Object o = left.eval(s);
682                 if (o == null) return "null";
683                 if (o.getClass() == String.class) return "string";
684                 if (o.getClass() == Boolean.class) return "boolean";
685                 if (o instanceof Number) return "number";
686                 if (o instanceof JS.Array) return "array";
687                 if (o instanceof JS) return "object";
688                 throw new EvaluatorException("typeof " + o.getClass().getName() + " unknown");
689             }
690
691             case Lexer.NUMBER: return number;
692             case Lexer.STRING: return string;
693
694             case Lexer.NULL: return null;
695             case Lexer.FALSE: return Boolean.FALSE;
696             case Lexer.TRUE: return Boolean.TRUE;
697             case Lexer.ASSERT: if (!toBoolean(left.eval(s))) throw new EvaluatorException("assertion failed");
698             case Lexer.THROW: throw new JS.Exn(left.eval(s));
699             case Lexer.NAME: return s.get(string);
700             case Lexer.THIS: return s.isTransparent() ? s : this.eval(s.getParentScope());
701             case Lexer.DOT: {
702                 final Object o = left.eval(s);
703                 if (o == null) throw new EvaluatorException("tried to get a property from the null value");
704                 Object v = (right.code == Lexer.STRING) ? right.string : right.eval(s);
705                 if (o instanceof String) {
706                     if (v.equals("length")) return new Integer(((String)o).length());
707                     else if (v.equals("substring")) return new JS.Function() {
708                             public Object _call(JS.Array args) {
709                                 if (args.length() == 1) return ((String)o).substring(toNumber(args.elementAt(0)).intValue());
710                                 else if (args.length() == 2) return ((String)o).substring(toNumber(args.elementAt(0)).intValue(),
711                                                                                           toNumber(args.elementAt(1)).intValue());
712                                 else throw new EvaluatorException("String.substring() can only take one or two arguments");
713                             }
714                         };
715                     else if (v.equals("toLowerCase")) return new JS.Function() {
716                             public Object _call(JS.Array args) {
717                                 return ((String)o).toLowerCase();
718                             } };
719                     else if (v.equals("toUpperCase")) return new JS.Function() {
720                             public Object _call(JS.Array args) {
721                                 return ((String)o).toString().toUpperCase();
722                             } };
723                     else if (v.equals("charAt")) return new JS.Function() {
724                             public Object _call(JS.Array args) {
725                                 return ((String)o).charAt(toNumber(args.elementAt(0)).intValue()) + "";
726                             } };
727                     else if (v.equals("lastIndexOf")) return new JS.Function() {
728                             public Object _call(JS.Array args) {
729                                 if (args.length() != 1) return null;
730                                 return new Integer(((String)o).lastIndexOf(args.elementAt(0).toString()));
731                             } };
732                     else if (v.equals("indexOf")) return new JS.Function() {
733                             public Object _call(JS.Array args) {
734                                 if (args.length() != 1) return null;
735                                 return new Integer(((String)o).indexOf(args.elementAt(0).toString()));
736                             } };
737                     throw new EvaluatorException("Not Implemented: properties on String objects");
738                 } else if (o instanceof Boolean) {
739                     throw new EvaluatorException("Not Implemented: properties on Boolean objects");
740                 } else if (o instanceof Number) {
741                     Log.log(this, "Not Implemented: properties on Number objects");
742                     return null;
743                     //throw new EvaluatorException("Not Implemented: properties on Number objects");
744                 } else if (o instanceof JS) {
745                     return ((JS)o).get(v);
746                 }
747             }
748
749             case Lexer.TRY: {
750                 boolean safeToExit = false;
751                 try {
752                     Object ret = left.eval(s);
753                     safeToExit = true;
754                     return ret;
755                 } catch (JS.Exn e) {
756                     ExprList list = (ExprList)right;
757                     Expr c = list.elementAt(0);
758                     if (c.code == Lexer.CATCH) {
759                         JS.Scope scope = new JS.Scope(s);
760                         s.put(c.left.string, e);
761                         c.right.eval(scope);
762                         c = list.elementAt(1);
763                     }
764                     if (c.code == Lexer.FINALLY) {
765                         JS.Scope scope = new JS.Scope(s);
766                         c.left.eval(scope);
767                     }
768                 } finally {
769                     if (!safeToExit) Log.log(this, "WARNING: Java exception penetrated a JavaScript try{} block");
770                 }
771                 return null;
772             }
773
774             case Lexer.LP:
775                 JS.Function f = (JS.Function)left.eval(s);
776                 JS.Array arguments = new JS.Array();
777                 for(int i=0; i<((ExprList)right).numExprs(); i++) arguments.addElement(((ExprList)right).elementAt(i).eval(s));
778                 if (f == null) throw new JS.Exn(new EvaluatorException("attempted to call null"));
779                 return f.call(arguments);
780             
781             case Lexer.FUNCTION:
782                 return new JS.Function() {
783                         public String toString() { return right.sourceName + ":" + right.line; }
784                         public String getSourceName() throws JS.Exn { return right.sourceName; }
785                         public Object _call(final JS.Array args) throws JS.Exn {
786                             Function save = JS.getCurrentFunction();
787                             JS.currentFunction.put(Thread.currentThread(), this);
788                             JS.Scope scope = new JS.Scope(s) {
789                                     // FIXME
790                                     public String getSourceName() { return sourceName; }
791                                     public Object get(Object key) throws JS.Exn {
792                                         if (key.equals("trapee")) return org.xwt.Trap.currentTrapee();
793                                         else if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction;
794                                         else if (key.equals("arguments")) return args;
795                                         return super.get(key);
796                                     }
797                                 };
798                             int i = 0;
799                             ExprList list = (ExprList)left;
800                             for(i=0; i<list.size(); i++) {
801                                 scope.declare(list.elementAt(i).string);
802                                 scope.put(list.elementAt(i).string, args.get(new Integer(i)));
803                             }
804                             try {
805                                 return right.eval(scope);
806                             } catch (ReturnException r) {
807                                 return r.retval;
808                             } catch (ControlTransferException c) {
809                                 throw new EvaluatorException("error, ControlTransferException tried to leave a function: "
810                                                              + c);
811                             } finally {
812                                 if (save == null) JS.currentFunction.remove(Thread.currentThread());
813                                 else JS.currentFunction.put(Thread.currentThread(), save);
814                             }
815                         }
816                     };
817                 
818             case Lexer.FOR:
819                 Object[] keys = ((JS)left.right.eval(s)).keys();
820                 for(int i=0; i<keys.length; i++) {
821                     JS.Scope scope = new JS.Scope(s);
822                     scope.declare(left.string);
823                     scope.put(left.string, keys[i]);
824                     try {
825                         right.eval(scope);
826                     } catch (ContinueException c) {
827                         if (c.label == null || c.label.equals(string)) continue;
828                     } catch (BreakException b) {
829                         if (b.label == null || b.label.equals(string)) return null;
830                         throw (BreakException)b.fillInStackTrace();
831                     }
832                 }
833                 return null;
834
835             case Lexer.SWITCH:
836                 Object switchVal = left.eval(s);
837                 boolean go = false;
838                 try {
839                     ExprList list = (ExprList)right;
840                     for(int i=0; i<list.size(); i++) {
841                         Expr e = list.elementAt(i);
842                         if (go || e.code == Lexer.DEFAULT || e.left.eval(s).equals(switchVal)) go = true;
843                         if (go) e.right.eval(s);
844                     }
845                 } catch (BreakException b) {
846                     if (b.label == null || b.label.equals(string)) return null;
847                     throw (BreakException)b.fillInStackTrace();
848                 }
849                 return null;
850
851             case Lexer.HOOK: return toBoolean(left.eval(s)) ? right.left.eval(s) : right.right.eval(s);
852             case Lexer.IF:
853                 if (right.code == ELSE) {
854                     if (toBoolean(left.eval(s))) right.left.eval(s);
855                     else right.right.eval(s);
856                     return null;
857                 } else {
858                     if (toBoolean(left.eval(s))) right.eval(s);
859                     return null;
860                 }
861             case Lexer.BREAK: throw new BreakException(string);
862             case Lexer.CONTINUE: throw new ContinueException(string);
863             case Lexer.RETURN: throw new ReturnException(left == null ? null : left.eval(s));
864
865             default: throw new EvaluatorException("don't know how to eval an Expr with code " + Lexer.codeToString[code] + "\n" + this);
866             }
867         }
868
869         class EvaluatorException extends RuntimeException {
870             public EvaluatorException(String s) { super(sourceName + ":" + line + " " + s); }
871         }
872     }
873
874         static abstract class ControlTransferException extends Exception { }
875         static class BreakException extends ControlTransferException {
876             public String label;
877             BreakException(String label) { this.label = label; }
878         }
879         static class ContinueException extends ControlTransferException {
880             public String label;
881             ContinueException(String label) { this.label = label; }
882         }
883         static class ReturnException extends ControlTransferException {
884             public Object retval;
885             ReturnException(Object retval) { this.retval = retval; }
886         }
887
888     class ParserException extends RuntimeException {
889         public ParserException(String s) { super(sourceName + ":" + line + " " + s); }
890     }
891
892         public static Number toNumber(Object o) {
893             if (o == null) return new Long(0);
894             if (o instanceof Number) return ((Number)o);
895             if (o instanceof String) try { return new Double((String)o); } catch (NumberFormatException e) { return new Double(0); }
896             if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? new Long(1) : new Long(0);
897             if (o instanceof JS) return ((JS)o).coerceToNumber();
898             // FIXME
899             throw new Error("toNumber() got object of type " + o.getClass().getName());
900         }
901  }
902