All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.js.rhino.Parser Maven / Gradle / Ivy

There is a newer version: 2.1.0-RC
Show newest version
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is Rhino code, released
 * May 6, 1999.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1997-1999 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s):
 * Mike Ang
 * Mike McCabe
 *
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU Public License (the "GPL"), in which case the
 * provisions of the GPL are applicable instead of those above.
 * If you wish to allow use of your version of this file only
 * under the terms of the GPL and not to allow others to use your
 * version of this file under the NPL, indicate your decision by
 * deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL.  If you do not delete
 * the provisions above, a recipient may use your version of this
 * file under either the NPL or the GPL.
 */
// Modified by Google

package com.google.gwt.dev.js.rhino;

import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 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 { private List listeners; public Parser(IRFactory nf, boolean insideFunction) { this.nf = nf; this.insideFunction = insideFunction; } public void addListener(ParserListener listener) { if (listeners == null) { listeners = new ArrayList<>(); } listeners.add(listener); } 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); } /* * 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 Node parse(TokenStream ts) throws IOException { this.ok = true; sourceTop = 0; functionNumber = 0; int tt; // last token from getToken(); /* * so we have something to add nodes to until we've collected all the source */ Node tempBlock = nf.createLeaf(TokenStream.BLOCK, ts.tokenPosition); 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 { tempBlock.addChildToBack(function(ts, false)); } catch (JavaScriptException e) { this.ok = false; break; } } else { ts.ungetToken(tt); tempBlock.addChildToBack(statement(ts)); } } if (!this.ok) { // XXX ts.clearPushback() call here? return null; } return nf.createScript(tempBlock); } /* * 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 Node parseFunctionBody(TokenStream ts) throws IOException { int oldflags = ts.flags; ts.flags &= ~(TokenStream.TSF_RETURN_EXPR | TokenStream.TSF_RETURN_VOID); ts.flags |= TokenStream.TSF_FUNCTION; Node pn = nf.createBlock(ts.tokenPosition); try { int tt; while ((tt = ts.peekToken()) > TokenStream.EOF && tt != TokenStream.RC) { if (tt == TokenStream.FUNCTION) { ts.getToken(); pn.addChildToBack(function(ts, false)); } else { pn.addChildToBack(statement(ts)); } } } catch (JavaScriptException e) { this.ok = false; } finally { // also in finally block: // flushNewLines, clearPushback. ts.flags = oldflags; } return pn; } private Node function(TokenStream ts, boolean isExpr) throws IOException, JavaScriptException { if (listeners != null) { for (ParserListener listener : listeners) { listener.functionStarted(); } } CodePosition basePosition = ts.tokenPosition; Node nameNode; Node memberExprNode = null; if (ts.matchToken(TokenStream.NAME)) { nameNode = nf.createName(ts.getString(), basePosition); 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 Node memberExprHead = nameNode; nameNode = null; memberExprNode = memberExprTail(ts, false, memberExprHead); } mustMatchToken(ts, TokenStream.LP, "msg.no.paren.parms"); } } else if (ts.matchToken(TokenStream.LP)) { // Anonymous function nameNode = null; } else { 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"); nameNode = null; } ++functionNumber; // Save current source top to restore it on exit not to include // function to parent source int savedSourceTop = sourceTop; int savedFunctionNumber = functionNumber; Node args; Node body; try { functionNumber = 0; args = nf.createLeaf(TokenStream.LP, ts.tokenPosition); if (!ts.matchToken(TokenStream.GWT)) { do { mustMatchToken(ts, TokenStream.NAME, "msg.no.parm"); String s = ts.getString(); args.addChildToBack(nf.createName(s, ts.tokenPosition)); } 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); body.setPosition(ts.tokenPosition); mustMatchToken(ts, TokenStream.RC, "msg.no.brace.after.body"); // skip the last EOL so nested functions work... } finally { sourceTop = savedSourceTop; functionNumber = savedFunctionNumber; } Node pn = nf.createFunction(nameNode, args, body, basePosition); if (memberExprNode != null) { pn = nf.createBinary(TokenStream.ASSIGN, TokenStream.NOP, memberExprNode, pn, basePosition); } // 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); } if (listeners != null) { for (ParserListener listener : listeners) { listener.functionEnded(ts); } } return pn; } private Node statements(TokenStream ts) throws IOException { Node pn = nf.createBlock(ts.tokenPosition); int tt; while ((tt = ts.peekToken()) > TokenStream.EOF && tt != TokenStream.RC) { pn.addChildToBack(statement(ts)); } return pn; } private Node condition(TokenStream ts) throws IOException, JavaScriptException { Node 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 Node matchLabel(TokenStream ts) throws IOException, JavaScriptException { CodePosition position = ts.tokenPosition; 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 != null ? nf.createString(label, position) : null; } private Node statement(TokenStream ts) throws IOException { CodePosition position = ts.lastPosition; try { Comment commentsBefore = getComments(ts); ts.collectCommentsAfter(); Node result = statementHelper(ts); result.setCommentsBeforeNode(commentsBefore); ts.collectCommentsAfter(); result.setCommentsAfterNode(getComments(ts)); return result; } catch (JavaScriptException e) { // skip to end of statement 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", position), position); } } /** * Whether the "catch (e: e instanceof Exception) { ... }" syntax is * implemented. */ private Node statementHelper(TokenStream ts) throws IOException, JavaScriptException { Node pn; int tt; int lastExprType; // For wellTerminated tt = ts.getToken(); CodePosition position = ts.tokenPosition; switch (tt) { case TokenStream.IF: { Node cond = condition(ts); Node ifTrue = statement(ts); Node ifFalse = null; if (ts.matchToken(TokenStream.ELSE)) { ifFalse = statement(ts); } pn = nf.createIf(cond, ifTrue, ifFalse, position); break; } case TokenStream.SWITCH: { pn = nf.createSwitch(position); Node curCase = null; // to kill warning Node caseStatements; mustMatchToken(ts, TokenStream.LP, "msg.no.paren.switch"); pn.addChildToBack(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: curCase = nf.createUnary(TokenStream.CASE, expr(ts, false), ts.tokenPosition); break; case TokenStream.DEFAULT: curCase = nf.createLeaf(TokenStream.DEFAULT, ts.tokenPosition); // 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"); caseStatements = nf.createLeaf(TokenStream.BLOCK, null); while ((tt = ts.peekToken()) != TokenStream.RC && tt != TokenStream.CASE && tt != TokenStream.DEFAULT && tt != TokenStream.EOF) { caseStatements.addChildToBack(statement(ts)); } // assert cur_case if (curCase != null) { curCase.addChildToBack(caseStatements); } pn.addChildToBack(curCase); } break; } case TokenStream.WHILE: { Node cond = condition(ts); Node body = statement(ts); pn = nf.createWhile(cond, body, position); break; } case TokenStream.DO: { Node body = statement(ts); mustMatchToken(ts, TokenStream.WHILE, "msg.no.while.do"); Node cond = condition(ts); pn = nf.createDoWhile(body, cond, position); break; } case TokenStream.FOR: { Node init; // Node init is also foo in 'foo in Object' Node cond; // Node cond is also object in 'foo in Object' Node incr = null; // to kill warning Node body; mustMatchToken(ts, TokenStream.LP, "msg.no.paren.for"); tt = ts.peekToken(); if (tt == TokenStream.SEMI) { init = nf.createLeaf(TokenStream.VOID, null); } else { if (tt == TokenStream.VAR) { // set init to a var list or initial ts.getToken(); // throw away the 'var' token init = variables(ts, true, ts.tokenPosition); } 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, null); } else { cond = expr(ts, false); } mustMatchToken(ts, TokenStream.SEMI, "msg.no.semi.for.cond"); if (ts.peekToken() == TokenStream.GWT) { incr = nf.createLeaf(TokenStream.VOID, null); } 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, position); } else { pn = nf.createFor(init, cond, incr, body, position); } break; } case TokenStream.TRY: { Node tryblock; Node catchblocks; Node finallyblock = null; tryblock = statement(ts); catchblocks = nf.createLeaf(TokenStream.BLOCK, null); boolean sawDefaultCatch = false; int peek = ts.peekToken(); if (peek == TokenStream.CATCH) { while (ts.matchToken(TokenStream.CATCH)) { if (sawDefaultCatch) { reportError(ts, "msg.catch.unreachable"); } CodePosition catchPosition = ts.tokenPosition; mustMatchToken(ts, TokenStream.LP, "msg.no.paren.catch"); mustMatchToken(ts, TokenStream.NAME, "msg.bad.catchcond"); Node varName = nf.createName(ts.getString(), ts.tokenPosition); Node 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"); catchblocks.addChildToBack(nf.createCatch(varName, catchCond, statements(ts), catchPosition)); 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, position); break; } case TokenStream.THROW: { int lineno = ts.getLineno(); pn = nf.createThrow(expr(ts, false), position); if (lineno == ts.getLineno()) { wellTerminated(ts, TokenStream.ERROR); } break; } case TokenStream.BREAK: { // matchLabel only matches if there is one Node label = matchLabel(ts); pn = nf.createBreak(label, position); break; } case TokenStream.CONTINUE: { // matchLabel only matches if there is one Node label = matchLabel(ts); pn = nf.createContinue(label, position); break; } case TokenStream.DEBUGGER: { pn = nf.createDebugger(position); 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"); mustMatchToken(ts, TokenStream.LP, "msg.no.paren.with"); Node obj = expr(ts, false); mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.after.with"); Node body = statement(ts); pn = nf.createWith(obj, body, position); break; } case TokenStream.VAR: { int lineno = ts.getLineno(); pn = variables(ts, false, position); if (ts.getLineno() == lineno) { wellTerminated(ts, TokenStream.ERROR); } break; } case TokenStream.RETURN: { Node retExpr = null; int lineno; // 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, position); 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, ts.tokenPosition); 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(nf.createString(name, position), position); // bruce: added to make it easier to bind labels to the // statements they modify // pn.addChildToBack(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, position); /* * 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 Node variables(TokenStream ts, boolean inForInit, CodePosition position) throws IOException, JavaScriptException { Node pn = nf.createVariables(position); while (true) { Node name; Node init; mustMatchToken(ts, TokenStream.NAME, "msg.bad.var"); String s = ts.getString(); name = nf.createName(s, ts.tokenPosition); // 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); name.addChildToBack(init); } pn.addChildToBack(name); if (!ts.matchToken(TokenStream.COMMA)) { break; } } return pn; } public Node expr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { Node pn = assignExpr(ts, inForInit); while (ts.matchToken(TokenStream.COMMA)) { CodePosition position = ts.tokenPosition; pn = nf.createBinary(TokenStream.COMMA, pn, assignExpr(ts, inForInit), position); } return pn; } private Node assignExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { Comment commentBeforeNode = getComments(ts); Node pn = condExpr(ts, inForInit); pn.setCommentsBeforeNode(commentBeforeNode); if (ts.matchToken(TokenStream.ASSIGN)) { // omitted: "invalid assignment left-hand side" check. CodePosition position = ts.tokenPosition; pn = nf.createBinary(TokenStream.ASSIGN, ts.getOp(), pn, assignExpr(ts, inForInit), position); } ts.collectCommentsAfter(); pn.setCommentsAfterNode(getComments(ts)); return pn; } private Node condExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { Node pn = orExpr(ts, inForInit); if (ts.matchToken(TokenStream.HOOK)) { CodePosition position = ts.tokenPosition; Node ifTrue = assignExpr(ts, false); mustMatchToken(ts, TokenStream.COLON, "msg.no.colon.cond"); Node ifFalse = assignExpr(ts, inForInit); return nf.createTernary(pn, ifTrue, ifFalse, position); } return pn; } private Node orExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { Node pn = andExpr(ts, inForInit); while (ts.matchToken(TokenStream.OR)) { CodePosition position = ts.tokenPosition; pn = nf.createBinary(TokenStream.OR, pn, andExpr(ts, inForInit), position); } return pn; } private Node andExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { Node pn = bitOrExpr(ts, inForInit); while (ts.matchToken(TokenStream.AND)) { CodePosition position = ts.tokenPosition; pn = nf.createBinary(TokenStream.AND, pn, bitOrExpr(ts, inForInit), position); } return pn; } private Node bitOrExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { Node pn = bitXorExpr(ts, inForInit); while (ts.matchToken(TokenStream.BITOR)) { CodePosition position = ts.tokenPosition; pn = nf.createBinary(TokenStream.BITOR, pn, bitXorExpr(ts, inForInit), position); } return pn; } private Node bitXorExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { Node pn = bitAndExpr(ts, inForInit); while (ts.matchToken(TokenStream.BITXOR)) { CodePosition position = ts.tokenPosition; pn = nf.createBinary(TokenStream.BITXOR, pn, bitAndExpr(ts, inForInit), position); } return pn; } private Node bitAndExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { Node pn = eqExpr(ts, inForInit); while (ts.matchToken(TokenStream.BITAND)) { CodePosition position = ts.tokenPosition; pn = nf.createBinary(TokenStream.BITAND, pn, eqExpr(ts, inForInit), position); } return pn; } private Node eqExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { Node pn = relExpr(ts, inForInit); while (ts.matchToken(TokenStream.EQOP)) { CodePosition position = ts.tokenPosition; pn = nf.createBinary(TokenStream.EQOP, ts.getOp(), pn, relExpr(ts, inForInit), position); } return pn; } private Node relExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { Node pn = shiftExpr(ts); CodePosition position = ts.tokenPosition; 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), position); position = ts.tokenPosition; } return pn; } private Node shiftExpr(TokenStream ts) throws IOException, JavaScriptException { Node pn = addExpr(ts); while (ts.matchToken(TokenStream.SHOP)) { CodePosition position = ts.tokenPosition; pn = nf.createBinary(TokenStream.SHOP, ts.getOp(), pn, addExpr(ts), position); } return pn; } private Node addExpr(TokenStream ts) throws IOException, JavaScriptException { int tt; Node pn = mulExpr(ts); while ((tt = ts.getToken()) == TokenStream.ADD || tt == TokenStream.SUB) { CodePosition position = ts.tokenPosition; pn = nf.createBinary(tt, pn, mulExpr(ts), position); } ts.ungetToken(tt); return pn; } private Node mulExpr(TokenStream ts) throws IOException, JavaScriptException { int tt; Node pn = unaryExpr(ts); while ((tt = ts.peekToken()) == TokenStream.MUL || tt == TokenStream.DIV || tt == TokenStream.MOD) { tt = ts.getToken(); CodePosition position = ts.tokenPosition; pn = nf.createBinary(tt, pn, unaryExpr(ts), position); } return pn; } private Node unaryExpr(TokenStream ts) throws IOException, JavaScriptException { int tt; ts.flags |= TokenStream.TSF_REGEXP; tt = ts.getToken(); ts.flags &= ~TokenStream.TSF_REGEXP; CodePosition position = ts.tokenPosition; switch (tt) { case TokenStream.UNARYOP: return nf.createUnary(TokenStream.UNARYOP, ts.getOp(), unaryExpr(ts), position); case TokenStream.ADD: case TokenStream.SUB: return nf.createUnary(TokenStream.UNARYOP, tt, unaryExpr(ts), position); case TokenStream.INC: case TokenStream.DEC: return nf.createUnary(tt, TokenStream.PRE, memberExpr(ts, true), position); case TokenStream.DELPROP: { Node argument = unaryExpr(ts); if (!isValidDeleteArgument(argument)) { Context.reportError("msg.wrong.delete argument", argument.getPosition(), ts.lastPosition); } return nf.createUnary(TokenStream.DELPROP, argument, position); } case TokenStream.ERROR: break; default: ts.ungetToken(tt); int lineno = ts.getLineno(); Node 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(); position = ts.tokenPosition; return nf.createUnary(pf, TokenStream.POST, pn, position); } return pn; } return nf.createName("err", position); // Only reached on error. Try to continue. } private static boolean isValidDeleteArgument(@NotNull Node node) { return node.type == TokenStream.GETPROP || node.type == TokenStream.GETELEM; } private Node argumentList(TokenStream ts, Node listNode) throws IOException, JavaScriptException { boolean matched; ts.flags |= TokenStream.TSF_REGEXP; matched = ts.matchToken(TokenStream.GWT); ts.flags &= ~TokenStream.TSF_REGEXP; if (!matched) { do { listNode.addChildToBack(assignExpr(ts, false)); } while (ts.matchToken(TokenStream.COMMA)); mustMatchToken(ts, TokenStream.GWT, "msg.no.paren.arg"); } return listNode; } private Node memberExpr(TokenStream ts, boolean allowCallSyntax) throws IOException, JavaScriptException { int tt; Node pn; CodePosition position = ts.tokenPosition; /* 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, position); pn.addChildToBack(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) { pn.addChildToBack(primaryExpr(ts)); } } else { pn = primaryExpr(ts); } return memberExprTail(ts, allowCallSyntax, pn); } private Node memberExprTail( TokenStream ts, boolean allowCallSyntax, Node pn ) throws IOException, JavaScriptException { lastExprEndLine = ts.getLineno(); int tt; while ((tt = ts.getToken()) > TokenStream.EOF) { CodePosition position = ts.tokenPosition; 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(), ts.tokenPosition), position); /* * 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), position); 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, position); /* Add the arguments to pn, if any are supplied. */ pn = argumentList(ts, pn); lastExprEndLine = ts.getLineno(); } else { ts.ungetToken(tt); break; } } return pn; } public Node primaryExpr(TokenStream ts) throws IOException, JavaScriptException { Comment commentsBeforeNode = getComments(ts); Node node = primaryExprHelper(ts); node.setCommentsBeforeNode(commentsBeforeNode); ts.collectCommentsAfter(); node.setCommentsAfterNode(getComments(ts)); return node; } private Node primaryExprHelper(TokenStream ts) throws IOException, JavaScriptException { int tt; Node pn; ts.flags |= TokenStream.TSF_REGEXP; tt = ts.getToken(); CodePosition position = ts.tokenPosition; ts.flags &= ~TokenStream.TSF_REGEXP; switch (tt) { case TokenStream.FUNCTION: return function(ts, true); case TokenStream.LB: { pn = nf.createLeaf(TokenStream.ARRAYLIT, position); 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) { pn.addChildToBack(nf.createLeaf(TokenStream.PRIMARY, TokenStream.UNDEFINED, position)); } else { pn.addChildToBack(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, position); if (!ts.matchToken(TokenStream.RC)) { commaloop: do { Node 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(), ts.tokenPosition); break; case TokenStream.NUMBER_INT: property = nf.createIntNumber(ts.getNumber(), ts.tokenPosition); break; case TokenStream.NUMBER: double d = ts.getNumber(); property = nf.createNumber(d, ts.tokenPosition); 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. pn.addChildToBack(property); pn.addChildToBack(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.IMPORT: // for import() and import.meta syntax if (ts.peekToken() != TokenStream.LP && ts.peekToken() != TokenStream.DOT) { reportError(ts, "msg.syntax"); } return nf.createName(TokenStream.tokenToName(TokenStream.IMPORT), position); case TokenStream.NAME: String name = ts.getString(); return nf.createName(name, position); case TokenStream.NUMBER_INT: return nf.createIntNumber(ts.getNumber(), position); case TokenStream.NUMBER: double d = ts.getNumber(); return nf.createNumber(d, position); case TokenStream.STRING: String s = ts.getString(); return nf.createString(s, position); case TokenStream.REGEXP: { String flags = ts.regExpFlags; ts.regExpFlags = null; String re = ts.getString(); return nf.createRegExp(re, flags, position); } case TokenStream.PRIMARY: return nf.createLeaf(TokenStream.PRIMARY, ts.getOp(), position); 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 Comment getComments(TokenStream ts) { Comment comment = ts.getHeadComment(); if (comment != null) { ts.releaseComments(); } return comment; } 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; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy