2004/01/03 04:27:27
[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             b.add(parserLine, GET_PRESERVE);
340             startExpr(b,  precedence[tok]);
341             int size = b.size;
342             if (tok == ASSIGN_ADD || tok == ASSIGN_SUB) {
343                 b.add(parserLine, tok);
344             }
345             // tok-1 is always s/^ASSIGN_// (0 is BITOR, 1 is ASSIGN_BITOR, etc) 
346             b.add(parserLine, tok - 1, tok-1==ADD ? JS.N(2) : null);
347             b.add(parserLine, PUT);
348             b.add(parserLine, SWAP);
349             b.add(parserLine, POP);
350             if (tok == ASSIGN_ADD || tok == ASSIGN_SUB) b.set(size, tok, JS.N(b.size - size));
351             break;
352         }
353         case INC: case DEC: { // postfix
354             b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
355             b.add(parserLine, LITERAL, JS.N(1));
356             b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
357             b.add(parserLine, PUT, null);
358             b.add(parserLine, SWAP, null);
359             b.add(parserLine, POP, null);
360             b.add(parserLine, LITERAL, JS.N(1));
361             b.add(parserLine, tok == INC ? SUB : ADD, null);   // undo what we just did, since this is postfix
362             break;
363         }
364         case ASSIGN: {
365             startExpr(b, precedence[tok]);
366             b.add(parserLine, PUT);
367             b.add(parserLine, SWAP);
368             b.add(parserLine, POP);
369             break;
370         }
371         case LP: {
372
373             // Method calls are implemented by doing a GET_PRESERVE
374             // first.  If the object supports method calls, it will
375             // return JS.METHOD
376             int n = parseArgs(b, 2);
377             b.add(parserLine, GET_PRESERVE);
378             b.add(parserLine, CALLMETHOD, JS.N(n));
379             break;
380         }
381         default: {
382             pushBackToken();
383             if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null)
384                 b.set(b.size-1,GET,b.getArg(b.size-1));
385             else
386                 b.add(parserLine, GET);
387             return;
388         }
389         }
390     }
391
392
393     /**
394      *  Assuming that a complete expression has just been parsed,
395      *  <tt>continueExpr</tt> will attempt to extend this expression by
396      *  parsing additional tokens and appending additional bytecodes.
397      *
398      *  No operators with precedence less than <tt>minPrecedence</tt>
399      *  will be parsed.
400      *
401      *  If any bytecodes are appended, they will not alter the stack
402      *  depth.
403      */
404     private void continueExpr(JSFunction b, int minPrecedence) throws IOException {
405         int saveParserLine = parserLine;
406         _continueExpr(b, minPrecedence);
407         parserLine = saveParserLine;
408     }
409     private void _continueExpr(JSFunction b, int minPrecedence) throws IOException {
410         if (b == null) throw new Error("got null b; this should never happen");
411         int tok = getToken();
412         if (tok == -1) return;
413         if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok]))) {
414             pushBackToken();
415             return;
416         }
417
418         switch (tok) {
419         case LP: {  // invocation (not grouping)
420             int n = parseArgs(b, 1);
421             b.add(parserLine, CALL, JS.N(n));
422             break;
423         }
424         case BITOR: case BITXOR: case BITAND: case SHEQ: case SHNE: case LSH:
425         case RSH: case URSH: case MUL: case DIV: case MOD:
426         case GT: case GE: case EQ: case NE: case LT: case LE: case SUB: {
427             startExpr(b, precedence[tok]);
428             b.add(parserLine, tok);
429             break;
430         }
431         case ADD: {
432             int count=1;
433             int nextTok;
434             do {
435                 startExpr(b,precedence[tok]);
436                 count++;
437                 nextTok = getToken();
438             } while(nextTok == tok);
439             pushBackToken();
440             b.add(parserLine, tok, JS.N(count));
441             break;
442         }
443         case OR: case AND: {
444             b.add(parserLine, tok == AND ? b.JF : b.JT, JS.ZERO);       // test to see if we can short-circuit
445             int size = b.size;
446             startExpr(b, precedence[tok]);                                     // otherwise check the second value
447             b.add(parserLine, JMP, JS.N(2));                            // leave the second value on the stack and jump to the end
448             b.add(parserLine, LITERAL, tok == AND ?
449                   JS.B(false) : JS.B(true));                     // target of the short-circuit jump is here
450             b.set(size - 1, JS.N(b.size - size));                     // write the target of the short-circuit jump
451             break;
452         }
453         case DOT: {
454             // support foo..bar syntax for foo[""].bar
455             if (peekToken() == DOT) {
456                 string = "";
457             } else {
458                 consume(NAME);
459             }
460             b.add(parserLine, LITERAL, string);
461             continueExprAfterAssignable(b,minPrecedence);
462             break;
463         }
464         case LB: { // subscripting (not array constructor)
465             startExpr(b, -1);
466             consume(RB);
467             continueExprAfterAssignable(b,minPrecedence);
468             break;
469         }
470         case HOOK: {
471             b.add(parserLine, JF, JS.ZERO);                // jump to the if-false expression
472             int size = b.size;
473             startExpr(b, minPrecedence);                          // write the if-true expression
474             b.add(parserLine, JMP, JS.ZERO);               // if true, jump *over* the if-false expression     
475             b.set(size - 1, JS.N(b.size - size + 1));    // now we know where the target of the jump is
476             consume(COLON);
477             size = b.size;
478             startExpr(b, minPrecedence);                          // write the if-false expression
479             b.set(size - 1, JS.N(b.size - size + 1));    // this is the end; jump to here
480             break;
481         }
482         case COMMA: {
483             // pop the result of the previous expression, it is ignored
484             b.add(parserLine,POP);
485             startExpr(b,-1);
486             break;
487         }
488         default: {
489             pushBackToken();
490             return;
491         }
492         }
493
494         continueExpr(b, minPrecedence);                           // try to continue the expression
495     }
496     
497     // parse a set of comma separated function arguments, assume LP has already been consumed
498     // if swap is true, (because the function is already on the stack) we will SWAP after each argument to keep it on top
499     private int parseArgs(JSFunction b, int pushdown) throws IOException {
500         int i = 0;
501         while(peekToken() != RP) {
502             i++;
503             if (peekToken() != COMMA) {
504                 startExpr(b, NO_COMMA);
505                 b.add(parserLine, SWAP, JS.N(pushdown));
506                 if (peekToken() == RP) break;
507             }
508             consume(COMMA);
509         }
510         consume(RP);
511         return i;
512     }
513     
514     /** Parse a block of statements which must be surrounded by LC..RC. */
515     void parseBlock(JSFunction b) throws IOException { parseBlock(b, null); }
516     void parseBlock(JSFunction b, String label) throws IOException {
517         int saveParserLine = parserLine;
518         _parseBlock(b, label);
519         parserLine = saveParserLine;
520     }
521     void _parseBlock(JSFunction b, String label) throws IOException {
522         if (peekToken() == -1) return;
523         else if (peekToken() != LC) parseStatement(b, null);
524         else {
525             consume(LC);
526             while(peekToken() != RC && peekToken() != -1) parseStatement(b, null);
527             consume(RC);
528         }
529     }
530
531     /** Parse a single statement, consuming the RC or SEMI which terminates it. */
532     void parseStatement(JSFunction b, String label) throws IOException {
533         int saveParserLine = parserLine;
534         _parseStatement(b, label);
535         parserLine = saveParserLine;
536     }
537     void _parseStatement(JSFunction b, String label) throws IOException {
538         int tok = peekToken();
539         if (tok == -1) return;
540         switch(tok = getToken()) {
541             
542         case THROW: case ASSERT: case RETURN: {
543             if (tok == RETURN && peekToken() == SEMI)
544                 b.add(parserLine, LITERAL, null);
545             else
546                 startExpr(b, -1);
547             b.add(parserLine, tok);
548             consume(SEMI);
549             break;
550         }
551         case BREAK: case CONTINUE: {
552             if (peekToken() == NAME) consume(NAME);
553             b.add(parserLine, tok, string);
554             consume(SEMI);
555             break;
556         }
557         case VAR: {
558             b.add(parserLine, TOPSCOPE);                         // push the current scope
559             while(true) {
560                 consume(NAME);
561                 b.add(parserLine, DECLARE, string);               // declare it
562                 if (peekToken() == ASSIGN) {                     // if there is an '=' after the variable name
563                     consume(ASSIGN);
564                     startExpr(b, NO_COMMA);
565                     b.add(parserLine, PUT);                      // assign it
566                     b.add(parserLine, POP);                      // clean the stack
567                 } else {
568                     b.add(parserLine, POP);                      // pop the string pushed by declare
569                 }   
570                 if (peekToken() != COMMA) break;
571                 consume(COMMA);
572             }
573             b.add(parserLine, POP);                              // pop off the topscope
574             if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
575             break;
576         }
577         case IF: {
578             consume(LP);
579             startExpr(b, -1);
580             consume(RP);
581             
582             b.add(parserLine, JF, JS.ZERO);                    // if false, jump to the else-block
583             int size = b.size;
584             parseStatement(b, null);
585             
586             if (peekToken() == ELSE) {
587                 consume(ELSE);
588                 b.add(parserLine, JMP, JS.ZERO);               // if we took the true-block, jump over the else-block
589                 b.set(size - 1, JS.N(b.size - size + 1));
590                 size = b.size;
591                 parseStatement(b, null);
592             }
593             b.set(size - 1, JS.N(b.size - size + 1));        // regardless of which branch we took, b[size] needs to point here
594             break;
595         }
596         case WHILE: {
597             consume(LP);
598             if (label != null) b.add(parserLine, LABEL, label);
599             b.add(parserLine, LOOP);
600             int size = b.size;
601             b.add(parserLine, POP);                                   // discard the first-iteration indicator
602             startExpr(b, -1);
603             b.add(parserLine, JT, JS.N(2));                    // if the while() clause is true, jump over the BREAK
604             b.add(parserLine, BREAK);
605             consume(RP);
606             parseStatement(b, null);
607             b.add(parserLine, CONTINUE);                              // if we fall out of the end, definately continue
608             b.set(size - 1, JS.N(b.size - size + 1));        // end of the loop
609             break;
610         }
611         case SWITCH: {
612             consume(LP);
613             if (label != null) b.add(parserLine, LABEL, label);
614             b.add(parserLine, LOOP);
615             int size0 = b.size;
616             startExpr(b, -1);
617             consume(RP);
618             consume(LC);
619             while(true)
620                 if (peekToken() == CASE) {                         // we compile CASE statements like a bunch of if..else's
621                     consume(CASE);
622                     b.add(parserLine, DUP);                        // duplicate the switch() value; we'll consume one copy
623                     startExpr(b, -1);
624                     consume(COLON);
625                     b.add(parserLine, EQ);                         // check if we should do this case-block
626                     b.add(parserLine, JF, JS.ZERO);         // if not, jump to the next one
627                     int size = b.size;
628                     while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null);
629                     b.set(size - 1, JS.N(1 + b.size - size));
630                 } else if (peekToken() == DEFAULT) {
631                     consume(DEFAULT);
632                     consume(COLON);
633                     while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null);
634                 } else if (peekToken() == RC) {
635                     consume(RC);
636                     b.add(parserLine, BREAK);                      // break out of the loop if we 'fall through'
637                     break;
638                 } else {
639                     throw pe("expected CASE, DEFAULT, or RC; got " + codeToString[peekToken()]);
640                 }
641             b.set(size0 - 1, JS.N(b.size - size0 + 1));      // end of the loop
642             break;
643         }
644             
645         case DO: {
646             if (label != null) b.add(parserLine, LABEL, label);
647             b.add(parserLine, LOOP);
648             int size = b.size;
649             parseStatement(b, null);
650             consume(WHILE);
651             consume(LP);
652             startExpr(b, -1);
653             b.add(parserLine, JT, JS.N(2));                  // check the while() clause; jump over the BREAK if true
654             b.add(parserLine, BREAK);
655             b.add(parserLine, CONTINUE);
656             consume(RP);
657             consume(SEMI);
658             b.set(size - 1, JS.N(b.size - size + 1));      // end of the loop; write this location to the LOOP instruction
659             break;
660         }
661             
662         case TRY: {
663             b.add(parserLine, TRY); // try bytecode causes a TryMarker to be pushed
664             int tryInsn = b.size - 1;
665             // parse the expression to be TRYed
666             parseStatement(b, null); 
667             // pop the try  marker. this is pushed when the TRY bytecode is executed                              
668             b.add(parserLine, POP);
669             // jump forward to the end of the catch block, start of the finally block                             
670             b.add(parserLine, JMP);                                  
671             int successJMPInsn = b.size - 1;
672             
673             if (peekToken() != CATCH && peekToken() != FINALLY)
674                 throw pe("try without catch or finally");
675             
676             int catchJMPDistance = -1;
677             if (peekToken() == CATCH) {
678                 catchJMPDistance = b.size - tryInsn;
679                 String exceptionVar;
680                 getToken();
681                 consume(LP);
682                 consume(NAME);
683                 exceptionVar = string;
684                 consume(RP);
685                 b.add(parserLine, TOPSCOPE);                        // the exception is on top of the stack; put it to the chosen name
686                 b.add(parserLine, SWAP);
687                 b.add(parserLine, LITERAL,exceptionVar);
688                 b.add(parserLine, SWAP);
689                 b.add(parserLine, PUT);
690                 b.add(parserLine, POP);
691                 b.add(parserLine, POP);
692                 parseStatement(b, null);
693                 // pop the try and catch markers
694                 b.add(parserLine,POP);
695                 b.add(parserLine,POP);
696             }
697             
698             // jump here if no exception was thrown
699             b.set(successJMPInsn, JS.N(b.size - successJMPInsn)); 
700                         
701             int finallyJMPDistance = -1;
702             if (peekToken() == FINALLY) {
703                 b.add(parserLine, LITERAL, null); // null FinallyData
704                 finallyJMPDistance = b.size - tryInsn;
705                 consume(FINALLY);
706                 parseStatement(b, null);
707                 b.add(parserLine,FINALLY_DONE); 
708             }
709             
710             // setup the TRY arguments
711             b.set(tryInsn, new int[] { catchJMPDistance, finallyJMPDistance });
712             
713             break;
714         }
715             
716         case FOR: {
717             consume(LP);
718             
719             tok = getToken();
720             boolean hadVar = false;                                      // if it's a for..in, we ignore the VAR
721             if (tok == VAR) { hadVar = true; tok = getToken(); }
722             String varName = string;
723             boolean forIn = peekToken() == IN;                           // determine if this is a for..in loop or not
724             pushBackToken(tok, varName);
725             
726             if (forIn) {
727                 b.add(parserLine, NEWSCOPE);                             // for-loops always create new scopes
728                 b.add(parserLine, LITERAL, varName);                     // declare the new variable
729                 b.add(parserLine, DECLARE);
730                     
731                 b.add(parserLine, LOOP);                                 // we actually only add this to ensure that BREAK works
732                 b.add(parserLine, POP);                                  // discard the first-iteration indicator
733                 int size = b.size;
734                 consume(NAME);
735                 consume(IN);
736                 startExpr(b, -1);
737                 b.add(parserLine, PUSHKEYS);                             // push the keys as an array; check the length
738                 b.add(parserLine, LITERAL, "length");
739                 b.add(parserLine, GET);
740                 consume(RP);
741                     
742                 b.add(parserLine, LITERAL, JS.N(1));              // decrement the length
743                 b.add(parserLine, SUB);
744                 b.add(parserLine, DUP);
745                 b.add(parserLine, LITERAL, JS.ZERO);              // see if we've exhausted all the elements
746                 b.add(parserLine, LT);
747                 b.add(parserLine, JF, JS.N(2));
748                 b.add(parserLine, BREAK);                                // if we have, then BREAK
749                 b.add(parserLine, GET_PRESERVE);                         // get the key out of the keys array
750                 b.add(parserLine, LITERAL, varName);
751                 b.add(parserLine, PUT);                                  // write it to this[varName]                          
752                 parseStatement(b, null);                                 // do some stuff
753                 b.add(parserLine, CONTINUE);                             // continue if we fall out the bottom
754
755                 b.set(size - 1, JS.N(b.size - size + 1));       // BREAK to here
756                 b.add(parserLine, OLDSCOPE);                             // restore the scope
757                     
758             } else {
759                 if (hadVar) pushBackToken(VAR, null);                    // yeah, this actually matters
760                 b.add(parserLine, NEWSCOPE);                             // grab a fresh scope
761                     
762                 parseStatement(b, null);                                 // initializer
763                 JSFunction e2 =                                    // we need to put the incrementor before the test
764                     new JSFunction(sourceName, parserLine, null);  // so we save the test here
765                 if (peekToken() != SEMI)
766                     startExpr(e2, -1);
767                 else
768                     e2.add(parserLine, b.LITERAL, Boolean.TRUE);         // handle the for(foo;;foo) case
769                 consume(SEMI);
770                 if (label != null) b.add(parserLine, LABEL, label);
771                 b.add(parserLine, LOOP);
772                 int size2 = b.size;
773                     
774                 b.add(parserLine, JT, JS.ZERO);                   // if we're on the first iteration, jump over the incrementor
775                 int size = b.size;
776                 if (peekToken() != RP) {                                 // do the increment thing
777                     startExpr(b, -1);
778                     b.add(parserLine, POP);
779                 }
780                 b.set(size - 1, JS.N(b.size - size + 1));
781                 consume(RP);
782                     
783                 b.paste(e2);                                             // ok, *now* test if we're done yet
784                 b.add(parserLine, JT, JS.N(2));                   // break out if we don't meet the test
785                 b.add(parserLine, BREAK);
786                 parseStatement(b, null);
787                 b.add(parserLine, CONTINUE);                             // if we fall out the bottom, CONTINUE
788                 b.set(size2 - 1, JS.N(b.size - size2 + 1));     // end of the loop
789                     
790                 b.add(parserLine, OLDSCOPE);                             // get our scope back
791             }
792             break;
793         }
794                 
795         case NAME: {  // either a label or an identifier; this is the one place we're not LL(1)
796             String possiblyTheLabel = string;
797             if (peekToken() == COLON) {      // label
798                 consume(COLON);
799                 parseStatement(b, possiblyTheLabel);
800                 break;
801             } else {                         // expression
802                 pushBackToken(NAME, possiblyTheLabel);  
803                 startExpr(b, -1);
804                 b.add(parserLine, POP);
805                 if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
806                 break;
807             }
808         }
809
810         case SEMI: return;                                               // yep, the null statement is valid
811
812         case LC: {  // blocks are statements too
813             pushBackToken();
814             b.add(parserLine, NEWSCOPE);
815             parseBlock(b, label);
816             b.add(parserLine, OLDSCOPE);
817             break;
818         }
819
820         default: {  // hope that it's an expression
821             pushBackToken();
822             startExpr(b, -1);
823             b.add(parserLine, POP);
824             if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
825             break;
826         }
827         }
828     }
829
830
831     // ParserException //////////////////////////////////////////////////////////////////////
832     private IOException pe(String s) { return new IOException(sourceName + ":" + parserLine + " " + s); }
833     
834 }
835