// Copyright 2000-2005 the Contributors, as shown in the revision logs. // Licensed under the Apache Public Source License 2.0 ("the License"). // You may not use this file except in compliance with the License. package org.ibex.js; import java.io.*; import java.util.*; import org.ibex.util.*; /** * Parses a stream of lexed tokens into a tree of JSFunction's. * * There are three kinds of things we parse: blocks, statements, and * expressions. * * - Expressions are a special type of statement that evaluates to a * value (for example, "break" is not an expression, * but "3+2" * is). Some tokens sequences start expressions (for * example, * literal numbers) and others continue an expression which * has * already been begun (for example, '+'). Finally, some * * expressions are valid targets for an assignment operation; after * * each of these expressions, continueExprAfterAssignable() is * called * to check for an assignment operation. * * - A statement ends with a semicolon and does not return a value. * * - A block is a single statement or a sequence of statements * surrounded by curly braces. * * Each parsing method saves the parserLine before doing its actual * work and restores it afterwards. This ensures that parsing a * subexpression does not modify the line number until a token * *after* the subexpression has been consumed by the parent * expression. * * Technically it would be a better design for this class to build an * intermediate parse tree and use that to emit bytecode. Here's the * tradeoff: * * Advantages of building a parse tree: * - easier to apply optimizations * - would let us handle more sophisticated languages than JavaScript * * Advantages of leaving out the parse tree * - faster compilation * - less load on the garbage collector * - much simpler code, easier to understand * - less error-prone * * Fortunately JS is such a simple language that we can get away with * the half-assed approach and still produce a working, complete * compiler. * * The bytecode language emitted doesn't really cause any appreciable * semantic loss, and is itself a parseable language very similar to * Forth or a postfix variant of LISP. This means that the bytecode * can be transformed into a parse tree, which can be manipulated. * So if we ever want to add an optimizer, it could easily be done by * producing a parse tree from the bytecode, optimizing that tree, * and then re-emitting the bytecode. The parse tree node class * would also be much simpler since the bytecode language has so few * operators. * * Actually, the above paragraph is slightly inaccurate -- there are * places where we push a value and then perform an arbitrary number * of operations using it before popping it; this doesn't parse well. * But these cases are clearly marked and easy to change if we do * need to move to a parse tree format. */ class Parser extends Lexer implements ByteCodes { // Constructors ////////////////////////////////////////////////////// private Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); } /** for debugging */ public static void main(String[] s) throws IOException { JS block = JSU.fromReader("stdin", 0, new InputStreamReader(System.in)); if (block == null) return; System.out.println(block); } // Statics //////////////////////////////////////////////////////////// static byte[] precedence = new byte[MAX_TOKEN + 1]; static boolean[] isRightAssociative = new boolean[MAX_TOKEN + 1]; // Use this as the precedence when we want anything up to the comma private final static int NO_COMMA = 2; static { isRightAssociative[ASSIGN] = isRightAssociative[ASSIGN_BITOR] = isRightAssociative[ASSIGN_BITXOR] = isRightAssociative[ASSIGN_BITAND] = isRightAssociative[ASSIGN_LSH] = isRightAssociative[ASSIGN_RSH] = isRightAssociative[ASSIGN_URSH] = isRightAssociative[ASSIGN_ADD] = isRightAssociative[ASSIGN_SUB] = isRightAssociative[ASSIGN_MUL] = isRightAssociative[ASSIGN_DIV] = isRightAssociative[ASSIGN_MOD] = isRightAssociative[ADD_TRAP] = isRightAssociative[DEL_TRAP] = true; precedence[COMMA] = 1; // 2 is intentionally left unassigned. we use minPrecedence==2 for comma separated lists precedence[ASSIGN] = precedence[ASSIGN_BITOR] = precedence[ASSIGN_BITXOR] = precedence[ASSIGN_BITAND] = precedence[ASSIGN_LSH] = precedence[ASSIGN_RSH] = precedence[ASSIGN_URSH] = precedence[ASSIGN_ADD] = precedence[ASSIGN_SUB] = precedence[ASSIGN_MUL] = precedence[ASSIGN_DIV] = precedence[ADD_TRAP] = precedence[DEL_TRAP] = precedence[ASSIGN_MOD] = 3; precedence[HOOK] = 4; precedence[OR] = 5; precedence[AND] = 6; precedence[BITOR] = 7; precedence[BITXOR] = 8; precedence[BITAND] = 9; precedence[EQ] = precedence[NE] = precedence[SHEQ] = precedence[SHNE] = 10; precedence[LT] = precedence[LE] = precedence[GT] = precedence[GE] = 11; precedence[LSH] = precedence[RSH] = precedence[URSH] = 12; precedence[ADD] = precedence[SUB] = 12; precedence[MUL] = precedence[DIV] = precedence[MOD] = 13; precedence[BITNOT] = precedence[BANG] = precedence[TYPEOF] = 14; precedence[DOT] = precedence[LB] = precedence[LP] = precedence[INC] = precedence[DEC] = 15; } // Local variable management Basket.Array scopeStack = new Basket.Array(); static class ScopeInfo { int base; int end; int newScopeInsn; Map mapping = new HashMap(); } Map globalCache = new HashMap(); JS scopeKey(String name) { if(globalCache.get(name) != null) return null; for(int i=scopeStack.size()-1;i>=0;i--) { JS key = (JS)((ScopeInfo) scopeStack.get(i)).mapping.get(name); if(key != null) return key; } globalCache.put(name,Boolean.TRUE); return null; } void scopeDeclare(String name) throws IOException { ScopeInfo si = (ScopeInfo) scopeStack.peek(); if(si.mapping.get(name) != null) throw pe("" + name + " already declared in this scope"); si.mapping.put(name,JSU.N(si.end++)); globalCache.put(name,null); } void scopePush(JSFunction b) { ScopeInfo prev = (ScopeInfo) scopeStack.peek(); ScopeInfo si = new ScopeInfo(); si.base = prev.end; si.end = si.base; si.newScopeInsn = b.size; scopeStack.push(si); b.add(parserLine, NEWSCOPE); } void scopePop(JSFunction b) { ScopeInfo si = (ScopeInfo) scopeStack.pop(); b.add(parserLine, OLDSCOPE); b.set(si.newScopeInsn,JSU.N((si.base<<16)|((si.end-si.base)<<0))); } // Parsing Logic ///////////////////////////////////////////////////////// /** parse and compile a function */ public static JSFunction fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException { JSFunction ret = new JSFunction(sourceName, firstLine, null); if (sourceCode == null) return ret; Parser p = new Parser(sourceCode, sourceName, firstLine); p.scopeStack.clear(); p.scopeStack.push(new ScopeInfo()); p.scopePush(ret); while(true) { //int s = ret.size; if(p.peekToken() == -1) break; // FIXME: Check this logic one more time p.parseStatement(ret, null); //if (s == ret.size) break; } p.scopePop(ret); if(p.scopeStack.size() != 1) throw new Error("scopeStack height mismatch"); ret.add(-1, LITERAL, null); ret.add(-1, RETURN); return ret; } /** gets a token and throws an exception if it is not code */ private void consume(int code) throws IOException { if (getToken() != code) { if(code == NAME) switch(op) { case RETURN: case TYPEOF: case BREAK: case CONTINUE: case TRY: case THROW: case ASSERT: case NULL: case TRUE: case FALSE: case IN: case IF: case ELSE: case SWITCH: case CASE: case DEFAULT: case WHILE: case VAR: case WITH: case CATCH: case FINALLY: throw pe("Bad variable name; '" + codeToString[op].toLowerCase() + "' is a javascript keyword"); } throw pe("expected " + codeToString[code] + ", got " + (op == -1 ? "EOF" : codeToString[op])); } } /** * Parse the largest possible expression containing no operators * of precedence below minPrecedence and append the * bytecodes for that expression to appendTo; the * appended bytecodes MUST grow the stack by exactly one element. */ private void startExpr(JSFunction appendTo, int minPrecedence) throws IOException { int saveParserLine = parserLine; _startExpr(appendTo, minPrecedence); parserLine = saveParserLine; } private void _startExpr(JSFunction appendTo, int minPrecedence) throws IOException { int tok = getToken(); JSFunction b = appendTo; switch (tok) { case -1: throw pe("expected expression"); // all of these simply push values onto the stack case NUMBER: b.add(parserLine, LITERAL, JSU.N(number)); break; case STRING: b.add(parserLine, LITERAL, JSString.intern(string)); break; case NULL: b.add(parserLine, LITERAL, null); break; case TRUE: case FALSE: b.add(parserLine, LITERAL, tok == TRUE ? JSU.T : JSU.F); break; // (.foo) syntax case DOT: { consume(NAME); b.add(parserLine, GLOBALSCOPE); b.add(parserLine, GET, JSU.S("",true)); b.add(parserLine, LITERAL, JSU.S(string,true)); continueExprAfterAssignable(b,minPrecedence,null); break; } case LB: { b.add(parserLine, ARRAY, JSU.ZERO); // push an array onto the stack int size0 = b.size; int i = 0; if (peekToken() != RB) while(true) { // iterate over the initialization values b.add(parserLine, LITERAL, JSU.N(i++)); // push the index in the array to place it into if (peekToken() == COMMA || peekToken() == RB) b.add(parserLine, LITERAL, null); // for stuff like [1,,2,] else startExpr(b, NO_COMMA); // push the value onto the stack b.add(parserLine, PUT); // put it into the array b.add(parserLine, POP); // discard the value remaining on the stack if (peekToken() == RB) break; consume(COMMA); } b.set(size0 - 1, JSU.N(i)); // back at the ARRAY instruction, write the size of the array consume(RB); break; } case SUB: case ADD: { if(peekToken() == NUMBER) { // literal consume(NUMBER); b.add(parserLine, LITERAL, JSU.N(number.doubleValue() * (tok == SUB ? -1 : 1))); } else { // unary +/- operator if(tok == SUB) b.add(parserLine, LITERAL, JSU.ZERO); // BITNOT has the same precedence as the unary +/- operators startExpr(b,precedence[BITNOT]); if(tok == ADD) b.add(parserLine, LITERAL, JSU.ZERO); // HACK to force expr into a numeric context b.add(parserLine, SUB); } break; } case LP: { // grouping (not calling) startExpr(b, -1); consume(RP); break; } case INC: case DEC: { // prefix (not postfix) startExpr(b, precedence[tok]); int prev = b.size - 1; boolean sg = b.get(prev) == SCOPEGET; if (b.get(prev) == GET && b.getArg(prev) != null) b.set(prev, LITERAL, b.getArg(prev)); else if(b.get(prev) == GET) b.pop(); else if(!sg) throw pe("prefixed increment/decrement can only be performed on a valid assignment target"); if(!sg) b.add(parserLine, GET_PRESERVE, Boolean.TRUE); b.add(parserLine, LITERAL, JSU.N(1)); b.add(parserLine, tok == INC ? ADD : SUB, JSU.N(2)); if(sg) { b.add(parserLine, SCOPEPUT, b.getArg(prev)); } else { b.add(parserLine, PUT, null); b.add(parserLine, SWAP, null); b.add(parserLine, POP, null); } break; } case BANG: case BITNOT: case TYPEOF: { startExpr(b, precedence[tok]); b.add(parserLine, tok); break; } case LC: { // object constructor b.add(parserLine, OBJECT, null); // put an object on the stack if (peekToken() != RC) while(true) { if (peekToken() != NAME && peekToken() != STRING) throw pe("expected NAME or STRING"); getToken(); b.add(parserLine, LITERAL, JSString.intern(string)); // grab the key consume(COLON); startExpr(b, NO_COMMA); // grab the value b.add(parserLine, PUT); // put the value into the object b.add(parserLine, POP); // discard the remaining value if (peekToken() == RC) break; consume(COMMA); if (peekToken() == RC) break; // we permit {,,} -- I'm not sure if ECMA does } consume(RC); break; } case NAME: { JS varKey = scopeKey(string); if(varKey == null) { b.add(parserLine, GLOBALSCOPE); b.add(parserLine, LITERAL, JSString.intern(string)); } continueExprAfterAssignable(b,minPrecedence,varKey); break; } case CASCADE: { if(peekToken() == ASSIGN) { consume(ASSIGN); startExpr(b, precedence[ASSIGN]); b.add(parserLine, CASCADE, JSU.T); } else { b.add(parserLine, CASCADE, JSU.F); } break; } case FUNCTION: { consume(LP); int numArgs = 0; JSFunction b2 = new JSFunction(sourceName, parserLine, null); b.add(parserLine, NEWFUNCTION, b2); // function prelude; arguments array is already on the stack scopePush(b2); scopeDeclare("arguments"); b2.add(parserLine, SCOPEPUT,scopeKey("arguments")); while(peekToken() != RP) { // run through the list of argument names numArgs++; if (peekToken() == NAME) { consume(NAME); // a named argument b2.add(parserLine, DUP); // dup the args array b2.add(parserLine, GET, JSU.N(numArgs - 1)); // retrieve it from the arguments array scopeDeclare(string); b2.add(parserLine, SCOPEPUT, scopeKey(string)); b2.add(parserLine, POP); } if (peekToken() == RP) break; consume(COMMA); } consume(RP); b2.numFormalArgs = numArgs; b2.add(parserLine, POP); // pop off the arguments array if(peekToken() != LC) throw pe("JSFunctions must have a block surrounded by curly brackets"); parseBlock(b2, null); // the function body scopePop(b2); b2.add(parserLine, LITERAL, null); // in case we "fall out the bottom", return NULL b2.add(parserLine, RETURN); break; } default: throw pe("expected expression, found " + codeToString[tok] + ", which cannot start an expression"); } // attempt to continue the expression continueExpr(b, minPrecedence); } /** * Assuming that a complete assignable (lvalue) has just been * parsed and the object and key are on the stack, * continueExprAfterAssignable will attempt to parse an * expression that modifies the assignable. This method always * decreases the stack depth by exactly one element. */ private void continueExprAfterAssignable(JSFunction b,int minPrecedence, JS varKey) throws IOException { int saveParserLine = parserLine; _continueExprAfterAssignable(b,minPrecedence,varKey); parserLine = saveParserLine; } private void _continueExprAfterAssignable(JSFunction b,int minPrecedence, JS varKey) throws IOException { if (b == null) throw new Error("got null b; this should never happen"); int tok = getToken(); if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok]))) // force the default case tok = -1; switch(tok) { case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH: case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: case ASSIGN_ADD: case ASSIGN_SUB: case ADD_TRAP: case DEL_TRAP: { if (tok != ADD_TRAP && tok != DEL_TRAP) b.add(parserLine, varKey == null ? GET_PRESERVE : SCOPEGET, varKey); startExpr(b, precedence[tok]); if (tok != ADD_TRAP && tok != DEL_TRAP) { // tok-1 is always s/^ASSIGN_// (0 is BITOR, 1 is ASSIGN_BITOR, etc) b.add(parserLine, tok - 1, tok-1==ADD ? JSU.N(2) : null); if(varKey == null) { b.add(parserLine, PUT); b.add(parserLine, SWAP); b.add(parserLine, POP); } else { b.add(parserLine, SCOPEPUT, varKey); } } else { if(varKey != null) throw pe("cannot place traps on local variables"); b.add(parserLine, tok); } break; } case INC: case DEC: { // postfix if(varKey == null) { b.add(parserLine, GET_PRESERVE, Boolean.TRUE); b.add(parserLine, LITERAL, JSU.N(1)); b.add(parserLine, tok == INC ? ADD : SUB, JSU.N(2)); b.add(parserLine, PUT, null); b.add(parserLine, SWAP, null); b.add(parserLine, POP, null); b.add(parserLine, LITERAL, JSU.N(1)); b.add(parserLine, tok == INC ? SUB : ADD, JSU.N(2)); // undo what we just did, since this is postfix } else { b.add(parserLine, SCOPEGET, varKey); b.add(parserLine, DUP); b.add(parserLine, LITERAL, JSU.ONE); b.add(parserLine, tok == INC ? ADD : SUB, JSU.N(2)); b.add(parserLine, SCOPEPUT, varKey); } break; } case ASSIGN: { startExpr(b, precedence[tok]); if(varKey == null) { b.add(parserLine, PUT); b.add(parserLine, SWAP); b.add(parserLine, POP); } else { b.add(parserLine, SCOPEPUT, varKey); } break; } case LP: { // Method calls are implemented by doing a GET_PRESERVE // first. If the object supports method calls, it will // return JS.METHOD b.add(parserLine, varKey == null ? GET_PRESERVE : SCOPEGET, varKey); int n = parseArgs(b); b.add(parserLine, varKey == null ? CALLMETHOD : CALL, JSU.N(n)); break; } default: { pushBackToken(); if(varKey != null) b.add(parserLine, SCOPEGET, varKey); else if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null) b.set(b.size-1,GET,b.getArg(b.size-1)); else b.add(parserLine, GET); return; } } } /** * Assuming that a complete expression has just been parsed, * continueExpr will attempt to extend this expression by * parsing additional tokens and appending additional bytecodes. * * No operators with precedence less than minPrecedence * will be parsed. * * If any bytecodes are appended, they will not alter the stack * depth. */ private void continueExpr(JSFunction b, int minPrecedence) throws IOException { int saveParserLine = parserLine; _continueExpr(b, minPrecedence); parserLine = saveParserLine; } private void _continueExpr(JSFunction b, int minPrecedence) throws IOException { if (b == null) throw new Error("got null b; this should never happen"); int tok = getToken(); if (tok == -1) return; if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok]))) { pushBackToken(); return; } switch (tok) { case LP: { // invocation (not grouping) int n = parseArgs(b); b.add(parserLine, CALL, JSU.N(n)); break; } case BITOR: case BITXOR: case BITAND: case SHEQ: case SHNE: case LSH: case RSH: case URSH: case MUL: case DIV: case MOD: case GT: case GE: case EQ: case NE: case LT: case LE: case SUB: { startExpr(b, precedence[tok]); b.add(parserLine, tok); break; } case ADD: { int count=1; int nextTok; do { startExpr(b,precedence[tok]); count++; nextTok = getToken(); } while(nextTok == tok); pushBackToken(); b.add(parserLine, tok, JSU.N(count)); break; } case OR: case AND: { b.add(parserLine, tok == AND ? JSFunction.JF : JSFunction.JT, JSU.ZERO); // test to see if we can short-circuit int size = b.size; startExpr(b, precedence[tok]); // otherwise check the second value b.add(parserLine, JMP, JSU.N(2)); // leave the second value on the stack and jump to the end b.add(parserLine, LITERAL, tok == AND ? JSU.B(false) : JSU.B(true)); // target of the short-circuit jump is here b.set(size - 1, JSU.N(b.size - size)); // write the target of the short-circuit jump break; } case DOT: { // support foo..bar syntax for foo[""].bar if (peekToken() == DOT) { string = ""; } else { consume(NAME); } b.add(parserLine, LITERAL, JSString.intern(string)); continueExprAfterAssignable(b,minPrecedence,null); break; } case LB: { // subscripting (not array constructor) startExpr(b, -1); consume(RB); continueExprAfterAssignable(b,minPrecedence,null); break; } case HOOK: { b.add(parserLine, JF, JSU.ZERO); // jump to the if-false expression int size = b.size; startExpr(b, minPrecedence); // write the if-true expression b.add(parserLine, JMP, JSU.ZERO); // if true, jump *over* the if-false expression b.set(size - 1, JSU.N(b.size - size + 1)); // now we know where the target of the jump is consume(COLON); size = b.size; startExpr(b, minPrecedence); // write the if-false expression b.set(size - 1, JSU.N(b.size - size + 1)); // this is the end; jump to here break; } case COMMA: { // pop the result of the previous expression, it is ignored b.add(parserLine,POP); startExpr(b,-1); break; } default: { pushBackToken(); return; } } continueExpr(b, minPrecedence); // try to continue the expression } // parse a set of comma separated function arguments, assume LP has already been consumed private int parseArgs(JSFunction b) throws IOException { int i = 0; while(peekToken() != RP) { i++; if (peekToken() != COMMA) { startExpr(b, NO_COMMA); if (peekToken() == RP) break; } consume(COMMA); } consume(RP); return i; } /** Parse a block of statements which must be surrounded by LC..RC. */ void parseBlock(JSFunction b) throws IOException { parseBlock(b, null); } void parseBlock(JSFunction b, String label) throws IOException { int saveParserLine = parserLine; _parseBlock(b, label); parserLine = saveParserLine; } void _parseBlock(JSFunction b, String label) throws IOException { if (peekToken() == -1) return; else if (peekToken() != LC) parseStatement(b, null); else { consume(LC); while(peekToken() != RC && peekToken() != -1) parseStatement(b, null); consume(RC); } } /** Parse a single statement, consuming the RC or SEMI which terminates it. */ void parseStatement(JSFunction b, String label) throws IOException { int saveParserLine = parserLine; _parseStatement(b, label); parserLine = saveParserLine; } void _parseStatement(JSFunction b, String label) throws IOException { int tok = peekToken(); if (tok == -1) return; switch(tok = getToken()) { case THROW: case ASSERT: case RETURN: { if (tok == RETURN && peekToken() == SEMI) b.add(parserLine, LITERAL, null); else startExpr(b, -1); b.add(parserLine, tok); consume(SEMI); break; } case BREAK: case CONTINUE: { if (peekToken() == NAME) consume(NAME); b.add(parserLine, tok, string); consume(SEMI); break; } case VAR: { while(true) { consume(NAME); String var = string; scopeDeclare(var); if (peekToken() == ASSIGN) { // if there is an '=' after the variable name consume(ASSIGN); startExpr(b, NO_COMMA); b.add(parserLine, SCOPEPUT, scopeKey(var)); // assign it b.add(parserLine, POP); // clean the stack } if (peekToken() != COMMA) break; consume(COMMA); } if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI); break; } case IF: { consume(LP); startExpr(b, -1); consume(RP); b.add(parserLine, JF, JSU.ZERO); // if false, jump to the else-block int size = b.size; parseStatement(b, null); if (peekToken() == ELSE) { consume(ELSE); b.add(parserLine, JMP, JSU.ZERO); // if we took the true-block, jump over the else-block b.set(size - 1, JSU.N(b.size - size + 1)); size = b.size; parseStatement(b, null); } b.set(size - 1, JSU.N(b.size - size + 1)); // regardless of which branch we took, b[size] needs to point here break; } case WHILE: { consume(LP); if (label != null) b.add(parserLine, LABEL, label); b.add(parserLine, LOOP); int size = b.size; b.add(parserLine, POP); // discard the first-iteration indicator startExpr(b, -1); b.add(parserLine, JT, JSU.N(2)); // if the while() clause is true, jump over the BREAK b.add(parserLine, BREAK); consume(RP); parseStatement(b, null); b.add(parserLine, CONTINUE); // if we fall out of the end, definately continue b.set(size - 1, JSU.N(b.size - size + 1)); // end of the loop break; } case SWITCH: { consume(LP); if (label != null) b.add(parserLine, LABEL, label); b.add(parserLine, LOOP); int size0 = b.size; startExpr(b, -1); consume(RP); consume(LC); while(true) if (peekToken() == CASE) { // we compile CASE statements like a bunch of if..else's consume(CASE); b.add(parserLine, DUP); // duplicate the switch() value; we'll consume one copy startExpr(b, -1); consume(COLON); b.add(parserLine, EQ); // check if we should do this case-block b.add(parserLine, JF, JSU.ZERO); // if not, jump to the next one int size = b.size; while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null); b.set(size - 1, JSU.N(1 + b.size - size)); } else if (peekToken() == DEFAULT) { consume(DEFAULT); consume(COLON); while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null); } else if (peekToken() == RC) { consume(RC); b.add(parserLine, BREAK); // break out of the loop if we 'fall through' break; } else { throw pe("expected CASE, DEFAULT, or RC; got " + codeToString[peekToken()]); } b.set(size0 - 1, JSU.N(b.size - size0 + 1)); // end of the loop break; } case DO: { if (label != null) b.add(parserLine, LABEL, label); b.add(parserLine, LOOP); int size = b.size; parseStatement(b, null); consume(WHILE); consume(LP); startExpr(b, -1); b.add(parserLine, JT, JSU.N(2)); // check the while() clause; jump over the BREAK if true b.add(parserLine, BREAK); b.add(parserLine, CONTINUE); consume(RP); consume(SEMI); b.set(size - 1, JSU.N(b.size - size + 1)); // end of the loop; write this location to the LOOP instruction break; } case TRY: { b.add(parserLine, TRY); // try bytecode causes a TryMarker to be pushed int tryInsn = b.size - 1; // parse the expression to be TRYed parseStatement(b, null); // pop the try marker. this is pushed when the TRY bytecode is executed b.add(parserLine, POP); // jump forward to the end of the catch block, start of the finally block b.add(parserLine, JMP); int successJMPInsn = b.size - 1; if (peekToken() != CATCH && peekToken() != FINALLY) throw pe("try without catch or finally"); int catchJMPDistance = -1; if (peekToken() == CATCH) { Basket.List catchEnds = new Basket.Array(); boolean catchAll = false; catchJMPDistance = b.size - tryInsn; while(peekToken() == CATCH && !catchAll) { String exceptionVar; getToken(); consume(LP); consume(NAME); exceptionVar = string; int[] writebacks = new int[] { -1, -1, -1 }; if (peekToken() != RP) { // extended Ibex catch block: catch(e faultCode "foo.bar.baz") consume(NAME); b.add(parserLine, DUP); b.add(parserLine, LITERAL, JSString.intern(string)); b.add(parserLine, GET); b.add(parserLine, DUP); b.add(parserLine, LITERAL, null); b.add(parserLine, EQ); b.add(parserLine, JT); writebacks[0] = b.size - 1; if (peekToken() == STRING) { consume(STRING); b.add(parserLine, DUP); b.add(parserLine, LITERAL, string); b.add(parserLine, LT); b.add(parserLine, JT); writebacks[1] = b.size - 1; b.add(parserLine, DUP); b.add(parserLine, LITERAL, string + "/"); // (slash is ASCII after dot) b.add(parserLine, GE); b.add(parserLine, JT); writebacks[2] = b.size - 1; } else { consume(NUMBER); b.add(parserLine, DUP); b.add(parserLine, LITERAL, number); b.add(parserLine, EQ); b.add(parserLine, JF); writebacks[1] = b.size - 1; } b.add(parserLine, POP); // pop the element thats on the stack from the compare } else { catchAll = true; } consume(RP); // the exception is on top of the stack; put it to the chosen name scopePush(b); scopeDeclare(exceptionVar); b.add(parserLine, SCOPEPUT, scopeKey(exceptionVar)); b.add(parserLine, POP); parseBlock(b, null); scopePop(b); b.add(parserLine, JMP); catchEnds.add(new Integer(b.size-1)); for(int i=0; i<3; i++) if (writebacks[i] != -1) b.set(writebacks[i], JSU.N(b.size-writebacks[i])); b.add(parserLine, POP); // pop the element thats on the stack from the compare } if(!catchAll) b.add(parserLine, THROW); for(int i=0;i