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