2004/01/07 20:37:33
[org.ibex.core.git] / src / org / xwt / js / Parser.java
1 // Copyright 2003 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 JSFunction's.
9  *
10  *  There are three kinds of things we parse: blocks, statements, and
11  *  expressions.
12  *
13  *  - Expressions are a special type of statement that evaluates to a
14  *    value (for example, "break" is not an expression, * but "3+2"
15  *    is).  Some tokens sequences start expressions (for * example,
16  *    literal numbers) and others continue an expression which * has
17  *    already been begun (for example, '+').  Finally, some *
18  *    expressions are valid targets for an assignment operation; after
19  *    * each of these expressions, continueExprAfterAssignable() is
20  *    called * to check for an assignment operation.
21  *
22  *  - A statement ends with a semicolon and does not return a value.
23  *
24  *  - A block is a single statement or a sequence of statements
25  *    surrounded by curly braces.
26  *
27  *  Each parsing method saves the parserLine before doing its actual
28  *  work and restores it afterwards.  This ensures that parsing a
29  *  subexpression does not modify the line number until a token
30  *  *after* the subexpression has been consumed by the parent
31  *  expression.
32  *
33  *  Technically it would be a better design for this class to build an
34  *  intermediate parse tree and use that to emit bytecode.  Here's the
35  *  tradeoff:
36  *
37  *  Advantages of building a parse tree:
38  *  - easier to apply optimizations
39  *  - would let us handle more sophisticated languages than JavaScript
40  *
41  *  Advantages of leaving out the parse tree
42  *  - faster compilation
43  *  - less load on the garbage collector
44  *  - much simpler code, easier to understand
45  *  - less error-prone
46  *
47  *  Fortunately JS is such a simple language that we can get away with
48  *  the half-assed approach and still produce a working, complete
49  *  compiler.
50  *
51  *  The bytecode language emitted doesn't really cause any appreciable
52  *  semantic loss, and is itself a parseable language very similar to
53  *  Forth or a postfix variant of LISP.  This means that the bytecode
54  *  can be transformed into a parse tree, which can be manipulated.
55  *  So if we ever want to add an optimizer, it could easily be done by
56  *  producing a parse tree from the bytecode, optimizing that tree,
57  *  and then re-emitting the bytecode.  The parse tree node class
58  *  would also be much simpler since the bytecode language has so few
59  *  operators.
60  *
61  *  Actually, the above paragraph is slightly inaccurate -- there are
62  *  places where we push a value and then perform an arbitrary number
63  *  of operations using it before popping it; this doesn't parse well.
64  *  But these cases are clearly marked and easy to change if we do
65  *  need to move to a parse tree format.
66  */
67 class Parser extends Lexer implements ByteCodes {
68
69
70     // Constructors //////////////////////////////////////////////////////
71
72     public Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); }
73
74     /** for debugging */
75     public static void main(String[] s) throws Exception {
76         JSFunction block = JSFunction.fromReader("stdin", 0, new InputStreamReader(System.in));
77         if (block == null) return;
78         System.out.println(block);
79     }
80
81
82     // Statics ////////////////////////////////////////////////////////////
83
84     static byte[] precedence = new byte[MAX_TOKEN + 1];
85     static boolean[] isRightAssociative = new boolean[MAX_TOKEN + 1];
86     // Use this as the precedence when we want anything up to the comma
87     private final static int NO_COMMA = 2;
88     static {
89         isRightAssociative[ASSIGN] =
90             isRightAssociative[ASSIGN_BITOR] =
91             isRightAssociative[ASSIGN_BITXOR] =
92             isRightAssociative[ASSIGN_BITAND] =
93             isRightAssociative[ASSIGN_LSH] =
94             isRightAssociative[ASSIGN_RSH] =
95             isRightAssociative[ASSIGN_URSH] =
96             isRightAssociative[ASSIGN_ADD] =
97             isRightAssociative[ASSIGN_SUB] =
98             isRightAssociative[ASSIGN_MUL] =
99             isRightAssociative[ASSIGN_DIV] =
100             isRightAssociative[ASSIGN_MOD] = true;
101
102         precedence[COMMA] = 1;
103         // 2 is intentionally left unassigned. we use minPrecedence==2 for comma separated lists
104         precedence[ASSIGN] =
105             precedence[ASSIGN_BITOR] =
106             precedence[ASSIGN_BITXOR] =
107             precedence[ASSIGN_BITAND] =
108             precedence[ASSIGN_LSH] =
109             precedence[ASSIGN_RSH] =
110             precedence[ASSIGN_URSH] =
111             precedence[ASSIGN_ADD] =
112             precedence[ASSIGN_SUB] =
113             precedence[ASSIGN_MUL] =
114             precedence[ASSIGN_DIV] =
115             precedence[ASSIGN_MOD] = 3;
116         precedence[HOOK] = 4;
117         precedence[OR] = 5;
118         precedence[AND] = 6;
119         precedence[BITOR] = 7;
120         precedence[BITXOR] = 8;
121         precedence[BITAND] = 9;
122         precedence[EQ] = precedence[NE] = precedence[SHEQ] = precedence[SHNE] = 10;
123         precedence[LT] = precedence[LE] = precedence[GT] = precedence[GE] = 11;
124         precedence[LSH] = precedence[RSH] = precedence[URSH] = 12;
125         precedence[ADD] = precedence[SUB] = 12;
126         precedence[MUL] = precedence[DIV] = precedence[MOD] = 13;
127         precedence[BITNOT] =  precedence[BANG] = precedence[TYPEOF] = 14;
128         precedence[DOT] = precedence[LB] = precedence[LP] =  precedence[INC] = precedence[DEC] = 15;
129     }
130
131
132     // Parsing Logic /////////////////////////////////////////////////////////
133
134     /** gets a token and throws an exception if it is not <tt>code</tt> */
135     private void consume(int code) throws IOException {
136         if (getToken() != code) {
137             if(code == NAME) switch(op) {
138                 case RETURN: case TYPEOF: case BREAK: case CONTINUE: case TRY: case THROW:
139                 case ASSERT: case NULL: case TRUE: case FALSE: case IN: case IF: case ELSE:
140                 case SWITCH: case CASE: case DEFAULT: case WHILE: case VAR: case WITH:
141                 case CATCH: case FINALLY:
142                     throw pe("Bad variable name; '" + codeToString[op].toLowerCase() + "' is a javascript keyword");
143             }
144             throw pe("expected " + codeToString[code] + ", got " + (op == -1 ? "EOF" : codeToString[op]));
145         }
146     }
147
148     /**
149      *  Parse the largest possible expression containing no operators
150      *  of precedence below <tt>minPrecedence</tt> and append the
151      *  bytecodes for that expression to <tt>appendTo</tt>; the
152      *  appended bytecodes MUST grow the stack by exactly one element.
153      */ 
154     private void startExpr(JSFunction appendTo, int minPrecedence) throws IOException {
155         int saveParserLine = parserLine;
156         _startExpr(appendTo, minPrecedence);
157         parserLine = saveParserLine;
158     }
159     private void _startExpr(JSFunction appendTo, int minPrecedence) throws IOException {
160         int tok = getToken();
161         JSFunction b = appendTo;
162
163         switch (tok) {
164         case -1: throw pe("expected expression");
165
166         // all of these simply push values onto the stack
167         case NUMBER: b.add(parserLine, LITERAL, number); break;
168         case STRING: b.add(parserLine, LITERAL, string); break;
169         case NULL: b.add(parserLine, LITERAL, null); break;
170         case TRUE: case FALSE: b.add(parserLine, LITERAL, JS.B(tok == TRUE)); break;
171
172         // (.foo) syntax
173         case DOT: {
174             consume(NAME);
175             b.add(parserLine, TOPSCOPE);
176             b.add(parserLine, LITERAL, "");
177             b.add(parserLine, GET);
178             b.add(parserLine, LITERAL, string);
179             b.add(parserLine, GET);
180             continueExpr(b, minPrecedence);
181             break;
182         }
183
184         case LB: {
185             b.add(parserLine, ARRAY, JS.ZERO);                       // push an array onto the stack
186             int size0 = b.size;
187             int i = 0;
188             if (peekToken() != RB)
189                 while(true) {                                               // iterate over the initialization values
190                     int size = b.size;
191                     b.add(parserLine, LITERAL, JS.N(i++));           // push the index in the array to place it into
192                     if (peekToken() == COMMA || peekToken() == RB)
193                         b.add(parserLine, LITERAL, null);                   // for stuff like [1,,2,]
194                     else
195                         startExpr(b, NO_COMMA);                             // push the value onto the stack
196                     b.add(parserLine, PUT);                                 // put it into the array
197                     b.add(parserLine, POP);                                 // discard the value remaining on the stack
198                     if (peekToken() == RB) break;
199                     consume(COMMA);
200                 }
201             b.set(size0 - 1, JS.N(i));                               // back at the ARRAY instruction, write the size of the array
202             consume(RB);
203             break;
204         }
205         case SUB: {  // negative literal (like "3 * -1")
206             consume(NUMBER);
207             b.add(parserLine, LITERAL, JS.N(number.doubleValue() * -1));
208             break;
209         }
210         case LP: {  // grouping (not calling)
211             startExpr(b, -1);
212             consume(RP);
213             break;
214         }
215         case INC: case DEC: {  // prefix (not postfix)
216             startExpr(b, precedence[tok]);
217             int prev = b.size - 1;
218             if (b.get(prev) == GET && b.getArg(prev) != null)
219                 b.set(prev, LITERAL, b.getArg(prev));
220             else if(b.get(prev) == GET)
221                 b.pop();
222             else
223                 throw pe("prefixed increment/decrement can only be performed on a valid assignment target");
224             b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
225             b.add(parserLine, LITERAL, JS.N(1));
226             b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
227             b.add(parserLine, PUT, null);
228             b.add(parserLine, SWAP, null);
229             b.add(parserLine, POP, null);
230             break;
231         }
232         case BANG: case BITNOT: case TYPEOF: {
233             startExpr(b, precedence[tok]);
234             b.add(parserLine, tok);
235             break;
236         }
237         case LC: { // object constructor
238             b.add(parserLine, OBJECT, null);                                     // put an object on the stack
239             if (peekToken() != RC)
240                 while(true) {
241                     if (peekToken() != NAME && peekToken() != STRING)
242                         throw pe("expected NAME or STRING");
243                     getToken();
244                     b.add(parserLine, LITERAL, string);                          // grab the key
245                     consume(COLON);
246                     startExpr(b, NO_COMMA);                                      // grab the value
247                     b.add(parserLine, PUT);                                      // put the value into the object
248                     b.add(parserLine, POP);                                      // discard the remaining value
249                     if (peekToken() == RC) break;
250                     consume(COMMA);
251                     if (peekToken() == RC) break;                                // we permit {,,} -- I'm not sure if ECMA does
252                 }
253             consume(RC);
254             break;
255         }
256         case NAME: {
257             b.add(parserLine, TOPSCOPE);
258             b.add(parserLine, LITERAL, string);
259             continueExprAfterAssignable(b,minPrecedence);
260             break;
261         }
262         case FUNCTION: {
263             consume(LP);
264             int numArgs = 0;
265             JSFunction b2 = new JSFunction(sourceName, parserLine, null);
266             b.add(parserLine, NEWFUNCTION, b2);
267
268             // function prelude; arguments array is already on the stack
269             b2.add(parserLine, TOPSCOPE);
270             b2.add(parserLine, SWAP);
271             b2.add(parserLine, DECLARE, "arguments");                     // declare arguments (equivalent to 'var arguments;')
272             b2.add(parserLine, SWAP);                                     // set this.arguments and leave the value on the stack
273             b2.add(parserLine, PUT);
274
275             while(peekToken() != RP) {                                    // run through the list of argument names
276                 numArgs++;
277                 if (peekToken() == NAME) {
278                     consume(NAME);                                        // a named argument
279                     String varName = string;
280                     
281                     b2.add(parserLine, DUP);                              // dup the args array 
282                     b2.add(parserLine, GET, JS.N(numArgs - 1));   // retrieve it from the arguments array
283                     b2.add(parserLine, TOPSCOPE);
284                     b2.add(parserLine, SWAP);
285                     b2.add(parserLine, DECLARE, varName);                  // declare the name
286                     b2.add(parserLine, SWAP);
287                     b2.add(parserLine, PUT); 
288                     b2.add(parserLine, POP);                               // pop the value
289                     b2.add(parserLine, POP);                               // pop the scope                  
290                 }
291                 if (peekToken() == RP) break;
292                 consume(COMMA);
293             }
294             consume(RP);
295
296             b2.numFormalArgs = numArgs;
297             b2.add(parserLine, POP);                                      // pop off the arguments array
298             b2.add(parserLine, POP);                                      // pop off TOPSCOPE
299             
300            if(peekToken() != LC)
301                 throw pe("JSFunctions must have a block surrounded by curly brackets");
302                 
303             parseBlock(b2, null);                                   // the function body
304
305             b2.add(parserLine, LITERAL, null);                        // in case we "fall out the bottom", return NULL
306             b2.add(parserLine, RETURN);
307
308             break;
309         }
310         default: throw pe("expected expression, found " + codeToString[tok] + ", which cannot start an expression");
311         }
312
313         // attempt to continue the expression
314         continueExpr(b, minPrecedence);
315     }
316
317
318     /**
319      *  Assuming that a complete assignable (lvalue) has just been
320      *  parsed and the object and key are on the stack,
321      *  <tt>continueExprAfterAssignable</tt> will attempt to parse an
322      *  expression that modifies the assignable.  This method always
323      *  decreases the stack depth by exactly one element.
324      */
325     private void continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
326         int saveParserLine = parserLine;
327         _continueExprAfterAssignable(b,minPrecedence);
328         parserLine = saveParserLine;
329     }
330     private void _continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
331         if (b == null) throw new Error("got null b; this should never happen");
332         int tok = getToken();
333         if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok])))
334             // force the default case
335             tok = -1;
336         switch(tok) {
337         case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH:
338         case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: case ASSIGN_ADD: case ASSIGN_SUB: {
339             if (tok != ASSIGN_ADD && tok != ASSIGN_SUB) b.add(parserLine, GET_PRESERVE);
340             
341             startExpr(b,  precedence[tok]);
342             
343             int size = b.size;
344             if (tok == ASSIGN_ADD || tok == ASSIGN_SUB) {
345                 b.add(parserLine, tok);
346                 b.add(parserLine, GET);
347                 b.add(parserLine, SWAP);
348             }
349             
350             // tok-1 is always s/^ASSIGN_// (0 is BITOR, 1 is ASSIGN_BITOR, etc) 
351             b.add(parserLine, tok - 1, tok-1==ADD ? JS.N(2) : null);
352             b.add(parserLine, PUT);
353             b.add(parserLine, SWAP);
354             b.add(parserLine, POP);
355             
356             if (tok == ASSIGN_ADD || tok == ASSIGN_SUB) b.set(size, tok, JS.N(b.size - size));
357             break;
358         }
359         case INC: case DEC: { // postfix
360             b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
361             b.add(parserLine, LITERAL, JS.N(1));
362             b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
363             b.add(parserLine, PUT, null);
364             b.add(parserLine, SWAP, null);
365             b.add(parserLine, POP, null);
366             b.add(parserLine, LITERAL, JS.N(1));
367             b.add(parserLine, tok == INC ? SUB : ADD, null);   // undo what we just did, since this is postfix
368             break;
369         }
370         case ASSIGN: {
371             startExpr(b, precedence[tok]);
372             b.add(parserLine, PUT);
373             b.add(parserLine, SWAP);
374             b.add(parserLine, POP);
375             break;
376         }
377         case LP: {
378
379             // Method calls are implemented by doing a GET_PRESERVE
380             // first.  If the object supports method calls, it will
381             // return JS.METHOD
382             int n = parseArgs(b, 2);
383             b.add(parserLine, GET_PRESERVE);
384             b.add(parserLine, CALLMETHOD, JS.N(n));
385             break;
386         }
387         default: {
388             pushBackToken();
389             if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null)
390                 b.set(b.size-1,GET,b.getArg(b.size-1));
391             else
392                 b.add(parserLine, GET);
393             return;
394         }
395         }
396     }
397
398
399     /**
400      *  Assuming that a complete expression has just been parsed,
401      *  <tt>continueExpr</tt> will attempt to extend this expression by
402      *  parsing additional tokens and appending additional bytecodes.
403      *
404      *  No operators with precedence less than <tt>minPrecedence</tt>
405      *  will be parsed.
406      *
407      *  If any bytecodes are appended, they will not alter the stack
408      *  depth.
409      */
410     private void continueExpr(JSFunction b, int minPrecedence) throws IOException {
411         int saveParserLine = parserLine;
412         _continueExpr(b, minPrecedence);
413         parserLine = saveParserLine;
414     }
415     private void _continueExpr(JSFunction b, int minPrecedence) throws IOException {
416         if (b == null) throw new Error("got null b; this should never happen");
417         int tok = getToken();
418         if (tok == -1) return;
419         if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok]))) {
420             pushBackToken();
421             return;
422         }
423
424         switch (tok) {
425         case LP: {  // invocation (not grouping)
426             int n = parseArgs(b, 1);
427             b.add(parserLine, CALL, JS.N(n));
428             break;
429         }
430         case BITOR: case BITXOR: case BITAND: case SHEQ: case SHNE: case LSH:
431         case RSH: case URSH: case MUL: case DIV: case MOD:
432         case GT: case GE: case EQ: case NE: case LT: case LE: case SUB: {
433             startExpr(b, precedence[tok]);
434             b.add(parserLine, tok);
435             break;
436         }
437         case ADD: {
438             int count=1;
439             int nextTok;
440             do {
441                 startExpr(b,precedence[tok]);
442                 count++;
443                 nextTok = getToken();
444             } while(nextTok == tok);
445             pushBackToken();
446             b.add(parserLine, tok, JS.N(count));
447             break;
448         }
449         case OR: case AND: {
450             b.add(parserLine, tok == AND ? b.JF : b.JT, JS.ZERO);       // test to see if we can short-circuit
451             int size = b.size;
452             startExpr(b, precedence[tok]);                                     // otherwise check the second value
453             b.add(parserLine, JMP, JS.N(2));                            // leave the second value on the stack and jump to the end
454             b.add(parserLine, LITERAL, tok == AND ?
455                   JS.B(false) : JS.B(true));                     // target of the short-circuit jump is here
456             b.set(size - 1, JS.N(b.size - size));                     // write the target of the short-circuit jump
457             break;
458         }
459         case DOT: {
460             // support foo..bar syntax for foo[""].bar
461             if (peekToken() == DOT) {
462                 string = "";
463             } else {
464                 consume(NAME);
465             }
466             b.add(parserLine, LITERAL, string);
467             continueExprAfterAssignable(b,minPrecedence);
468             break;
469         }
470         case LB: { // subscripting (not array constructor)
471             startExpr(b, -1);
472             consume(RB);
473             continueExprAfterAssignable(b,minPrecedence);
474             break;
475         }
476         case HOOK: {
477             b.add(parserLine, JF, JS.ZERO);                // jump to the if-false expression
478             int size = b.size;
479             startExpr(b, minPrecedence);                          // write the if-true expression
480             b.add(parserLine, JMP, JS.ZERO);               // if true, jump *over* the if-false expression     
481             b.set(size - 1, JS.N(b.size - size + 1));    // now we know where the target of the jump is
482             consume(COLON);
483             size = b.size;
484             startExpr(b, minPrecedence);                          // write the if-false expression
485             b.set(size - 1, JS.N(b.size - size + 1));    // this is the end; jump to here
486             break;
487         }
488         case COMMA: {
489             // pop the result of the previous expression, it is ignored
490             b.add(parserLine,POP);
491             startExpr(b,-1);
492             break;
493         }
494         default: {
495             pushBackToken();
496             return;
497         }
498         }
499
500         continueExpr(b, minPrecedence);                           // try to continue the expression
501     }
502     
503     // parse a set of comma separated function arguments, assume LP has already been consumed
504     // if swap is true, (because the function is already on the stack) we will SWAP after each argument to keep it on top
505     private int parseArgs(JSFunction b, int pushdown) throws IOException {
506         int i = 0;
507         while(peekToken() != RP) {
508             i++;
509             if (peekToken() != COMMA) {
510                 startExpr(b, NO_COMMA);
511                 b.add(parserLine, SWAP, JS.N(pushdown));
512                 if (peekToken() == RP) break;
513             }
514             consume(COMMA);
515         }
516         consume(RP);
517         return i;
518     }
519     
520     /** Parse a block of statements which must be surrounded by LC..RC. */
521     void parseBlock(JSFunction b) throws IOException { parseBlock(b, null); }
522     void parseBlock(JSFunction b, String label) throws IOException {
523         int saveParserLine = parserLine;
524         _parseBlock(b, label);
525         parserLine = saveParserLine;
526     }
527     void _parseBlock(JSFunction b, String label) throws IOException {
528         if (peekToken() == -1) return;
529         else if (peekToken() != LC) parseStatement(b, null);
530         else {
531             consume(LC);
532             while(peekToken() != RC && peekToken() != -1) parseStatement(b, null);
533             consume(RC);
534         }
535     }
536
537     /** Parse a single statement, consuming the RC or SEMI which terminates it. */
538     void parseStatement(JSFunction b, String label) throws IOException {
539         int saveParserLine = parserLine;
540         _parseStatement(b, label);
541         parserLine = saveParserLine;
542     }
543     void _parseStatement(JSFunction b, String label) throws IOException {
544         int tok = peekToken();
545         if (tok == -1) return;
546         switch(tok = getToken()) {
547             
548         case THROW: case ASSERT: case RETURN: {
549             if (tok == RETURN && peekToken() == SEMI)
550                 b.add(parserLine, LITERAL, null);
551             else
552                 startExpr(b, -1);
553             b.add(parserLine, tok);
554             consume(SEMI);
555             break;
556         }
557         case BREAK: case CONTINUE: {
558             if (peekToken() == NAME) consume(NAME);
559             b.add(parserLine, tok, string);
560             consume(SEMI);
561             break;
562         }
563         case VAR: {
564             b.add(parserLine, TOPSCOPE);                         // push the current scope
565             while(true) {
566                 consume(NAME);
567                 b.add(parserLine, DECLARE, string);               // declare it
568                 if (peekToken() == ASSIGN) {                     // if there is an '=' after the variable name
569                     consume(ASSIGN);
570                     startExpr(b, NO_COMMA);
571                     b.add(parserLine, PUT);                      // assign it
572                     b.add(parserLine, POP);                      // clean the stack
573                 } else {
574                     b.add(parserLine, POP);                      // pop the string pushed by declare
575                 }   
576                 if (peekToken() != COMMA) break;
577                 consume(COMMA);
578             }
579             b.add(parserLine, POP);                              // pop off the topscope
580             if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
581             break;
582         }
583         case IF: {
584             consume(LP);
585             startExpr(b, -1);
586             consume(RP);
587             
588             b.add(parserLine, JF, JS.ZERO);                    // if false, jump to the else-block
589             int size = b.size;
590             parseStatement(b, null);
591             
592             if (peekToken() == ELSE) {
593                 consume(ELSE);
594                 b.add(parserLine, JMP, JS.ZERO);               // if we took the true-block, jump over the else-block
595                 b.set(size - 1, JS.N(b.size - size + 1));
596                 size = b.size;
597                 parseStatement(b, null);
598             }
599             b.set(size - 1, JS.N(b.size - size + 1));        // regardless of which branch we took, b[size] needs to point here
600             break;
601         }
602         case WHILE: {
603             consume(LP);
604             if (label != null) b.add(parserLine, LABEL, label);
605             b.add(parserLine, LOOP);
606             int size = b.size;
607             b.add(parserLine, POP);                                   // discard the first-iteration indicator
608             startExpr(b, -1);
609             b.add(parserLine, JT, JS.N(2));                    // if the while() clause is true, jump over the BREAK
610             b.add(parserLine, BREAK);
611             consume(RP);
612             parseStatement(b, null);
613             b.add(parserLine, CONTINUE);                              // if we fall out of the end, definately continue
614             b.set(size - 1, JS.N(b.size - size + 1));        // end of the loop
615             break;
616         }
617         case SWITCH: {
618             consume(LP);
619             if (label != null) b.add(parserLine, LABEL, label);
620             b.add(parserLine, LOOP);
621             int size0 = b.size;
622             startExpr(b, -1);
623             consume(RP);
624             consume(LC);
625             while(true)
626                 if (peekToken() == CASE) {                         // we compile CASE statements like a bunch of if..else's
627                     consume(CASE);
628                     b.add(parserLine, DUP);                        // duplicate the switch() value; we'll consume one copy
629                     startExpr(b, -1);
630                     consume(COLON);
631                     b.add(parserLine, EQ);                         // check if we should do this case-block
632                     b.add(parserLine, JF, JS.ZERO);         // if not, jump to the next one
633                     int size = b.size;
634                     while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null);
635                     b.set(size - 1, JS.N(1 + b.size - size));
636                 } else if (peekToken() == DEFAULT) {
637                     consume(DEFAULT);
638                     consume(COLON);
639                     while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null);
640                 } else if (peekToken() == RC) {
641                     consume(RC);
642                     b.add(parserLine, BREAK);                      // break out of the loop if we 'fall through'
643                     break;
644                 } else {
645                     throw pe("expected CASE, DEFAULT, or RC; got " + codeToString[peekToken()]);
646                 }
647             b.set(size0 - 1, JS.N(b.size - size0 + 1));      // end of the loop
648             break;
649         }
650             
651         case DO: {
652             if (label != null) b.add(parserLine, LABEL, label);
653             b.add(parserLine, LOOP);
654             int size = b.size;
655             parseStatement(b, null);
656             consume(WHILE);
657             consume(LP);
658             startExpr(b, -1);
659             b.add(parserLine, JT, JS.N(2));                  // check the while() clause; jump over the BREAK if true
660             b.add(parserLine, BREAK);
661             b.add(parserLine, CONTINUE);
662             consume(RP);
663             consume(SEMI);
664             b.set(size - 1, JS.N(b.size - size + 1));      // end of the loop; write this location to the LOOP instruction
665             break;
666         }
667             
668         case TRY: {
669             b.add(parserLine, TRY); // try bytecode causes a TryMarker to be pushed
670             int tryInsn = b.size - 1;
671             // parse the expression to be TRYed
672             parseStatement(b, null); 
673             // pop the try  marker. this is pushed when the TRY bytecode is executed                              
674             b.add(parserLine, POP);
675             // jump forward to the end of the catch block, start of the finally block                             
676             b.add(parserLine, JMP);                                  
677             int successJMPInsn = b.size - 1;
678             
679             if (peekToken() != CATCH && peekToken() != FINALLY)
680                 throw pe("try without catch or finally");
681             
682             int catchJMPDistance = -1;
683             if (peekToken() == CATCH) {
684                 catchJMPDistance = b.size - tryInsn;
685                 String exceptionVar;
686                 getToken();
687                 consume(LP);
688                 consume(NAME);
689                 exceptionVar = string;
690                 consume(RP);
691                 b.add(parserLine, TOPSCOPE);                        // the exception is on top of the stack; put it to the chosen name
692                 b.add(parserLine, SWAP);
693                 b.add(parserLine, LITERAL,exceptionVar);
694                 b.add(parserLine, SWAP);
695                 b.add(parserLine, PUT);
696                 b.add(parserLine, POP);
697                 b.add(parserLine, POP);
698                 parseStatement(b, null);
699                 // pop the try and catch markers
700                 b.add(parserLine,POP);
701                 b.add(parserLine,POP);
702             }
703             
704             // jump here if no exception was thrown
705             b.set(successJMPInsn, JS.N(b.size - successJMPInsn)); 
706                         
707             int finallyJMPDistance = -1;
708             if (peekToken() == FINALLY) {
709                 b.add(parserLine, LITERAL, null); // null FinallyData
710                 finallyJMPDistance = b.size - tryInsn;
711                 consume(FINALLY);
712                 parseStatement(b, null);
713                 b.add(parserLine,FINALLY_DONE); 
714             }
715             
716             // setup the TRY arguments
717             b.set(tryInsn, new int[] { catchJMPDistance, finallyJMPDistance });
718             
719             break;
720         }
721             
722         case FOR: {
723             consume(LP);
724             
725             tok = getToken();
726             boolean hadVar = false;                                      // if it's a for..in, we ignore the VAR
727             if (tok == VAR) { hadVar = true; tok = getToken(); }
728             String varName = string;
729             boolean forIn = peekToken() == IN;                           // determine if this is a for..in loop or not
730             pushBackToken(tok, varName);
731             
732             if (forIn) {
733                 b.add(parserLine, NEWSCOPE);                             // for-loops always create new scopes
734                 b.add(parserLine, LITERAL, varName);                     // declare the new variable
735                 b.add(parserLine, DECLARE);
736                     
737                 b.add(parserLine, LOOP);                                 // we actually only add this to ensure that BREAK works
738                 b.add(parserLine, POP);                                  // discard the first-iteration indicator
739                 int size = b.size;
740                 consume(NAME);
741                 consume(IN);
742                 startExpr(b, -1);
743                 b.add(parserLine, PUSHKEYS);                             // push the keys as an array; check the length
744                 b.add(parserLine, LITERAL, "length");
745                 b.add(parserLine, GET);
746                 consume(RP);
747                     
748                 b.add(parserLine, LITERAL, JS.N(1));              // decrement the length
749                 b.add(parserLine, SUB);
750                 b.add(parserLine, DUP);
751                 b.add(parserLine, LITERAL, JS.ZERO);              // see if we've exhausted all the elements
752                 b.add(parserLine, LT);
753                 b.add(parserLine, JF, JS.N(2));
754                 b.add(parserLine, BREAK);                                // if we have, then BREAK
755                 b.add(parserLine, GET_PRESERVE);                         // get the key out of the keys array
756                 b.add(parserLine, LITERAL, varName);
757                 b.add(parserLine, PUT);                                  // write it to this[varName]                          
758                 parseStatement(b, null);                                 // do some stuff
759                 b.add(parserLine, CONTINUE);                             // continue if we fall out the bottom
760
761                 b.set(size - 1, JS.N(b.size - size + 1));       // BREAK to here
762                 b.add(parserLine, OLDSCOPE);                             // restore the scope
763                     
764             } else {
765                 if (hadVar) pushBackToken(VAR, null);                    // yeah, this actually matters
766                 b.add(parserLine, NEWSCOPE);                             // grab a fresh scope
767                     
768                 parseStatement(b, null);                                 // initializer
769                 JSFunction e2 =                                    // we need to put the incrementor before the test
770                     new JSFunction(sourceName, parserLine, null);  // so we save the test here
771                 if (peekToken() != SEMI)
772                     startExpr(e2, -1);
773                 else
774                     e2.add(parserLine, b.LITERAL, Boolean.TRUE);         // handle the for(foo;;foo) case
775                 consume(SEMI);
776                 if (label != null) b.add(parserLine, LABEL, label);
777                 b.add(parserLine, LOOP);
778                 int size2 = b.size;
779                     
780                 b.add(parserLine, JT, JS.ZERO);                   // if we're on the first iteration, jump over the incrementor
781                 int size = b.size;
782                 if (peekToken() != RP) {                                 // do the increment thing
783                     startExpr(b, -1);
784                     b.add(parserLine, POP);
785                 }
786                 b.set(size - 1, JS.N(b.size - size + 1));
787                 consume(RP);
788                     
789                 b.paste(e2);                                             // ok, *now* test if we're done yet
790                 b.add(parserLine, JT, JS.N(2));                   // break out if we don't meet the test
791                 b.add(parserLine, BREAK);
792                 parseStatement(b, null);
793                 b.add(parserLine, CONTINUE);                             // if we fall out the bottom, CONTINUE
794                 b.set(size2 - 1, JS.N(b.size - size2 + 1));     // end of the loop
795                     
796                 b.add(parserLine, OLDSCOPE);                             // get our scope back
797             }
798             break;
799         }
800                 
801         case NAME: {  // either a label or an identifier; this is the one place we're not LL(1)
802             String possiblyTheLabel = string;
803             if (peekToken() == COLON) {      // label
804                 consume(COLON);
805                 parseStatement(b, possiblyTheLabel);
806                 break;
807             } else {                         // expression
808                 pushBackToken(NAME, possiblyTheLabel);  
809                 startExpr(b, -1);
810                 b.add(parserLine, POP);
811                 if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
812                 break;
813             }
814         }
815
816         case SEMI: return;                                               // yep, the null statement is valid
817
818         case LC: {  // blocks are statements too
819             pushBackToken();
820             b.add(parserLine, NEWSCOPE);
821             parseBlock(b, label);
822             b.add(parserLine, OLDSCOPE);
823             break;
824         }
825
826         default: {  // hope that it's an expression
827             pushBackToken();
828             startExpr(b, -1);
829             b.add(parserLine, POP);
830             if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
831             break;
832         }
833         }
834     }
835
836
837     // ParserException //////////////////////////////////////////////////////////////////////
838     private IOException pe(String s) { return new IOException(sourceName + ":" + parserLine + " " + s); }
839     
840 }
841