com.google.gwt.dev.js.rhino.Parser Maven / Gradle / Ivy
/* -*- 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 {
CodePosition namePosition = ts.tokenPosition;
mustMatchToken(ts, TokenStream.NAME, "msg.no.parm");
String s = ts.getString();
args.addChildToBack(nf.createName(s, namePosition));
}
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;
}