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