2508 lines
83 KiB
Java
2508 lines
83 KiB
Java
/* -*- 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.Reader;
|
|
import java.io.IOException;
|
|
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 TokenStream
|
|
*
|
|
* @author Mike McCabe
|
|
* @author Brendan Eich
|
|
*/
|
|
|
|
public class Parser
|
|
{
|
|
// 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 TokenStream 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<String,Node> 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 Parser(CompilerEnvirons compilerEnv, ErrorReporter errorReporter)
|
|
{
|
|
this.compilerEnv = compilerEnv;
|
|
this.errorReporter = errorReporter;
|
|
}
|
|
|
|
protected Decompiler createDecompiler(CompilerEnvirons compilerEnv)
|
|
{
|
|
return new Decompiler();
|
|
}
|
|
|
|
void addStrictWarning(String messageId, String messageArg)
|
|
{
|
|
if (compilerEnv.isStrictMode())
|
|
addWarning(messageId, messageArg);
|
|
}
|
|
|
|
void addWarning(String messageId, String messageArg)
|
|
{
|
|
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(String messageId)
|
|
{
|
|
++syntaxErrorCount;
|
|
String message = ScriptRuntime.getMessage0(messageId);
|
|
errorReporter.error(message, sourceURI, ts.getLineno(),
|
|
ts.getLine(), ts.getOffset());
|
|
}
|
|
|
|
void addError(String messageId, String messageArg)
|
|
{
|
|
++syntaxErrorCount;
|
|
String message = ScriptRuntime.getMessage1(messageId, messageArg);
|
|
errorReporter.error(message, sourceURI, ts.getLineno(),
|
|
ts.getLine(), ts.getOffset());
|
|
}
|
|
|
|
RuntimeException reportError(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 == Token.EOF) {
|
|
tt = ts.getToken();
|
|
if (tt == Token.EOL) {
|
|
do {
|
|
tt = ts.getToken();
|
|
} while (tt == Token.EOL);
|
|
tt |= TI_AFTER_EOL;
|
|
}
|
|
currentFlaggedToken = tt;
|
|
}
|
|
return tt & CLEAR_TI_MASK;
|
|
}
|
|
|
|
private int peekFlaggedToken()
|
|
throws IOException
|
|
{
|
|
peekToken();
|
|
return currentFlaggedToken;
|
|
}
|
|
|
|
private void consumeToken()
|
|
{
|
|
currentFlaggedToken = Token.EOF;
|
|
}
|
|
|
|
private int nextToken()
|
|
throws IOException
|
|
{
|
|
int tt = peekToken();
|
|
consumeToken();
|
|
return tt;
|
|
}
|
|
|
|
private int nextFlaggedToken()
|
|
throws IOException
|
|
{
|
|
peekToken();
|
|
int ttFlagged = currentFlaggedToken;
|
|
consumeToken();
|
|
return ttFlagged;
|
|
}
|
|
|
|
private boolean matchToken(int toMatch)
|
|
throws IOException
|
|
{
|
|
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 = Token.EOL;
|
|
}
|
|
return tt;
|
|
}
|
|
|
|
private void setCheckForLabel()
|
|
{
|
|
if ((currentFlaggedToken & CLEAR_TI_MASK) != Token.NAME)
|
|
throw Kit.codeBug();
|
|
currentFlaggedToken |= TI_CHECK_LABEL;
|
|
}
|
|
|
|
private void mustMatchToken(int toMatch, 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(Node node) {
|
|
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(Node loopLabel, boolean doPushScope)
|
|
{
|
|
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(boolean doPopScope)
|
|
{
|
|
loopSet.pop();
|
|
loopAndSwitchSet.pop();
|
|
if (doPopScope) {
|
|
popScope();
|
|
}
|
|
}
|
|
|
|
private Node enterSwitch(Node switchSelector, int lineno)
|
|
{
|
|
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(String sourceString,
|
|
String sourceURI, int lineno)
|
|
{
|
|
this.sourceURI = sourceURI;
|
|
this.ts = new TokenStream(this, null, sourceString, lineno);
|
|
try {
|
|
return parse();
|
|
} catch (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(Reader sourceReader,
|
|
String sourceURI, int lineno)
|
|
throws IOException
|
|
{
|
|
this.sourceURI = sourceURI;
|
|
this.ts = new TokenStream(this, sourceReader, null, lineno);
|
|
return parse();
|
|
}
|
|
|
|
private ScriptOrFnNode parse()
|
|
throws IOException
|
|
{
|
|
this.decompiler = createDecompiler(compilerEnv);
|
|
this.nf = new IRFactory(this);
|
|
currentScriptOrFn = nf.createScript();
|
|
currentScope = currentScriptOrFn;
|
|
int sourceStartOffset = decompiler.getCurrentOffset();
|
|
this.encodedSource = null;
|
|
decompiler.addToken(Token.SCRIPT);
|
|
|
|
this.currentFlaggedToken = Token.EOF;
|
|
this.syntaxErrorCount = 0;
|
|
|
|
int baseLineno = ts.getLineno(); // line number where source starts
|
|
|
|
/* so we have something to add nodes to until
|
|
* we've collected all the source */
|
|
Node pn = nf.createLeaf(Token.BLOCK);
|
|
|
|
try {
|
|
for (;;) {
|
|
int tt = peekToken();
|
|
|
|
if (tt <= Token.EOF) {
|
|
break;
|
|
}
|
|
|
|
Node n;
|
|
if (tt == Token.FUNCTION) {
|
|
consumeToken();
|
|
try {
|
|
n = function(calledByCompileFunction
|
|
? FunctionNode.FUNCTION_EXPRESSION
|
|
: FunctionNode.FUNCTION_STATEMENT);
|
|
} catch (ParserException e) {
|
|
break;
|
|
}
|
|
} else {
|
|
n = statement();
|
|
}
|
|
nf.addChildToBack(pn, n);
|
|
}
|
|
} catch (StackOverflowError ex) {
|
|
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());
|
|
|
|
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;
|
|
Node pn = nf.createBlock(ts.getLineno());
|
|
try {
|
|
bodyLoop: for (;;) {
|
|
Node n;
|
|
int tt = peekToken();
|
|
switch (tt) {
|
|
case Token.ERROR:
|
|
case Token.EOF:
|
|
case Token.RC:
|
|
break bodyLoop;
|
|
|
|
case Token.FUNCTION:
|
|
consumeToken();
|
|
n = function(FunctionNode.FUNCTION_STATEMENT);
|
|
break;
|
|
default:
|
|
n = statement();
|
|
break;
|
|
}
|
|
nf.addChildToBack(pn, n);
|
|
}
|
|
} catch (ParserException e) {
|
|
// Ignore it
|
|
} finally {
|
|
--nestingOfFunction;
|
|
}
|
|
|
|
return pn;
|
|
}
|
|
|
|
private Node function(int functionType)
|
|
throws IOException, ParserException
|
|
{
|
|
int syntheticType = functionType;
|
|
int baseLineno = ts.getLineno(); // line number where source starts
|
|
|
|
int functionSourceStart = decompiler.markFunctionStart(functionType);
|
|
String name;
|
|
Node memberExprNode = null;
|
|
if (matchToken(Token.NAME)) {
|
|
name = ts.getString();
|
|
decompiler.addName(name);
|
|
if (!matchToken(Token.LP)) {
|
|
if (compilerEnv.isAllowMemberExprAsFunctionName()) {
|
|
// Extension to ECMA: if 'function <name>' does not follow
|
|
// by '(', assume <name> starts memberExpr
|
|
Node memberExprHead = nf.createName(name);
|
|
name = "";
|
|
memberExprNode = memberExprTail(false, memberExprHead);
|
|
}
|
|
mustMatchToken(Token.LP, "msg.no.paren.parms");
|
|
}
|
|
} else if (matchToken(Token.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(Token.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(Token.FUNCTION, false, name);
|
|
}
|
|
|
|
boolean nested = insideFunction();
|
|
|
|
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;
|
|
}
|
|
int functionIndex = currentScriptOrFn.addFunction(fnNode);
|
|
|
|
int functionSourceEnd;
|
|
|
|
ScriptOrFnNode savedScriptOrFn = currentScriptOrFn;
|
|
currentScriptOrFn = fnNode;
|
|
Node.Scope savedCurrentScope = currentScope;
|
|
currentScope = fnNode;
|
|
int savedNestingOfWith = nestingOfWith;
|
|
nestingOfWith = 0;
|
|
Map<String,Node> savedLabelSet = labelSet;
|
|
labelSet = null;
|
|
ObjArray savedLoopSet = loopSet;
|
|
loopSet = null;
|
|
ObjArray savedLoopAndSwitchSet = loopAndSwitchSet;
|
|
loopAndSwitchSet = null;
|
|
int savedFunctionEndFlags = endFlags;
|
|
endFlags = 0;
|
|
|
|
Node destructuring = null;
|
|
Node body;
|
|
try {
|
|
decompiler.addToken(Token.LP);
|
|
if (!matchToken(Token.RP)) {
|
|
boolean first = true;
|
|
do {
|
|
if (!first)
|
|
decompiler.addToken(Token.COMMA);
|
|
first = false;
|
|
int tt = peekToken();
|
|
if (tt == Token.LB || tt == Token.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(Token.COMMA);
|
|
}
|
|
String parmName = currentScriptOrFn.getNextTempName();
|
|
defineSymbol(Token.LP, false, parmName);
|
|
destructuring.addChildToBack(
|
|
nf.createDestructuringAssignment(Token.VAR,
|
|
primaryExpr(), nf.createName(parmName)));
|
|
} else {
|
|
mustMatchToken(Token.NAME, "msg.no.parm");
|
|
String s = ts.getString();
|
|
defineSymbol(Token.LP, false, s);
|
|
decompiler.addName(s);
|
|
}
|
|
} while (matchToken(Token.COMMA));
|
|
|
|
mustMatchToken(Token.RP, "msg.no.paren.after.parms");
|
|
}
|
|
decompiler.addToken(Token.RP);
|
|
|
|
mustMatchToken(Token.LC, "msg.no.brace.body");
|
|
decompiler.addEOL(Token.LC);
|
|
body = parseFunctionBody();
|
|
if (destructuring != null) {
|
|
body.addChildToFront(
|
|
new Node(Token.EXPR_VOID, destructuring, ts.getLineno()));
|
|
}
|
|
mustMatchToken(Token.RC, "msg.no.brace.after.body");
|
|
|
|
if (compilerEnv.isStrictMode() && !body.hasConsistentReturnUsage())
|
|
{
|
|
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(Token.FUNCTION, false, name);
|
|
}
|
|
|
|
decompiler.addToken(Token.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(Token.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(Token.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(Node scope)
|
|
throws IOException
|
|
{
|
|
Node pn = scope != null ? scope : nf.createBlock(ts.getLineno());
|
|
|
|
int tt;
|
|
while ((tt = peekToken()) > Token.EOF && tt != Token.RC) {
|
|
nf.addChildToBack(pn, statement());
|
|
}
|
|
|
|
return pn;
|
|
}
|
|
|
|
private Node condition()
|
|
throws IOException, ParserException
|
|
{
|
|
mustMatchToken(Token.LP, "msg.no.paren.cond");
|
|
decompiler.addToken(Token.LP);
|
|
Node pn = expr(false);
|
|
mustMatchToken(Token.RP, "msg.no.paren.after.cond");
|
|
decompiler.addToken(Token.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() == Token.SETNAME || pn.getType() == Token.SETPROP ||
|
|
pn.getType() == Token.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;
|
|
|
|
int tt = peekTokenOrEOL();
|
|
if (tt == Token.NAME) {
|
|
consumeToken();
|
|
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 {
|
|
Node pn = statementHelper(null);
|
|
if (pn != null) {
|
|
if (compilerEnv.isStrictMode() && !pn.hasSideEffects())
|
|
addStrictWarning("msg.no.side.effects", "");
|
|
return pn;
|
|
}
|
|
} catch (ParserException e) { }
|
|
|
|
// skip to end of statement
|
|
int lineno = ts.getLineno();
|
|
guessingStatementEnd: for (;;) {
|
|
int tt = peekTokenOrEOL();
|
|
consumeToken();
|
|
switch (tt) {
|
|
case Token.ERROR:
|
|
case Token.EOF:
|
|
case Token.EOL:
|
|
case Token.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 Token.IF: {
|
|
consumeToken();
|
|
|
|
decompiler.addToken(Token.IF);
|
|
int lineno = ts.getLineno();
|
|
Node cond = condition();
|
|
decompiler.addEOL(Token.LC);
|
|
Node ifTrue = statement();
|
|
Node ifFalse = null;
|
|
if (matchToken(Token.ELSE)) {
|
|
decompiler.addToken(Token.RC);
|
|
decompiler.addToken(Token.ELSE);
|
|
decompiler.addEOL(Token.LC);
|
|
ifFalse = statement();
|
|
}
|
|
decompiler.addEOL(Token.RC);
|
|
pn = nf.createIf(cond, ifTrue, ifFalse, lineno);
|
|
return pn;
|
|
}
|
|
|
|
case Token.SWITCH: {
|
|
consumeToken();
|
|
|
|
decompiler.addToken(Token.SWITCH);
|
|
int lineno = ts.getLineno();
|
|
mustMatchToken(Token.LP, "msg.no.paren.switch");
|
|
decompiler.addToken(Token.LP);
|
|
pn = enterSwitch(expr(false), lineno);
|
|
try {
|
|
mustMatchToken(Token.RP, "msg.no.paren.after.switch");
|
|
decompiler.addToken(Token.RP);
|
|
mustMatchToken(Token.LC, "msg.no.brace.switch");
|
|
decompiler.addEOL(Token.LC);
|
|
|
|
boolean hasDefault = false;
|
|
switchLoop: for (;;) {
|
|
tt = nextToken();
|
|
Node caseExpression;
|
|
switch (tt) {
|
|
case Token.RC:
|
|
break switchLoop;
|
|
|
|
case Token.CASE:
|
|
decompiler.addToken(Token.CASE);
|
|
caseExpression = expr(false);
|
|
mustMatchToken(Token.COLON, "msg.no.colon.case");
|
|
decompiler.addEOL(Token.COLON);
|
|
break;
|
|
|
|
case Token.DEFAULT:
|
|
if (hasDefault) {
|
|
reportError("msg.double.switch.default");
|
|
}
|
|
decompiler.addToken(Token.DEFAULT);
|
|
hasDefault = true;
|
|
caseExpression = null;
|
|
mustMatchToken(Token.COLON, "msg.no.colon.case");
|
|
decompiler.addEOL(Token.COLON);
|
|
break;
|
|
|
|
default:
|
|
reportError("msg.bad.switch");
|
|
break switchLoop;
|
|
}
|
|
|
|
Node block = nf.createLeaf(Token.BLOCK);
|
|
while ((tt = peekToken()) != Token.RC
|
|
&& tt != Token.CASE
|
|
&& tt != Token.DEFAULT
|
|
&& tt != Token.EOF)
|
|
{
|
|
nf.addChildToBack(block, statement());
|
|
}
|
|
|
|
// caseExpression == null => add default label
|
|
nf.addSwitchCase(pn, caseExpression, block);
|
|
}
|
|
decompiler.addEOL(Token.RC);
|
|
nf.closeSwitch(pn);
|
|
} finally {
|
|
exitSwitch();
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
case Token.WHILE: {
|
|
consumeToken();
|
|
decompiler.addToken(Token.WHILE);
|
|
|
|
Node loop = enterLoop(statementLabel, true);
|
|
try {
|
|
Node cond = condition();
|
|
decompiler.addEOL(Token.LC);
|
|
Node body = statement();
|
|
decompiler.addEOL(Token.RC);
|
|
pn = nf.createWhile(loop, cond, body);
|
|
} finally {
|
|
exitLoop(true);
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
case Token.DO: {
|
|
consumeToken();
|
|
decompiler.addToken(Token.DO);
|
|
decompiler.addEOL(Token.LC);
|
|
|
|
Node loop = enterLoop(statementLabel, true);
|
|
try {
|
|
Node body = statement();
|
|
decompiler.addToken(Token.RC);
|
|
mustMatchToken(Token.WHILE, "msg.no.while.do");
|
|
decompiler.addToken(Token.WHILE);
|
|
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(Token.SEMI);
|
|
decompiler.addEOL(Token.SEMI);
|
|
return pn;
|
|
}
|
|
|
|
case Token.FOR: {
|
|
consumeToken();
|
|
boolean isForEach = false;
|
|
decompiler.addToken(Token.FOR);
|
|
|
|
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(Token.NAME)) {
|
|
decompiler.addName(ts.getString());
|
|
if (ts.getString().equals("each")) {
|
|
isForEach = true;
|
|
} else {
|
|
reportError("msg.no.paren.for");
|
|
}
|
|
}
|
|
|
|
mustMatchToken(Token.LP, "msg.no.paren.for");
|
|
decompiler.addToken(Token.LP);
|
|
tt = peekToken();
|
|
if (tt == Token.SEMI) {
|
|
init = nf.createLeaf(Token.EMPTY);
|
|
} else {
|
|
if (tt == Token.VAR || tt == Token.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(Token.IN)) {
|
|
decompiler.addToken(Token.IN);
|
|
// 'cond' is the object over which we're iterating
|
|
cond = expr(false);
|
|
} else { // ordinary for loop
|
|
mustMatchToken(Token.SEMI, "msg.no.semi.for");
|
|
decompiler.addToken(Token.SEMI);
|
|
if (peekToken() == Token.SEMI) {
|
|
// no loop condition
|
|
cond = nf.createLeaf(Token.EMPTY);
|
|
} else {
|
|
cond = expr(false);
|
|
}
|
|
|
|
mustMatchToken(Token.SEMI, "msg.no.semi.for.cond");
|
|
decompiler.addToken(Token.SEMI);
|
|
if (peekToken() == Token.RP) {
|
|
incr = nf.createLeaf(Token.EMPTY);
|
|
} else {
|
|
incr = expr(false);
|
|
}
|
|
}
|
|
|
|
mustMatchToken(Token.RP, "msg.no.paren.for.ctrl");
|
|
decompiler.addToken(Token.RP);
|
|
decompiler.addEOL(Token.LC);
|
|
body = statement();
|
|
decompiler.addEOL(Token.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 Token.TRY: {
|
|
consumeToken();
|
|
int lineno = ts.getLineno();
|
|
|
|
Node tryblock;
|
|
Node catchblocks = null;
|
|
Node finallyblock = null;
|
|
|
|
decompiler.addToken(Token.TRY);
|
|
if (peekToken() != Token.LC) {
|
|
reportError("msg.no.brace.try");
|
|
}
|
|
decompiler.addEOL(Token.LC);
|
|
tryblock = statement();
|
|
decompiler.addEOL(Token.RC);
|
|
|
|
catchblocks = nf.createLeaf(Token.BLOCK);
|
|
|
|
boolean sawDefaultCatch = false;
|
|
int peek = peekToken();
|
|
if (peek == Token.CATCH) {
|
|
while (matchToken(Token.CATCH)) {
|
|
if (sawDefaultCatch) {
|
|
reportError("msg.catch.unreachable");
|
|
}
|
|
decompiler.addToken(Token.CATCH);
|
|
mustMatchToken(Token.LP, "msg.no.paren.catch");
|
|
decompiler.addToken(Token.LP);
|
|
|
|
mustMatchToken(Token.NAME, "msg.bad.catchcond");
|
|
String varName = ts.getString();
|
|
decompiler.addName(varName);
|
|
|
|
Node catchCond = null;
|
|
if (matchToken(Token.IF)) {
|
|
decompiler.addToken(Token.IF);
|
|
catchCond = expr(false);
|
|
} else {
|
|
sawDefaultCatch = true;
|
|
}
|
|
|
|
mustMatchToken(Token.RP, "msg.bad.catchcond");
|
|
decompiler.addToken(Token.RP);
|
|
mustMatchToken(Token.LC, "msg.no.brace.catchblock");
|
|
decompiler.addEOL(Token.LC);
|
|
|
|
nf.addChildToBack(catchblocks,
|
|
nf.createCatch(varName, catchCond,
|
|
statements(null),
|
|
ts.getLineno()));
|
|
|
|
mustMatchToken(Token.RC, "msg.no.brace.after.body");
|
|
decompiler.addEOL(Token.RC);
|
|
}
|
|
} else if (peek != Token.FINALLY) {
|
|
mustMatchToken(Token.FINALLY, "msg.try.no.catchfinally");
|
|
}
|
|
|
|
if (matchToken(Token.FINALLY)) {
|
|
decompiler.addToken(Token.FINALLY);
|
|
decompiler.addEOL(Token.LC);
|
|
finallyblock = statement();
|
|
decompiler.addEOL(Token.RC);
|
|
}
|
|
|
|
pn = nf.createTryCatchFinally(tryblock, catchblocks,
|
|
finallyblock, lineno);
|
|
|
|
return pn;
|
|
}
|
|
|
|
case Token.THROW: {
|
|
consumeToken();
|
|
if (peekTokenOrEOL() == Token.EOL) {
|
|
// ECMAScript does not allow new lines before throw expression,
|
|
// see bug 256617
|
|
reportError("msg.bad.throw.eol");
|
|
}
|
|
|
|
int lineno = ts.getLineno();
|
|
decompiler.addToken(Token.THROW);
|
|
pn = nf.createThrow(expr(false), lineno);
|
|
break;
|
|
}
|
|
|
|
case Token.BREAK: {
|
|
consumeToken();
|
|
int lineno = ts.getLineno();
|
|
|
|
decompiler.addToken(Token.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 Token.CONTINUE: {
|
|
consumeToken();
|
|
int lineno = ts.getLineno();
|
|
|
|
decompiler.addToken(Token.CONTINUE);
|
|
|
|
Node loop;
|
|
// matchJumpLabelName only matches if there is one
|
|
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 Token.WITH: {
|
|
consumeToken();
|
|
|
|
decompiler.addToken(Token.WITH);
|
|
int lineno = ts.getLineno();
|
|
mustMatchToken(Token.LP, "msg.no.paren.with");
|
|
decompiler.addToken(Token.LP);
|
|
Node obj = expr(false);
|
|
mustMatchToken(Token.RP, "msg.no.paren.after.with");
|
|
decompiler.addToken(Token.RP);
|
|
decompiler.addEOL(Token.LC);
|
|
|
|
++nestingOfWith;
|
|
Node body;
|
|
try {
|
|
body = statement();
|
|
} finally {
|
|
--nestingOfWith;
|
|
}
|
|
|
|
decompiler.addEOL(Token.RC);
|
|
|
|
pn = nf.createWith(obj, body, lineno);
|
|
return pn;
|
|
}
|
|
|
|
case Token.CONST:
|
|
case Token.VAR: {
|
|
consumeToken();
|
|
decompiler.addToken(tt);
|
|
pn = variables(false, tt);
|
|
break;
|
|
}
|
|
|
|
case Token.LET: {
|
|
consumeToken();
|
|
decompiler.addToken(Token.LET);
|
|
if (peekToken() == Token.LP) {
|
|
return let(true);
|
|
} else {
|
|
pn = variables(false, tt);
|
|
if (peekToken() == Token.SEMI)
|
|
break;
|
|
return pn;
|
|
}
|
|
}
|
|
|
|
case Token.RETURN:
|
|
case Token.YIELD: {
|
|
pn = returnOrYield(tt, false);
|
|
break;
|
|
}
|
|
|
|
case Token.DEBUGGER:
|
|
consumeToken();
|
|
decompiler.addToken(Token.DEBUGGER);
|
|
pn = nf.createDebugger(ts.getLineno());
|
|
break;
|
|
|
|
case Token.LC:
|
|
consumeToken();
|
|
if (statementLabel != null) {
|
|
decompiler.addToken(Token.LC);
|
|
}
|
|
Node scope = nf.createScopeNode(Token.BLOCK, ts.getLineno());
|
|
pushScope(scope);
|
|
try {
|
|
statements(scope);
|
|
mustMatchToken(Token.RC, "msg.no.brace.block");
|
|
if (statementLabel != null) {
|
|
decompiler.addEOL(Token.RC);
|
|
}
|
|
return scope;
|
|
} finally {
|
|
popScope();
|
|
}
|
|
|
|
case Token.ERROR:
|
|
// Fall thru, to have a node for error recovery to work on
|
|
case Token.SEMI:
|
|
consumeToken();
|
|
pn = nf.createLeaf(Token.EMPTY);
|
|
return pn;
|
|
|
|
case Token.FUNCTION: {
|
|
consumeToken();
|
|
pn = function(FunctionNode.FUNCTION_EXPRESSION_STATEMENT);
|
|
return pn;
|
|
}
|
|
|
|
case Token.DEFAULT :
|
|
consumeToken();
|
|
mustHaveXML();
|
|
|
|
decompiler.addToken(Token.DEFAULT);
|
|
int nsLine = ts.getLineno();
|
|
|
|
if (!(matchToken(Token.NAME)
|
|
&& ts.getString().equals("xml")))
|
|
{
|
|
reportError("msg.bad.namespace");
|
|
}
|
|
decompiler.addName(" xml");
|
|
|
|
if (!(matchToken(Token.NAME)
|
|
&& ts.getString().equals("namespace")))
|
|
{
|
|
reportError("msg.bad.namespace");
|
|
}
|
|
decompiler.addName(" namespace");
|
|
|
|
if (!matchToken(Token.ASSIGN)) {
|
|
reportError("msg.bad.namespace");
|
|
}
|
|
decompiler.addToken(Token.ASSIGN);
|
|
|
|
Node expr = expr(false);
|
|
pn = nf.createDefaultNamespace(expr, nsLine);
|
|
break;
|
|
|
|
case Token.NAME: {
|
|
int lineno = ts.getLineno();
|
|
String name = ts.getString();
|
|
setCheckForLabel();
|
|
pn = expr(false);
|
|
if (pn.getType() != Token.LABEL) {
|
|
pn = nf.createExprStatement(pn, lineno);
|
|
} else {
|
|
// Parsed the label: push back token should be
|
|
// colon that primaryExpr left untouched.
|
|
if (peekToken() != Token.COLON) Kit.codeBug();
|
|
consumeToken();
|
|
// depend on decompiling lookahead to guess that that
|
|
// last name was a label.
|
|
decompiler.addName(name);
|
|
decompiler.addEOL(Token.COLON);
|
|
|
|
if (labelSet == null) {
|
|
labelSet = new HashMap<String,Node>();
|
|
} 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: {
|
|
int lineno = ts.getLineno();
|
|
pn = expr(false);
|
|
pn = nf.createExprStatement(pn, lineno);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int ttFlagged = peekFlaggedToken();
|
|
switch (ttFlagged & CLEAR_TI_MASK) {
|
|
case Token.SEMI:
|
|
// Consume ';' as a part of expression
|
|
consumeToken();
|
|
break;
|
|
case Token.ERROR:
|
|
case Token.EOF:
|
|
case Token.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(Token.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(int before, int after, int mask)
|
|
{
|
|
return ((before & mask) != mask) && ((after & mask) == mask);
|
|
}
|
|
|
|
private Node returnOrYield(int tt, boolean exprContext)
|
|
throws IOException, ParserException
|
|
{
|
|
if (!insideFunction()) {
|
|
reportError(tt == Token.RETURN ? "msg.bad.return"
|
|
: "msg.bad.yield");
|
|
}
|
|
consumeToken();
|
|
decompiler.addToken(tt);
|
|
int lineno = ts.getLineno();
|
|
|
|
Node e;
|
|
/* This is ugly, but we don't want to require a semicolon. */
|
|
switch (peekTokenOrEOL()) {
|
|
case Token.SEMI:
|
|
case Token.RC:
|
|
case Token.EOF:
|
|
case Token.EOL:
|
|
case Token.ERROR:
|
|
case Token.RB:
|
|
case Token.RP:
|
|
case Token.YIELD:
|
|
e = null;
|
|
break;
|
|
default:
|
|
e = expr(false);
|
|
break;
|
|
}
|
|
|
|
int before = endFlags;
|
|
Node ret;
|
|
|
|
if (tt == Token.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(Token.EXPR_VOID, ret, lineno);
|
|
}
|
|
|
|
// see if we are mixing yields and value returns.
|
|
if (nowAllSet(before, endFlags,
|
|
Node.END_YIELDS|Node.END_RETURNS_VALUE))
|
|
{
|
|
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(boolean inFor, int declType)
|
|
throws IOException, ParserException
|
|
{
|
|
Node result = nf.createVariables(declType, ts.getLineno());
|
|
boolean first = true;
|
|
for (;;) {
|
|
Node destructuring = null;
|
|
String s = null;
|
|
int tt = peekToken();
|
|
if (tt == Token.LB || tt == Token.LC) {
|
|
// Destructuring assignment, e.g., var [a,b] = ...
|
|
destructuring = primaryExpr();
|
|
} else {
|
|
// Simple variable name
|
|
mustMatchToken(Token.NAME, "msg.bad.var");
|
|
s = ts.getString();
|
|
|
|
if (!first)
|
|
decompiler.addToken(Token.COMMA);
|
|
first = false;
|
|
|
|
decompiler.addName(s);
|
|
defineSymbol(declType, inFor, s);
|
|
}
|
|
|
|
Node init = null;
|
|
if (matchToken(Token.ASSIGN)) {
|
|
decompiler.addToken(Token.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 {
|
|
Node name = nf.createName(s);
|
|
if (init != null)
|
|
nf.addChildToBack(name, init);
|
|
nf.addChildToBack(result, name);
|
|
}
|
|
|
|
if (!matchToken(Token.COMMA))
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
private Node let(boolean isStatement)
|
|
throws IOException, ParserException
|
|
{
|
|
mustMatchToken(Token.LP, "msg.no.paren.after.let");
|
|
decompiler.addToken(Token.LP);
|
|
Node result = nf.createScopeNode(Token.LET, ts.getLineno());
|
|
pushScope(result);
|
|
try {
|
|
Node vars = variables(false, Token.LET);
|
|
nf.addChildToBack(result, vars);
|
|
mustMatchToken(Token.RP, "msg.no.paren.let");
|
|
decompiler.addToken(Token.RP);
|
|
if (isStatement && peekToken() == Token.LC) {
|
|
// let statement
|
|
consumeToken();
|
|
decompiler.addEOL(Token.LC);
|
|
nf.addChildToBack(result, statements(null));
|
|
mustMatchToken(Token.RC, "msg.no.curly.let");
|
|
decompiler.addToken(Token.RC);
|
|
} else {
|
|
// let expression
|
|
result.setType(Token.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(int declType, boolean ignoreNotInBlock, String name) {
|
|
Node.Scope definingScope = currentScope.getDefiningScope(name);
|
|
Node.Scope.Symbol symbol = definingScope != null
|
|
? definingScope.getSymbol(name)
|
|
: null;
|
|
boolean error = false;
|
|
if (symbol != null && (symbol.declType == Token.CONST ||
|
|
declType == Token.CONST))
|
|
{
|
|
error = true;
|
|
} else {
|
|
switch (declType) {
|
|
case Token.LET:
|
|
if (symbol != null && definingScope == currentScope) {
|
|
error = symbol.declType == Token.LET;
|
|
}
|
|
int currentScopeType = currentScope.getType();
|
|
if (!ignoreNotInBlock &&
|
|
((currentScopeType == Token.LOOP) ||
|
|
(currentScopeType == Token.IF)))
|
|
{
|
|
addError("msg.let.decl.not.in.block");
|
|
}
|
|
currentScope.putSymbol(name,
|
|
new Node.Scope.Symbol(declType, name));
|
|
break;
|
|
|
|
case Token.VAR:
|
|
case Token.CONST:
|
|
case Token.FUNCTION:
|
|
if (symbol != null) {
|
|
if (symbol.declType == Token.VAR)
|
|
addStrictWarning("msg.var.redecl", name);
|
|
else if (symbol.declType == Token.LP) {
|
|
addStrictWarning("msg.var.hides.arg", name);
|
|
}
|
|
} else {
|
|
currentScriptOrFn.putSymbol(name,
|
|
new Node.Scope.Symbol(declType, name));
|
|
}
|
|
break;
|
|
|
|
case Token.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 == Token.CONST ? "msg.const.redecl" :
|
|
symbol.declType == Token.LET ? "msg.let.redecl" :
|
|
symbol.declType == Token.VAR ? "msg.var.redecl" :
|
|
symbol.declType == Token.FUNCTION ? "msg.fn.redecl" :
|
|
"msg.parm.redecl", name);
|
|
}
|
|
}
|
|
|
|
private Node expr(boolean inForInit)
|
|
throws IOException, ParserException
|
|
{
|
|
Node pn = assignExpr(inForInit);
|
|
while (matchToken(Token.COMMA)) {
|
|
decompiler.addToken(Token.COMMA);
|
|
if (compilerEnv.isStrictMode() && !pn.hasSideEffects())
|
|
addStrictWarning("msg.no.side.effects", "");
|
|
if (peekToken() == Token.YIELD) {
|
|
reportError("msg.yield.parenthesized");
|
|
}
|
|
pn = nf.createBinary(Token.COMMA, pn, assignExpr(inForInit));
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
private Node assignExpr(boolean inForInit)
|
|
throws IOException, ParserException
|
|
{
|
|
int tt = peekToken();
|
|
if (tt == Token.YIELD) {
|
|
consumeToken();
|
|
return returnOrYield(tt, true);
|
|
}
|
|
Node pn = condExpr(inForInit);
|
|
|
|
tt = peekToken();
|
|
if (Token.FIRST_ASSIGN <= tt && tt <= Token.LAST_ASSIGN) {
|
|
consumeToken();
|
|
decompiler.addToken(tt);
|
|
pn = nf.createAssignment(tt, pn, assignExpr(inForInit));
|
|
}
|
|
|
|
return pn;
|
|
}
|
|
|
|
private Node condExpr(boolean inForInit)
|
|
throws IOException, ParserException
|
|
{
|
|
Node pn = orExpr(inForInit);
|
|
|
|
if (matchToken(Token.HOOK)) {
|
|
decompiler.addToken(Token.HOOK);
|
|
Node ifTrue = assignExpr(false);
|
|
mustMatchToken(Token.COLON, "msg.no.colon.cond");
|
|
decompiler.addToken(Token.COLON);
|
|
Node ifFalse = assignExpr(inForInit);
|
|
return nf.createCondExpr(pn, ifTrue, ifFalse);
|
|
}
|
|
|
|
return pn;
|
|
}
|
|
|
|
private Node orExpr(boolean inForInit)
|
|
throws IOException, ParserException
|
|
{
|
|
Node pn = andExpr(inForInit);
|
|
if (matchToken(Token.OR)) {
|
|
decompiler.addToken(Token.OR);
|
|
pn = nf.createBinary(Token.OR, pn, orExpr(inForInit));
|
|
}
|
|
|
|
return pn;
|
|
}
|
|
|
|
private Node andExpr(boolean inForInit)
|
|
throws IOException, ParserException
|
|
{
|
|
Node pn = bitOrExpr(inForInit);
|
|
if (matchToken(Token.AND)) {
|
|
decompiler.addToken(Token.AND);
|
|
pn = nf.createBinary(Token.AND, pn, andExpr(inForInit));
|
|
}
|
|
|
|
return pn;
|
|
}
|
|
|
|
private Node bitOrExpr(boolean inForInit)
|
|
throws IOException, ParserException
|
|
{
|
|
Node pn = bitXorExpr(inForInit);
|
|
while (matchToken(Token.BITOR)) {
|
|
decompiler.addToken(Token.BITOR);
|
|
pn = nf.createBinary(Token.BITOR, pn, bitXorExpr(inForInit));
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
private Node bitXorExpr(boolean inForInit)
|
|
throws IOException, ParserException
|
|
{
|
|
Node pn = bitAndExpr(inForInit);
|
|
while (matchToken(Token.BITXOR)) {
|
|
decompiler.addToken(Token.BITXOR);
|
|
pn = nf.createBinary(Token.BITXOR, pn, bitAndExpr(inForInit));
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
private Node bitAndExpr(boolean inForInit)
|
|
throws IOException, ParserException
|
|
{
|
|
Node pn = eqExpr(inForInit);
|
|
while (matchToken(Token.BITAND)) {
|
|
decompiler.addToken(Token.BITAND);
|
|
pn = nf.createBinary(Token.BITAND, pn, eqExpr(inForInit));
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
private Node eqExpr(boolean inForInit)
|
|
throws IOException, ParserException
|
|
{
|
|
Node pn = relExpr(inForInit);
|
|
for (;;) {
|
|
int tt = peekToken();
|
|
switch (tt) {
|
|
case Token.EQ:
|
|
case Token.NE:
|
|
case Token.SHEQ:
|
|
case Token.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 Token.EQ:
|
|
parseToken = Token.SHEQ;
|
|
break;
|
|
case Token.NE:
|
|
parseToken = Token.SHNE;
|
|
break;
|
|
case Token.SHEQ:
|
|
decompilerToken = Token.EQ;
|
|
break;
|
|
case Token.SHNE:
|
|
decompilerToken = Token.NE;
|
|
break;
|
|
}
|
|
}
|
|
decompiler.addToken(decompilerToken);
|
|
pn = nf.createBinary(parseToken, pn, relExpr(inForInit));
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
private Node relExpr(boolean inForInit)
|
|
throws IOException, ParserException
|
|
{
|
|
Node pn = shiftExpr();
|
|
for (;;) {
|
|
int tt = peekToken();
|
|
switch (tt) {
|
|
case Token.IN:
|
|
if (inForInit)
|
|
break;
|
|
// fall through
|
|
case Token.INSTANCEOF:
|
|
case Token.LE:
|
|
case Token.LT:
|
|
case Token.GE:
|
|
case Token.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 (;;) {
|
|
int tt = peekToken();
|
|
switch (tt) {
|
|
case Token.LSH:
|
|
case Token.URSH:
|
|
case Token.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 (;;) {
|
|
int tt = peekToken();
|
|
if (tt == Token.ADD || tt == Token.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 (;;) {
|
|
int tt = peekToken();
|
|
switch (tt) {
|
|
case Token.MUL:
|
|
case Token.DIV:
|
|
case Token.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 Token.VOID:
|
|
case Token.NOT:
|
|
case Token.BITNOT:
|
|
case Token.TYPEOF:
|
|
consumeToken();
|
|
decompiler.addToken(tt);
|
|
return nf.createUnary(tt, unaryExpr());
|
|
|
|
case Token.ADD:
|
|
consumeToken();
|
|
// Convert to special POS token in decompiler and parse tree
|
|
decompiler.addToken(Token.POS);
|
|
return nf.createUnary(Token.POS, unaryExpr());
|
|
|
|
case Token.SUB:
|
|
consumeToken();
|
|
// Convert to special NEG token in decompiler and parse tree
|
|
decompiler.addToken(Token.NEG);
|
|
return nf.createUnary(Token.NEG, unaryExpr());
|
|
|
|
case Token.INC:
|
|
case Token.DEC:
|
|
consumeToken();
|
|
decompiler.addToken(tt);
|
|
return nf.createIncDec(tt, false, memberExpr(true));
|
|
|
|
case Token.DELPROP:
|
|
consumeToken();
|
|
decompiler.addToken(Token.DELPROP);
|
|
return nf.createUnary(Token.DELPROP, unaryExpr());
|
|
|
|
case Token.ERROR:
|
|
consumeToken();
|
|
break;
|
|
|
|
// XML stream encountered in expression.
|
|
case Token.LT:
|
|
if (compilerEnv.isXmlAvailable()) {
|
|
consumeToken();
|
|
Node pn = xmlInitializer();
|
|
return memberExprTail(true, pn);
|
|
}
|
|
// Fall thru to the default handling of RELOP
|
|
|
|
default:
|
|
Node pn = memberExpr(true);
|
|
|
|
// Don't look across a newline boundary for a postfix incop.
|
|
tt = peekTokenOrEOL();
|
|
if (tt == Token.INC || tt == Token.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 != Token.XML && tt != Token.XMLEND) {
|
|
reportError("msg.syntax");
|
|
return null;
|
|
}
|
|
|
|
/* Make a NEW node to append to. */
|
|
Node pnXML = nf.createLeaf(Token.NEW);
|
|
|
|
String xml = ts.getString();
|
|
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 Token.XML:
|
|
xml = ts.getString();
|
|
decompiler.addName(xml);
|
|
mustMatchToken(Token.LC, "msg.syntax");
|
|
decompiler.addToken(Token.LC);
|
|
expr = (peekToken() == Token.RC)
|
|
? nf.createString("")
|
|
: expr(false);
|
|
mustMatchToken(Token.RC, "msg.syntax");
|
|
decompiler.addToken(Token.RC);
|
|
if (pn == null) {
|
|
pn = nf.createString(xml);
|
|
} else {
|
|
pn = nf.createBinary(Token.ADD, pn, nf.createString(xml));
|
|
}
|
|
if (ts.isXMLAttribute()) {
|
|
/* Need to put the result in double quotes */
|
|
expr = nf.createUnary(Token.ESCXMLATTR, expr);
|
|
Node prepend = nf.createBinary(Token.ADD,
|
|
nf.createString("\""),
|
|
expr);
|
|
expr = nf.createBinary(Token.ADD,
|
|
prepend,
|
|
nf.createString("\""));
|
|
} else {
|
|
expr = nf.createUnary(Token.ESCXMLTEXT, expr);
|
|
}
|
|
pn = nf.createBinary(Token.ADD, pn, expr);
|
|
break;
|
|
case Token.XMLEND:
|
|
xml = ts.getString();
|
|
decompiler.addName(xml);
|
|
if (pn == null) {
|
|
pn = nf.createString(xml);
|
|
} else {
|
|
pn = nf.createBinary(Token.ADD, pn, nf.createString(xml));
|
|
}
|
|
|
|
nf.addChildToBack(pnXML, pn);
|
|
return pnXML;
|
|
default:
|
|
reportError("msg.syntax");
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void argumentList(Node listNode)
|
|
throws IOException, ParserException
|
|
{
|
|
boolean matched;
|
|
matched = matchToken(Token.RP);
|
|
if (!matched) {
|
|
boolean first = true;
|
|
do {
|
|
if (!first)
|
|
decompiler.addToken(Token.COMMA);
|
|
first = false;
|
|
if (peekToken() == Token.YIELD) {
|
|
reportError("msg.yield.parenthesized");
|
|
}
|
|
nf.addChildToBack(listNode, assignExpr(false));
|
|
} while (matchToken(Token.COMMA));
|
|
|
|
mustMatchToken(Token.RP, "msg.no.paren.arg");
|
|
}
|
|
decompiler.addToken(Token.RP);
|
|
}
|
|
|
|
private Node memberExpr(boolean allowCallSyntax)
|
|
throws IOException, ParserException
|
|
{
|
|
int tt;
|
|
|
|
Node pn;
|
|
|
|
/* Check for new expressions. */
|
|
tt = peekToken();
|
|
if (tt == Token.NEW) {
|
|
/* Eat the NEW token. */
|
|
consumeToken();
|
|
decompiler.addToken(Token.NEW);
|
|
|
|
/* Make a NEW node to append to. */
|
|
pn = nf.createCallOrNew(Token.NEW, memberExpr(false));
|
|
|
|
if (matchToken(Token.LP)) {
|
|
decompiler.addToken(Token.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 == Token.LC) {
|
|
nf.addChildToBack(pn, primaryExpr());
|
|
}
|
|
} else {
|
|
pn = primaryExpr();
|
|
}
|
|
|
|
return memberExprTail(allowCallSyntax, pn);
|
|
}
|
|
|
|
private Node memberExprTail(boolean allowCallSyntax, Node pn)
|
|
throws IOException, ParserException
|
|
{
|
|
tailLoop:
|
|
for (;;) {
|
|
int tt = peekToken();
|
|
switch (tt) {
|
|
|
|
case Token.DOT:
|
|
case Token.DOTDOT:
|
|
{
|
|
int memberTypeFlags;
|
|
String s;
|
|
|
|
consumeToken();
|
|
decompiler.addToken(tt);
|
|
memberTypeFlags = 0;
|
|
if (tt == Token.DOTDOT) {
|
|
mustHaveXML();
|
|
memberTypeFlags = Node.DESCENDANTS_FLAG;
|
|
}
|
|
if (!compilerEnv.isXmlAvailable()) {
|
|
mustMatchToken(Token.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 Token.THROW:
|
|
decompiler.addName("throw");
|
|
pn = propertyName(pn, "throw", memberTypeFlags);
|
|
break;
|
|
|
|
// handles: name, ns::name, ns::*, ns::[expr]
|
|
case Token.NAME:
|
|
s = ts.getString();
|
|
decompiler.addName(s);
|
|
pn = propertyName(pn, s, memberTypeFlags);
|
|
break;
|
|
|
|
// handles: *, *::name, *::*, *::[expr]
|
|
case Token.MUL:
|
|
decompiler.addName("*");
|
|
pn = propertyName(pn, "*", memberTypeFlags);
|
|
break;
|
|
|
|
// handles: '@attr', '@ns::attr', '@ns::*', '@ns::*',
|
|
// '@::attr', '@::*', '@*', '@*::attr', '@*::*'
|
|
case Token.XMLATTR:
|
|
decompiler.addToken(Token.XMLATTR);
|
|
pn = attributeAccess(pn, memberTypeFlags);
|
|
break;
|
|
|
|
default:
|
|
reportError("msg.no.name.after.dot");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Token.DOTQUERY:
|
|
consumeToken();
|
|
mustHaveXML();
|
|
decompiler.addToken(Token.DOTQUERY);
|
|
pn = nf.createDotQuery(pn, expr(false), ts.getLineno());
|
|
mustMatchToken(Token.RP, "msg.no.paren");
|
|
decompiler.addToken(Token.RP);
|
|
break;
|
|
|
|
case Token.LB:
|
|
consumeToken();
|
|
decompiler.addToken(Token.LB);
|
|
pn = nf.createElementGet(pn, null, expr(false), 0);
|
|
mustMatchToken(Token.RB, "msg.no.bracket.index");
|
|
decompiler.addToken(Token.RB);
|
|
break;
|
|
|
|
case Token.LP:
|
|
if (!allowCallSyntax) {
|
|
break tailLoop;
|
|
}
|
|
consumeToken();
|
|
decompiler.addToken(Token.LP);
|
|
pn = nf.createCallOrNew(Token.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;
|
|
int tt = nextToken();
|
|
|
|
switch (tt) {
|
|
// handles: @name, @ns::name, @ns::*, @ns::[expr]
|
|
case Token.NAME:
|
|
{
|
|
String s = ts.getString();
|
|
decompiler.addName(s);
|
|
pn = propertyName(pn, s, memberTypeFlags);
|
|
}
|
|
break;
|
|
|
|
// handles: @*, @*::name, @*::*, @*::[expr]
|
|
case Token.MUL:
|
|
decompiler.addName("*");
|
|
pn = propertyName(pn, "*", memberTypeFlags);
|
|
break;
|
|
|
|
// handles @[expr]
|
|
case Token.LB:
|
|
decompiler.addToken(Token.LB);
|
|
pn = nf.createElementGet(pn, null, expr(false), memberTypeFlags);
|
|
mustMatchToken(Token.RB, "msg.no.bracket.index");
|
|
decompiler.addToken(Token.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, int memberTypeFlags)
|
|
throws IOException, ParserException
|
|
{
|
|
String namespace = null;
|
|
if (matchToken(Token.COLONCOLON)) {
|
|
decompiler.addToken(Token.COLONCOLON);
|
|
namespace = name;
|
|
|
|
int tt = nextToken();
|
|
switch (tt) {
|
|
// handles name::name
|
|
case Token.NAME:
|
|
name = ts.getString();
|
|
decompiler.addName(name);
|
|
break;
|
|
|
|
// handles name::*
|
|
case Token.MUL:
|
|
decompiler.addName("*");
|
|
name = "*";
|
|
break;
|
|
|
|
// handles name::[expr]
|
|
case Token.LB:
|
|
decompiler.addToken(Token.LB);
|
|
pn = nf.createElementGet(pn, namespace, expr(false),
|
|
memberTypeFlags);
|
|
mustMatchToken(Token.RB, "msg.no.bracket.index");
|
|
decompiler.addToken(Token.RB);
|
|
return pn;
|
|
|
|
default:
|
|
reportError("msg.no.name.after.coloncolon");
|
|
name = "?";
|
|
}
|
|
}
|
|
|
|
pn = nf.createPropertyGet(pn, namespace, name, memberTypeFlags);
|
|
return pn;
|
|
}
|
|
|
|
private Node arrayComprehension(String arrayName, Node expr)
|
|
throws IOException, ParserException
|
|
{
|
|
if (nextToken() != Token.FOR)
|
|
throw Kit.codeBug(); // shouldn't be here if next token isn't 'for'
|
|
decompiler.addName(" "); // space after array literal expr
|
|
decompiler.addToken(Token.FOR);
|
|
boolean isForEach = false;
|
|
if (matchToken(Token.NAME)) {
|
|
decompiler.addName(ts.getString());
|
|
if (ts.getString().equals("each")) {
|
|
isForEach = true;
|
|
} else {
|
|
reportError("msg.no.paren.for");
|
|
}
|
|
}
|
|
mustMatchToken(Token.LP, "msg.no.paren.for");
|
|
decompiler.addToken(Token.LP);
|
|
String name;
|
|
int tt = peekToken();
|
|
if (tt == Token.LB || tt == Token.LC) {
|
|
// handle destructuring assignment
|
|
name = currentScriptOrFn.getNextTempName();
|
|
defineSymbol(Token.LP, false, name);
|
|
expr = nf.createBinary(Token.COMMA,
|
|
nf.createAssignment(Token.ASSIGN, primaryExpr(),
|
|
nf.createName(name)),
|
|
expr);
|
|
} else if (tt == Token.NAME) {
|
|
consumeToken();
|
|
name = ts.getString();
|
|
decompiler.addName(name);
|
|
} else {
|
|
reportError("msg.bad.var");
|
|
return nf.createNumber(0);
|
|
}
|
|
|
|
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(Token.LET, false, name);
|
|
|
|
mustMatchToken(Token.IN, "msg.in.after.for.name");
|
|
decompiler.addToken(Token.IN);
|
|
Node iterator = expr(false);
|
|
mustMatchToken(Token.RP, "msg.no.paren.for.ctrl");
|
|
decompiler.addToken(Token.RP);
|
|
|
|
Node body;
|
|
tt = peekToken();
|
|
if (tt == Token.FOR) {
|
|
body = arrayComprehension(arrayName, expr);
|
|
} else {
|
|
Node call = nf.createCallOrNew(Token.CALL,
|
|
nf.createPropertyGet(nf.createName(arrayName), null,
|
|
"push", 0));
|
|
call.addChildToBack(expr);
|
|
body = new Node(Token.EXPR_VOID, call, ts.getLineno());
|
|
if (tt == Token.IF) {
|
|
consumeToken();
|
|
decompiler.addToken(Token.IF);
|
|
int lineno = ts.getLineno();
|
|
Node cond = condition();
|
|
body = nf.createIf(cond, body, null, lineno);
|
|
}
|
|
mustMatchToken(Token.RB, "msg.no.bracket.arg");
|
|
decompiler.addToken(Token.RB);
|
|
}
|
|
|
|
Node loop = enterLoop(null, true);
|
|
try {
|
|
return nf.createForIn(Token.LET, loop, init, iterator, body,
|
|
isForEach);
|
|
} finally {
|
|
exitLoop(false);
|
|
}
|
|
}
|
|
|
|
private Node primaryExpr()
|
|
throws IOException, ParserException
|
|
{
|
|
Node pn;
|
|
|
|
int ttFlagged = nextFlaggedToken();
|
|
int tt = ttFlagged & CLEAR_TI_MASK;
|
|
|
|
switch(tt) {
|
|
|
|
case Token.FUNCTION:
|
|
return function(FunctionNode.FUNCTION_EXPRESSION);
|
|
|
|
case Token.LB: {
|
|
ObjArray elems = new ObjArray();
|
|
int skipCount = 0;
|
|
int destructuringLen = 0;
|
|
decompiler.addToken(Token.LB);
|
|
boolean after_lb_or_comma = true;
|
|
for (;;) {
|
|
tt = peekToken();
|
|
|
|
if (tt == Token.COMMA) {
|
|
consumeToken();
|
|
decompiler.addToken(Token.COMMA);
|
|
if (!after_lb_or_comma) {
|
|
after_lb_or_comma = true;
|
|
} else {
|
|
elems.add(null);
|
|
++skipCount;
|
|
}
|
|
} else if (tt == Token.RB) {
|
|
consumeToken();
|
|
decompiler.addToken(Token.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 == Token.FOR)
|
|
{
|
|
Node scopeNode = nf.createScopeNode(Token.ARRAYCOMP,
|
|
ts.getLineno());
|
|
String tempName = currentScriptOrFn.getNextTempName();
|
|
pushScope(scopeNode);
|
|
try {
|
|
defineSymbol(Token.LET, false, tempName);
|
|
Node expr = (Node) elems.get(0);
|
|
Node block = nf.createBlock(ts.getLineno());
|
|
Node init = new Node(Token.EXPR_VOID,
|
|
nf.createAssignment(Token.ASSIGN,
|
|
nf.createName(tempName),
|
|
nf.createCallOrNew(Token.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 Token.LC: {
|
|
ObjArray elems = new ObjArray();
|
|
decompiler.addToken(Token.LC);
|
|
if (!matchToken(Token.RC)) {
|
|
|
|
boolean first = true;
|
|
commaloop:
|
|
do {
|
|
Object property;
|
|
|
|
if (!first)
|
|
decompiler.addToken(Token.COMMA);
|
|
else
|
|
first = false;
|
|
|
|
tt = peekToken();
|
|
switch(tt) {
|
|
case Token.NAME:
|
|
case Token.STRING:
|
|
consumeToken();
|
|
// map NAMEs to STRINGs in object literal context
|
|
// but tell the decompiler the proper type
|
|
String s = ts.getString();
|
|
if (tt == Token.NAME) {
|
|
if (s.equals("get") &&
|
|
peekToken() == Token.NAME) {
|
|
decompiler.addToken(Token.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() == Token.NAME) {
|
|
decompiler.addToken(Token.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 Token.NUMBER:
|
|
consumeToken();
|
|
double n = ts.getNumber();
|
|
decompiler.addNumber(n);
|
|
property = ScriptRuntime.getIndexObject(n);
|
|
plainProperty(elems, property);
|
|
break;
|
|
|
|
case Token.RC:
|
|
// trailing comma is OK.
|
|
break commaloop;
|
|
default:
|
|
reportError("msg.bad.prop");
|
|
break commaloop;
|
|
}
|
|
} while (matchToken(Token.COMMA));
|
|
|
|
mustMatchToken(Token.RC, "msg.no.brace.prop");
|
|
}
|
|
decompiler.addToken(Token.RC);
|
|
return nf.createObjectLiteral(elems);
|
|
}
|
|
|
|
case Token.LET:
|
|
decompiler.addToken(Token.LET);
|
|
return let(false);
|
|
|
|
case Token.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(Token.LP);
|
|
pn = expr(false);
|
|
pn.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE);
|
|
decompiler.addToken(Token.RP);
|
|
mustMatchToken(Token.RP, "msg.no.paren");
|
|
return pn;
|
|
|
|
case Token.XMLATTR:
|
|
mustHaveXML();
|
|
decompiler.addToken(Token.XMLATTR);
|
|
pn = attributeAccess(null, 0);
|
|
return pn;
|
|
|
|
case Token.NAME: {
|
|
String name = ts.getString();
|
|
if ((ttFlagged & TI_CHECK_LABEL) != 0) {
|
|
if (peekToken() == Token.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 Token.NUMBER: {
|
|
double n = ts.getNumber();
|
|
decompiler.addNumber(n);
|
|
return nf.createNumber(n);
|
|
}
|
|
|
|
case Token.STRING: {
|
|
String s = ts.getString();
|
|
decompiler.addString(s);
|
|
return nf.createString(s);
|
|
}
|
|
|
|
case Token.DIV:
|
|
case Token.ASSIGN_DIV: {
|
|
// Got / or /= which should be treated as regexp in fact
|
|
ts.readRegExp(tt);
|
|
String flags = ts.regExpFlags;
|
|
ts.regExpFlags = null;
|
|
String re = ts.getString();
|
|
decompiler.addRegexp(re, flags);
|
|
int index = currentScriptOrFn.addRegexp(re, flags);
|
|
return nf.createRegExp(index);
|
|
}
|
|
|
|
case Token.NULL:
|
|
case Token.THIS:
|
|
case Token.FALSE:
|
|
case Token.TRUE:
|
|
decompiler.addToken(tt);
|
|
return nf.createLeaf(tt);
|
|
|
|
case Token.RESERVED:
|
|
reportError("msg.reserved.id");
|
|
break;
|
|
|
|
case Token.ERROR:
|
|
/* the scanner or one of its subroutines reported the error. */
|
|
break;
|
|
|
|
case Token.EOF:
|
|
reportError("msg.unexpected.eof");
|
|
break;
|
|
|
|
default:
|
|
reportError("msg.syntax");
|
|
break;
|
|
}
|
|
return null; // should never reach here
|
|
}
|
|
|
|
private void plainProperty(ObjArray elems, Object property)
|
|
throws IOException {
|
|
mustMatchToken(Token.COLON, "msg.no.colon.prop");
|
|
|
|
// OBJLIT is used as ':' in object literal for
|
|
// decompilation to solve spacing ambiguity.
|
|
decompiler.addToken(Token.OBJECTLIT);
|
|
elems.add(property);
|
|
elems.add(assignExpr(false));
|
|
}
|
|
|
|
private boolean getterSetterProperty(ObjArray elems, Object property,
|
|
boolean isGetter) throws IOException {
|
|
Node f = function(FunctionNode.FUNCTION_EXPRESSION);
|
|
if (f.getType() != Token.FUNCTION) {
|
|
reportError("msg.bad.prop");
|
|
return false;
|
|
}
|
|
int fnIndex = f.getExistingIntProp(Node.FUNCTION_PROP);
|
|
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(Token.GET, f));
|
|
} else {
|
|
elems.add(nf.createUnary(Token.SET, f));
|
|
}
|
|
return true;
|
|
}
|
|
}
|