diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java index 0978c94976c..72e23997b5b 100644 --- a/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java @@ -44,1115 +44,1114 @@ import java.util.Observable; /** * This class implements the JavaScript parser. - * + *

* It is based on the C source files jsparse.c and jsparse.h in the jsref * package. - * + * * @see TokenStream */ public class Parser extends Observable { - public Parser(IRFactory nf, boolean insideFunction) { - this.nf = nf; - this.insideFunction = insideFunction; - } - - private void mustMatchToken(TokenStream ts, int toMatch, String messageId) - throws IOException, JavaScriptException { - int tt; - if ((tt = ts.getToken()) != toMatch) { - reportError(ts, messageId); - ts.ungetToken(tt); // In case the parser decides to continue + public Parser(IRFactory nf, boolean insideFunction) { + this.nf = nf; + this.insideFunction = insideFunction; } - } - private void reportError(TokenStream ts, String messageId) - throws JavaScriptException { - this.ok = false; - ts.reportSyntaxError(messageId, null); + private void mustMatchToken(TokenStream ts, int toMatch, String messageId) throws IOException, JavaScriptException { + int tt; + if ((tt = ts.getToken()) != toMatch) { + reportError(ts, messageId); + ts.ungetToken(tt); // In case the parser decides to continue + } + } + + private void reportError(TokenStream ts, String messageId) throws JavaScriptException { + this.ok = false; + ts.reportSyntaxError(messageId, null); + + /* + * Throw an exception to unwind the recursive descent parse. We use + * JavaScriptException here even though it is really a different use of the + * exception than it is usually used for. + */ + throw new JavaScriptException(messageId); + } /* - * Throw an exception to unwind the recursive descent parse. We use - * JavaScriptException here even though it is really a different use of the - * exception than it is usually used for. + * Build a parse tree from the given TokenStream. + * + * @param ts the TokenStream to parse + * + * @return an Object representing the parsed program. If the parse fails, null + * will be returned. (The parse failure will result in a call to the current + * Context's ErrorReporter.) */ - throw new JavaScriptException(messageId); - } + public Object parse(TokenStream ts) throws IOException { + this.ok = true; + sourceTop = 0; + functionNumber = 0; - /* - * Build a parse tree from the given TokenStream. - * - * @param ts the TokenStream to parse - * - * @return an Object representing the parsed program. If the parse fails, null - * will be returned. (The parse failure will result in a call to the current - * Context's ErrorReporter.) - */ - public Object parse(TokenStream ts) throws IOException { - this.ok = true; - sourceTop = 0; - functionNumber = 0; - - int tt; // last token from getToken(); - int baseLineno = ts.getLineno(); // line number where source starts - - /* - * so we have something to add nodes to until we've collected all the source - */ - Object tempBlock = nf.createLeaf(TokenStream.BLOCK); - ((Node) tempBlock).setIsSyntheticBlock(true); - - while (true) { - ts.flags |= TokenStream.TSF_REGEXP; - tt = ts.getToken(); - ts.flags &= ~TokenStream.TSF_REGEXP; - - if (tt <= TokenStream.EOF) { - break; - } - - if (tt == TokenStream.FUNCTION) { - try { - nf.addChildToBack(tempBlock, function(ts, false)); - } catch (JavaScriptException e) { - this.ok = false; - break; - } - } else { - ts.ungetToken(tt); - nf.addChildToBack(tempBlock, statement(ts)); - } - } - - if (!this.ok) { - // XXX ts.clearPushback() call here? - return null; - } - - Object pn = nf.createScript(tempBlock, ts.getSourceName(), baseLineno, ts - .getLineno(), null); - ((Node) pn).setIsSyntheticBlock(true); - return pn; - } - - /* - * The C version of this function takes an argument list, which doesn't seem - * to be needed for tree generation... it'd only be useful for checking - * argument hiding, which I'm not doing anyway... - */ - private Object parseFunctionBody(TokenStream ts) throws IOException { - int oldflags = ts.flags; - ts.flags &= ~(TokenStream.TSF_RETURN_EXPR | TokenStream.TSF_RETURN_VOID); - ts.flags |= TokenStream.TSF_FUNCTION; - - Object pn = nf.createBlock(ts.getLineno()); - try { - int tt; - while ((tt = ts.peekToken()) > TokenStream.EOF && tt != TokenStream.RC) { - if (tt == TokenStream.FUNCTION) { - ts.getToken(); - nf.addChildToBack(pn, function(ts, false)); - } else { - nf.addChildToBack(pn, statement(ts)); - } - } - } catch (JavaScriptException e) { - this.ok = false; - } finally { - // also in finally block: - // flushNewLines, clearPushback. - - ts.flags = oldflags; - } - - return pn; - } - - private Object function(TokenStream ts, boolean isExpr) throws IOException, JavaScriptException { - notifyObservers(new ParserEvents.OnFunctionParsingStart()); - int baseLineno = ts.getLineno(); // line number where source starts - - String name; - Object memberExprNode = null; - if (ts.matchToken(TokenStream.NAME)) { - name = ts.getString(); - if (!ts.matchToken(TokenStream.LP)) { - if (Context.getContext().hasFeature( - Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) { - // Extension to ECMA: if 'function ' does not follow - // by '(', assume starts memberExpr - Object memberExprHead = nf.createName(name); - name = null; - memberExprNode = memberExprTail(ts, false, memberExprHead); - } - mustMatchToken(ts, TokenStream.LP, "msg.no.paren.parms"); - } - } else if (ts.matchToken(TokenStream.LP)) { - // Anonymous function - name = null; - } else { - name = null; - if (Context.getContext().hasFeature( - Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) { - // Note that memberExpr can not start with '(' like - // in (1+2).toString, because 'function (' already - // processed as anonymous function - memberExprNode = memberExpr(ts, false); - } - mustMatchToken(ts, TokenStream.LP, "msg.no.paren.parms"); - } - - ++functionNumber; - - // Save current source top to restore it on exit not to include - // function to parent source - int savedSourceTop = sourceTop; - int savedFunctionNumber = functionNumber; - Object args; - Object body; - try { - functionNumber = 0; - args = nf.createLeaf(TokenStream.LP); - - if (!ts.matchToken(TokenStream.GWT)) { - do { - mustMatchToken(ts, TokenStream.NAME, "msg.no.parm"); - String s = ts.getString(); - nf.addChildToBack(args, nf.createName(s)); - - } while (ts.matchToken(TokenStream.COMMA)); - - mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.after.parms"); - } - - mustMatchToken(ts, TokenStream.LC, "msg.no.brace.body"); - body = parseFunctionBody(ts); - mustMatchToken(ts, TokenStream.RC, "msg.no.brace.after.body"); - // skip the last EOL so nested functions work... - } finally { - sourceTop = savedSourceTop; - functionNumber = savedFunctionNumber; - } - - Object pn = nf.createFunction(name, args, body, ts.getSourceName(), - baseLineno, ts.getLineno(), null, isExpr || memberExprNode != null); - if (memberExprNode != null) { - pn = nf.createBinary(TokenStream.ASSIGN, TokenStream.NOP, memberExprNode, pn); - } - - // Add EOL but only if function is not part of expression, in which - // case it gets SEMI + EOL from Statement. - if (!isExpr) { - wellTerminated(ts, TokenStream.FUNCTION); - } - - notifyObservers(new ParserEvents.OnFunctionParsingEnd(ts)); - return pn; - } - - private Object statements(TokenStream ts) throws IOException { - Object pn = nf.createBlock(ts.getLineno()); - - int tt; - while ((tt = ts.peekToken()) > TokenStream.EOF && tt != TokenStream.RC) { - nf.addChildToBack(pn, statement(ts)); - } - - return pn; - } - - private Object condition(TokenStream ts) throws IOException, - JavaScriptException { - Object pn; - mustMatchToken(ts, TokenStream.LP, "msg.no.paren.cond"); - pn = expr(ts, false); - mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.after.cond"); - - // there's a check here in jsparse.c that corrects = to == - - return pn; - } - - private void wellTerminated(TokenStream ts, int lastExprType) - throws IOException, JavaScriptException { - int tt = ts.peekTokenSameLine(); - if (tt == TokenStream.ERROR) { - return; - } - - if (tt != TokenStream.EOF && tt != TokenStream.EOL && tt != TokenStream.SEMI && tt != TokenStream.RC) { - int version = Context.getContext().getLanguageVersion(); - if ((tt == TokenStream.FUNCTION || lastExprType == TokenStream.FUNCTION) - && (version < Context.VERSION_1_2)) { - /* - * Checking against version < 1.2 and version >= 1.0 in the above line - * breaks old javascript, so we keep it this way for now... XXX warning - * needed? - */ - } else { - reportError(ts, "msg.no.semi.stmt"); - } - } - } - - // match a NAME; return null if no match. - private String matchLabel(TokenStream ts) throws IOException, - JavaScriptException { - int lineno = ts.getLineno(); - - String label = null; - int tt; - tt = ts.peekTokenSameLine(); - if (tt == TokenStream.NAME) { - ts.getToken(); - label = ts.getString(); - } - - if (lineno == ts.getLineno()) - wellTerminated(ts, TokenStream.ERROR); - - return label; - } - - private Object statement(TokenStream ts) throws IOException { - try { - return statementHelper(ts); - } catch (JavaScriptException e) { - // skip to end of statement - int lineno = ts.getLineno(); - int t; - do { - t = ts.getToken(); - } while (t != TokenStream.SEMI && t != TokenStream.EOL - && t != TokenStream.EOF && t != TokenStream.ERROR); - return nf.createExprStatement(nf.createName("error"), lineno); - } - } - - /** - * Whether the "catch (e: e instanceof Exception) { ... }" syntax is - * implemented. - */ - - private Object statementHelper(TokenStream ts) throws IOException, - JavaScriptException { - Object pn; - - int tt; - - int lastExprType; // For wellTerminated - - tt = ts.getToken(); - - switch (tt) { - case TokenStream.IF: { - int lineno = ts.getLineno(); - Object cond = condition(ts); - Object ifTrue = statement(ts); - Object ifFalse = null; - if (ts.matchToken(TokenStream.ELSE)) { - ifFalse = statement(ts); - } - pn = nf.createIf(cond, ifTrue, ifFalse, lineno); - break; - } - - case TokenStream.SWITCH: { - pn = nf.createSwitch(ts.getLineno()); - - Object cur_case = null; // to kill warning - Object case_statements; - - mustMatchToken(ts, TokenStream.LP, "msg.no.paren.switch"); - nf.addChildToBack(pn, expr(ts, false)); - mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.after.switch"); - mustMatchToken(ts, TokenStream.LC, "msg.no.brace.switch"); - - while ((tt = ts.getToken()) != TokenStream.RC && tt != TokenStream.EOF) { - switch (tt) { - case TokenStream.CASE: - cur_case = nf.createUnary(TokenStream.CASE, expr(ts, false)); - break; - - case TokenStream.DEFAULT: - cur_case = nf.createLeaf(TokenStream.DEFAULT); - // XXX check that there isn't more than one default - break; - - default: - reportError(ts, "msg.bad.switch"); - break; - } - mustMatchToken(ts, TokenStream.COLON, "msg.no.colon.case"); - - case_statements = nf.createLeaf(TokenStream.BLOCK); - ((Node) case_statements).setIsSyntheticBlock(true); - - while ((tt = ts.peekToken()) != TokenStream.RC && tt != TokenStream.CASE - && tt != TokenStream.DEFAULT && tt != TokenStream.EOF) { - nf.addChildToBack(case_statements, statement(ts)); - } - // assert cur_case - nf.addChildToBack(cur_case, case_statements); - - nf.addChildToBack(pn, cur_case); - } - break; - } - - case TokenStream.WHILE: { - int lineno = ts.getLineno(); - Object cond = condition(ts); - Object body = statement(ts); - - pn = nf.createWhile(cond, body, lineno); - break; - - } - - case TokenStream.DO: { - int lineno = ts.getLineno(); - - Object body = statement(ts); - - mustMatchToken(ts, TokenStream.WHILE, "msg.no.while.do"); - Object cond = condition(ts); - - pn = nf.createDoWhile(body, cond, lineno); - break; - } - - case TokenStream.FOR: { - int lineno = ts.getLineno(); - - Object init; // Node init is also foo in 'foo in Object' - Object cond; // Node cond is also object in 'foo in Object' - Object incr = null; // to kill warning - Object body; - - mustMatchToken(ts, TokenStream.LP, "msg.no.paren.for"); - tt = ts.peekToken(); - if (tt == TokenStream.SEMI) { - init = nf.createLeaf(TokenStream.VOID); - } else { - if (tt == TokenStream.VAR) { - // set init to a var list or initial - ts.getToken(); // throw away the 'var' token - init = variables(ts, true); - } else { - init = expr(ts, true); - } - } - - tt = ts.peekToken(); - if (tt == TokenStream.RELOP && ts.getOp() == TokenStream.IN) { - ts.matchToken(TokenStream.RELOP); - // 'cond' is the object over which we're iterating - cond = expr(ts, false); - } else { // ordinary for loop - mustMatchToken(ts, TokenStream.SEMI, "msg.no.semi.for"); - if (ts.peekToken() == TokenStream.SEMI) { - // no loop condition - cond = nf.createLeaf(TokenStream.VOID); - } else { - cond = expr(ts, false); - } - - mustMatchToken(ts, TokenStream.SEMI, "msg.no.semi.for.cond"); - if (ts.peekToken() == TokenStream.GWT) { - incr = nf.createLeaf(TokenStream.VOID); - } else { - incr = expr(ts, false); - } - } - - mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.for.ctrl"); - body = statement(ts); - - if (incr == null) { - // cond could be null if 'in obj' got eaten by the init node. - pn = nf.createForIn(init, cond, body, lineno); - } else { - pn = nf.createFor(init, cond, incr, body, lineno); - } - break; - } - - case TokenStream.TRY: { - int lineno = ts.getLineno(); - - Object tryblock; - Object catchblocks; - Object finallyblock = null; - - tryblock = statement(ts); - - catchblocks = nf.createLeaf(TokenStream.BLOCK); - - boolean sawDefaultCatch = false; - int peek = ts.peekToken(); - if (peek == TokenStream.CATCH) { - while (ts.matchToken(TokenStream.CATCH)) { - if (sawDefaultCatch) { - reportError(ts, "msg.catch.unreachable"); - } - mustMatchToken(ts, TokenStream.LP, "msg.no.paren.catch"); - - mustMatchToken(ts, TokenStream.NAME, "msg.bad.catchcond"); - String varName = ts.getString(); - - Object catchCond = null; - if (ts.matchToken(TokenStream.IF)) { - catchCond = expr(ts, false); - } else { - sawDefaultCatch = true; - } - - mustMatchToken(ts, TokenStream.GWT, "msg.bad.catchcond"); - mustMatchToken(ts, TokenStream.LC, "msg.no.brace.catchblock"); - - nf.addChildToBack(catchblocks, nf.createCatch(varName, catchCond, - statements(ts), ts.getLineno())); - - mustMatchToken(ts, TokenStream.RC, "msg.no.brace.after.body"); - } - } else if (peek != TokenStream.FINALLY) { - mustMatchToken(ts, TokenStream.FINALLY, "msg.try.no.catchfinally"); - } - - if (ts.matchToken(TokenStream.FINALLY)) { - finallyblock = statement(ts); - } - - pn = nf.createTryCatchFinally(tryblock, catchblocks, finallyblock, - lineno); - - break; - } - case TokenStream.THROW: { - int lineno = ts.getLineno(); - pn = nf.createThrow(expr(ts, false), lineno); - if (lineno == ts.getLineno()) - wellTerminated(ts, TokenStream.ERROR); - break; - } - case TokenStream.BREAK: { - int lineno = ts.getLineno(); - - // matchLabel only matches if there is one - String label = matchLabel(ts); - pn = nf.createBreak(label, lineno); - break; - } - case TokenStream.CONTINUE: { - int lineno = ts.getLineno(); - - // matchLabel only matches if there is one - String label = matchLabel(ts); - pn = nf.createContinue(label, lineno); - break; - } - case TokenStream.DEBUGGER: { - int lineno = ts.getLineno(); - pn = nf.createDebugger(lineno); - break; - } - case TokenStream.WITH: { - // bruce: we don't support this is JSNI code because it's impossible - // to identify bindings even passably well - // - - reportError(ts, "msg.jsni.unsupported.with"); - - int lineno = ts.getLineno(); - mustMatchToken(ts, TokenStream.LP, "msg.no.paren.with"); - Object obj = expr(ts, false); - mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.after.with"); - Object body = statement(ts); - pn = nf.createWith(obj, body, lineno); - break; - } - case TokenStream.VAR: { - int lineno = ts.getLineno(); - pn = variables(ts, false); - if (ts.getLineno() == lineno) - wellTerminated(ts, TokenStream.ERROR); - break; - } - case TokenStream.RETURN: { - Object retExpr = null; - int lineno = 0; - // bail if we're not in a (toplevel) function - if ((!insideFunction) && ((ts.flags & TokenStream.TSF_FUNCTION) == 0)) { - reportError(ts, "msg.bad.return"); - } - - /* This is ugly, but we don't want to require a semicolon. */ - ts.flags |= TokenStream.TSF_REGEXP; - tt = ts.peekTokenSameLine(); - ts.flags &= ~TokenStream.TSF_REGEXP; - - if (tt != TokenStream.EOF && tt != TokenStream.EOL && tt != TokenStream.SEMI && tt != TokenStream.RC) { - lineno = ts.getLineno(); - retExpr = expr(ts, false); - if (ts.getLineno() == lineno) - wellTerminated(ts, TokenStream.ERROR); - ts.flags |= TokenStream.TSF_RETURN_EXPR; - } else { - ts.flags |= TokenStream.TSF_RETURN_VOID; - } - - // XXX ASSERT pn - pn = nf.createReturn(retExpr, lineno); - break; - } - case TokenStream.LC: - pn = statements(ts); - mustMatchToken(ts, TokenStream.RC, "msg.no.brace.block"); - break; - - case TokenStream.ERROR: - // Fall thru, to have a node for error recovery to work on - case TokenStream.EOL: - case TokenStream.SEMI: - pn = nf.createLeaf(TokenStream.VOID); - break; - - default: { - lastExprType = tt; - int tokenno = ts.getTokenno(); - ts.ungetToken(tt); - int lineno = ts.getLineno(); - - pn = expr(ts, false); - - if (ts.peekToken() == TokenStream.COLON) { - /* - * check that the last thing the tokenizer returned was a NAME and - * that only one token was consumed. - */ - if (lastExprType != TokenStream.NAME || (ts.getTokenno() != tokenno)) - reportError(ts, "msg.bad.label"); - - ts.getToken(); // eat the COLON - - /* - * in the C source, the label is associated with the statement that - * follows: nf.addChildToBack(pn, statement(ts)); - */ - String name = ts.getString(); - pn = nf.createLabel(name, lineno); - - // bruce: added to make it easier to bind labels to the - // statements they modify - // - nf.addChildToBack(pn, statement(ts)); - - // depend on decompiling lookahead to guess that that - // last name was a label. - return pn; - } - - if (lastExprType == TokenStream.FUNCTION) { - if (nf.getLeafType(pn) != TokenStream.FUNCTION) { - reportError(ts, "msg.syntax"); - } - } - - pn = nf.createExprStatement(pn, lineno); + int tt; // last token from getToken(); + int baseLineno = ts.getLineno(); // line number where source starts /* - * Check explicitly against (multi-line) function statement. - * - * lastExprEndLine is a hack to fix an automatic semicolon insertion - * problem with function expressions; the ts.getLineno() == lineno check - * was firing after a function definition even though the next statement - * was on a new line, because speculative getToken calls advanced the - * line number even when they didn't succeed. + * so we have something to add nodes to until we've collected all the source */ - if (ts.getLineno() == lineno - || (lastExprType == TokenStream.FUNCTION && ts.getLineno() == lastExprEndLine)) { - wellTerminated(ts, lastExprType); - } - break; - } - } - ts.matchToken(TokenStream.SEMI); + Object tempBlock = nf.createLeaf(TokenStream.BLOCK); + ((Node) tempBlock).setIsSyntheticBlock(true); - return pn; - } - - private Object variables(TokenStream ts, boolean inForInit) - throws IOException, JavaScriptException { - Object pn = nf.createVariables(ts.getLineno()); - - while (true) { - Object name; - Object init; - mustMatchToken(ts, TokenStream.NAME, "msg.bad.var"); - String s = ts.getString(); - name = nf.createName(s); - - // omitted check for argument hiding - - if (ts.matchToken(TokenStream.ASSIGN)) { - if (ts.getOp() != TokenStream.NOP) { - reportError(ts, "msg.bad.var.init"); - } - - init = assignExpr(ts, inForInit); - nf.addChildToBack(name, init); - } - nf.addChildToBack(pn, name); - if (!ts.matchToken(TokenStream.COMMA)) { - break; - } - } - return pn; - } - - private Object expr(TokenStream ts, boolean inForInit) throws IOException, - JavaScriptException { - Object pn = assignExpr(ts, inForInit); - while (ts.matchToken(TokenStream.COMMA)) { - pn = nf.createBinary(TokenStream.COMMA, pn, assignExpr(ts, inForInit)); - } - return pn; - } - - private Object assignExpr(TokenStream ts, boolean inForInit) - throws IOException, JavaScriptException { - Object pn = condExpr(ts, inForInit); - - if (ts.matchToken(TokenStream.ASSIGN)) { - // omitted: "invalid assignment left-hand side" check. - pn = nf - .createBinary(TokenStream.ASSIGN, ts.getOp(), pn, assignExpr(ts, inForInit)); - } - - return pn; - } - - private Object condExpr(TokenStream ts, boolean inForInit) - throws IOException, JavaScriptException { - Object ifTrue; - Object ifFalse; - - Object pn = orExpr(ts, inForInit); - - if (ts.matchToken(TokenStream.HOOK)) { - ifTrue = assignExpr(ts, false); - mustMatchToken(ts, TokenStream.COLON, "msg.no.colon.cond"); - ifFalse = assignExpr(ts, inForInit); - return nf.createTernary(pn, ifTrue, ifFalse); - } - - return pn; - } - - private Object orExpr(TokenStream ts, boolean inForInit) throws IOException, - JavaScriptException { - Object pn = andExpr(ts, inForInit); - if (ts.matchToken(TokenStream.OR)) { - pn = nf.createBinary(TokenStream.OR, pn, orExpr(ts, inForInit)); - } - - return pn; - } - - private Object andExpr(TokenStream ts, boolean inForInit) throws IOException, - JavaScriptException { - Object pn = bitOrExpr(ts, inForInit); - if (ts.matchToken(TokenStream.AND)) { - pn = nf.createBinary(TokenStream.AND, pn, andExpr(ts, inForInit)); - } - - return pn; - } - - private Object bitOrExpr(TokenStream ts, boolean inForInit) - throws IOException, JavaScriptException { - Object pn = bitXorExpr(ts, inForInit); - while (ts.matchToken(TokenStream.BITOR)) { - pn = nf.createBinary(TokenStream.BITOR, pn, bitXorExpr(ts, inForInit)); - } - return pn; - } - - private Object bitXorExpr(TokenStream ts, boolean inForInit) - throws IOException, JavaScriptException { - Object pn = bitAndExpr(ts, inForInit); - while (ts.matchToken(TokenStream.BITXOR)) { - pn = nf.createBinary(TokenStream.BITXOR, pn, bitAndExpr(ts, inForInit)); - } - return pn; - } - - private Object bitAndExpr(TokenStream ts, boolean inForInit) - throws IOException, JavaScriptException { - Object pn = eqExpr(ts, inForInit); - while (ts.matchToken(TokenStream.BITAND)) { - pn = nf.createBinary(TokenStream.BITAND, pn, eqExpr(ts, inForInit)); - } - return pn; - } - - private Object eqExpr(TokenStream ts, boolean inForInit) throws IOException, - JavaScriptException { - Object pn = relExpr(ts, inForInit); - while (ts.matchToken(TokenStream.EQOP)) { - pn = nf.createBinary(TokenStream.EQOP, ts.getOp(), pn, relExpr(ts, inForInit)); - } - return pn; - } - - private Object relExpr(TokenStream ts, boolean inForInit) throws IOException, - JavaScriptException { - Object pn = shiftExpr(ts); - while (ts.matchToken(TokenStream.RELOP)) { - int op = ts.getOp(); - if (inForInit && op == TokenStream.IN) { - ts.ungetToken(TokenStream.RELOP); - break; - } - - pn = nf.createBinary(TokenStream.RELOP, op, pn, shiftExpr(ts)); - } - return pn; - } - - private Object shiftExpr(TokenStream ts) throws IOException, - JavaScriptException { - Object pn = addExpr(ts); - while (ts.matchToken(TokenStream.SHOP)) { - pn = nf.createBinary(TokenStream.SHOP, ts.getOp(), pn, addExpr(ts)); - } - return pn; - } - - private Object addExpr(TokenStream ts) throws IOException, - JavaScriptException { - int tt; - Object pn = mulExpr(ts); - - while ((tt = ts.getToken()) == TokenStream.ADD || tt == TokenStream.SUB) { - // flushNewLines - pn = nf.createBinary(tt, pn, mulExpr(ts)); - } - ts.ungetToken(tt); - - return pn; - } - - private Object mulExpr(TokenStream ts) throws IOException, - JavaScriptException { - int tt; - - Object pn = unaryExpr(ts); - - while ((tt = ts.peekToken()) == TokenStream.MUL || tt == TokenStream.DIV || tt == TokenStream.MOD) { - tt = ts.getToken(); - pn = nf.createBinary(tt, pn, unaryExpr(ts)); - } - - return pn; - } - - private Object unaryExpr(TokenStream ts) throws IOException, - JavaScriptException { - int tt; - - ts.flags |= TokenStream.TSF_REGEXP; - tt = ts.getToken(); - ts.flags &= ~TokenStream.TSF_REGEXP; - - switch (tt) { - case TokenStream.UNARYOP: - return nf.createUnary(TokenStream.UNARYOP, ts.getOp(), unaryExpr(ts)); - - case TokenStream.ADD: - case TokenStream.SUB: - return nf.createUnary(TokenStream.UNARYOP, tt, unaryExpr(ts)); - - case TokenStream.INC: - case TokenStream.DEC: - return nf.createUnary(tt, TokenStream.PRE, memberExpr(ts, true)); - - case TokenStream.DELPROP: - return nf.createUnary(TokenStream.DELPROP, unaryExpr(ts)); - - case TokenStream.ERROR: - break; - - default: - ts.ungetToken(tt); - - int lineno = ts.getLineno(); - - Object pn = memberExpr(ts, true); - - /* - * don't look across a newline boundary for a postfix incop. - * - * the rhino scanner seems to work differently than the js scanner here; - * in js, it works to have the line number check precede the peekToken - * calls. It'd be better if they had similar behavior... - */ - int peeked; - if (((peeked = ts.peekToken()) == TokenStream.INC || peeked == TokenStream.DEC) - && ts.getLineno() == lineno) { - int pf = ts.getToken(); - return nf.createUnary(pf, TokenStream.POST, pn); - } - return pn; - } - return nf.createName("err"); // Only reached on error. Try to continue. - - } - - private Object argumentList(TokenStream ts, Object listNode) - throws IOException, JavaScriptException { - boolean matched; - ts.flags |= TokenStream.TSF_REGEXP; - matched = ts.matchToken(TokenStream.GWT); - ts.flags &= ~TokenStream.TSF_REGEXP; - if (!matched) { - do { - nf.addChildToBack(listNode, assignExpr(ts, false)); - } while (ts.matchToken(TokenStream.COMMA)); - - mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.arg"); - } - return listNode; - } - - private Object memberExpr(TokenStream ts, boolean allowCallSyntax) - throws IOException, JavaScriptException { - int tt; - - Object pn; - - /* Check for new expressions. */ - ts.flags |= TokenStream.TSF_REGEXP; - tt = ts.peekToken(); - ts.flags &= ~TokenStream.TSF_REGEXP; - if (tt == TokenStream.NEW) { - /* Eat the NEW token. */ - ts.getToken(); - - /* Make a NEW node to append to. */ - pn = nf.createLeaf(TokenStream.NEW); - nf.addChildToBack(pn, memberExpr(ts, false)); - - if (ts.matchToken(TokenStream.LP)) { - /* Add the arguments to pn, if any are supplied. */ - pn = argumentList(ts, pn); - } - - /* - * XXX there's a check in the C source against "too many constructor - * arguments" - how many do we claim to support? - */ - - /* - * Experimental syntax: allow an object literal to follow a new - * expression, which will mean a kind of anonymous class built with the - * JavaAdapter. the object literal will be passed as an additional - * argument to the constructor. - */ - tt = ts.peekToken(); - if (tt == TokenStream.LC) { - nf.addChildToBack(pn, primaryExpr(ts)); - } - } else { - pn = primaryExpr(ts); - } - - return memberExprTail(ts, allowCallSyntax, pn); - } - - private Object memberExprTail(TokenStream ts, boolean allowCallSyntax, - Object pn) throws IOException, JavaScriptException { - lastExprEndLine = ts.getLineno(); - int tt; - while ((tt = ts.getToken()) > TokenStream.EOF) { - if (tt == TokenStream.DOT) { - ts.treatKeywordAsIdentifier = true; - mustMatchToken(ts, TokenStream.NAME, "msg.no.name.after.dot"); - ts.treatKeywordAsIdentifier = false; - pn = nf.createBinary(TokenStream.DOT, pn, nf.createName(ts.getString())); - /* - * pn = nf.createBinary(ts.DOT, pn, memberExpr(ts)) is the version in - * Brendan's IR C version. Not in ECMA... does it reflect the 'new' - * operator syntax he mentioned? - */ - lastExprEndLine = ts.getLineno(); - } else if (tt == TokenStream.LB) { - pn = nf.createBinary(TokenStream.LB, pn, expr(ts, false)); - - mustMatchToken(ts, TokenStream.RB, "msg.no.bracket.index"); - lastExprEndLine = ts.getLineno(); - } else if (allowCallSyntax && tt == TokenStream.LP) { - /* make a call node */ - pn = nf.createUnary(TokenStream.CALL, pn); - - /* Add the arguments to pn, if any are supplied. */ - pn = argumentList(ts, pn); - lastExprEndLine = ts.getLineno(); - } else { - ts.ungetToken(tt); - - break; - } - } - return pn; - } - - public Object primaryExpr(TokenStream ts) throws IOException, - JavaScriptException { - int tt; - - Object pn; - - ts.flags |= TokenStream.TSF_REGEXP; - tt = ts.getToken(); - ts.flags &= ~TokenStream.TSF_REGEXP; - - switch (tt) { - - case TokenStream.FUNCTION: - return function(ts, true); - - case TokenStream.LB: { - pn = nf.createLeaf(TokenStream.ARRAYLIT); - - ts.flags |= TokenStream.TSF_REGEXP; - boolean matched = ts.matchToken(TokenStream.RB); - ts.flags &= ~TokenStream.TSF_REGEXP; - - if (!matched) { - do { + while (true) { ts.flags |= TokenStream.TSF_REGEXP; - tt = ts.peekToken(); + tt = ts.getToken(); ts.flags &= ~TokenStream.TSF_REGEXP; - if (tt == TokenStream.RB) { // to fix [,,,].length behavior... - break; + if (tt <= TokenStream.EOF) { + break; } - if (tt == TokenStream.COMMA) { - nf.addChildToBack(pn, nf.createLeaf(TokenStream.PRIMARY, TokenStream.UNDEFINED)); - } else { - nf.addChildToBack(pn, assignExpr(ts, false)); + if (tt == TokenStream.FUNCTION) { + try { + nf.addChildToBack(tempBlock, function(ts, false)); + } + catch (JavaScriptException e) { + this.ok = false; + break; + } } - - } while (ts.matchToken(TokenStream.COMMA)); - mustMatchToken(ts, TokenStream.RB, "msg.no.bracket.arg"); - } - - return nf.createArrayLiteral(pn); - } - - case TokenStream.LC: { - pn = nf.createLeaf(TokenStream.OBJLIT); - - if (!ts.matchToken(TokenStream.RC)) { - - commaloop : do { - Object property; - - tt = ts.getToken(); - switch (tt) { - // map NAMEs to STRINGs in object literal context. - case TokenStream.NAME: - case TokenStream.STRING: - property = nf.createString(ts.getString()); - break; - case TokenStream.NUMBER_INT: - int n = (int) ts.getNumber(); - property = nf.createNumber(n); - break; - case TokenStream.NUMBER: - double d = ts.getNumber(); - property = nf.createNumber(d); - break; - case TokenStream.RC: - // trailing comma is OK. + else { ts.ungetToken(tt); - break commaloop; - default: - reportError(ts, "msg.bad.prop"); - break commaloop; + nf.addChildToBack(tempBlock, statement(ts)); } - mustMatchToken(ts, TokenStream.COLON, "msg.no.colon.prop"); - - // OBJLIT is used as ':' in object literal for - // decompilation to solve spacing ambiguity. - nf.addChildToBack(pn, property); - nf.addChildToBack(pn, assignExpr(ts, false)); - - } while (ts.matchToken(TokenStream.COMMA)); - - mustMatchToken(ts, TokenStream.RC, "msg.no.brace.prop"); } - return nf.createObjectLiteral(pn); - } - case TokenStream.LP: + if (!this.ok) { + // XXX ts.clearPushback() call here? + return null; + } - /* - * Brendan's IR-jsparse.c makes a new node tagged with TOK_LP here... - * I'm not sure I understand why. Isn't the grouping already implicit in - * the structure of the parse tree? also TOK_LP is already overloaded (I - * think) in the C IR as 'function call.' - */ - pn = expr(ts, false); - mustMatchToken(ts, TokenStream.GWT, "msg.no.paren"); + Object pn = nf.createScript(tempBlock, ts.getSourceName(), baseLineno, ts.getLineno(), null); + ((Node) pn).setIsSyntheticBlock(true); return pn; - - case TokenStream.NAME: - String name = ts.getString(); - return nf.createName(name); - - case TokenStream.NUMBER_INT: - int n = (int)ts.getNumber(); - return nf.createNumber(n); - - case TokenStream.NUMBER: - double d = ts.getNumber(); - return nf.createNumber(d); - - case TokenStream.STRING: - String s = ts.getString(); - return nf.createString(s); - - case TokenStream.REGEXP: { - String flags = ts.regExpFlags; - ts.regExpFlags = null; - String re = ts.getString(); - return nf.createRegExp(re, flags); - } - - case TokenStream.PRIMARY: - return nf.createLeaf(TokenStream.PRIMARY, ts.getOp()); - - case TokenStream.ERROR: - /* the scanner or one of its subroutines reported the error. */ - break; - - default: - reportError(ts, "msg.syntax"); - break; - } - return null; // should never reach here - } - private int lastExprEndLine; // Hack to handle function expr termination. - private final IRFactory nf; - private boolean ok; // Did the parse encounter an error? + /* + * The C version of this function takes an argument list, which doesn't seem + * to be needed for tree generation... it'd only be useful for checking + * argument hiding, which I'm not doing anyway... + */ + private Object parseFunctionBody(TokenStream ts) throws IOException { + int oldflags = ts.flags; + ts.flags &= ~(TokenStream.TSF_RETURN_EXPR | TokenStream.TSF_RETURN_VOID); + ts.flags |= TokenStream.TSF_FUNCTION; - private int sourceTop; - private int functionNumber; - private final boolean insideFunction; + Object pn = nf.createBlock(ts.getLineno()); + try { + int tt; + while ((tt = ts.peekToken()) > TokenStream.EOF && tt != TokenStream.RC) { + if (tt == TokenStream.FUNCTION) { + ts.getToken(); + nf.addChildToBack(pn, function(ts, false)); + } + else { + nf.addChildToBack(pn, statement(ts)); + } + } + } + catch (JavaScriptException e) { + this.ok = false; + } + finally { + // also in finally block: + // flushNewLines, clearPushback. + + ts.flags = oldflags; + } + + return pn; + } + + private Object function(TokenStream ts, boolean isExpr) throws IOException, JavaScriptException { + notifyObservers(new ParserEvents.OnFunctionParsingStart()); + int baseLineno = ts.getLineno(); // line number where source starts + + String name; + Object memberExprNode = null; + if (ts.matchToken(TokenStream.NAME)) { + name = ts.getString(); + if (!ts.matchToken(TokenStream.LP)) { + if (Context.getContext().hasFeature(Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) { + // Extension to ECMA: if 'function ' does not follow + // by '(', assume starts memberExpr + Object memberExprHead = nf.createName(name); + name = null; + memberExprNode = memberExprTail(ts, false, memberExprHead); + } + mustMatchToken(ts, TokenStream.LP, "msg.no.paren.parms"); + } + } + else if (ts.matchToken(TokenStream.LP)) { + // Anonymous function + name = null; + } + else { + name = null; + if (Context.getContext().hasFeature(Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) { + // Note that memberExpr can not start with '(' like + // in (1+2).toString, because 'function (' already + // processed as anonymous function + memberExprNode = memberExpr(ts, false); + } + mustMatchToken(ts, TokenStream.LP, "msg.no.paren.parms"); + } + + ++functionNumber; + + // Save current source top to restore it on exit not to include + // function to parent source + int savedSourceTop = sourceTop; + int savedFunctionNumber = functionNumber; + Object args; + Object body; + try { + functionNumber = 0; + args = nf.createLeaf(TokenStream.LP); + + if (!ts.matchToken(TokenStream.GWT)) { + do { + mustMatchToken(ts, TokenStream.NAME, "msg.no.parm"); + String s = ts.getString(); + nf.addChildToBack(args, nf.createName(s)); + } + while (ts.matchToken(TokenStream.COMMA)); + + mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.after.parms"); + } + + mustMatchToken(ts, TokenStream.LC, "msg.no.brace.body"); + body = parseFunctionBody(ts); + mustMatchToken(ts, TokenStream.RC, "msg.no.brace.after.body"); + // skip the last EOL so nested functions work... + } + finally { + sourceTop = savedSourceTop; + functionNumber = savedFunctionNumber; + } + + Object pn = nf.createFunction(name, args, body, ts.getSourceName(), + baseLineno, ts.getLineno(), null, isExpr || memberExprNode != null); + if (memberExprNode != null) { + pn = nf.createBinary(TokenStream.ASSIGN, TokenStream.NOP, memberExprNode, pn); + } + + // Add EOL but only if function is not part of expression, in which + // case it gets SEMI + EOL from Statement. + if (!isExpr) { + wellTerminated(ts, TokenStream.FUNCTION); + } + + notifyObservers(new ParserEvents.OnFunctionParsingEnd(ts)); + return pn; + } + + private Object statements(TokenStream ts) throws IOException { + Object pn = nf.createBlock(ts.getLineno()); + + int tt; + while ((tt = ts.peekToken()) > TokenStream.EOF && tt != TokenStream.RC) { + nf.addChildToBack(pn, statement(ts)); + } + + return pn; + } + + private Object condition(TokenStream ts) throws IOException, JavaScriptException { + Object pn; + mustMatchToken(ts, TokenStream.LP, "msg.no.paren.cond"); + pn = expr(ts, false); + mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.after.cond"); + + // there's a check here in jsparse.c that corrects = to == + + return pn; + } + + private void wellTerminated(TokenStream ts, int lastExprType) throws IOException, JavaScriptException { + int tt = ts.peekTokenSameLine(); + if (tt == TokenStream.ERROR) { + return; + } + + if (tt != TokenStream.EOF && tt != TokenStream.EOL && tt != TokenStream.SEMI && tt != TokenStream.RC) { + int version = Context.getContext().getLanguageVersion(); + if ((tt == TokenStream.FUNCTION || lastExprType == TokenStream.FUNCTION) && (version < Context.VERSION_1_2)) { + /* + * Checking against version < 1.2 and version >= 1.0 in the above line + * breaks old javascript, so we keep it this way for now... XXX warning + * needed? + */ + } + else { + reportError(ts, "msg.no.semi.stmt"); + } + } + } + + // match a NAME; return null if no match. + private String matchLabel(TokenStream ts) throws IOException, JavaScriptException { + int lineno = ts.getLineno(); + + String label = null; + int tt; + tt = ts.peekTokenSameLine(); + if (tt == TokenStream.NAME) { + ts.getToken(); + label = ts.getString(); + } + + if (lineno == ts.getLineno()) { + wellTerminated(ts, TokenStream.ERROR); + } + + return label; + } + + private Object statement(TokenStream ts) throws IOException { + try { + return statementHelper(ts); + } + catch (JavaScriptException e) { + // skip to end of statement + int lineno = ts.getLineno(); + int t; + do { + t = ts.getToken(); + } + while (t != TokenStream.SEMI && t != TokenStream.EOL + && t != TokenStream.EOF && t != TokenStream.ERROR); + return nf.createExprStatement(nf.createName("error"), lineno); + } + } + + /** + * Whether the "catch (e: e instanceof Exception) { ... }" syntax is + * implemented. + */ + + private Object statementHelper(TokenStream ts) throws IOException, JavaScriptException { + Object pn; + + int tt; + + int lastExprType; // For wellTerminated + + tt = ts.getToken(); + + switch (tt) { + case TokenStream.IF: { + int lineno = ts.getLineno(); + Object cond = condition(ts); + Object ifTrue = statement(ts); + Object ifFalse = null; + if (ts.matchToken(TokenStream.ELSE)) { + ifFalse = statement(ts); + } + pn = nf.createIf(cond, ifTrue, ifFalse, lineno); + break; + } + + case TokenStream.SWITCH: { + pn = nf.createSwitch(ts.getLineno()); + + Object cur_case = null; // to kill warning + Object case_statements; + + mustMatchToken(ts, TokenStream.LP, "msg.no.paren.switch"); + nf.addChildToBack(pn, expr(ts, false)); + mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.after.switch"); + mustMatchToken(ts, TokenStream.LC, "msg.no.brace.switch"); + + while ((tt = ts.getToken()) != TokenStream.RC && tt != TokenStream.EOF) { + switch (tt) { + case TokenStream.CASE: + cur_case = nf.createUnary(TokenStream.CASE, expr(ts, false)); + break; + + case TokenStream.DEFAULT: + cur_case = nf.createLeaf(TokenStream.DEFAULT); + // XXX check that there isn't more than one default + break; + + default: + reportError(ts, "msg.bad.switch"); + break; + } + mustMatchToken(ts, TokenStream.COLON, "msg.no.colon.case"); + + case_statements = nf.createLeaf(TokenStream.BLOCK); + ((Node) case_statements).setIsSyntheticBlock(true); + + while ((tt = ts.peekToken()) != TokenStream.RC && tt != TokenStream.CASE + && tt != TokenStream.DEFAULT && tt != TokenStream.EOF) { + nf.addChildToBack(case_statements, statement(ts)); + } + // assert cur_case + nf.addChildToBack(cur_case, case_statements); + + nf.addChildToBack(pn, cur_case); + } + break; + } + + case TokenStream.WHILE: { + int lineno = ts.getLineno(); + Object cond = condition(ts); + Object body = statement(ts); + + pn = nf.createWhile(cond, body, lineno); + break; + } + + case TokenStream.DO: { + int lineno = ts.getLineno(); + + Object body = statement(ts); + + mustMatchToken(ts, TokenStream.WHILE, "msg.no.while.do"); + Object cond = condition(ts); + + pn = nf.createDoWhile(body, cond, lineno); + break; + } + + case TokenStream.FOR: { + int lineno = ts.getLineno(); + + Object init; // Node init is also foo in 'foo in Object' + Object cond; // Node cond is also object in 'foo in Object' + Object incr = null; // to kill warning + Object body; + + mustMatchToken(ts, TokenStream.LP, "msg.no.paren.for"); + tt = ts.peekToken(); + if (tt == TokenStream.SEMI) { + init = nf.createLeaf(TokenStream.VOID); + } + else { + if (tt == TokenStream.VAR) { + // set init to a var list or initial + ts.getToken(); // throw away the 'var' token + init = variables(ts, true); + } + else { + init = expr(ts, true); + } + } + + tt = ts.peekToken(); + if (tt == TokenStream.RELOP && ts.getOp() == TokenStream.IN) { + ts.matchToken(TokenStream.RELOP); + // 'cond' is the object over which we're iterating + cond = expr(ts, false); + } + else { // ordinary for loop + mustMatchToken(ts, TokenStream.SEMI, "msg.no.semi.for"); + if (ts.peekToken() == TokenStream.SEMI) { + // no loop condition + cond = nf.createLeaf(TokenStream.VOID); + } + else { + cond = expr(ts, false); + } + + mustMatchToken(ts, TokenStream.SEMI, "msg.no.semi.for.cond"); + if (ts.peekToken() == TokenStream.GWT) { + incr = nf.createLeaf(TokenStream.VOID); + } + else { + incr = expr(ts, false); + } + } + + mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.for.ctrl"); + body = statement(ts); + + if (incr == null) { + // cond could be null if 'in obj' got eaten by the init node. + pn = nf.createForIn(init, cond, body, lineno); + } + else { + pn = nf.createFor(init, cond, incr, body, lineno); + } + break; + } + + case TokenStream.TRY: { + int lineno = ts.getLineno(); + + Object tryblock; + Object catchblocks; + Object finallyblock = null; + + tryblock = statement(ts); + + catchblocks = nf.createLeaf(TokenStream.BLOCK); + + boolean sawDefaultCatch = false; + int peek = ts.peekToken(); + if (peek == TokenStream.CATCH) { + while (ts.matchToken(TokenStream.CATCH)) { + if (sawDefaultCatch) { + reportError(ts, "msg.catch.unreachable"); + } + mustMatchToken(ts, TokenStream.LP, "msg.no.paren.catch"); + + mustMatchToken(ts, TokenStream.NAME, "msg.bad.catchcond"); + String varName = ts.getString(); + + Object catchCond = null; + if (ts.matchToken(TokenStream.IF)) { + catchCond = expr(ts, false); + } + else { + sawDefaultCatch = true; + } + + mustMatchToken(ts, TokenStream.GWT, "msg.bad.catchcond"); + mustMatchToken(ts, TokenStream.LC, "msg.no.brace.catchblock"); + + nf.addChildToBack(catchblocks, nf.createCatch(varName, catchCond, statements(ts), ts.getLineno())); + + mustMatchToken(ts, TokenStream.RC, "msg.no.brace.after.body"); + } + } + else if (peek != TokenStream.FINALLY) { + mustMatchToken(ts, TokenStream.FINALLY, "msg.try.no.catchfinally"); + } + + if (ts.matchToken(TokenStream.FINALLY)) { + finallyblock = statement(ts); + } + + pn = nf.createTryCatchFinally(tryblock, catchblocks, finallyblock, lineno); + + break; + } + case TokenStream.THROW: { + int lineno = ts.getLineno(); + pn = nf.createThrow(expr(ts, false), lineno); + if (lineno == ts.getLineno()) { + wellTerminated(ts, TokenStream.ERROR); + } + break; + } + case TokenStream.BREAK: { + int lineno = ts.getLineno(); + + // matchLabel only matches if there is one + String label = matchLabel(ts); + pn = nf.createBreak(label, lineno); + break; + } + case TokenStream.CONTINUE: { + int lineno = ts.getLineno(); + + // matchLabel only matches if there is one + String label = matchLabel(ts); + pn = nf.createContinue(label, lineno); + break; + } + case TokenStream.DEBUGGER: { + int lineno = ts.getLineno(); + pn = nf.createDebugger(lineno); + break; + } + case TokenStream.WITH: { + // bruce: we don't support this is JSNI code because it's impossible + // to identify bindings even passably well + // + + reportError(ts, "msg.jsni.unsupported.with"); + + int lineno = ts.getLineno(); + mustMatchToken(ts, TokenStream.LP, "msg.no.paren.with"); + Object obj = expr(ts, false); + mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.after.with"); + Object body = statement(ts); + pn = nf.createWith(obj, body, lineno); + break; + } + case TokenStream.VAR: { + int lineno = ts.getLineno(); + pn = variables(ts, false); + if (ts.getLineno() == lineno) { + wellTerminated(ts, TokenStream.ERROR); + } + break; + } + case TokenStream.RETURN: { + Object retExpr = null; + int lineno = 0; + // bail if we're not in a (toplevel) function + if ((!insideFunction) && ((ts.flags & TokenStream.TSF_FUNCTION) == 0)) { + reportError(ts, "msg.bad.return"); + } + + /* This is ugly, but we don't want to require a semicolon. */ + ts.flags |= TokenStream.TSF_REGEXP; + tt = ts.peekTokenSameLine(); + ts.flags &= ~TokenStream.TSF_REGEXP; + + if (tt != TokenStream.EOF && tt != TokenStream.EOL && tt != TokenStream.SEMI && tt != TokenStream.RC) { + lineno = ts.getLineno(); + retExpr = expr(ts, false); + if (ts.getLineno() == lineno) { + wellTerminated(ts, TokenStream.ERROR); + } + ts.flags |= TokenStream.TSF_RETURN_EXPR; + } + else { + ts.flags |= TokenStream.TSF_RETURN_VOID; + } + + // XXX ASSERT pn + pn = nf.createReturn(retExpr, lineno); + break; + } + case TokenStream.LC: + pn = statements(ts); + mustMatchToken(ts, TokenStream.RC, "msg.no.brace.block"); + break; + + case TokenStream.ERROR: + // Fall thru, to have a node for error recovery to work on + case TokenStream.EOL: + case TokenStream.SEMI: + pn = nf.createLeaf(TokenStream.VOID); + break; + + default: { + lastExprType = tt; + int tokenno = ts.getTokenno(); + ts.ungetToken(tt); + int lineno = ts.getLineno(); + + pn = expr(ts, false); + + if (ts.peekToken() == TokenStream.COLON) { + /* + * check that the last thing the tokenizer returned was a NAME and + * that only one token was consumed. + */ + if (lastExprType != TokenStream.NAME || (ts.getTokenno() != tokenno)) { + reportError(ts, "msg.bad.label"); + } + + ts.getToken(); // eat the COLON + + /* + * in the C source, the label is associated with the statement that + * follows: nf.addChildToBack(pn, statement(ts)); + */ + String name = ts.getString(); + pn = nf.createLabel(name, lineno); + + // bruce: added to make it easier to bind labels to the + // statements they modify + // + nf.addChildToBack(pn, statement(ts)); + + // depend on decompiling lookahead to guess that that + // last name was a label. + return pn; + } + + if (lastExprType == TokenStream.FUNCTION) { + if (nf.getLeafType(pn) != TokenStream.FUNCTION) { + reportError(ts, "msg.syntax"); + } + } + + pn = nf.createExprStatement(pn, lineno); + + /* + * Check explicitly against (multi-line) function statement. + * + * lastExprEndLine is a hack to fix an automatic semicolon insertion + * problem with function expressions; the ts.getLineno() == lineno check + * was firing after a function definition even though the next statement + * was on a new line, because speculative getToken calls advanced the + * line number even when they didn't succeed. + */ + if (ts.getLineno() == lineno || (lastExprType == TokenStream.FUNCTION && ts.getLineno() == lastExprEndLine)) { + wellTerminated(ts, lastExprType); + } + break; + } + } + ts.matchToken(TokenStream.SEMI); + + return pn; + } + + private Object variables(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + Object pn = nf.createVariables(ts.getLineno()); + + while (true) { + Object name; + Object init; + mustMatchToken(ts, TokenStream.NAME, "msg.bad.var"); + String s = ts.getString(); + name = nf.createName(s); + + // omitted check for argument hiding + + if (ts.matchToken(TokenStream.ASSIGN)) { + if (ts.getOp() != TokenStream.NOP) { + reportError(ts, "msg.bad.var.init"); + } + + init = assignExpr(ts, inForInit); + nf.addChildToBack(name, init); + } + nf.addChildToBack(pn, name); + if (!ts.matchToken(TokenStream.COMMA)) { + break; + } + } + return pn; + } + + private Object expr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + Object pn = assignExpr(ts, inForInit); + while (ts.matchToken(TokenStream.COMMA)) { + pn = nf.createBinary(TokenStream.COMMA, pn, assignExpr(ts, inForInit)); + } + return pn; + } + + private Object assignExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + Object pn = condExpr(ts, inForInit); + + if (ts.matchToken(TokenStream.ASSIGN)) { + // omitted: "invalid assignment left-hand side" check. + pn = nf.createBinary(TokenStream.ASSIGN, ts.getOp(), pn, assignExpr(ts, inForInit)); + } + + return pn; + } + + private Object condExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + Object ifTrue; + Object ifFalse; + + Object pn = orExpr(ts, inForInit); + + if (ts.matchToken(TokenStream.HOOK)) { + ifTrue = assignExpr(ts, false); + mustMatchToken(ts, TokenStream.COLON, "msg.no.colon.cond"); + ifFalse = assignExpr(ts, inForInit); + return nf.createTernary(pn, ifTrue, ifFalse); + } + + return pn; + } + + private Object orExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + Object pn = andExpr(ts, inForInit); + if (ts.matchToken(TokenStream.OR)) { + pn = nf.createBinary(TokenStream.OR, pn, orExpr(ts, inForInit)); + } + + return pn; + } + + private Object andExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + Object pn = bitOrExpr(ts, inForInit); + if (ts.matchToken(TokenStream.AND)) { + pn = nf.createBinary(TokenStream.AND, pn, andExpr(ts, inForInit)); + } + + return pn; + } + + private Object bitOrExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + Object pn = bitXorExpr(ts, inForInit); + while (ts.matchToken(TokenStream.BITOR)) { + pn = nf.createBinary(TokenStream.BITOR, pn, bitXorExpr(ts, inForInit)); + } + return pn; + } + + private Object bitXorExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + Object pn = bitAndExpr(ts, inForInit); + while (ts.matchToken(TokenStream.BITXOR)) { + pn = nf.createBinary(TokenStream.BITXOR, pn, bitAndExpr(ts, inForInit)); + } + return pn; + } + + private Object bitAndExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + Object pn = eqExpr(ts, inForInit); + while (ts.matchToken(TokenStream.BITAND)) { + pn = nf.createBinary(TokenStream.BITAND, pn, eqExpr(ts, inForInit)); + } + return pn; + } + + private Object eqExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + Object pn = relExpr(ts, inForInit); + while (ts.matchToken(TokenStream.EQOP)) { + pn = nf.createBinary(TokenStream.EQOP, ts.getOp(), pn, relExpr(ts, inForInit)); + } + return pn; + } + + private Object relExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + Object pn = shiftExpr(ts); + while (ts.matchToken(TokenStream.RELOP)) { + int op = ts.getOp(); + if (inForInit && op == TokenStream.IN) { + ts.ungetToken(TokenStream.RELOP); + break; + } + + pn = nf.createBinary(TokenStream.RELOP, op, pn, shiftExpr(ts)); + } + return pn; + } + + private Object shiftExpr(TokenStream ts) throws IOException, JavaScriptException { + Object pn = addExpr(ts); + while (ts.matchToken(TokenStream.SHOP)) { + pn = nf.createBinary(TokenStream.SHOP, ts.getOp(), pn, addExpr(ts)); + } + return pn; + } + + private Object addExpr(TokenStream ts) throws IOException, JavaScriptException { + int tt; + Object pn = mulExpr(ts); + + while ((tt = ts.getToken()) == TokenStream.ADD || tt == TokenStream.SUB) { + // flushNewLines + pn = nf.createBinary(tt, pn, mulExpr(ts)); + } + ts.ungetToken(tt); + + return pn; + } + + private Object mulExpr(TokenStream ts) throws IOException, JavaScriptException { + int tt; + + Object pn = unaryExpr(ts); + + while ((tt = ts.peekToken()) == TokenStream.MUL || tt == TokenStream.DIV || tt == TokenStream.MOD) { + tt = ts.getToken(); + pn = nf.createBinary(tt, pn, unaryExpr(ts)); + } + + return pn; + } + + private Object unaryExpr(TokenStream ts) throws IOException, JavaScriptException { + int tt; + + ts.flags |= TokenStream.TSF_REGEXP; + tt = ts.getToken(); + ts.flags &= ~TokenStream.TSF_REGEXP; + + switch (tt) { + case TokenStream.UNARYOP: + return nf.createUnary(TokenStream.UNARYOP, ts.getOp(), unaryExpr(ts)); + + case TokenStream.ADD: + case TokenStream.SUB: + return nf.createUnary(TokenStream.UNARYOP, tt, unaryExpr(ts)); + + case TokenStream.INC: + case TokenStream.DEC: + return nf.createUnary(tt, TokenStream.PRE, memberExpr(ts, true)); + + case TokenStream.DELPROP: + return nf.createUnary(TokenStream.DELPROP, unaryExpr(ts)); + + case TokenStream.ERROR: + break; + + default: + ts.ungetToken(tt); + + int lineno = ts.getLineno(); + + Object pn = memberExpr(ts, true); + + /* + * don't look across a newline boundary for a postfix incop. + * + * the rhino scanner seems to work differently than the js scanner here; + * in js, it works to have the line number check precede the peekToken + * calls. It'd be better if they had similar behavior... + */ + int peeked; + if (((peeked = ts.peekToken()) == TokenStream.INC || peeked == TokenStream.DEC) + && ts.getLineno() == lineno) { + int pf = ts.getToken(); + return nf.createUnary(pf, TokenStream.POST, pn); + } + return pn; + } + return nf.createName("err"); // Only reached on error. Try to continue. + } + + private Object argumentList(TokenStream ts, Object listNode) throws IOException, JavaScriptException { + boolean matched; + ts.flags |= TokenStream.TSF_REGEXP; + matched = ts.matchToken(TokenStream.GWT); + ts.flags &= ~TokenStream.TSF_REGEXP; + if (!matched) { + do { + nf.addChildToBack(listNode, assignExpr(ts, false)); + } + while (ts.matchToken(TokenStream.COMMA)); + + mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.arg"); + } + return listNode; + } + + private Object memberExpr(TokenStream ts, boolean allowCallSyntax) throws IOException, JavaScriptException { + int tt; + + Object pn; + + /* Check for new expressions. */ + ts.flags |= TokenStream.TSF_REGEXP; + tt = ts.peekToken(); + ts.flags &= ~TokenStream.TSF_REGEXP; + if (tt == TokenStream.NEW) { + /* Eat the NEW token. */ + ts.getToken(); + + /* Make a NEW node to append to. */ + pn = nf.createLeaf(TokenStream.NEW); + nf.addChildToBack(pn, memberExpr(ts, false)); + + if (ts.matchToken(TokenStream.LP)) { + /* Add the arguments to pn, if any are supplied. */ + pn = argumentList(ts, pn); + } + + /* + * XXX there's a check in the C source against "too many constructor + * arguments" - how many do we claim to support? + */ + + /* + * Experimental syntax: allow an object literal to follow a new + * expression, which will mean a kind of anonymous class built with the + * JavaAdapter. the object literal will be passed as an additional + * argument to the constructor. + */ + tt = ts.peekToken(); + if (tt == TokenStream.LC) { + nf.addChildToBack(pn, primaryExpr(ts)); + } + } + else { + pn = primaryExpr(ts); + } + + return memberExprTail(ts, allowCallSyntax, pn); + } + + private Object memberExprTail( + TokenStream ts, boolean allowCallSyntax, + Object pn + ) throws IOException, JavaScriptException { + lastExprEndLine = ts.getLineno(); + int tt; + while ((tt = ts.getToken()) > TokenStream.EOF) { + if (tt == TokenStream.DOT) { + ts.treatKeywordAsIdentifier = true; + mustMatchToken(ts, TokenStream.NAME, "msg.no.name.after.dot"); + ts.treatKeywordAsIdentifier = false; + pn = nf.createBinary(TokenStream.DOT, pn, nf.createName(ts.getString())); + /* + * pn = nf.createBinary(ts.DOT, pn, memberExpr(ts)) is the version in + * Brendan's IR C version. Not in ECMA... does it reflect the 'new' + * operator syntax he mentioned? + */ + lastExprEndLine = ts.getLineno(); + } + else if (tt == TokenStream.LB) { + pn = nf.createBinary(TokenStream.LB, pn, expr(ts, false)); + + mustMatchToken(ts, TokenStream.RB, "msg.no.bracket.index"); + lastExprEndLine = ts.getLineno(); + } + else if (allowCallSyntax && tt == TokenStream.LP) { + /* make a call node */ + pn = nf.createUnary(TokenStream.CALL, pn); + + /* Add the arguments to pn, if any are supplied. */ + pn = argumentList(ts, pn); + lastExprEndLine = ts.getLineno(); + } + else { + ts.ungetToken(tt); + + break; + } + } + return pn; + } + + public Object primaryExpr(TokenStream ts) throws IOException, JavaScriptException { + int tt; + + Object pn; + + ts.flags |= TokenStream.TSF_REGEXP; + tt = ts.getToken(); + ts.flags &= ~TokenStream.TSF_REGEXP; + + switch (tt) { + + case TokenStream.FUNCTION: + return function(ts, true); + + case TokenStream.LB: { + pn = nf.createLeaf(TokenStream.ARRAYLIT); + + ts.flags |= TokenStream.TSF_REGEXP; + boolean matched = ts.matchToken(TokenStream.RB); + ts.flags &= ~TokenStream.TSF_REGEXP; + + if (!matched) { + do { + ts.flags |= TokenStream.TSF_REGEXP; + tt = ts.peekToken(); + ts.flags &= ~TokenStream.TSF_REGEXP; + + if (tt == TokenStream.RB) { // to fix [,,,].length behavior... + break; + } + + if (tt == TokenStream.COMMA) { + nf.addChildToBack(pn, nf.createLeaf(TokenStream.PRIMARY, TokenStream.UNDEFINED)); + } + else { + nf.addChildToBack(pn, assignExpr(ts, false)); + } + } + while (ts.matchToken(TokenStream.COMMA)); + mustMatchToken(ts, TokenStream.RB, "msg.no.bracket.arg"); + } + + return nf.createArrayLiteral(pn); + } + + case TokenStream.LC: { + pn = nf.createLeaf(TokenStream.OBJLIT); + + if (!ts.matchToken(TokenStream.RC)) { + + commaloop: + do { + Object property; + + tt = ts.getToken(); + switch (tt) { + // map NAMEs to STRINGs in object literal context. + case TokenStream.NAME: + case TokenStream.STRING: + property = nf.createString(ts.getString()); + break; + case TokenStream.NUMBER_INT: + int n = (int) ts.getNumber(); + property = nf.createNumber(n); + break; + case TokenStream.NUMBER: + double d = ts.getNumber(); + property = nf.createNumber(d); + break; + case TokenStream.RC: + // trailing comma is OK. + ts.ungetToken(tt); + break commaloop; + default: + reportError(ts, "msg.bad.prop"); + break commaloop; + } + mustMatchToken(ts, TokenStream.COLON, "msg.no.colon.prop"); + + // OBJLIT is used as ':' in object literal for + // decompilation to solve spacing ambiguity. + nf.addChildToBack(pn, property); + nf.addChildToBack(pn, assignExpr(ts, false)); + } + while (ts.matchToken(TokenStream.COMMA)); + + mustMatchToken(ts, TokenStream.RC, "msg.no.brace.prop"); + } + return nf.createObjectLiteral(pn); + } + + case TokenStream.LP: + + /* + * Brendan's IR-jsparse.c makes a new node tagged with TOK_LP here... + * I'm not sure I understand why. Isn't the grouping already implicit in + * the structure of the parse tree? also TOK_LP is already overloaded (I + * think) in the C IR as 'function call.' + */ + pn = expr(ts, false); + mustMatchToken(ts, TokenStream.GWT, "msg.no.paren"); + return pn; + + case TokenStream.NAME: + String name = ts.getString(); + return nf.createName(name); + + case TokenStream.NUMBER_INT: + int n = (int) ts.getNumber(); + return nf.createNumber(n); + + case TokenStream.NUMBER: + double d = ts.getNumber(); + return nf.createNumber(d); + + case TokenStream.STRING: + String s = ts.getString(); + return nf.createString(s); + + case TokenStream.REGEXP: { + String flags = ts.regExpFlags; + ts.regExpFlags = null; + String re = ts.getString(); + return nf.createRegExp(re, flags); + } + + case TokenStream.PRIMARY: + return nf.createLeaf(TokenStream.PRIMARY, ts.getOp()); + + case TokenStream.ERROR: + /* the scanner or one of its subroutines reported the error. */ + break; + + default: + reportError(ts, "msg.syntax"); + break; + } + return null; // should never reach here + } + + private int lastExprEndLine; // Hack to handle function expr termination. + private final IRFactory nf; + private boolean ok; // Did the parse encounter an error? + + private int sourceTop; + private int functionNumber; + private final boolean insideFunction; }