
org.mozilla.javascript.Parser1 Maven / Gradle / Ivy
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla 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/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or 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 the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Mike Ang
* Igor Bukanov
* Yuh-Ruey Chen
* Ethan Hugg
* Bob Jervis
* Terry Lucas
* Mike McCabe
* Milen Nankov
* Norris Boyd
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (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
* MPL, indicate your decision by deleting the provisions above and replacing
* 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 MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.javascript;
import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
/**
* This class implements the JavaScript parser.
*
* It is based on the C source files jsparse.c and jsparse.h in the jsref package.
*
* @see TokenStream1
*
* @author Mike McCabe
* @author Brendan Eich
*/
@Deprecated
public class Parser1
{
// TokenInformation flags : currentFlaggedToken stores them together
// with token type
final static int CLEAR_TI_MASK = 0xFFFF, // mask to clear token information bits
TI_AFTER_EOL = 1 << 16, // first token of the source line
TI_CHECK_LABEL = 1 << 17; // indicates to check for label
CompilerEnvirons compilerEnv;
private ErrorReporter errorReporter;
private String sourceURI;
boolean calledByCompileFunction;
private TokenStream1 ts;
private int currentFlaggedToken;
private int syntaxErrorCount;
private IRFactory nf;
private int nestingOfFunction;
private Decompiler decompiler;
private String encodedSource;
// The following are per function variables and should be saved/restored
// during function parsing.
// XXX Move to separated class?
ScriptOrFnNode currentScriptOrFn;
Node.Scope currentScope;
private int nestingOfWith;
private Map labelSet; // map of label names into nodes
private ObjArray loopSet;
private ObjArray loopAndSwitchSet;
private int endFlags;
// end of per function variables
public int getCurrentLineNumber()
{
return ts.getLineno();
}
// Exception to unwind
private static class ParserException extends RuntimeException
{
static final long serialVersionUID = 5882582646773765630L;
}
public Parser1(final CompilerEnvirons compilerEnv, final ErrorReporter errorReporter)
{
this.compilerEnv = compilerEnv;
this.errorReporter = errorReporter;
}
protected Decompiler createDecompiler(final CompilerEnvirons compilerEnv)
{
return new Decompiler();
}
void addStrictWarning(final String messageId, final String messageArg)
{
if (compilerEnv.isStrictMode())
addWarning(messageId, messageArg);
}
void addWarning(final String messageId, final String messageArg)
{
final String message = ScriptRuntime.getMessage1(messageId, messageArg);
if (compilerEnv.reportWarningAsError())
{
++syntaxErrorCount;
errorReporter.error(message, sourceURI, ts.getLineno(), ts.getLine(), ts.getOffset());
}
else
errorReporter.warning(message, sourceURI, ts.getLineno(), ts.getLine(), ts.getOffset());
}
void addError(final String messageId)
{
++syntaxErrorCount;
final String message = ScriptRuntime.getMessage0(messageId);
errorReporter.error(message, sourceURI, ts.getLineno(), ts.getLine(), ts.getOffset());
}
void addError(final String messageId, final String messageArg)
{
++syntaxErrorCount;
final String message = ScriptRuntime.getMessage1(messageId, messageArg);
errorReporter.error(message, sourceURI, ts.getLineno(), ts.getLine(), ts.getOffset());
}
RuntimeException reportError(final String messageId)
{
addError(messageId);
// Throw a ParserException exception to unwind the recursive descent
// parse.
throw new ParserException();
}
private int peekToken() throws IOException
{
int tt = currentFlaggedToken;
if (tt == Token1.EOF)
{
tt = ts.getToken();
if (tt == Token1.EOL)
{
do
{
tt = ts.getToken();
}
while (tt == Token1.EOL);
tt |= TI_AFTER_EOL;
}
currentFlaggedToken = tt;
}
return tt & CLEAR_TI_MASK;
}
private int peekFlaggedToken() throws IOException
{
peekToken();
return currentFlaggedToken;
}
private void consumeToken()
{
currentFlaggedToken = Token1.EOF;
}
private int nextToken() throws IOException
{
final int tt = peekToken();
consumeToken();
return tt;
}
private int nextFlaggedToken() throws IOException
{
peekToken();
final int ttFlagged = currentFlaggedToken;
consumeToken();
return ttFlagged;
}
private boolean matchToken(final int toMatch) throws IOException
{
final int tt = peekToken();
if (tt != toMatch)
{
return false;
}
consumeToken();
return true;
}
private int peekTokenOrEOL() throws IOException
{
int tt = peekToken();
// Check for last peeked token flags
if ((currentFlaggedToken & TI_AFTER_EOL) != 0)
{
tt = Token1.EOL;
}
return tt;
}
private void setCheckForLabel()
{
if ((currentFlaggedToken & CLEAR_TI_MASK) != Token1.NAME)
throw Kit.codeBug();
currentFlaggedToken |= TI_CHECK_LABEL;
}
private void mustMatchToken(final int toMatch, final String messageId) throws IOException,
ParserException
{
if (!matchToken(toMatch))
{
reportError(messageId);
}
}
private void mustHaveXML()
{
if (!compilerEnv.isXmlAvailable())
{
reportError("msg.XML.not.available");
}
}
public String getEncodedSource()
{
return encodedSource;
}
public boolean eof()
{
return ts.eof();
}
boolean insideFunction()
{
return nestingOfFunction != 0;
}
void pushScope(final Node node)
{
final Node.Scope scopeNode = (Node.Scope) node;
if (scopeNode.getParentScope() != null)
throw Kit.codeBug();
scopeNode.setParent(currentScope);
currentScope = scopeNode;
}
void popScope()
{
currentScope = currentScope.getParentScope();
}
private Node enterLoop(final Node loopLabel, final boolean doPushScope)
{
final Node loop = nf.createLoopNode(loopLabel, ts.getLineno());
if (loopSet == null)
{
loopSet = new ObjArray();
if (loopAndSwitchSet == null)
{
loopAndSwitchSet = new ObjArray();
}
}
loopSet.push(loop);
loopAndSwitchSet.push(loop);
if (doPushScope)
{
pushScope(loop);
}
return loop;
}
private void exitLoop(final boolean doPopScope)
{
loopSet.pop();
loopAndSwitchSet.pop();
if (doPopScope)
{
popScope();
}
}
private Node enterSwitch(final Node switchSelector, final int lineno)
{
final Node switchNode = nf.createSwitch(switchSelector, lineno);
if (loopAndSwitchSet == null)
{
loopAndSwitchSet = new ObjArray();
}
loopAndSwitchSet.push(switchNode);
return switchNode;
}
private void exitSwitch()
{
loopAndSwitchSet.pop();
}
/*
* Build a parse tree from the given sourceString.
* @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 ErrorReporter from
* CompilerEnvirons.)
*/
public ScriptOrFnNode parse(final String sourceString, final String sourceURI, final int lineno)
{
this.sourceURI = sourceURI;
this.ts = new TokenStream1(this, null, sourceString, lineno);
try
{
return parse();
}
catch (final IOException ex)
{
// Should never happen
throw new IllegalStateException();
}
}
/*
* Build a parse tree from the given sourceString.
* @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 ErrorReporter from
* CompilerEnvirons.)
*/
public ScriptOrFnNode parse(final Reader sourceReader, final String sourceURI, final int lineno)
throws IOException
{
this.sourceURI = sourceURI;
this.ts = new TokenStream1(this, sourceReader, null, lineno);
return parse();
}
private ScriptOrFnNode parse() throws IOException
{
this.decompiler = createDecompiler(compilerEnv);
// this.nf = new IRFactory(this);
this.nf = new IRFactory(new Parser(compilerEnv, errorReporter));
currentScriptOrFn = nf.createScript();
currentScope = currentScriptOrFn;
final int sourceStartOffset = decompiler.getCurrentOffset();
this.encodedSource = null;
decompiler.addToken(Token1.SCRIPT);
this.currentFlaggedToken = Token1.EOF;
this.syntaxErrorCount = 0;
final int baseLineno = ts.getLineno(); // line number where source starts
/*
* so we have something to add nodes to until we've collected all the source
*/
final Node pn = nf.createLeaf(Token1.BLOCK);
try
{
for (;;)
{
final int tt = peekToken();
if (tt <= Token1.EOF)
{
break;
}
Node n;
if (tt == Token1.FUNCTION)
{
consumeToken();
try
{
n =
function(calledByCompileFunction ? FunctionNode.FUNCTION_EXPRESSION
: FunctionNode.FUNCTION_STATEMENT);
}
catch (final ParserException e)
{
break;
}
}
else
{
n = statement();
}
nf.addChildToBack(pn, n);
}
}
catch (final StackOverflowError ex)
{
final String msg = ScriptRuntime.getMessage0("msg.too.deep.parser.recursion");
throw Context.reportRuntimeError(msg, sourceURI, ts.getLineno(), null, 0);
}
if (this.syntaxErrorCount != 0)
{
String msg = String.valueOf(this.syntaxErrorCount);
msg = ScriptRuntime.getMessage1("msg.got.syntax.errors", msg);
throw errorReporter.runtimeError(msg, sourceURI, baseLineno, null, 0);
}
currentScriptOrFn.setSourceName(sourceURI);
currentScriptOrFn.setBaseLineno(baseLineno);
currentScriptOrFn.setEndLineno(ts.getLineno());
final int sourceEndOffset = decompiler.getCurrentOffset();
currentScriptOrFn.setEncodedSourceBounds(sourceStartOffset, sourceEndOffset);
nf.initScript(currentScriptOrFn, pn);
if (compilerEnv.isGeneratingSource())
{
encodedSource = decompiler.getEncodedSource();
}
this.decompiler = null; // It helps GC
return currentScriptOrFn;
}
/*
* 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() throws IOException
{
++nestingOfFunction;
final Node pn = nf.createBlock(ts.getLineno());
try
{
bodyLoop: for (;;)
{
Node n;
final int tt = peekToken();
switch (tt)
{
case Token1.ERROR:
case Token1.EOF:
case Token1.RC:
break bodyLoop;
case Token1.FUNCTION:
consumeToken();
n = function(FunctionNode.FUNCTION_STATEMENT);
break;
default:
n = statement();
break;
}
nf.addChildToBack(pn, n);
}
}
catch (final ParserException e)
{
// Ignore it
}
finally
{
--nestingOfFunction;
}
return pn;
}
private Node function(final int functionType) throws IOException, ParserException
{
int syntheticType = functionType;
final int baseLineno = ts.getLineno(); // line number where source starts
final int functionSourceStart = decompiler.markFunctionStart(functionType);
String name;
Node memberExprNode = null;
if (matchToken(Token1.NAME))
{
name = ts.getString();
decompiler.addName(name);
if (!matchToken(Token1.LP))
{
if (compilerEnv.isAllowMemberExprAsFunctionName())
{
// Extension to ECMA: if 'function ' does not follow
// by '(', assume starts memberExpr
final Node memberExprHead = nf.createName(name);
name = "";
memberExprNode = memberExprTail(false, memberExprHead);
}
mustMatchToken(Token1.LP, "msg.no.paren.parms");
}
}
else if (matchToken(Token1.LP))
{
// Anonymous function
name = "";
}
else
{
name = "";
if (compilerEnv.isAllowMemberExprAsFunctionName())
{
// Note that memberExpr can not start with '(' like
// in function (1+2).toString(), because 'function (' already
// processed as anonymous function
memberExprNode = memberExpr(false);
}
mustMatchToken(Token1.LP, "msg.no.paren.parms");
}
if (memberExprNode != null)
{
syntheticType = FunctionNode.FUNCTION_EXPRESSION;
}
if (syntheticType != FunctionNode.FUNCTION_EXPRESSION && name.length() > 0)
{
// Function statements define a symbol in the enclosing scope
defineSymbol(Token1.FUNCTION, false, name);
}
final boolean nested = insideFunction();
final FunctionNode fnNode = nf.createFunction(name);
if (nested || nestingOfWith > 0)
{
// 1. Nested functions are not affected by the dynamic scope flag
// as dynamic scope is already a parent of their scope.
// 2. Functions defined under the with statement also immune to
// this setup, in which case dynamic scope is ignored in favor
// of with object.
fnNode.itsIgnoreDynamicScope = true;
}
final int functionIndex = currentScriptOrFn.addFunction(fnNode);
int functionSourceEnd;
final ScriptOrFnNode savedScriptOrFn = currentScriptOrFn;
currentScriptOrFn = fnNode;
final Node.Scope savedCurrentScope = currentScope;
currentScope = fnNode;
final int savedNestingOfWith = nestingOfWith;
nestingOfWith = 0;
final Map savedLabelSet = labelSet;
labelSet = null;
final ObjArray savedLoopSet = loopSet;
loopSet = null;
final ObjArray savedLoopAndSwitchSet = loopAndSwitchSet;
loopAndSwitchSet = null;
final int savedFunctionEndFlags = endFlags;
endFlags = 0;
Node destructuring = null;
Node body;
try
{
decompiler.addToken(Token1.LP);
if (!matchToken(Token1.RP))
{
boolean first = true;
do
{
if (!first)
decompiler.addToken(Token1.COMMA);
first = false;
final int tt = peekToken();
if (tt == Token1.LB || tt == Token1.LC)
{
// Destructuring assignment for parameters: add a
// dummy parameter name, and add a statement to the
// body to initialize variables from the destructuring
// assignment
if (destructuring == null)
{
destructuring = new Node(Token1.COMMA);
}
final String parmName = currentScriptOrFn.getNextTempName();
defineSymbol(Token1.LP, false, parmName);
destructuring.addChildToBack(nf.createDestructuringAssignment(Token1.VAR,
primaryExpr(), nf.createName(parmName)));
}
else
{
mustMatchToken(Token1.NAME, "msg.no.parm");
final String s = ts.getString();
defineSymbol(Token1.LP, false, s);
decompiler.addName(s);
}
}
while (matchToken(Token1.COMMA));
mustMatchToken(Token1.RP, "msg.no.paren.after.parms");
}
decompiler.addToken(Token1.RP);
mustMatchToken(Token1.LC, "msg.no.brace.body");
decompiler.addEOL(Token1.LC);
body = parseFunctionBody();
if (destructuring != null)
{
body.addChildToFront(new Node(Token1.EXPR_VOID, destructuring, ts.getLineno()));
}
mustMatchToken(Token1.RC, "msg.no.brace.after.body");
if (compilerEnv.isStrictMode() && !body.hasConsistentReturnUsage())
{
final String msg =
name.length() > 0 ? "msg.no.return.value" : "msg.anon.no.return.value";
addStrictWarning(msg, name);
}
if (syntheticType == FunctionNode.FUNCTION_EXPRESSION && name.length() > 0
&& currentScope.getSymbol(name) == null)
{
// Function expressions define a name only in the body of the
// function, and only if not hidden by a parameter name
defineSymbol(Token1.FUNCTION, false, name);
}
decompiler.addToken(Token1.RC);
functionSourceEnd = decompiler.markFunctionEnd(functionSourceStart);
if (functionType != FunctionNode.FUNCTION_EXPRESSION)
{
// Add EOL only if function is not part of expression
// since it gets SEMI + EOL from Statement in that case
decompiler.addToken(Token1.EOL);
}
}
finally
{
endFlags = savedFunctionEndFlags;
loopAndSwitchSet = savedLoopAndSwitchSet;
loopSet = savedLoopSet;
labelSet = savedLabelSet;
nestingOfWith = savedNestingOfWith;
currentScriptOrFn = savedScriptOrFn;
currentScope = savedCurrentScope;
}
fnNode.setEncodedSourceBounds(functionSourceStart, functionSourceEnd);
fnNode.setSourceName(sourceURI);
fnNode.setBaseLineno(baseLineno);
fnNode.setEndLineno(ts.getLineno());
Node pn = nf.initFunction(fnNode, functionIndex, body, syntheticType);
if (memberExprNode != null)
{
pn = nf.createAssignment(Token1.ASSIGN, memberExprNode, pn);
if (functionType != FunctionNode.FUNCTION_EXPRESSION)
{
// XXX check JScript behavior: should it be createExprStatement?
pn = nf.createExprStatementNoReturn(pn, baseLineno);
}
}
return pn;
}
private Node statements(final Node scope) throws IOException
{
final Node pn = scope != null ? scope : nf.createBlock(ts.getLineno());
int tt;
while ((tt = peekToken()) > Token1.EOF && tt != Token1.RC)
{
nf.addChildToBack(pn, statement());
}
return pn;
}
private Node condition() throws IOException, ParserException
{
mustMatchToken(Token1.LP, "msg.no.paren.cond");
decompiler.addToken(Token1.LP);
final Node pn = expr(false);
mustMatchToken(Token1.RP, "msg.no.paren.after.cond");
decompiler.addToken(Token1.RP);
// Report strict warning on code like "if (a = 7) ...". Suppress the
// warning if the condition is parenthesized, like "if ((a = 7)) ...".
if (pn.getProp(Node.PARENTHESIZED_PROP) == null
&& (pn.getType() == Token1.SETNAME || pn.getType() == Token1.SETPROP || pn.getType() == Token1.SETELEM))
{
addStrictWarning("msg.equal.as.assign", "");
}
return pn;
}
// match a NAME; return null if no match.
private Node matchJumpLabelName() throws IOException, ParserException
{
Node label = null;
final int tt = peekTokenOrEOL();
if (tt == Token1.NAME)
{
consumeToken();
final String name = ts.getString();
decompiler.addName(name);
if (labelSet != null)
{
label = labelSet.get(name);
}
if (label == null)
{
reportError("msg.undef.label");
}
}
return label;
}
private Node statement() throws IOException
{
try
{
final Node pn = statementHelper(null);
if (pn != null)
{
if (compilerEnv.isStrictMode() && !pn.hasSideEffects())
addStrictWarning("msg.no.side.effects", "");
return pn;
}
}
catch (final ParserException e)
{
}
// skip to end of statement
final int lineno = ts.getLineno();
guessingStatementEnd: for (;;)
{
final int tt = peekTokenOrEOL();
consumeToken();
switch (tt)
{
case Token1.ERROR:
case Token1.EOF:
case Token1.EOL:
case Token1.SEMI:
break guessingStatementEnd;
}
}
return nf.createExprStatement(nf.createName("error"), lineno);
}
private Node statementHelper(Node statementLabel) throws IOException, ParserException
{
Node pn = null;
int tt = peekToken();
switch (tt)
{
case Token1.IF:
{
consumeToken();
decompiler.addToken(Token1.IF);
final int lineno = ts.getLineno();
final Node cond = condition();
decompiler.addEOL(Token1.LC);
final Node ifTrue = statement();
Node ifFalse = null;
if (matchToken(Token1.ELSE))
{
decompiler.addToken(Token1.RC);
decompiler.addToken(Token1.ELSE);
decompiler.addEOL(Token1.LC);
ifFalse = statement();
}
decompiler.addEOL(Token1.RC);
pn = nf.createIf(cond, ifTrue, ifFalse, lineno);
return pn;
}
case Token1.SWITCH:
{
consumeToken();
decompiler.addToken(Token1.SWITCH);
final int lineno = ts.getLineno();
mustMatchToken(Token1.LP, "msg.no.paren.switch");
decompiler.addToken(Token1.LP);
pn = enterSwitch(expr(false), lineno);
try
{
mustMatchToken(Token1.RP, "msg.no.paren.after.switch");
decompiler.addToken(Token1.RP);
mustMatchToken(Token1.LC, "msg.no.brace.switch");
decompiler.addEOL(Token1.LC);
boolean hasDefault = false;
switchLoop: for (;;)
{
tt = nextToken();
Node caseExpression;
switch (tt)
{
case Token1.RC:
break switchLoop;
case Token1.CASE:
decompiler.addToken(Token1.CASE);
caseExpression = expr(false);
mustMatchToken(Token1.COLON, "msg.no.colon.case");
decompiler.addEOL(Token1.COLON);
break;
case Token1.DEFAULT:
if (hasDefault)
{
reportError("msg.double.switch.default");
}
decompiler.addToken(Token1.DEFAULT);
hasDefault = true;
caseExpression = null;
mustMatchToken(Token1.COLON, "msg.no.colon.case");
decompiler.addEOL(Token1.COLON);
break;
default:
reportError("msg.bad.switch");
break switchLoop;
}
final Node block = nf.createLeaf(Token1.BLOCK);
while ((tt = peekToken()) != Token1.RC && tt != Token1.CASE
&& tt != Token1.DEFAULT && tt != Token1.EOF)
{
nf.addChildToBack(block, statement());
}
// caseExpression == null => add default label
nf.addSwitchCase(pn, caseExpression, block);
}
decompiler.addEOL(Token1.RC);
nf.closeSwitch(pn);
}
finally
{
exitSwitch();
}
return pn;
}
case Token1.WHILE:
{
consumeToken();
decompiler.addToken(Token1.WHILE);
final Node loop = enterLoop(statementLabel, true);
try
{
final Node cond = condition();
decompiler.addEOL(Token1.LC);
final Node body = statement();
decompiler.addEOL(Token1.RC);
pn = nf.createWhile(loop, cond, body);
}
finally
{
exitLoop(true);
}
return pn;
}
case Token1.DO:
{
consumeToken();
decompiler.addToken(Token1.DO);
decompiler.addEOL(Token1.LC);
final Node loop = enterLoop(statementLabel, true);
try
{
final Node body = statement();
decompiler.addToken(Token1.RC);
mustMatchToken(Token1.WHILE, "msg.no.while.do");
decompiler.addToken(Token1.WHILE);
final Node cond = condition();
pn = nf.createDoWhile(loop, body, cond);
}
finally
{
exitLoop(true);
}
// Always auto-insert semicolon to follow SpiderMonkey:
// It is required by ECMAScript but is ignored by the rest of
// world, see bug 238945
matchToken(Token1.SEMI);
decompiler.addEOL(Token1.SEMI);
return pn;
}
case Token1.FOR:
{
consumeToken();
boolean isForEach = false;
decompiler.addToken(Token1.FOR);
final Node loop = enterLoop(statementLabel, true);
try
{
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;
Node body;
int declType = -1;
// See if this is a for each () instead of just a for ()
if (matchToken(Token1.NAME))
{
decompiler.addName(ts.getString());
if (ts.getString().equals("each"))
{
isForEach = true;
}
else
{
reportError("msg.no.paren.for");
}
}
mustMatchToken(Token1.LP, "msg.no.paren.for");
decompiler.addToken(Token1.LP);
tt = peekToken();
if (tt == Token1.SEMI)
{
init = nf.createLeaf(Token1.EMPTY);
}
else
{
if (tt == Token1.VAR || tt == Token1.LET)
{
// set init to a var list or initial
consumeToken(); // consume the token
decompiler.addToken(tt);
init = variables(true, tt);
declType = tt;
}
else
{
init = expr(true);
}
}
if (matchToken(Token1.IN))
{
decompiler.addToken(Token1.IN);
// 'cond' is the object over which we're iterating
cond = expr(false);
}
else
{ // ordinary for loop
mustMatchToken(Token1.SEMI, "msg.no.semi.for");
decompiler.addToken(Token1.SEMI);
if (peekToken() == Token1.SEMI)
{
// no loop condition
cond = nf.createLeaf(Token1.EMPTY);
}
else
{
cond = expr(false);
}
mustMatchToken(Token1.SEMI, "msg.no.semi.for.cond");
decompiler.addToken(Token1.SEMI);
if (peekToken() == Token1.RP)
{
incr = nf.createLeaf(Token1.EMPTY);
}
else
{
incr = expr(false);
}
}
mustMatchToken(Token1.RP, "msg.no.paren.for.ctrl");
decompiler.addToken(Token1.RP);
decompiler.addEOL(Token1.LC);
body = statement();
decompiler.addEOL(Token1.RC);
if (incr == null)
{
// cond could be null if 'in obj' got eaten
// by the init node.
pn = nf.createForIn(declType, loop, init, cond, body, isForEach);
}
else
{
pn = nf.createFor(loop, init, cond, incr, body);
}
}
finally
{
exitLoop(true);
}
return pn;
}
case Token1.TRY:
{
consumeToken();
final int lineno = ts.getLineno();
Node tryblock;
Node catchblocks = null;
Node finallyblock = null;
decompiler.addToken(Token1.TRY);
if (peekToken() != Token1.LC)
{
reportError("msg.no.brace.try");
}
decompiler.addEOL(Token1.LC);
tryblock = statement();
decompiler.addEOL(Token1.RC);
catchblocks = nf.createLeaf(Token1.BLOCK);
boolean sawDefaultCatch = false;
final int peek = peekToken();
if (peek == Token1.CATCH)
{
while (matchToken(Token1.CATCH))
{
if (sawDefaultCatch)
{
reportError("msg.catch.unreachable");
}
decompiler.addToken(Token1.CATCH);
mustMatchToken(Token1.LP, "msg.no.paren.catch");
decompiler.addToken(Token1.LP);
mustMatchToken(Token1.NAME, "msg.bad.catchcond");
final String varName = ts.getString();
decompiler.addName(varName);
Node catchCond = null;
if (matchToken(Token1.IF))
{
decompiler.addToken(Token1.IF);
catchCond = expr(false);
}
else
{
sawDefaultCatch = true;
}
mustMatchToken(Token1.RP, "msg.bad.catchcond");
decompiler.addToken(Token1.RP);
mustMatchToken(Token1.LC, "msg.no.brace.catchblock");
decompiler.addEOL(Token1.LC);
nf.addChildToBack(catchblocks,
nf.createCatch(varName, catchCond, statements(null), ts.getLineno()));
mustMatchToken(Token1.RC, "msg.no.brace.after.body");
decompiler.addEOL(Token1.RC);
}
}
else if (peek != Token1.FINALLY)
{
mustMatchToken(Token1.FINALLY, "msg.try.no.catchfinally");
}
if (matchToken(Token1.FINALLY))
{
decompiler.addToken(Token1.FINALLY);
decompiler.addEOL(Token1.LC);
finallyblock = statement();
decompiler.addEOL(Token1.RC);
}
pn = nf.createTryCatchFinally(tryblock, catchblocks, finallyblock, lineno);
return pn;
}
case Token1.THROW:
{
consumeToken();
if (peekTokenOrEOL() == Token1.EOL)
{
// ECMAScript does not allow new lines before throw expression,
// see bug 256617
reportError("msg.bad.throw.eol");
}
final int lineno = ts.getLineno();
decompiler.addToken(Token1.THROW);
pn = nf.createThrow(expr(false), lineno);
break;
}
case Token1.BREAK:
{
consumeToken();
final int lineno = ts.getLineno();
decompiler.addToken(Token1.BREAK);
// matchJumpLabelName only matches if there is one
Node breakStatement = matchJumpLabelName();
if (breakStatement == null)
{
if (loopAndSwitchSet == null || loopAndSwitchSet.size() == 0)
{
reportError("msg.bad.break");
return null;
}
breakStatement = (Node) loopAndSwitchSet.peek();
}
pn = nf.createBreak(breakStatement, lineno);
break;
}
case Token1.CONTINUE:
{
consumeToken();
final int lineno = ts.getLineno();
decompiler.addToken(Token1.CONTINUE);
Node loop;
// matchJumpLabelName only matches if there is one
final Node label = matchJumpLabelName();
if (label == null)
{
if (loopSet == null || loopSet.size() == 0)
{
reportError("msg.continue.outside");
return null;
}
loop = (Node) loopSet.peek();
}
else
{
loop = nf.getLabelLoop(label);
if (loop == null)
{
reportError("msg.continue.nonloop");
return null;
}
}
pn = nf.createContinue(loop, lineno);
break;
}
case Token1.WITH:
{
consumeToken();
decompiler.addToken(Token1.WITH);
final int lineno = ts.getLineno();
mustMatchToken(Token1.LP, "msg.no.paren.with");
decompiler.addToken(Token1.LP);
final Node obj = expr(false);
mustMatchToken(Token1.RP, "msg.no.paren.after.with");
decompiler.addToken(Token1.RP);
decompiler.addEOL(Token1.LC);
++nestingOfWith;
Node body;
try
{
body = statement();
}
finally
{
--nestingOfWith;
}
decompiler.addEOL(Token1.RC);
pn = nf.createWith(obj, body, lineno);
return pn;
}
case Token1.CONST:
case Token1.VAR:
{
consumeToken();
decompiler.addToken(tt);
pn = variables(false, tt);
break;
}
case Token1.LET:
{
consumeToken();
decompiler.addToken(Token1.LET);
if (peekToken() == Token1.LP)
{
return let(true);
}
else
{
pn = variables(false, tt);
if (peekToken() == Token1.SEMI)
break;
return pn;
}
}
case Token1.RETURN:
case Token1.YIELD:
{
pn = returnOrYield(tt, false);
break;
}
case Token1.DEBUGGER:
consumeToken();
decompiler.addToken(Token1.DEBUGGER);
pn = nf.createDebugger(ts.getLineno());
break;
case Token1.LC:
consumeToken();
if (statementLabel != null)
{
decompiler.addToken(Token1.LC);
}
final Node scope = nf.createScopeNode(Token1.BLOCK, ts.getLineno());
pushScope(scope);
try
{
statements(scope);
mustMatchToken(Token1.RC, "msg.no.brace.block");
if (statementLabel != null)
{
decompiler.addEOL(Token1.RC);
}
return scope;
}
finally
{
popScope();
}
case Token1.ERROR:
// Fall thru, to have a node for error recovery to work on
case Token1.SEMI:
consumeToken();
pn = nf.createLeaf(Token1.EMPTY);
return pn;
case Token1.FUNCTION:
{
consumeToken();
pn = function(FunctionNode.FUNCTION_EXPRESSION_STATEMENT);
return pn;
}
case Token1.DEFAULT:
consumeToken();
mustHaveXML();
decompiler.addToken(Token1.DEFAULT);
final int nsLine = ts.getLineno();
if (!(matchToken(Token1.NAME) && ts.getString().equals("xml")))
{
reportError("msg.bad.namespace");
}
decompiler.addName(" xml");
if (!(matchToken(Token1.NAME) && ts.getString().equals("namespace")))
{
reportError("msg.bad.namespace");
}
decompiler.addName(" namespace");
if (!matchToken(Token1.ASSIGN))
{
reportError("msg.bad.namespace");
}
decompiler.addToken(Token1.ASSIGN);
final Node expr = expr(false);
pn = nf.createDefaultNamespace(expr, nsLine);
break;
case Token1.NAME:
{
final int lineno = ts.getLineno();
final String name = ts.getString();
setCheckForLabel();
pn = expr(false);
if (pn.getType() != Token1.LABEL)
{
pn = nf.createExprStatement(pn, lineno);
}
else
{
// Parsed the label: push back token should be
// colon that primaryExpr left untouched.
if (peekToken() != Token1.COLON)
Kit.codeBug();
consumeToken();
// depend on decompiling lookahead to guess that that
// last name was a label.
decompiler.addName(name);
decompiler.addEOL(Token1.COLON);
if (labelSet == null)
{
labelSet = new HashMap();
}
else if (labelSet.containsKey(name))
{
reportError("msg.dup.label");
}
boolean firstLabel;
if (statementLabel == null)
{
firstLabel = true;
statementLabel = pn;
}
else
{
// Discard multiple label nodes and use only
// the first: it allows to simplify IRFactory
firstLabel = false;
}
labelSet.put(name, statementLabel);
try
{
pn = statementHelper(statementLabel);
}
finally
{
labelSet.remove(name);
}
if (firstLabel)
{
pn = nf.createLabeledStatement(statementLabel, pn);
}
return pn;
}
break;
}
default:
{
final int lineno = ts.getLineno();
pn = expr(false);
pn = nf.createExprStatement(pn, lineno);
break;
}
}
final int ttFlagged = peekFlaggedToken();
switch (ttFlagged & CLEAR_TI_MASK)
{
case Token1.SEMI:
// Consume ';' as a part of expression
consumeToken();
break;
case Token1.ERROR:
case Token1.EOF:
case Token1.RC:
// Autoinsert ;
break;
default:
if ((ttFlagged & TI_AFTER_EOL) == 0)
{
// Report error if no EOL or autoinsert ; otherwise
reportError("msg.no.semi.stmt");
}
break;
}
decompiler.addEOL(Token1.SEMI);
return pn;
}
/**
* Returns whether or not the bits in the mask have changed to all set.
*
* @param before
* bits before change
* @param after
* bits after change
* @param mask
* mask for bits
* @return true if all the bits in the mask are set in "after" but not "before"
*/
private static final boolean nowAllSet(final int before, final int after, final int mask)
{
return ((before & mask) != mask) && ((after & mask) == mask);
}
private Node returnOrYield(final int tt, final boolean exprContext) throws IOException,
ParserException
{
if (!insideFunction())
{
reportError(tt == Token1.RETURN ? "msg.bad.return" : "msg.bad.yield");
}
consumeToken();
decompiler.addToken(tt);
final int lineno = ts.getLineno();
Node e;
/* This is ugly, but we don't want to require a semicolon. */
switch (peekTokenOrEOL())
{
case Token1.SEMI:
case Token1.RC:
case Token1.EOF:
case Token1.EOL:
case Token1.ERROR:
case Token1.RB:
case Token1.RP:
case Token1.YIELD:
e = null;
break;
default:
e = expr(false);
break;
}
final int before = endFlags;
Node ret;
if (tt == Token1.RETURN)
{
if (e == null)
{
endFlags |= Node.END_RETURNS;
}
else
{
endFlags |= Node.END_RETURNS_VALUE;
}
ret = nf.createReturn(e, lineno);
// see if we need a strict mode warning
if (nowAllSet(before, endFlags, Node.END_RETURNS | Node.END_RETURNS_VALUE))
{
addStrictWarning("msg.return.inconsistent", "");
}
}
else
{
endFlags |= Node.END_YIELDS;
ret = nf.createYield(e, lineno);
if (!exprContext)
ret = new Node(Token1.EXPR_VOID, ret, lineno);
}
// see if we are mixing yields and value returns.
if (nowAllSet(before, endFlags, Node.END_YIELDS | Node.END_RETURNS_VALUE))
{
final String name = ((FunctionNode) currentScriptOrFn).getFunctionName();
if (name.length() == 0)
addError("msg.anon.generator.returns", "");
else
addError("msg.generator.returns", name);
}
return ret;
}
/**
* Parse a 'var' or 'const' statement, or a 'var' init list in a for statement.
*
* @param inFor
* true if we are currently in the midst of the init clause of a for.
* @param declType
* A token value: either VAR, CONST, or LET depending on context.
* @return The parsed statement
* @throws IOException
* @throws ParserException
*/
private Node variables(final boolean inFor, final int declType) throws IOException,
ParserException
{
final Node result = nf.createVariables(declType, ts.getLineno());
boolean first = true;
for (;;)
{
Node destructuring = null;
String s = null;
final int tt = peekToken();
if (tt == Token1.LB || tt == Token1.LC)
{
// Destructuring assignment, e.g., var [a,b] = ...
destructuring = primaryExpr();
}
else
{
// Simple variable name
mustMatchToken(Token1.NAME, "msg.bad.var");
s = ts.getString();
if (!first)
decompiler.addToken(Token1.COMMA);
first = false;
decompiler.addName(s);
defineSymbol(declType, inFor, s);
}
Node init = null;
if (matchToken(Token1.ASSIGN))
{
decompiler.addToken(Token1.ASSIGN);
init = assignExpr(inFor);
}
if (destructuring != null)
{
if (init == null)
{
if (!inFor)
reportError("msg.destruct.assign.no.init");
nf.addChildToBack(result, destructuring);
}
else
{
nf.addChildToBack(result,
nf.createDestructuringAssignment(declType, destructuring, init));
}
}
else
{
final Node name = nf.createName(s);
if (init != null)
nf.addChildToBack(name, init);
nf.addChildToBack(result, name);
}
if (!matchToken(Token1.COMMA))
break;
}
return result;
}
private Node let(final boolean isStatement) throws IOException, ParserException
{
mustMatchToken(Token1.LP, "msg.no.paren.after.let");
decompiler.addToken(Token1.LP);
Node result = nf.createScopeNode(Token1.LET, ts.getLineno());
pushScope(result);
try
{
final Node vars = variables(false, Token1.LET);
nf.addChildToBack(result, vars);
mustMatchToken(Token1.RP, "msg.no.paren.let");
decompiler.addToken(Token1.RP);
if (isStatement && peekToken() == Token1.LC)
{
// let statement
consumeToken();
decompiler.addEOL(Token1.LC);
nf.addChildToBack(result, statements(null));
mustMatchToken(Token1.RC, "msg.no.curly.let");
decompiler.addToken(Token1.RC);
}
else
{
// let expression
result.setType(Token1.LETEXPR);
nf.addChildToBack(result, expr(false));
if (isStatement)
{
// let expression in statement context
result = nf.createExprStatement(result, ts.getLineno());
}
}
}
finally
{
popScope();
}
return result;
}
void defineSymbol(final int declType, final boolean ignoreNotInBlock, final String name)
{
final Node.Scope definingScope = currentScope.getDefiningScope(name);
final Node.Scope.Symbol symbol =
definingScope != null ? definingScope.getSymbol(name) : null;
boolean error = false;
if (symbol != null && (symbol.declType == Token1.CONST || declType == Token1.CONST))
{
error = true;
}
else
{
switch (declType)
{
case Token1.LET:
if (symbol != null && definingScope == currentScope)
{
error = symbol.declType == Token1.LET;
}
final int currentScopeType = currentScope.getType();
if (!ignoreNotInBlock
&& ((currentScopeType == Token1.LOOP) || (currentScopeType == Token1.IF)))
{
addError("msg.let.decl.not.in.block");
}
currentScope.putSymbol(name, new Node.Scope.Symbol(declType, name));
break;
case Token1.VAR:
case Token1.CONST:
case Token1.FUNCTION:
if (symbol != null)
{
if (symbol.declType == Token1.VAR)
addStrictWarning("msg.var.redecl", name);
else if (symbol.declType == Token1.LP)
{
addStrictWarning("msg.var.hides.arg", name);
}
}
else
{
currentScriptOrFn.putSymbol(name, new Node.Scope.Symbol(declType, name));
}
break;
case Token1.LP:
if (symbol != null)
{
// must be duplicate parameter. Second parameter hides the
// first, so go ahead and add the second pararameter
addWarning("msg.dup.parms", name);
}
currentScriptOrFn.putSymbol(name, new Node.Scope.Symbol(declType, name));
break;
default:
throw Kit.codeBug();
}
}
if (error)
{
addError(symbol.declType == Token1.CONST ? "msg.const.redecl"
: symbol.declType == Token1.LET ? "msg.let.redecl" : symbol.declType == Token1.VAR
? "msg.var.redecl" : symbol.declType == Token1.FUNCTION ? "msg.fn.redecl"
: "msg.parm.redecl", name);
}
}
private Node expr(final boolean inForInit) throws IOException, ParserException
{
Node pn = assignExpr(inForInit);
while (matchToken(Token1.COMMA))
{
decompiler.addToken(Token1.COMMA);
if (compilerEnv.isStrictMode() && !pn.hasSideEffects())
addStrictWarning("msg.no.side.effects", "");
if (peekToken() == Token1.YIELD)
{
reportError("msg.yield.parenthesized");
}
pn = nf.createBinary(Token1.COMMA, pn, assignExpr(inForInit));
}
return pn;
}
private Node assignExpr(final boolean inForInit) throws IOException, ParserException
{
int tt = peekToken();
if (tt == Token1.YIELD)
{
consumeToken();
return returnOrYield(tt, true);
}
Node pn = condExpr(inForInit);
tt = peekToken();
if (Token1.FIRST_ASSIGN <= tt && tt <= Token1.LAST_ASSIGN)
{
consumeToken();
decompiler.addToken(tt);
pn = nf.createAssignment(tt, pn, assignExpr(inForInit));
}
return pn;
}
private Node condExpr(final boolean inForInit) throws IOException, ParserException
{
final Node pn = orExpr(inForInit);
if (matchToken(Token1.HOOK))
{
decompiler.addToken(Token1.HOOK);
final Node ifTrue = assignExpr(false);
mustMatchToken(Token1.COLON, "msg.no.colon.cond");
decompiler.addToken(Token1.COLON);
final Node ifFalse = assignExpr(inForInit);
return nf.createCondExpr(pn, ifTrue, ifFalse);
}
return pn;
}
private Node orExpr(final boolean inForInit) throws IOException, ParserException
{
Node pn = andExpr(inForInit);
if (matchToken(Token1.OR))
{
decompiler.addToken(Token1.OR);
pn = nf.createBinary(Token1.OR, pn, orExpr(inForInit));
}
return pn;
}
private Node andExpr(final boolean inForInit) throws IOException, ParserException
{
Node pn = bitOrExpr(inForInit);
if (matchToken(Token1.AND))
{
decompiler.addToken(Token1.AND);
pn = nf.createBinary(Token1.AND, pn, andExpr(inForInit));
}
return pn;
}
private Node bitOrExpr(final boolean inForInit) throws IOException, ParserException
{
Node pn = bitXorExpr(inForInit);
while (matchToken(Token1.BITOR))
{
decompiler.addToken(Token1.BITOR);
pn = nf.createBinary(Token1.BITOR, pn, bitXorExpr(inForInit));
}
return pn;
}
private Node bitXorExpr(final boolean inForInit) throws IOException, ParserException
{
Node pn = bitAndExpr(inForInit);
while (matchToken(Token1.BITXOR))
{
decompiler.addToken(Token1.BITXOR);
pn = nf.createBinary(Token1.BITXOR, pn, bitAndExpr(inForInit));
}
return pn;
}
private Node bitAndExpr(final boolean inForInit) throws IOException, ParserException
{
Node pn = eqExpr(inForInit);
while (matchToken(Token1.BITAND))
{
decompiler.addToken(Token1.BITAND);
pn = nf.createBinary(Token1.BITAND, pn, eqExpr(inForInit));
}
return pn;
}
private Node eqExpr(final boolean inForInit) throws IOException, ParserException
{
Node pn = relExpr(inForInit);
for (;;)
{
final int tt = peekToken();
switch (tt)
{
case Token1.EQ:
case Token1.NE:
case Token1.SHEQ:
case Token1.SHNE:
consumeToken();
int decompilerToken = tt;
int parseToken = tt;
if (compilerEnv.getLanguageVersion() == Context.VERSION_1_2)
{
// JavaScript 1.2 uses shallow equality for == and != .
// In addition, convert === and !== for decompiler into
// == and != since the decompiler is supposed to show
// canonical source and in 1.2 ===, !== are allowed
// only as an alias to ==, !=.
switch (tt)
{
case Token1.EQ:
parseToken = Token1.SHEQ;
break;
case Token1.NE:
parseToken = Token1.SHNE;
break;
case Token1.SHEQ:
decompilerToken = Token1.EQ;
break;
case Token1.SHNE:
decompilerToken = Token1.NE;
break;
}
}
decompiler.addToken(decompilerToken);
pn = nf.createBinary(parseToken, pn, relExpr(inForInit));
continue;
}
break;
}
return pn;
}
private Node relExpr(final boolean inForInit) throws IOException, ParserException
{
Node pn = shiftExpr();
for (;;)
{
final int tt = peekToken();
switch (tt)
{
case Token1.IN:
if (inForInit)
break;
// fall through
case Token1.INSTANCEOF:
case Token1.LE:
case Token1.LT:
case Token1.GE:
case Token1.GT:
consumeToken();
decompiler.addToken(tt);
pn = nf.createBinary(tt, pn, shiftExpr());
continue;
}
break;
}
return pn;
}
private Node shiftExpr() throws IOException, ParserException
{
Node pn = addExpr();
for (;;)
{
final int tt = peekToken();
switch (tt)
{
case Token1.LSH:
case Token1.URSH:
case Token1.RSH:
consumeToken();
decompiler.addToken(tt);
pn = nf.createBinary(tt, pn, addExpr());
continue;
}
break;
}
return pn;
}
private Node addExpr() throws IOException, ParserException
{
Node pn = mulExpr();
for (;;)
{
final int tt = peekToken();
if (tt == Token1.ADD || tt == Token1.SUB)
{
consumeToken();
decompiler.addToken(tt);
// flushNewLines
pn = nf.createBinary(tt, pn, mulExpr());
continue;
}
break;
}
return pn;
}
private Node mulExpr() throws IOException, ParserException
{
Node pn = unaryExpr();
for (;;)
{
final int tt = peekToken();
switch (tt)
{
case Token1.MUL:
case Token1.DIV:
case Token1.MOD:
consumeToken();
decompiler.addToken(tt);
pn = nf.createBinary(tt, pn, unaryExpr());
continue;
}
break;
}
return pn;
}
private Node unaryExpr() throws IOException, ParserException
{
int tt;
tt = peekToken();
switch (tt)
{
case Token1.VOID:
case Token1.NOT:
case Token1.BITNOT:
case Token1.TYPEOF:
consumeToken();
decompiler.addToken(tt);
return nf.createUnary(tt, unaryExpr());
case Token1.ADD:
consumeToken();
// Convert to special POS token in decompiler and parse tree
decompiler.addToken(Token1.POS);
return nf.createUnary(Token1.POS, unaryExpr());
case Token1.SUB:
consumeToken();
// Convert to special NEG token in decompiler and parse tree
decompiler.addToken(Token1.NEG);
return nf.createUnary(Token1.NEG, unaryExpr());
case Token1.INC:
case Token1.DEC:
consumeToken();
decompiler.addToken(tt);
return nf.createIncDec(tt, false, memberExpr(true));
case Token1.DELPROP:
consumeToken();
decompiler.addToken(Token1.DELPROP);
return nf.createUnary(Token1.DELPROP, unaryExpr());
case Token1.ERROR:
consumeToken();
break;
// XML stream encountered in expression.
case Token1.LT:
if (compilerEnv.isXmlAvailable())
{
consumeToken();
final Node pn = xmlInitializer();
return memberExprTail(true, pn);
}
// Fall thru to the default handling of RELOP
default:
final Node pn = memberExpr(true);
// Don't look across a newline boundary for a postfix incop.
tt = peekTokenOrEOL();
if (tt == Token1.INC || tt == Token1.DEC)
{
consumeToken();
decompiler.addToken(tt);
return nf.createIncDec(tt, true, pn);
}
return pn;
}
return nf.createName("error"); // Only reached on error.Try to continue.
}
private Node xmlInitializer() throws IOException
{
int tt = ts.getFirstXMLToken();
if (tt != Token1.XML && tt != Token1.XMLEND)
{
reportError("msg.syntax");
return null;
}
/* Make a NEW node to append to. */
final Node pnXML = nf.createLeaf(Token1.NEW);
String xml = ts.getString();
final boolean fAnonymous = xml.trim().startsWith("<>");
Node pn = nf.createName(fAnonymous ? "XMLList" : "XML");
nf.addChildToBack(pnXML, pn);
pn = null;
Node expr;
for (;; tt = ts.getNextXMLToken())
{
switch (tt)
{
case Token1.XML:
xml = ts.getString();
decompiler.addName(xml);
mustMatchToken(Token1.LC, "msg.syntax");
decompiler.addToken(Token1.LC);
expr = (peekToken() == Token1.RC) ? nf.createString("") : expr(false);
mustMatchToken(Token1.RC, "msg.syntax");
decompiler.addToken(Token1.RC);
if (pn == null)
{
pn = nf.createString(xml);
}
else
{
pn = nf.createBinary(Token1.ADD, pn, nf.createString(xml));
}
if (ts.isXMLAttribute())
{
/* Need to put the result in double quotes */
expr = nf.createUnary(Token1.ESCXMLATTR, expr);
final Node prepend =
nf.createBinary(Token1.ADD, nf.createString("\""), expr);
expr = nf.createBinary(Token1.ADD, prepend, nf.createString("\""));
}
else
{
expr = nf.createUnary(Token1.ESCXMLTEXT, expr);
}
pn = nf.createBinary(Token1.ADD, pn, expr);
break;
case Token1.XMLEND:
xml = ts.getString();
decompiler.addName(xml);
if (pn == null)
{
pn = nf.createString(xml);
}
else
{
pn = nf.createBinary(Token1.ADD, pn, nf.createString(xml));
}
nf.addChildToBack(pnXML, pn);
return pnXML;
default:
reportError("msg.syntax");
return null;
}
}
}
private void argumentList(final Node listNode) throws IOException, ParserException
{
boolean matched;
matched = matchToken(Token1.RP);
if (!matched)
{
boolean first = true;
do
{
if (!first)
decompiler.addToken(Token1.COMMA);
first = false;
if (peekToken() == Token1.YIELD)
{
reportError("msg.yield.parenthesized");
}
nf.addChildToBack(listNode, assignExpr(false));
}
while (matchToken(Token1.COMMA));
mustMatchToken(Token1.RP, "msg.no.paren.arg");
}
decompiler.addToken(Token1.RP);
}
private Node memberExpr(final boolean allowCallSyntax) throws IOException, ParserException
{
int tt;
Node pn;
/* Check for new expressions. */
tt = peekToken();
if (tt == Token1.NEW)
{
/* Eat the NEW token. */
consumeToken();
decompiler.addToken(Token1.NEW);
/* Make a NEW node to append to. */
pn = nf.createCallOrNew(Token1.NEW, memberExpr(false));
if (matchToken(Token1.LP))
{
decompiler.addToken(Token1.LP);
/* Add the arguments to pn, if any are supplied. */
argumentList(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 = peekToken();
if (tt == Token1.LC)
{
nf.addChildToBack(pn, primaryExpr());
}
}
else
{
pn = primaryExpr();
}
return memberExprTail(allowCallSyntax, pn);
}
private Node memberExprTail(final boolean allowCallSyntax, Node pn) throws IOException,
ParserException
{
tailLoop: for (;;)
{
int tt = peekToken();
switch (tt)
{
case Token1.DOT:
case Token1.DOTDOT:
{
int memberTypeFlags;
String s;
consumeToken();
decompiler.addToken(tt);
memberTypeFlags = 0;
if (tt == Token1.DOTDOT)
{
mustHaveXML();
memberTypeFlags = Node.DESCENDANTS_FLAG;
}
if (!compilerEnv.isXmlAvailable())
{
mustMatchToken(Token1.NAME, "msg.no.name.after.dot");
s = ts.getString();
decompiler.addName(s);
pn = nf.createPropertyGet(pn, null, s, memberTypeFlags);
break;
}
tt = nextToken();
switch (tt)
{
// needed for generator.throw();
case Token1.THROW:
decompiler.addName("throw");
pn = propertyName(pn, "throw", memberTypeFlags);
break;
// handles: name, ns::name, ns::*, ns::[expr]
case Token1.NAME:
s = ts.getString();
decompiler.addName(s);
pn = propertyName(pn, s, memberTypeFlags);
break;
// handles: *, *::name, *::*, *::[expr]
case Token1.MUL:
decompiler.addName("*");
pn = propertyName(pn, "*", memberTypeFlags);
break;
// handles: '@attr', '@ns::attr', '@ns::*', '@ns::*',
// '@::attr', '@::*', '@*', '@*::attr', '@*::*'
case Token1.XMLATTR:
decompiler.addToken(Token1.XMLATTR);
pn = attributeAccess(pn, memberTypeFlags);
break;
default:
reportError("msg.no.name.after.dot");
}
}
break;
case Token1.DOTQUERY:
consumeToken();
mustHaveXML();
decompiler.addToken(Token1.DOTQUERY);
pn = nf.createDotQuery(pn, expr(false), ts.getLineno());
mustMatchToken(Token1.RP, "msg.no.paren");
decompiler.addToken(Token1.RP);
break;
case Token1.LB:
consumeToken();
decompiler.addToken(Token1.LB);
pn = nf.createElementGet(pn, null, expr(false), 0);
mustMatchToken(Token1.RB, "msg.no.bracket.index");
decompiler.addToken(Token1.RB);
break;
case Token1.LP:
if (!allowCallSyntax)
{
break tailLoop;
}
consumeToken();
decompiler.addToken(Token1.LP);
pn = nf.createCallOrNew(Token1.CALL, pn);
/* Add the arguments to pn, if any are supplied. */
argumentList(pn);
break;
default:
break tailLoop;
}
}
return pn;
}
/*
* Xml attribute expression: '@attr', '@ns::attr', '@ns::*', '@ns::*', '@*',
* '@*::attr', '@*::*'
*/
private Node attributeAccess(Node pn, int memberTypeFlags) throws IOException
{
memberTypeFlags |= Node.ATTRIBUTE_FLAG;
final int tt = nextToken();
switch (tt)
{
// handles: @name, @ns::name, @ns::*, @ns::[expr]
case Token1.NAME:
{
final String s = ts.getString();
decompiler.addName(s);
pn = propertyName(pn, s, memberTypeFlags);
}
break;
// handles: @*, @*::name, @*::*, @*::[expr]
case Token1.MUL:
decompiler.addName("*");
pn = propertyName(pn, "*", memberTypeFlags);
break;
// handles @[expr]
case Token1.LB:
decompiler.addToken(Token1.LB);
pn = nf.createElementGet(pn, null, expr(false), memberTypeFlags);
mustMatchToken(Token1.RB, "msg.no.bracket.index");
decompiler.addToken(Token1.RB);
break;
default:
reportError("msg.no.name.after.xmlAttr");
pn = nf.createPropertyGet(pn, null, "?", memberTypeFlags);
break;
}
return pn;
}
/**
* Check if :: follows name in which case it becomes qualified name
*/
private Node propertyName(Node pn, String name, final int memberTypeFlags) throws IOException,
ParserException
{
String namespace = null;
if (matchToken(Token1.COLONCOLON))
{
decompiler.addToken(Token1.COLONCOLON);
namespace = name;
final int tt = nextToken();
switch (tt)
{
// handles name::name
case Token1.NAME:
name = ts.getString();
decompiler.addName(name);
break;
// handles name::*
case Token1.MUL:
decompiler.addName("*");
name = "*";
break;
// handles name::[expr]
case Token1.LB:
decompiler.addToken(Token1.LB);
pn = nf.createElementGet(pn, namespace, expr(false), memberTypeFlags);
mustMatchToken(Token1.RB, "msg.no.bracket.index");
decompiler.addToken(Token1.RB);
return pn;
default:
reportError("msg.no.name.after.coloncolon");
name = "?";
}
}
pn = nf.createPropertyGet(pn, namespace, name, memberTypeFlags);
return pn;
}
private Node arrayComprehension(final String arrayName, Node expr) throws IOException,
ParserException
{
if (nextToken() != Token1.FOR)
throw Kit.codeBug(); // shouldn't be here if next token isn't 'for'
decompiler.addName(" "); // space after array literal expr
decompiler.addToken(Token1.FOR);
boolean isForEach = false;
if (matchToken(Token1.NAME))
{
decompiler.addName(ts.getString());
if (ts.getString().equals("each"))
{
isForEach = true;
}
else
{
reportError("msg.no.paren.for");
}
}
mustMatchToken(Token1.LP, "msg.no.paren.for");
decompiler.addToken(Token1.LP);
String name;
int tt = peekToken();
if (tt == Token1.LB || tt == Token1.LC)
{
// handle destructuring assignment
name = currentScriptOrFn.getNextTempName();
defineSymbol(Token1.LP, false, name);
expr =
nf.createBinary(Token1.COMMA,
nf.createAssignment(Token1.ASSIGN, primaryExpr(), nf.createName(name)), expr);
}
else if (tt == Token1.NAME)
{
consumeToken();
name = ts.getString();
decompiler.addName(name);
}
else
{
reportError("msg.bad.var");
return nf.createNumber(0);
}
final Node init = nf.createName(name);
// Define as a let since we want the scope of the variable to
// be restricted to the array comprehension
defineSymbol(Token1.LET, false, name);
mustMatchToken(Token1.IN, "msg.in.after.for.name");
decompiler.addToken(Token1.IN);
final Node iterator = expr(false);
mustMatchToken(Token1.RP, "msg.no.paren.for.ctrl");
decompiler.addToken(Token1.RP);
Node body;
tt = peekToken();
if (tt == Token1.FOR)
{
body = arrayComprehension(arrayName, expr);
}
else
{
final Node call =
nf.createCallOrNew(Token1.CALL,
nf.createPropertyGet(nf.createName(arrayName), null, "push", 0));
call.addChildToBack(expr);
body = new Node(Token1.EXPR_VOID, call, ts.getLineno());
if (tt == Token1.IF)
{
consumeToken();
decompiler.addToken(Token1.IF);
final int lineno = ts.getLineno();
final Node cond = condition();
body = nf.createIf(cond, body, null, lineno);
}
mustMatchToken(Token1.RB, "msg.no.bracket.arg");
decompiler.addToken(Token1.RB);
}
final Node loop = enterLoop(null, true);
try
{
return nf.createForIn(Token1.LET, loop, init, iterator, body, isForEach);
}
finally
{
exitLoop(false);
}
}
private Node primaryExpr() throws IOException, ParserException
{
Node pn;
final int ttFlagged = nextFlaggedToken();
int tt = ttFlagged & CLEAR_TI_MASK;
switch (tt)
{
case Token1.FUNCTION:
return function(FunctionNode.FUNCTION_EXPRESSION);
case Token1.LB:
{
final ObjArray elems = new ObjArray();
int skipCount = 0;
int destructuringLen = 0;
decompiler.addToken(Token1.LB);
boolean after_lb_or_comma = true;
for (;;)
{
tt = peekToken();
if (tt == Token1.COMMA)
{
consumeToken();
decompiler.addToken(Token1.COMMA);
if (!after_lb_or_comma)
{
after_lb_or_comma = true;
}
else
{
elems.add(null);
++skipCount;
}
}
else if (tt == Token1.RB)
{
consumeToken();
decompiler.addToken(Token1.RB);
// for ([a,] in obj) is legal, but for ([a] in obj) is
// not since we have both key and value supplied. The
// trick is that [a,] and [a] are equivalent in other
// array literal contexts. So we calculate a special
// length value just for destructuring assignment.
destructuringLen = elems.size() + (after_lb_or_comma ? 1 : 0);
break;
}
else if (skipCount == 0 && elems.size() == 1 && tt == Token1.FOR)
{
final Node scopeNode = nf.createScopeNode(Token1.ARRAYCOMP, ts.getLineno());
final String tempName = currentScriptOrFn.getNextTempName();
pushScope(scopeNode);
try
{
defineSymbol(Token1.LET, false, tempName);
final Node expr = (Node) elems.get(0);
final Node block = nf.createBlock(ts.getLineno());
final Node init =
new Node(Token1.EXPR_VOID, nf.createAssignment(Token1.ASSIGN,
nf.createName(tempName),
nf.createCallOrNew(Token1.NEW, nf.createName("Array"))),
ts.getLineno());
block.addChildToBack(init);
block.addChildToBack(arrayComprehension(tempName, expr));
scopeNode.addChildToBack(block);
scopeNode.addChildToBack(nf.createName(tempName));
return scopeNode;
}
finally
{
popScope();
}
}
else
{
if (!after_lb_or_comma)
{
reportError("msg.no.bracket.arg");
}
elems.add(assignExpr(false));
after_lb_or_comma = false;
}
}
return nf.createArrayLiteral(elems, skipCount, destructuringLen);
}
case Token1.LC:
{
final ObjArray elems = new ObjArray();
decompiler.addToken(Token1.LC);
if (!matchToken(Token1.RC))
{
boolean first = true;
commaloop: do
{
Object property;
if (!first)
decompiler.addToken(Token1.COMMA);
else
first = false;
tt = peekToken();
switch (tt)
{
case Token1.NAME:
case Token1.STRING:
consumeToken();
// map NAMEs to STRINGs in object literal context
// but tell the decompiler the proper type
String s = ts.getString();
if (tt == Token1.NAME)
{
if (s.equals("get") && peekToken() == Token1.NAME)
{
decompiler.addToken(Token1.GET);
consumeToken();
s = ts.getString();
decompiler.addName(s);
property = ScriptRuntime.getIndexObject(s);
if (!getterSetterProperty(elems, property, true))
break commaloop;
break;
}
else if (s.equals("set") && peekToken() == Token1.NAME)
{
decompiler.addToken(Token1.SET);
consumeToken();
s = ts.getString();
decompiler.addName(s);
property = ScriptRuntime.getIndexObject(s);
if (!getterSetterProperty(elems, property, false))
break commaloop;
break;
}
decompiler.addName(s);
}
else
{
decompiler.addString(s);
}
property = ScriptRuntime.getIndexObject(s);
plainProperty(elems, property);
break;
case Token1.NUMBER:
consumeToken();
final double n = ts.getNumber();
decompiler.addNumber(n);
property = ScriptRuntime.getIndexObject(n);
plainProperty(elems, property);
break;
case Token1.RC:
// trailing comma is OK.
break commaloop;
default:
reportError("msg.bad.prop");
break commaloop;
}
}
while (matchToken(Token1.COMMA));
mustMatchToken(Token1.RC, "msg.no.brace.prop");
}
decompiler.addToken(Token1.RC);
return nf.createObjectLiteral(elems);
}
case Token1.LET:
decompiler.addToken(Token1.LET);
return let(false);
case Token1.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.'
*/
decompiler.addToken(Token1.LP);
pn = expr(false);
pn.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE);
decompiler.addToken(Token1.RP);
mustMatchToken(Token1.RP, "msg.no.paren");
return pn;
case Token1.XMLATTR:
mustHaveXML();
decompiler.addToken(Token1.XMLATTR);
pn = attributeAccess(null, 0);
return pn;
case Token1.NAME:
{
final String name = ts.getString();
if ((ttFlagged & TI_CHECK_LABEL) != 0)
{
if (peekToken() == Token1.COLON)
{
// Do not consume colon, it is used as unwind indicator
// to return to statementHelper.
// XXX Better way?
return nf.createLabel(ts.getLineno());
}
}
decompiler.addName(name);
if (compilerEnv.isXmlAvailable())
{
pn = propertyName(null, name, 0);
}
else
{
pn = nf.createName(name);
}
return pn;
}
case Token1.NUMBER:
{
final double n = ts.getNumber();
decompiler.addNumber(n);
return nf.createNumber(n);
}
case Token1.STRING:
{
final String s = ts.getString();
decompiler.addString(s);
return nf.createString(s);
}
case Token1.DIV:
case Token1.ASSIGN_DIV:
{
// Got / or /= which should be treated as regexp in fact
ts.readRegExp(tt);
final String flags = ts.regExpFlags;
ts.regExpFlags = null;
final String re = ts.getString();
decompiler.addRegexp(re, flags);
final int index = currentScriptOrFn.addRegexp(re, flags);
return nf.createRegExp(index);
}
case Token1.NULL:
case Token1.THIS:
case Token1.FALSE:
case Token1.TRUE:
decompiler.addToken(tt);
return nf.createLeaf(tt);
case Token1.RESERVED:
reportError("msg.reserved.id");
break;
case Token1.ERROR:
/* the scanner or one of its subroutines reported the error. */
break;
case Token1.EOF:
reportError("msg.unexpected.eof");
break;
default:
reportError("msg.syntax");
break;
}
return null; // should never reach here
}
private void plainProperty(final ObjArray elems, final Object property) throws IOException
{
mustMatchToken(Token1.COLON, "msg.no.colon.prop");
// OBJLIT is used as ':' in object literal for
// decompilation to solve spacing ambiguity.
decompiler.addToken(Token1.OBJECTLIT);
elems.add(property);
elems.add(assignExpr(false));
}
private boolean getterSetterProperty(final ObjArray elems, final Object property,
final boolean isGetter) throws IOException
{
final Node f = function(FunctionNode.FUNCTION_EXPRESSION);
if (f.getType() != Token1.FUNCTION)
{
reportError("msg.bad.prop");
return false;
}
final int fnIndex = f.getExistingIntProp(Node.FUNCTION_PROP);
final FunctionNode fn = currentScriptOrFn.getFunctionNode(fnIndex);
if (fn.getFunctionName().length() != 0)
{
reportError("msg.bad.prop");
return false;
}
elems.add(property);
if (isGetter)
{
elems.add(nf.createUnary(Token1.GET, f));
}
else
{
elems.add(nf.createUnary(Token1.SET, f));
}
return true;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy