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