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