2003/06/03 08:56:56
[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 consume(int code, int code2) throws IOException {
66         int got = getToken();
67         if (got != code && got != code2)
68             throw new ParserException("expected " + codeToString[code] + " or " + codeToString[code2] +
69                                       ", got " + (got == -1 ? "EOL" : codeToString[got]));
70     }
71     public void expect(int code) throws IOException {
72         int got = peekToken();
73         if (got != code) throw new ParserException("expected " + codeToString[code] + ", got " + (got == -1 ? "EOL" : codeToString[got]));
74     }
75
76     /** parses the largest possible expression */
77     public Expr parseMaximalExpr() throws IOException { return parseMaximalExpr(null, -1); }
78     public Expr parseMaximalExpr(Expr prefix, int minPrecedence) throws IOException {
79         while(true) {
80             if (peekToken() == -1) break;
81             Expr save = prefix;
82             prefix = parseSingleExpr(prefix, minPrecedence);
83             if (save == prefix) break;
84             if (prefix == null) throw new ParserException("parseSingleExpr() returned null");
85         }
86         return prefix;
87     }
88
89     public Expr parseSingleExpr(Expr prefix, int minPrecedence) throws IOException {
90         Expr e1 = null, e2 = null, e3 = null, head = null, tail = null, ret = null;
91
92         int tok = peekToken();
93         if (minPrecedence > 0 &&
94             tok < precedence.length &&
95             precedence[tok] != 0 &&
96             (isRightAssociative[tok] ?
97              (precedence[tok] < minPrecedence) :
98              (precedence[tok] <= minPrecedence))) {
99             return prefix;
100              }
101         getToken();
102         int curLine = line;
103
104         // these case arms match the precedence of operators; each arm is a precedence level.
105         switch (tok) {
106
107         case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH:
108         case ASSIGN_RSH: case ASSIGN_URSH: case ASSIGN_ADD: case ASSIGN_SUB: case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD:
109             return new Expr(curLine, ASSIGN, prefix, new Expr(curLine, tok - 1, prefix, parseMaximalExpr(null, precedence[ASSIGN])));
110
111             // DONE //
112
113             // FIXME: ugly hack!!
114         case INC: case DEC:
115             if (prefix == null) {
116                 // prefix
117                 ByteCode b = (ByteCode)parseMaximalExpr(null, precedence[tok]);
118                 b.set(b.size() - 1, tok, new Boolean(true));
119                 return b;
120             } else {
121                 // postfix
122                 ByteCode b = (ByteCode)prefix;
123                 b.set(b.size() - 1, tok, new Boolean(false));
124                 return b;
125             }
126
127         case LP:
128             if (prefix == null) {  // grouping
129                 ByteCode b = new ByteCode(curLine, ByteCode.EXPR, parseMaximalExpr());
130                 consume(RP);
131                 return b;
132
133             } else {  // invocation
134                 ByteCode b = new ByteCode(curLine);
135                 int i = 0;
136                 b.add(b.EXPR, prefix);
137                 while(peekToken() != RP) {
138                     b.add(b.EXPR, parseMaximalExpr());
139                     i++;
140                     if (peekToken() == RP) break;
141                     consume(COMMA);
142                 }
143                 consume(RP);
144                 b.add(b.CALL, new Integer(i));
145                 return b;
146             }
147
148         case BANG: case BITNOT: case INSTANCEOF: case TYPEOF: {
149             if (prefix != null) { pushBackToken(); return prefix; }
150             ByteCode b = new ByteCode(curLine);
151             b.add(b.EXPR, parseMaximalExpr(null, precedence[tok]));
152             b.add(tok, NO_ARG);
153             return b;
154         }
155
156         case SUB:
157             if (prefix == null && peekToken() == NUMBER) {
158                 getToken();
159                 return new ByteCode(curLine, ByteCode.LITERAL, new Double(number.doubleValue() * -1));
160             } // else fall through
161         case BITOR: case BITXOR: case BITAND: case SHEQ: case SHNE: case LSH:
162         case RSH: case URSH: case ADD: case MUL: case DIV: case MOD:
163         case GT: case GE: case EQ: case NE: case LT: case LE: {
164             if (prefix == null) throw new ParserException("the " + codeToString[tok] + " token cannot start an expression");
165             ByteCode b = new ByteCode(curLine);
166             b.add(b.EXPR, prefix);
167             b.add(b.EXPR, parseMaximalExpr(null, precedence[tok]));
168             b.add(tok, NO_ARG);
169             return b;
170         }
171
172             // includes short-circuit logic
173         case OR: case AND: {
174             if (prefix == null) throw new ParserException("the " + codeToString[tok] + " token cannot start an expression");
175             ByteCode b = new ByteCode(curLine);
176             b.add(b.LITERAL, tok == AND ? new Boolean(false) : new Boolean(true));
177             b.add(b.EXPR, prefix);
178             b.add(tok == AND ? b.JF : b.JT, new Integer(3));
179             b.add(b.POP, NO_ARG);
180             b.add(b.EXPR, parseMaximalExpr(null, precedence[tok]));
181             return b;
182         }
183
184         case ASSIGN: {
185             if (prefix == null) throw new ParserException("the " + codeToString[tok] + " token cannot start an expression");
186             ByteCode b = new ByteCode(curLine);
187             b.add(b.THIS, NO_ARG);
188             b.add(b.LITERAL, prefix.string);   // FIXME, this is ass-ugly
189             b.add(b.EXPR, parseMaximalExpr(null, precedence[ASSIGN]));
190             b.add(b.PUT, NO_ARG);
191             b.add(b.SWAP, NO_ARG);
192             b.add(b.POP, NO_ARG);
193             return b;
194         }
195
196         case WITH: throw new ParserException("XWT does not allow the WITH keyword");
197         case VOID: case RESERVED: throw new ParserException("reserved word that you shouldn't be using");
198
199         case NUMBER:
200             if (prefix != null) { pushBackToken(); return prefix; }
201             return new ByteCode(curLine, ByteCode.LITERAL, number);
202
203         case STRING:
204             if (prefix != null) { pushBackToken(); return prefix; }
205             return new ByteCode(curLine, ByteCode.LITERAL, string);
206
207         case NULL: case TRUE: case FALSE: case NOP:
208             if (prefix != null) { pushBackToken(); return prefix; }
209             return new ByteCode(curLine, ByteCode.LITERAL, (tok == NULL || tok == NOP) ? null : new Boolean(tok == TRUE));
210
211         case COMMA: pushBackToken(); return prefix;
212
213         case THIS:
214             if (prefix != null) { pushBackToken(); return prefix; } 
215             return new ByteCode(curLine, ByteCode.THIS, NO_ARG);
216
217             // potential lvalues
218
219         case NAME: {
220             if (prefix != null) { pushBackToken(); return prefix; }
221             String name = string;
222             ByteCode b = new ByteCode(curLine);
223             if (peekToken() == ASSIGN) {
224                 consume(ASSIGN);
225                 b.add(ByteCode.THIS, NO_ARG);
226                 b.add(ByteCode.LITERAL, name);
227                 b.add(ByteCode.EXPR, parseMaximalExpr(null, minPrecedence));
228                 b.add(ByteCode.PUT, NO_ARG);
229                 b.add(ByteCode.SWAP, NO_ARG);
230                 b.add(ByteCode.POP, NO_ARG);
231                 return b;
232             } else {
233                 b.add(ByteCode.THIS, NO_ARG);
234                 b.add(ByteCode.LITERAL, name);
235                 b.add(ByteCode.GET, NO_ARG);
236                 return parseMaximalExpr(b, minPrecedence);
237             }
238         }
239
240         case DOT: {
241             consume(NAME);
242             String target = string;
243             ByteCode b = new ByteCode(curLine);
244             b.add(b.EXPR, prefix);
245             if (peekToken() == ASSIGN) {
246                 consume(ASSIGN);
247                 Expr val = parseMaximalExpr();
248                 b.add(b.LITERAL, target);
249                 b.add(b.EXPR, val);
250                 b.add(b.PUT, NO_ARG);
251                 b.add(b.SWAP, NO_ARG);
252                 b.add(b.POP, NO_ARG);
253             } else {
254                 b.add(b.LITERAL, target);
255                 b.add(b.GET, NO_ARG);
256             }
257             return b;
258         }
259
260         case LB: {
261             ByteCode b = new ByteCode(curLine);
262             if (prefix == null) {
263                 b.add(b.ARRAY, new Integer(0));
264                 int i = 0;
265                 while(true) {
266                     Expr e = parseMaximalExpr();
267                     if (e == null && peekToken() == RB) { consume(RB); return b; }
268                     if (e == null) e = new Expr(curLine, NULL);
269                     b.add(b.LITERAL, new Integer(i++));
270                     b.add(b.EXPR, e);
271                     b.add(b.PUT, NO_ARG);
272                     b.add(b.POP, NO_ARG);
273                     if (peekToken() == RB) { consume(RB); return b; }
274                     consume(COMMA);
275                 }
276             } else {
277                 b.add(b.EXPR, prefix);
278                 b.add(b.EXPR, parseMaximalExpr());
279                 consume(RB);
280                 if (peekToken() == ASSIGN) {
281                     consume(ASSIGN);
282                     b.add(b.EXPR, parseMaximalExpr());
283                     b.add(b.PUT, NO_ARG);
284                     b.add(b.SWAP, NO_ARG);
285                     b.add(b.POP, NO_ARG);
286                 } else {
287                     b.add(b.GET, NO_ARG);
288                 }
289                 return b;
290             }
291         }
292
293             // end //
294             
295         case LC: {
296             if (prefix != null) { pushBackToken(); return prefix; }
297             ByteCode b = new ByteCode(curLine);
298             b.add(b.OBJECT, null);
299             if (peekToken() == RC) { consume(RC); return b; }
300             while(true) {
301                 consume(NAME, STRING);
302                 b.add(b.LITERAL, string);
303                 consume(COLON);
304                 b.add(b.EXPR, parseMaximalExpr());
305                 b.add(b.PUT, NO_ARG);
306                 b.add(b.POP, NO_ARG);
307                 if (peekToken() == RC) { consume(RC); return b; }
308                 consume(COMMA);
309                 if (peekToken() == RC) { consume(RC); return b; }
310             }
311         }
312             
313         case HOOK: {
314             ByteCode b = new ByteCode(curLine);
315             b.add(b.EXPR, prefix);
316             b.add(b.JF, new Integer(3));
317             b.add(b.EXPR, parseMaximalExpr());
318             b.add(b.JMP, new Integer(2));
319             consume(COLON);
320             b.add(b.EXPR, parseMaximalExpr());
321             return b;
322         }
323             
324         case FUNCTION: {
325             if (prefix != null) { pushBackToken(); return prefix; }
326             consume(LP);
327             ExprList list = new ExprList(curLine, LC);
328             if (peekToken() == RP) consume(RP);
329             else while(true) {
330                 tok = peekToken();
331                 if (tok == COMMA) {
332                     consume(COMMA);
333                     list.add(new Expr(curLine, NULL));
334                 } else {
335                     consume(NAME);
336                     list.add(new Expr(curLine, NAME, string));
337                     if (peekToken() == RP) { consume(RP); break; }
338                     consume(COMMA);
339                 }
340             }
341             return new Expr(curLine, FUNCTION, list, parseBlock(true));
342         }
343             
344         case VAR: {
345             if (prefix != null) { pushBackToken(); return prefix; }
346             ByteCode b = new ByteCode(curLine);
347             b.add(b.THIS, NO_ARG);
348             while(true) {
349                 consume(NAME);
350                 String name = string;
351                 b.add(b.DECLARE, name);
352                 if (peekToken() == ASSIGN) {
353                     b.add(b.LITERAL, name);
354                     consume(ASSIGN);
355                     b.add(b.EXPR, parseMaximalExpr());
356                     b.add(b.PUT, NO_ARG);
357                     b.add(b.POP, NO_ARG);
358                 }
359                 if (peekToken() != COMMA) break;
360                 consume(COMMA);
361             }
362             return b;
363         }
364
365         case IN: pushBackToken(); return prefix;
366
367         case IF: {
368             if (prefix != null) { pushBackToken(); return prefix; }
369             ByteCode b = new ByteCode(curLine);
370             consume(LP);
371             b.add(b.EXPR, parseMaximalExpr());
372             consume(RP);
373             b.add(b.JF, new Integer(3));
374             b.add(b.EXPR, parseBlock(false));
375             b.add(b.JMP, new Integer(2));
376             if (peekToken() != ELSE) return b.add(b.LITERAL, null);
377             consume(ELSE);
378             b.add(b.EXPR, parseBlock(false));
379             return b;
380         }
381
382             // Needs break //
383
384         case SWITCH: {
385             if (prefix != null) { pushBackToken(); return prefix; }
386             if (getToken() != LP) throw new ParserException("expected left paren");
387             Expr switchExpr = parseMaximalExpr();
388             if (getToken() != RP) throw new ParserException("expected left paren");
389             if (getToken() != LC) throw new ParserException("expected left brace");
390             ExprList toplevel = new ExprList(curLine, LC);
391             while(true) {
392                 tok = getToken();
393                 Expr caseExpr;
394                 if (tok == DEFAULT) caseExpr = null;
395                 else if (tok == CASE) caseExpr = parseMaximalExpr();
396                 else throw new ParserException("expected CASE or DEFAULT");
397                 consume(COLON);
398                 // FIXME: we shouldn't be creating a scope here
399                 ExprList list = new ExprList(curLine, LC);
400                 while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) {
401                     if ((e1 = parseBlock(false)) == null) break;
402                     list.add(e1);
403                 }
404                 toplevel.add(new Expr(curLine, tok, caseExpr, list));
405                 if (peekToken() == RC) { consume(RC); return new Expr(curLine, SWITCH, switchExpr, toplevel); }
406             }
407         }
408             
409         case TRY: {
410             // We deliberately allow you to omit braces in catch{}/finally{} if they are single statements...
411             if (prefix != null) { pushBackToken(); return prefix; }
412             Expr tryBlock = parseBlock(true);
413             
414             tok = peekToken();
415             ExprList list = new ExprList(curLine, TRY);
416             if (tok == CATCH) {
417                 getToken();
418                 if (getToken() != LP) throw new ParserException("expected (");
419                 if (getToken() != NAME) throw new ParserException("expected name");
420                 Expr name = new Expr(curLine, NAME, string);
421                 if (getToken() != RP) throw new ParserException("expected )");
422                 list.add(new Expr(curLine, CATCH, name, parseBlock(false)));
423                 tok = peekToken();
424             }
425             if (tok == FINALLY) {
426                 getToken();
427                 list.add(new Expr(curLine, FINALLY, parseBlock(false)));
428             }
429
430             if (list.size() == 0) throw new ParserException("try without catch or finally");
431             return new Expr(curLine, TRY, tryBlock, list);
432         }
433
434         case WHILE: {
435             if (prefix != null) { pushBackToken(); return prefix; }
436             consume(LP);
437             Expr parenExpr = parseMaximalExpr();
438             int t;
439             if ((t = getToken()) != RP)
440                 throw new ParserException("expected right paren, but got " + codeToString[t]);
441             Expr firstBlock = parseBlock(false);
442             ExprList list = new ExprList(curLine, WHILE);
443             list.add(parenExpr);
444             list.add(firstBlock);
445             list.add(new Expr(curLine, NULL));
446             return list;
447         }
448
449         case FOR:
450             if (prefix != null) { pushBackToken(); return prefix; }
451             if (getToken() != LP) throw new ParserException("expected left paren");
452             e1 = parseMaximalExpr();
453             if (peekToken() == IN) {
454                 getToken();
455                 e2 = parseMaximalExpr();
456                 if (getToken() != RP) throw new ParserException("expected right paren");
457                 return new Expr(curLine, FOR, new Expr(curLine, IN, e1.left, e2), parseBlock(false));
458                 
459             } else {
460                 Expr initExpr = e1;
461                 if (initExpr == null) initExpr = new Expr(curLine, NULL);
462                 expect(SEMI); getToken();
463                 Expr whileExpr = parseMaximalExpr();
464                 expect(SEMI); getToken();
465                 Expr incExpr = parseMaximalExpr();
466                 expect(RP); getToken();
467                 Expr body = parseBlock(false);
468                 Expr loop = new Expr(curLine, WHILE, whileExpr, body);
469                 ExprList list = new ExprList(curLine, LC);
470                 list.add(initExpr);
471                 ExprList list2 = new ExprList(curLine, WHILE);
472                 list.add(list2);
473                 list2.add(whileExpr);
474                 list2.add(body);
475                 list2.add(incExpr);
476                 return list;
477             }
478             
479         case DO: {
480             if (prefix != null) { pushBackToken(); return prefix; }
481             ExprList list = new ExprList(curLine, DO);
482             Expr body = parseBlock(false);
483             consume(WHILE);
484             consume(LP);
485             list.add(parseMaximalExpr());
486             list.add(body);
487             list.add(new Expr(curLine, NULL));
488             consume(RP);
489             consume(SEMI);
490             return list;
491         }
492             
493         default:
494             pushBackToken();
495             return prefix;
496         }
497     }
498     
499     // Expr //////////////////////////////////////////////////////////////////////
500
501     public static final Object NO_ARG = new Object();
502
503     class ByteCode extends Expr {
504
505         // This class interprets a small forthlike bytecode language
506         // we use to represent JavaScript.  It's a stack machine.
507
508         // Each instruction is an opcode and a literal.  Any operation
509         // that accesses the top of the stack and does not have a
510         // mandatory literal can be performed on a literal instead of
511         // the top of the stack.
512
513         // opcodes:
514         public static final byte arithmetic = -1;  //                               -- arithmetic operators from parser
515         public static final byte LITERAL = -2;     // < String | Number | null >    -- push a literal onto the stack
516         public static final byte ARRAY = -3;       // < size >                      -- create a new array of size <size>
517         public static final byte OBJECT = -4;      //                               -- push an empty object onto the stack
518         public static final byte FUNCTION = -5;    // < bytecode_block >            -- push a new instance of a function with the given bytecode
519         public static final byte DECLARE = -6;     // < name >                      -- declare <name> in the current scope
520         public static final byte THIS = -7;        //                               -- push the topmost non-transparent scope onto the stack
521         public static final byte GET = -8;         //                               -- get stack[0] from stack[1]
522         public static final byte PUT = -9;         //                               -- put stack[1] to key stack[0] on stack[2]; leaves object on the stack
523         public static final byte THROW = -10;      //                               -- throw topmost element
524         public static final byte RETURN = -11;     //                               -- return the topmost value on the stack
525         public static final byte ASSERT = -12;     //                               -- fail if topmost element is not true
526         public static final byte JT = -13;         // < relative_address >          -- pop the stack; if true, jump to <relative_address>
527         public static final byte JF = -21;         // < relative_address >          -- pop the stack; if false, jump to <relative_address>
528         public static final byte JMP = -22;        // < relative_address >          -- jump to <relative_address>
529         static public final byte POP = -14;        //                               -- discard the top element on the stack
530
531         public static final byte CALL = -15;       // < numargs >                   -- call stack[0] with the topmost <numargs> values as arguments
532         public static final byte TRY = -16;        // < bytecode_block >            -- run the block pointed to; returns with thrown exn on top of stack
533
534         public static final byte INSTANCEOF = -17; //                               -- ??
535         public static final byte TYPEOF = -18;     //                               --
536
537         public static final byte FOR__IN = -19;    //                               -- ??
538         public static final byte EXPR = -20;       //                               -- transitional
539         public static final byte SWAP = -23;       //                               -- transitional
540         public static final byte SCOPE = -30;       //                               -- transitional
541
542         int[] op = new int[10];
543         Object[] arg = new Object[10];
544         int size = 0;
545
546         public ByteCode(int line) { super(line, "foo"); }
547         public ByteCode(int line, int op_, Object arg_) { this(line); add(op_, arg_); }
548
549         public int size() { return size; }
550         public void set(int pos, int op_, Object arg_) { op[pos] = op_; arg[pos] = arg_; }
551         public ByteCode add(int op_, Object arg_) {
552             if (size == op.length - 1) {
553                 int[] op2 = new int[op.length * 2]; System.arraycopy(op, 0, op2, 0, op.length); op = op2;
554                 Object[] arg2 = new Object[op.length * 2]; System.arraycopy(arg, 0, arg2, 0, arg.length); arg = arg2;
555             }
556             op[size] = op_;
557             arg[size] = arg_;
558             size++;
559             return this;
560         }
561         
562         public Object eval(final JS.Scope s) throws ControlTransferException, JS.Exn {
563             return eval(s, new Parser.Thread());
564         }
565         public Object eval(JS.Scope s, Parser.Thread t) throws ControlTransferException {
566             for(int i=0; i<size; i++)
567                 switch(op[i]) {
568                 case arithmetic: break;
569                 case LITERAL:
570                     t.push(arg[i]);
571                     break;
572                 case OBJECT: t.push(new JS.Obj()); break;
573                 case ARRAY: t.push(new JS.Array(toNumber(arg[i]).intValue())); break;
574                 case DECLARE: s.declare(arg[i] == NO_ARG ? (String)t.pop() : (String)arg[i]); break;
575                 case THIS: t.push(s); break;   // FIXME: transparents
576                 case ASSERT: if (!toBoolean(t.pop())) throw new EvaluatorException("assertion failed"); break;
577                 case THROW: throw new JS.Exn(t.pop());
578                 case JT: if (toBoolean(t.pop())) i += toNumber(arg[i]).intValue() - 1; break;
579                 case JF: if (!toBoolean(t.pop())) i += toNumber(arg[i]).intValue() - 1; break;
580                 case JMP: i += toNumber(arg[i]).intValue() - 1; break;
581                 case POP: t.pop(); break;
582                 case SWAP: t.swap(); break;
583                     
584                 case PUT: {
585                     Object val = arg[i] == NO_ARG ? t.pop() : arg[i];
586                     Object key = t.pop();
587                     ((JS)t.peek()).put(key, val);
588                     t.push(val);
589                     break;
590                 }
591
592                 case GET: {
593                     Object v = arg[i] == NO_ARG ? t.pop() : arg[i];
594                     Object o = t.pop();
595                     t.push(doGet(o, v));
596                     break;
597                 }
598                 case NOP: break;
599                     
600                 case EXPR: t.push(((Expr)arg[i]).eval(s)); break;
601                 case SCOPE: t.push(((Expr)arg[i]).eval(new JS.Scope(s))); break;
602                     
603                 case CALL:
604                     JS.Array arguments = new JS.Array();
605                     int numArgs = toNumber(arg[i]).intValue();
606                     arguments.setSize(numArgs);
607                     for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(t.pop(), j);
608                     JS.Function f = (JS.Function)t.pop();
609                     if (f == null) throw new JS.Exn(new EvaluatorException("attempted to call null"));
610                     t.push(f.call(arguments));
611                     break;
612
613                 case FUNCTION: break;
614                 case RETURN: break;
615                 case TRY: break;
616                 case INSTANCEOF: break;
617                 case TYPEOF: break;
618                 case FOR__IN: break;
619
620                 case Lexer.BITNOT: t.push(new Long(~toLong(t.pop()))); break;
621                 case Lexer.BANG: t.push(new Boolean(!toBoolean(t.pop()))); break;
622
623                 case Lexer.INC: case Lexer.DEC: {
624                     boolean isPrefix = toBoolean(arg[i]);
625                     Object key = t.pop();
626                     JS obj = (JS)t.pop();
627                     Number num = toNumber(obj.get(key));
628                     Number val = new Double(op[i] == Lexer.INC ? num.doubleValue() + 1.0 : num.doubleValue() - 1.0);
629                     obj.put(key, val);
630                     t.push(isPrefix ? val : num);
631                     break;
632                 }
633
634                 default: {
635                     Object right = t.pop();
636                     Object left = t.pop();
637                     switch(op[i]) {
638
639                     case Lexer.BITOR: t.push(new Long(toLong(left) | toLong(right))); break;
640                     case Lexer.BITXOR: t.push(new Long(toLong(left) ^ toLong(right))); break;
641                     case Lexer.BITAND: t.push(new Long(toLong(left) & toLong(right))); break;
642
643                     case Lexer.ADD: {
644                         Object l = left;
645                         Object r = right;
646                         if (l instanceof String || r instanceof String) {
647                             if (l == null) l = "null";
648                             if (r == null) r = "null";
649                             if (l instanceof Number && ((Number)l).doubleValue() == ((Number)l).longValue())
650                                 l = new Long(((Number)l).longValue());
651                             if (r instanceof Number && ((Number)r).doubleValue() == ((Number)r).longValue())
652                                 r = new Long(((Number)r).longValue());
653                             t.push(l.toString() + r.toString()); break;
654                         }
655                         t.push(new Double(toDouble(l) + toDouble(r))); break;
656                     }
657                         
658                     case Lexer.SUB: t.push(new Double(toDouble(left) - toDouble(right))); break;
659                     case Lexer.MUL: t.push(new Double(toDouble(left) * toDouble(right))); break;
660                     case Lexer.DIV: t.push(new Double(toDouble(left) / toDouble(right))); break;
661                     case Lexer.MOD: t.push(new Double(toDouble(left) % toDouble(right))); break;
662                         
663                     case Lexer.LSH: t.push(new Long(toLong(left) << toLong(right))); break;
664                     case Lexer.RSH: t.push(new Long(toLong(left) >> toLong(right))); break;
665                     case Lexer.URSH: t.push(new Long(toLong(left) >>> toLong(right))); break;
666                         
667                         // FIXME: these need to work on strings
668                     case Lexer.LT: t.push(toDouble(left) < toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
669                     case Lexer.LE: t.push(toDouble(left) <= toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
670                     case Lexer.GT: t.push(toDouble(left) > toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
671                     case Lexer.GE: t.push(toDouble(left) >= toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
672                     
673                     case Lexer.EQ:
674                     case Lexer.NE: {
675                         // FIXME: should use Javascript coercion-equality rules
676                         Object l = left;
677                         Object r = right;
678                         boolean ret;
679                         if (l == null) { Object tmp = r; r = l; l = tmp; }
680                         if (l == null && r == null) ret = true;
681                         else if (l instanceof Boolean) ret = new Boolean(toBoolean(r)).equals(l);
682                         else if (l instanceof Number) ret = toNumber(r).doubleValue() == toNumber(l).doubleValue();
683                         else if (l instanceof String) ret = r != null && l.equals(r.toString());
684                         else ret = l.equals(r);
685                         t.push(new Boolean(op[i] == Lexer.EQ ? ret : !ret)); break;
686                     }
687
688                     default: throw new Error("unknown opcode " + op[i]);
689                     } }
690                 }
691             if (t.size() != 1) {
692                 throw new EvaluatorException("eval() terminated with " + t.size() + " elements on the stack; one expected");
693             }
694             return t.pop();
695         }
696
697         public Object doGet(final Object o, final Object v) {
698             if (o == null) throw new EvaluatorException("tried to get property \"" + v + "\" from the null value");
699             if (o instanceof String) {
700                 if (v.equals("length")) return new Integer(((String)o).length());
701                 else if (v.equals("substring")) return new JS.Function() {
702                         public Object _call(JS.Array args) {
703                             if (args.length() == 1) return ((String)o).substring(toNumber(args.elementAt(0)).intValue());
704                             else if (args.length() == 2) return ((String)o).substring(toNumber(args.elementAt(0)).intValue(),
705                                                                                       toNumber(args.elementAt(1)).intValue());
706                             else throw new Error("String.substring() can only take one or two arguments");
707                         }
708                     };
709                 else if (v.equals("toLowerCase")) return new JS.Function() {
710                         public Object _call(JS.Array args) {
711                             return ((String)o).toLowerCase();
712                         } };
713                 else if (v.equals("toUpperCase")) return new JS.Function() {
714                         public Object _call(JS.Array args) {
715                             return ((String)o).toString().toUpperCase();
716                         } };
717                 else if (v.equals("charAt")) return new JS.Function() {
718                         public Object _call(JS.Array args) {
719                             return ((String)o).charAt(toNumber(args.elementAt(0)).intValue()) + "";
720                         } };
721                 else if (v.equals("lastIndexOf")) return new JS.Function() {
722                         public Object _call(JS.Array args) {
723                             if (args.length() != 1) return null;
724                             return new Integer(((String)o).lastIndexOf(args.elementAt(0).toString()));
725                         } };
726                 else if (v.equals("indexOf")) return new JS.Function() {
727                         public Object _call(JS.Array args) {
728                             if (args.length() != 1) return null;
729                             return new Integer(((String)o).indexOf(args.elementAt(0).toString()));
730                         } };
731                 throw new Error("Not Implemented: properties on String objects");
732             } else if (o instanceof Boolean) {
733                 throw new Error("Not Implemented: properties on Boolean objects");
734             } else if (o instanceof Number) {
735                 Log.log(this, "Not Implemented: properties on Number objects");
736                 return null;
737                 //throw new Error("Not Implemented: properties on Number objects");
738             } else if (o instanceof JS) {
739                 return ((JS)o).get(v);
740             }
741             return null;
742         }
743     }
744
745     class Thread {
746         public Object[] os = new Object[256];
747         int size = 0;
748         public void push(Object o) { os[size++] = o; }
749         public Object pop() { return os[--size]; }
750         public Object peek() { return os[size - 1]; }
751         public void swap() { Object temp = os[size - 1]; os[size - 1] = os[size - 2]; os[size - 2] = temp; }
752         public int size() { return size; }
753     }
754
755     class ExprList extends Expr {
756         Vec v = new Vec();
757         public ExprList(int curLine, int code) { super(curLine, code); }
758         public void add(Expr e) { v.addElement(e); }
759         public int numExprs() { return v.size(); }
760         public int size() { return v.size(); }
761         public Expr elementAt(int i) { return (Expr)v.elementAt(i); }
762         public Object eval(final JS.Scope s) throws ControlTransferException, JS.Exn {
763             switch(code) {
764             case LC: {
765                 // Block
766                 JS.Scope scope = new JS.Scope(s);
767                 for(int i=0; i<v.size(); i++) ((Expr)v.elementAt(i)).eval(scope);
768                 return null;
769             }
770             case Lexer.LB: {
771                 // Array ctor
772                 JS.Array ret = new JS.Array();
773                 for(int i=0; i<numExprs(); i++) ret.addElement(elementAt(i).eval(s));
774                 return ret;
775             }
776             case Lexer.RC: {
777                 // Object ctor
778                 JS.Obj ret = new JS.Obj();
779                 for(int i=0; i<numExprs(); i++) {
780                     Expr e = elementAt(i);
781                     Object key = e.left.string;
782                     Object val = e.right.eval(s);
783                     ret.put(key, val);
784                 }
785                 return ret;
786             }
787             case Lexer.WHILE:
788             case Lexer.DO:
789                 try {
790                     boolean first = true;
791                     Object bogus = null;
792                     ExprList list = this;
793                     Expr whileExpr = list.elementAt(0);
794                     Expr bodyExpr = list.elementAt(1);
795                     Expr incExpr = list.elementAt(2);
796                     for(;(first && code == Lexer.DO) || toBoolean(whileExpr.eval(s)); bogus = incExpr.eval(s)) {
797                         first = false;
798                         try {
799                             bodyExpr.eval(s);
800                         } catch (ContinueException c) {
801                             if (c.label == null || c.label.equals(string)) continue;
802                         } catch (BreakException b) {
803                             if (b.label == null || b.label.equals(string)) return null;
804                             throw (BreakException)b.fillInStackTrace();
805                         }
806                     }
807                 } catch (BreakException e) {
808                     if (e.label != null && !e.label.equals(string)) throw e;
809                 }
810                 return null;
811             case Lexer.VAR:
812                 for(int i=0; i<size(); i++) {
813                     Expr e = elementAt(i);
814                     s.declare(e.left.string);
815                     if (e.right != null) e.right.eval(s);
816                 }
817                 return null;
818             default: throw new Error("augh!");
819             }
820         }
821     }
822     
823     /** a block is either a single statement or a list of statements surrounded by curly braces; all expressions are also statements */
824     public Expr parseBlock(boolean requireBraces) throws IOException {
825         Expr smt = null;
826         int tok = peekToken();
827         if (tok == -1) return null;
828         boolean braced = tok == LC;
829         if (requireBraces && !braced) throw new ParserException("expected {, got " + codeToString[tok]);
830         if (braced) consume(LC);
831         int curLine = line;
832         ByteCode ret = new ByteCode(curLine);
833         ByteCode block = new ByteCode(curLine);
834         block.add(ret.LITERAL, Boolean.TRUE);
835         ret.add(block.SCOPE, block);
836         while(true) {
837             switch(tok = peekToken()) {
838
839             case LC:
840                 smt = parseBlock(true); break;
841
842             case THROW: case RETURN: case ASSERT:
843                 getToken();
844                 if (peekToken() == SEMI) {
845                     if (tok == THROW || tok == ASSERT) throw new ParserException(codeToString[tok] + " requires an argument");
846                     consume(SEMI);
847                     smt = new Expr(curLine, tok);
848                 } else {
849                     smt = new Expr(curLine, tok, parseMaximalExpr());
850                     consume(SEMI);
851                 }
852                 break;
853
854             case GOTO: case BREAK: case CONTINUE: {
855                 getToken();
856                 int t = peekToken();
857                 if (t == NAME) {
858                     getToken();
859                     smt = new Expr(curLine, tok, new Expr(curLine, string));
860                 } else if (tok == GOTO) {
861                     throw new ParserException("goto must be followed by a label");
862                 }
863                 smt = new Expr(curLine, tok, new Expr(curLine, string));
864                 consume(SEMI);
865                 break;
866             }
867                 
868             case RC:
869                 if (braced) consume(RC);
870                 return block.size() == 0 ? null : ret;
871                 
872             case SEMI:
873                 consume(SEMI);
874                 if (!braced) return block.size() == 0 ? null : ret;
875                 continue;
876                 
877             case NAME: {
878                 String name = string;
879                 consume(NAME);
880                 if (peekToken() == COLON) {
881                     consume(COLON);
882                     smt = new Expr(curLine, COLON, new Expr(curLine, name), parseBlock(false));
883                     break;
884                 } else {
885                     pushBackToken(NAME, name);
886                     // fall through to default case
887                 }
888             }
889
890             case -1:
891             default:
892                 smt = parseMaximalExpr();
893                 if (smt == null) return block.size() == 0 ? null : ret;
894                 if (peekToken() == SEMI) getToken();
895                 break;
896             }
897
898             if (!braced) return smt;
899             block.add(block.EXPR, smt);
900             block.add(block.POP, NO_ARG);
901         }
902     }
903     
904
905     /** sorta like gcc trees */
906     class Expr {
907         int code = -1;
908     
909         final Expr left;
910         final Expr right;
911         int line = -1;
912         String sourceName = "unknown";
913     
914         String string = null;
915         Number number = null;
916     
917         public String toString() { return toString(0); }
918         public String toString(int indent) {
919             String ret = "";
920             for(int i=0; i<indent; i++) ret += " ";
921             ret += Lexer.codeToString[code];
922             if (code == Lexer.NUMBER) ret += " " + number;
923             else if (string != null) ret += " \"" + string + "\"";
924             ret += "\n";
925             if (left != null) ret += left.toString(indent + 2);
926             if (right != null) ret += right.toString(indent + 2);
927             return ret;
928         }
929     
930         public Expr(int line, String s) { this(line, Lexer.STRING); this.string = s; }  // an identifier or label
931         public Expr(int line, int code, String s) { this(line, code); this.string = s; }
932         public Expr(int line, Number n) { this(line, Lexer.NUMBER); this.number = n; }  // an identifier or label
933         public Expr(int line, int code) { this(line, code, null, null); }
934         public Expr(int line, int code, Expr left) { this(line, code, left, null); }
935         public Expr(int line, int code, Expr left, Expr right) {
936             this.code = code; this.left = left; this.right = right;
937             this.line = line;
938             this.sourceName = Parser.this.sourceName;
939         }
940
941         public double toDouble(Object o) { return toNumber(o).doubleValue(); }
942         public long toLong(Object o) { return toNumber(o).longValue(); }
943         public boolean toBoolean(Object o) {
944             if (o == null) return false;
945             if (o instanceof Boolean) return ((Boolean)o).booleanValue();
946             if (o instanceof Number) return o.equals(new Integer(0));
947             return true;
948         }
949
950         public Object eval(final JS.Scope s) throws ControlTransferException, JS.Exn {
951             switch(code) {
952
953             case Lexer.TYPEOF: {
954                 Object o = left.eval(s);
955                 if (o == null) return "null";
956                 if (o.getClass() == String.class) return "string";
957                 if (o.getClass() == Boolean.class) return "boolean";
958                 if (o instanceof Number) return "number";
959                 if (o instanceof JS.Array) return "array";
960                 if (o instanceof JS) return "object";
961                 throw new EvaluatorException("typeof " + o.getClass().getName() + " unknown");
962             }
963
964             case Lexer.NUMBER: return number;
965             case Lexer.STRING: return string;
966
967             case Lexer.ASSIGN: {
968                 Object v = (code == Lexer.ASSIGN) ? right.eval(s) : new Double(toDouble(left.eval(s)) + (code == Lexer.INC ? 1 : -1));
969                 if (left.code == Lexer.DOT) {
970                     Object o = left.left.eval(s);
971                     if (o instanceof String) {
972                         throw new EvaluatorException("can't set properties on a String");
973                     } else if (o instanceof Number) {
974                         throw new EvaluatorException("can't set properties on a Number");
975                     } else if (o instanceof Boolean) {
976                         throw new EvaluatorException("can't set properties on a Boolean");
977                     } else {
978                         JS target = (JS)o;
979                         if (target == null) throw new JS.Exn(new EvaluatorException("attempted to put to the null value"));
980                         target.put(left.right.eval(s), v);
981                         return v;
982                     }
983                 } else {
984                     s.put(left.string, v);
985                     return v;
986                 }
987             }
988
989             case Lexer.NULL: return null;
990             case Lexer.FALSE: return Boolean.FALSE;
991             case Lexer.TRUE: return Boolean.TRUE;
992             case Lexer.ASSERT: if (!toBoolean(left.eval(s))) throw new EvaluatorException("assertion failed");
993             case Lexer.THROW: throw new JS.Exn(left.eval(s));
994             case Lexer.NAME: return s.get(string);
995             case Lexer.THIS: return s.isTransparent() ? s : this.eval(s.getParentScope());
996
997             case Lexer.TRY: {
998                 boolean safeToExit = false;
999                 try {
1000                     Object ret = left.eval(s);
1001                     safeToExit = true;
1002                     return ret;
1003                 } catch (JS.Exn e) {
1004                     ExprList list = (ExprList)right;
1005                     Expr c = list.elementAt(0);
1006                     if (c.code == Lexer.CATCH) {
1007                         JS.Scope scope = new JS.Scope(s);
1008                         s.put(c.left.string, e);
1009                         c.right.eval(scope);
1010                         c = list.elementAt(1);
1011                     }
1012                     if (c.code == Lexer.FINALLY) {
1013                         JS.Scope scope = new JS.Scope(s);
1014                         c.left.eval(scope);
1015                     }
1016                 } finally {
1017                     if (!safeToExit) Log.log(this, "WARNING: Java exception penetrated a JavaScript try{} block");
1018                 }
1019                 return null;
1020             }
1021
1022             case Lexer.FUNCTION:
1023                 return new JS.Function() {
1024                         public String toString() { return right.sourceName + ":" + right.line; }
1025                         public String getSourceName() throws JS.Exn { return right.sourceName; }
1026                         public Object _call(final JS.Array args) throws JS.Exn {
1027                             Function save = JS.getCurrentFunction();
1028                             JS.currentFunction.put(java.lang.Thread.currentThread(), this);
1029                             JS.Scope scope = new JS.Scope(s) {
1030                                     // FIXME
1031                                     public String getSourceName() { return sourceName; }
1032                                     public Object get(Object key) throws JS.Exn {
1033                                         if (key.equals("trapee")) return org.xwt.Trap.currentTrapee();
1034                                         else if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction;
1035                                         else if (key.equals("arguments")) return args;
1036                                         return super.get(key);
1037                                     }
1038                                 };
1039                             int i = 0;
1040                             ExprList list = (ExprList)left;
1041                             for(i=0; i<list.size(); i++) {
1042                                 scope.declare(list.elementAt(i).string);
1043                                 scope.put(list.elementAt(i).string, args.get(new Integer(i)));
1044                             }
1045                             try {
1046                                 return right.eval(scope);
1047                             } catch (ReturnException r) {
1048                                 return r.retval;
1049                             } catch (ControlTransferException c) {
1050                                 throw new EvaluatorException("error, ControlTransferException tried to leave a function: "
1051                                                              + c);
1052                             } finally {
1053                                 if (save == null) JS.currentFunction.remove(java.lang.Thread.currentThread());
1054                                 else JS.currentFunction.put(java.lang.Thread.currentThread(), save);
1055                             }
1056                         }
1057                     };
1058                 
1059             case Lexer.FOR:
1060                 Object[] keys = ((JS)left.right.eval(s)).keys();
1061                 for(int i=0; i<keys.length; i++) {
1062                     JS.Scope scope = new JS.Scope(s);
1063                     scope.declare(left.string);
1064                     scope.put(left.string, keys[i]);
1065                     try {
1066                         right.eval(scope);
1067                     } catch (ContinueException c) {
1068                         if (c.label == null || c.label.equals(string)) continue;
1069                     } catch (BreakException b) {
1070                         if (b.label == null || b.label.equals(string)) return null;
1071                         throw (BreakException)b.fillInStackTrace();
1072                     }
1073                 }
1074                 return null;
1075
1076             case Lexer.SWITCH:
1077                 Object switchVal = left.eval(s);
1078                 boolean go = false;
1079                 try {
1080                     ExprList list = (ExprList)right;
1081                     for(int i=0; i<list.size(); i++) {
1082                         Expr e = list.elementAt(i);
1083                         if (go || e.code == Lexer.DEFAULT || e.left.eval(s).equals(switchVal)) go = true;
1084                         if (go) e.right.eval(s);
1085                     }
1086                 } catch (BreakException b) {
1087                     if (b.label == null || b.label.equals(string)) return null;
1088                     throw (BreakException)b.fillInStackTrace();
1089                 }
1090                 return null;
1091
1092             case Lexer.BREAK: throw new BreakException(string);
1093             case Lexer.CONTINUE: throw new ContinueException(string);
1094             case Lexer.RETURN: throw new ReturnException(left == null ? null : left.eval(s));
1095
1096             default: throw new EvaluatorException("don't know how to eval an Expr with code " + Lexer.codeToString[code] + "\n" + this);
1097             }
1098         }
1099
1100         class EvaluatorException extends RuntimeException {
1101             public EvaluatorException(String s) { super(sourceName + ":" + line + " " + s); }
1102         }
1103     }
1104
1105         static abstract class ControlTransferException extends Exception { }
1106         static class BreakException extends ControlTransferException {
1107             public String label;
1108             BreakException(String label) { this.label = label; }
1109         }
1110         static class ContinueException extends ControlTransferException {
1111             public String label;
1112             ContinueException(String label) { this.label = label; }
1113         }
1114         static class ReturnException extends ControlTransferException {
1115             public Object retval;
1116             ReturnException(Object retval) { this.retval = retval; }
1117         }
1118
1119     class ParserException extends RuntimeException {
1120         public ParserException(String s) { super(sourceName + ":" + line + " " + s); }
1121     }
1122
1123         public static Number toNumber(Object o) {
1124             if (o == null) return new Long(0);
1125             if (o instanceof Number) return ((Number)o);
1126             if (o instanceof String) try { return new Double((String)o); } catch (NumberFormatException e) { return new Double(0); }
1127             if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? new Long(1) : new Long(0);
1128             if (o instanceof JS) return ((JS)o).coerceToNumber();
1129             // FIXME
1130             throw new Error("toNumber() got object of type " + o.getClass().getName());
1131         }
1132  }
1133