1 // Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
8 * Parses a stream of lexed tokens into a tree of JSFunction's.
10 * There are three kinds of things we parse: blocks, statements, and
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.
22 * - A statement ends with a semicolon and does not return a value.
24 * - A block is a single statement or a sequence of statements
25 * surrounded by curly braces.
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
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
37 * Advantages of building a parse tree:
38 * - easier to apply optimizations
39 * - would let us handle more sophisticated languages than JavaScript
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
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
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
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.
67 class Parser extends Lexer implements ByteCodes {
70 // Constructors //////////////////////////////////////////////////////
72 public Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); }
75 public static void main(String[] s) throws Exception {
76 JS block = JS.fromReader("stdin", 0, new InputStreamReader(System.in));
77 if (block == null) return;
78 System.out.println(block);
82 // Statics ////////////////////////////////////////////////////////////
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;
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;
102 precedence[COMMA] = 1;
103 // 2 is intentionally left unassigned. we use minPrecedence==2 for comma separated lists
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;
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;
132 // Parsing Logic /////////////////////////////////////////////////////////
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");
144 throw pe("expected " + codeToString[code] + ", got " + (op == -1 ? "EOF" : codeToString[op]));
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.
154 private void startExpr(JSFunction appendTo, int minPrecedence) throws IOException {
155 int saveParserLine = parserLine;
156 _startExpr(appendTo, minPrecedence);
157 parserLine = saveParserLine;
159 private void _startExpr(JSFunction appendTo, int minPrecedence) throws IOException {
160 int tok = getToken();
161 JSFunction b = appendTo;
164 case -1: throw pe("expected expression");
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;
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);
185 b.add(parserLine, ARRAY, JS.ZERO); // push an array onto the stack
188 if (peekToken() != RB)
189 while(true) { // iterate over the initialization values
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,]
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;
201 b.set(size0 - 1, JS.N(i)); // back at the ARRAY instruction, write the size of the array
205 case SUB: { // negative literal (like "3 * -1")
207 b.add(parserLine, LITERAL, JS.N(number.doubleValue() * -1));
210 case LP: { // grouping (not calling)
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)
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);
232 case BANG: case BITNOT: case TYPEOF: {
233 startExpr(b, precedence[tok]);
234 b.add(parserLine, tok);
237 case LC: { // object constructor
238 b.add(parserLine, OBJECT, null); // put an object on the stack
239 if (peekToken() != RC)
241 if (peekToken() != NAME && peekToken() != STRING)
242 throw pe("expected NAME or STRING");
244 b.add(parserLine, LITERAL, string); // grab the key
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;
251 if (peekToken() == RC) break; // we permit {,,} -- I'm not sure if ECMA does
257 b.add(parserLine, TOPSCOPE);
258 b.add(parserLine, LITERAL, string);
259 continueExprAfterAssignable(b,minPrecedence);
265 JSFunction b2 = new JSFunction(sourceName, parserLine, null);
266 b.add(parserLine, NEWFUNCTION, b2);
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);
275 while(peekToken() != RP) { // run through the list of argument names
277 if (peekToken() == NAME) {
278 consume(NAME); // a named argument
279 String varName = string;
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
291 if (peekToken() == RP) break;
296 b2.numFormalArgs = numArgs;
297 b2.add(parserLine, POP); // pop off the arguments array
298 b2.add(parserLine, POP); // pop off TOPSCOPE
300 if(peekToken() != LC)
301 throw pe("JSFunctions must have a block surrounded by curly brackets");
303 parseBlock(b2, null); // the function body
305 b2.add(parserLine, LITERAL, null); // in case we "fall out the bottom", return NULL
306 b2.add(parserLine, RETURN);
310 default: throw pe("expected expression, found " + codeToString[tok] + ", which cannot start an expression");
313 // attempt to continue the expression
314 continueExpr(b, minPrecedence);
317 private Grammar parseGrammar(Grammar g) throws IOException {
318 int tok = getToken();
321 case BITOR: return new Grammar.Alternative(g, parseGrammar(null));
322 case ADD: return parseGrammar(new Grammar.Repetition(g, 1, Integer.MAX_VALUE));
323 case MUL: return parseGrammar(new Grammar.Repetition(g, 0, Integer.MAX_VALUE));
324 case HOOK: return parseGrammar(new Grammar.Repetition(g, 0, 1));
328 //case NUMBER: g0 = new Grammar.Literal(number); break;
329 case NAME: g0 = new Grammar.Reference(string); break;
331 g0 = new Grammar.Literal(string);
332 if (peekToken() == DOT) {
337 if (old.length() != 1 || string.length() != 1) throw pe("literal ranges must be single-char strings");
338 g0 = new Grammar.Range(old.charAt(0), string.charAt(0));
341 case LP: g0 = parseGrammar(null); consume(RP); break;
342 default: pushBackToken(); return g;
344 if (g == null) return parseGrammar(g0);
345 return parseGrammar(new Grammar.Juxtaposition(g, g0));
349 * Assuming that a complete assignable (lvalue) has just been
350 * parsed and the object and key are on the stack,
351 * <tt>continueExprAfterAssignable</tt> will attempt to parse an
352 * expression that modifies the assignable. This method always
353 * decreases the stack depth by exactly one element.
355 private void continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
356 int saveParserLine = parserLine;
357 _continueExprAfterAssignable(b,minPrecedence);
358 parserLine = saveParserLine;
360 private void _continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
361 if (b == null) throw new Error("got null b; this should never happen");
362 int tok = getToken();
363 if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok])))
364 // force the default case
369 b.add(parserLine, GET_PRESERVE);
370 Grammar g = parseGrammar(null);
371 if (peekToken() == LC) {
372 g.action = new JSFunction(sourceName, parserLine, null);
373 parseBlock((JSFunction)g.action);
374 ((JSFunction)g.action).add(parserLine, LITERAL, null); // in case we "fall out the bottom", return NULL
375 ((JSFunction)g.action).add(parserLine, RETURN);
377 b.add(parserLine, MAKE_GRAMMAR, g);
378 b.add(parserLine, PUT);
382 case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH:
383 case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: case ASSIGN_ADD: case ASSIGN_SUB: {
384 if (tok != ASSIGN_ADD && tok != ASSIGN_SUB) b.add(parserLine, GET_PRESERVE);
386 startExpr(b, precedence[tok]);
389 if (tok == ASSIGN_ADD || tok == ASSIGN_SUB) {
390 b.add(parserLine, tok);
391 b.add(parserLine, GET);
392 b.add(parserLine, SWAP);
395 // tok-1 is always s/^ASSIGN_// (0 is BITOR, 1 is ASSIGN_BITOR, etc)
396 b.add(parserLine, tok - 1, tok-1==ADD ? JS.N(2) : null);
397 b.add(parserLine, PUT);
398 b.add(parserLine, SWAP);
399 b.add(parserLine, POP);
401 if (tok == ASSIGN_ADD || tok == ASSIGN_SUB) b.set(size, tok, JS.N(b.size - size));
404 case INC: case DEC: { // postfix
405 b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
406 b.add(parserLine, LITERAL, JS.N(1));
407 b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
408 b.add(parserLine, PUT, null);
409 b.add(parserLine, SWAP, null);
410 b.add(parserLine, POP, null);
411 b.add(parserLine, LITERAL, JS.N(1));
412 b.add(parserLine, tok == INC ? SUB : ADD, null); // undo what we just did, since this is postfix
416 startExpr(b, precedence[tok]);
417 b.add(parserLine, PUT);
418 b.add(parserLine, SWAP);
419 b.add(parserLine, POP);
424 // Method calls are implemented by doing a GET_PRESERVE
425 // first. If the object supports method calls, it will
427 int n = parseArgs(b, 2);
428 b.add(parserLine, GET_PRESERVE);
429 b.add(parserLine, CALLMETHOD, JS.N(n));
434 if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null)
435 b.set(b.size-1,GET,b.getArg(b.size-1));
437 b.add(parserLine, GET);
445 * Assuming that a complete expression has just been parsed,
446 * <tt>continueExpr</tt> will attempt to extend this expression by
447 * parsing additional tokens and appending additional bytecodes.
449 * No operators with precedence less than <tt>minPrecedence</tt>
452 * If any bytecodes are appended, they will not alter the stack
455 private void continueExpr(JSFunction b, int minPrecedence) throws IOException {
456 int saveParserLine = parserLine;
457 _continueExpr(b, minPrecedence);
458 parserLine = saveParserLine;
460 private void _continueExpr(JSFunction b, int minPrecedence) throws IOException {
461 if (b == null) throw new Error("got null b; this should never happen");
462 int tok = getToken();
463 if (tok == -1) return;
464 if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok]))) {
470 case LP: { // invocation (not grouping)
471 int n = parseArgs(b, 1);
472 b.add(parserLine, CALL, JS.N(n));
475 case BITOR: case BITXOR: case BITAND: case SHEQ: case SHNE: case LSH:
476 case RSH: case URSH: case MUL: case DIV: case MOD:
477 case GT: case GE: case EQ: case NE: case LT: case LE: case SUB: {
478 startExpr(b, precedence[tok]);
479 b.add(parserLine, tok);
486 startExpr(b,precedence[tok]);
488 nextTok = getToken();
489 } while(nextTok == tok);
491 b.add(parserLine, tok, JS.N(count));
495 b.add(parserLine, tok == AND ? b.JF : b.JT, JS.ZERO); // test to see if we can short-circuit
497 startExpr(b, precedence[tok]); // otherwise check the second value
498 b.add(parserLine, JMP, JS.N(2)); // leave the second value on the stack and jump to the end
499 b.add(parserLine, LITERAL, tok == AND ?
500 JS.B(false) : JS.B(true)); // target of the short-circuit jump is here
501 b.set(size - 1, JS.N(b.size - size)); // write the target of the short-circuit jump
505 // support foo..bar syntax for foo[""].bar
506 if (peekToken() == DOT) {
511 b.add(parserLine, LITERAL, string);
512 continueExprAfterAssignable(b,minPrecedence);
515 case LB: { // subscripting (not array constructor)
518 continueExprAfterAssignable(b,minPrecedence);
522 b.add(parserLine, JF, JS.ZERO); // jump to the if-false expression
524 startExpr(b, minPrecedence); // write the if-true expression
525 b.add(parserLine, JMP, JS.ZERO); // if true, jump *over* the if-false expression
526 b.set(size - 1, JS.N(b.size - size + 1)); // now we know where the target of the jump is
529 startExpr(b, minPrecedence); // write the if-false expression
530 b.set(size - 1, JS.N(b.size - size + 1)); // this is the end; jump to here
534 // pop the result of the previous expression, it is ignored
535 b.add(parserLine,POP);
545 continueExpr(b, minPrecedence); // try to continue the expression
548 // parse a set of comma separated function arguments, assume LP has already been consumed
549 // if swap is true, (because the function is already on the stack) we will SWAP after each argument to keep it on top
550 private int parseArgs(JSFunction b, int pushdown) throws IOException {
552 while(peekToken() != RP) {
554 if (peekToken() != COMMA) {
555 startExpr(b, NO_COMMA);
556 b.add(parserLine, SWAP, JS.N(pushdown));
557 if (peekToken() == RP) break;
565 /** Parse a block of statements which must be surrounded by LC..RC. */
566 void parseBlock(JSFunction b) throws IOException { parseBlock(b, null); }
567 void parseBlock(JSFunction b, String label) throws IOException {
568 int saveParserLine = parserLine;
569 _parseBlock(b, label);
570 parserLine = saveParserLine;
572 void _parseBlock(JSFunction b, String label) throws IOException {
573 if (peekToken() == -1) return;
574 else if (peekToken() != LC) parseStatement(b, null);
577 while(peekToken() != RC && peekToken() != -1) parseStatement(b, null);
582 /** Parse a single statement, consuming the RC or SEMI which terminates it. */
583 void parseStatement(JSFunction b, String label) throws IOException {
584 int saveParserLine = parserLine;
585 _parseStatement(b, label);
586 parserLine = saveParserLine;
588 void _parseStatement(JSFunction b, String label) throws IOException {
589 int tok = peekToken();
590 if (tok == -1) return;
591 switch(tok = getToken()) {
593 case THROW: case ASSERT: case RETURN: {
594 if (tok == RETURN && peekToken() == SEMI)
595 b.add(parserLine, LITERAL, null);
598 b.add(parserLine, tok);
602 case BREAK: case CONTINUE: {
603 if (peekToken() == NAME) consume(NAME);
604 b.add(parserLine, tok, string);
609 b.add(parserLine, TOPSCOPE); // push the current scope
612 b.add(parserLine, DECLARE, string); // declare it
613 if (peekToken() == ASSIGN) { // if there is an '=' after the variable name
615 startExpr(b, NO_COMMA);
616 b.add(parserLine, PUT); // assign it
617 b.add(parserLine, POP); // clean the stack
619 b.add(parserLine, POP); // pop the string pushed by declare
621 if (peekToken() != COMMA) break;
624 b.add(parserLine, POP); // pop off the topscope
625 if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
633 b.add(parserLine, JF, JS.ZERO); // if false, jump to the else-block
635 parseStatement(b, null);
637 if (peekToken() == ELSE) {
639 b.add(parserLine, JMP, JS.ZERO); // if we took the true-block, jump over the else-block
640 b.set(size - 1, JS.N(b.size - size + 1));
642 parseStatement(b, null);
644 b.set(size - 1, JS.N(b.size - size + 1)); // regardless of which branch we took, b[size] needs to point here
649 if (label != null) b.add(parserLine, LABEL, label);
650 b.add(parserLine, LOOP);
652 b.add(parserLine, POP); // discard the first-iteration indicator
654 b.add(parserLine, JT, JS.N(2)); // if the while() clause is true, jump over the BREAK
655 b.add(parserLine, BREAK);
657 parseStatement(b, null);
658 b.add(parserLine, CONTINUE); // if we fall out of the end, definately continue
659 b.set(size - 1, JS.N(b.size - size + 1)); // end of the loop
664 if (label != null) b.add(parserLine, LABEL, label);
665 b.add(parserLine, LOOP);
671 if (peekToken() == CASE) { // we compile CASE statements like a bunch of if..else's
673 b.add(parserLine, DUP); // duplicate the switch() value; we'll consume one copy
676 b.add(parserLine, EQ); // check if we should do this case-block
677 b.add(parserLine, JF, JS.ZERO); // if not, jump to the next one
679 while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null);
680 b.set(size - 1, JS.N(1 + b.size - size));
681 } else if (peekToken() == DEFAULT) {
684 while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null);
685 } else if (peekToken() == RC) {
687 b.add(parserLine, BREAK); // break out of the loop if we 'fall through'
690 throw pe("expected CASE, DEFAULT, or RC; got " + codeToString[peekToken()]);
692 b.set(size0 - 1, JS.N(b.size - size0 + 1)); // end of the loop
697 if (label != null) b.add(parserLine, LABEL, label);
698 b.add(parserLine, LOOP);
700 parseStatement(b, null);
704 b.add(parserLine, JT, JS.N(2)); // check the while() clause; jump over the BREAK if true
705 b.add(parserLine, BREAK);
706 b.add(parserLine, CONTINUE);
709 b.set(size - 1, JS.N(b.size - size + 1)); // end of the loop; write this location to the LOOP instruction
714 b.add(parserLine, TRY); // try bytecode causes a TryMarker to be pushed
715 int tryInsn = b.size - 1;
716 // parse the expression to be TRYed
717 parseStatement(b, null);
718 // pop the try marker. this is pushed when the TRY bytecode is executed
719 b.add(parserLine, POP);
720 // jump forward to the end of the catch block, start of the finally block
721 b.add(parserLine, JMP);
722 int successJMPInsn = b.size - 1;
724 if (peekToken() != CATCH && peekToken() != FINALLY)
725 throw pe("try without catch or finally");
727 int catchJMPDistance = -1;
728 if (peekToken() == CATCH) {
729 Vec catchEnds = new Vec();
730 boolean catchAll = false;
732 catchJMPDistance = b.size - tryInsn;
734 while(peekToken() == CATCH && !catchAll) {
739 exceptionVar = string;
740 int[] writebacks = new int[] { -1, -1, -1 };
741 if (peekToken() != RP) {
742 // extended XWT catch block: catch(e faultCode "foo.bar.baz")
744 String propName = string;
745 b.add(parserLine, DUP);
746 b.add(parserLine, LITERAL, string);
747 b.add(parserLine, GET);
748 b.add(parserLine, DUP);
749 b.add(parserLine, LITERAL, null);
750 b.add(parserLine, EQ);
751 b.add(parserLine, JT);
752 writebacks[0] = b.size - 1;
753 if (peekToken() == STRING) {
755 b.add(parserLine, DUP);
756 b.add(parserLine, LITERAL, string);
757 b.add(parserLine, LT);
758 b.add(parserLine, JT);
759 writebacks[1] = b.size - 1;
760 b.add(parserLine, DUP);
761 b.add(parserLine, LITERAL, string + "/"); // (slash is ASCII after dot)
762 b.add(parserLine, GE);
763 b.add(parserLine, JT);
764 writebacks[2] = b.size - 1;
767 b.add(parserLine, DUP);
768 b.add(parserLine, LITERAL, number);
769 b.add(parserLine, EQ);
770 b.add(parserLine, JF);
771 writebacks[1] = b.size - 1;
773 b.add(parserLine, POP); // pop the element thats on the stack from the compare
778 // the exception is on top of the stack; put it to the chosen name
779 b.add(parserLine, NEWSCOPE);
780 b.add(parserLine, TOPSCOPE);
781 b.add(parserLine, SWAP);
782 b.add(parserLine, LITERAL,exceptionVar);
783 b.add(parserLine, DECLARE);
784 b.add(parserLine, SWAP);
785 b.add(parserLine, PUT);
786 b.add(parserLine, POP);
787 b.add(parserLine, POP);
789 b.add(parserLine, OLDSCOPE);
791 b.add(parserLine, JMP);
792 catchEnds.addElement(new Integer(b.size-1));
794 for(int i=0; i<3; i++) if (writebacks[i] != -1) b.set(writebacks[i], JS.N(b.size-writebacks[i]));
795 b.add(parserLine, POP); // pop the element thats on the stack from the compare
799 b.add(parserLine, THROW);
801 for(int i=0;i<catchEnds.size();i++) {
802 int n = ((Integer)catchEnds.elementAt(i)).intValue();
803 b.set(n, JS.N(b.size-n));
806 // pop the try and catch markers
807 b.add(parserLine,POP);
808 b.add(parserLine,POP);
811 // jump here if no exception was thrown
812 b.set(successJMPInsn, JS.N(b.size - successJMPInsn));
814 int finallyJMPDistance = -1;
815 if (peekToken() == FINALLY) {
816 b.add(parserLine, LITERAL, null); // null FinallyData
817 finallyJMPDistance = b.size - tryInsn;
819 parseStatement(b, null);
820 b.add(parserLine,FINALLY_DONE);
823 // setup the TRY arguments
824 b.set(tryInsn, new int[] { catchJMPDistance, finallyJMPDistance });
833 boolean hadVar = false; // if it's a for..in, we ignore the VAR
834 if (tok == VAR) { hadVar = true; tok = getToken(); }
835 String varName = string;
836 boolean forIn = peekToken() == IN; // determine if this is a for..in loop or not
837 pushBackToken(tok, varName);
840 b.add(parserLine, NEWSCOPE); // for-loops always create new scopes
841 b.add(parserLine, LITERAL, varName); // declare the new variable
842 b.add(parserLine, DECLARE);
844 b.add(parserLine, LOOP); // we actually only add this to ensure that BREAK works
845 b.add(parserLine, POP); // discard the first-iteration indicator
850 b.add(parserLine, PUSHKEYS); // push the keys as an array; check the length
851 b.add(parserLine, LITERAL, "length");
852 b.add(parserLine, GET);
855 b.add(parserLine, LITERAL, JS.N(1)); // decrement the length
856 b.add(parserLine, SUB);
857 b.add(parserLine, DUP);
858 b.add(parserLine, LITERAL, JS.ZERO); // see if we've exhausted all the elements
859 b.add(parserLine, LT);
860 b.add(parserLine, JF, JS.N(2));
861 b.add(parserLine, BREAK); // if we have, then BREAK
862 b.add(parserLine, GET_PRESERVE); // get the key out of the keys array
863 b.add(parserLine, LITERAL, varName);
864 b.add(parserLine, PUT); // write it to this[varName]
865 parseStatement(b, null); // do some stuff
866 b.add(parserLine, CONTINUE); // continue if we fall out the bottom
868 b.set(size - 1, JS.N(b.size - size + 1)); // BREAK to here
869 b.add(parserLine, OLDSCOPE); // restore the scope
872 if (hadVar) pushBackToken(VAR, null); // yeah, this actually matters
873 b.add(parserLine, NEWSCOPE); // grab a fresh scope
875 parseStatement(b, null); // initializer
876 JSFunction e2 = // we need to put the incrementor before the test
877 new JSFunction(sourceName, parserLine, null); // so we save the test here
878 if (peekToken() != SEMI)
881 e2.add(parserLine, b.LITERAL, Boolean.TRUE); // handle the for(foo;;foo) case
883 if (label != null) b.add(parserLine, LABEL, label);
884 b.add(parserLine, LOOP);
887 b.add(parserLine, JT, JS.ZERO); // if we're on the first iteration, jump over the incrementor
889 if (peekToken() != RP) { // do the increment thing
891 b.add(parserLine, POP);
893 b.set(size - 1, JS.N(b.size - size + 1));
896 b.paste(e2); // ok, *now* test if we're done yet
897 b.add(parserLine, JT, JS.N(2)); // break out if we don't meet the test
898 b.add(parserLine, BREAK);
899 parseStatement(b, null);
900 b.add(parserLine, CONTINUE); // if we fall out the bottom, CONTINUE
901 b.set(size2 - 1, JS.N(b.size - size2 + 1)); // end of the loop
903 b.add(parserLine, OLDSCOPE); // get our scope back
908 case NAME: { // either a label or an identifier; this is the one place we're not LL(1)
909 String possiblyTheLabel = string;
910 if (peekToken() == COLON) { // label
912 parseStatement(b, possiblyTheLabel);
914 } else { // expression
915 pushBackToken(NAME, possiblyTheLabel);
917 b.add(parserLine, POP);
918 if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
923 case SEMI: return; // yep, the null statement is valid
925 case LC: { // blocks are statements too
927 b.add(parserLine, NEWSCOPE);
928 parseBlock(b, label);
929 b.add(parserLine, OLDSCOPE);
933 default: { // hope that it's an expression
936 b.add(parserLine, POP);
937 if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
944 // ParserException //////////////////////////////////////////////////////////////////////
945 private IOException pe(String s) { return new IOException(sourceName + ":" + parserLine + " " + s); }