org.mozilla.javascript.Parser Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import org.mozilla.javascript.ast.*; // we use basically every class
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
/**
* This class implements the JavaScript parser.
*
* It is based on the SpiderMonkey C source files jsparse.c and jsparse.h in the
* jsref package.
*
* The parser generates an {@link AstRoot} parse tree representing the source
* code. No tree rewriting is permitted at this stage, so that the parse tree
* is a faithful representation of the source for frontend processing tools and
* IDEs.
*
* This parser implementation is not intended to be reused after a parse
* finishes, and will throw an IllegalStateException() if invoked again.
*
* @see TokenStream
*
* @author Mike McCabe
* @author Brendan Eich
*/
public class Parser
{
/**
* Maximum number of allowed function or constructor arguments,
* to follow SpiderMonkey.
*/
public static final int ARGC_LIMIT = 1 << 16;
// 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 IdeErrorReporter errorCollector;
private String sourceURI;
private char[] sourceChars;
boolean calledByCompileFunction; // ugly - set directly by Context
private boolean parseFinished; // set when finished to prevent reuse
private TokenStream ts;
private int currentFlaggedToken = Token.EOF;
private int currentToken;
private int syntaxErrorCount;
private List scannedComments;
private Comment currentJsDocComment;
protected int nestingOfFunction;
private LabeledStatement currentLabel;
private boolean inDestructuringAssignment;
protected boolean inUseStrictDirective;
// The following are per function variables and should be saved/restored
// during function parsing. See PerFunctionVariables class below.
ScriptNode currentScriptOrFn;
Scope currentScope;
private int endFlags;
private boolean inForInit; // bound temporarily during forStatement()
private Map labelSet;
private List loopSet;
private List loopAndSwitchSet;
// end of per function variables
// Lacking 2-token lookahead, labels become a problem.
// These vars store the token info of the last matched name,
// iff it wasn't the last matched token.
private int prevNameTokenStart;
private String prevNameTokenString = "";
private int prevNameTokenLineno;
// Exception to unwind
private static class ParserException extends RuntimeException
{
static final long serialVersionUID = 5882582646773765630L;
}
public Parser() {
this(new CompilerEnvirons());
}
public Parser(CompilerEnvirons compilerEnv) {
this(compilerEnv, compilerEnv.getErrorReporter());
}
public Parser(CompilerEnvirons compilerEnv, ErrorReporter errorReporter) {
this.compilerEnv = compilerEnv;
this.errorReporter = errorReporter;
if (errorReporter instanceof IdeErrorReporter) {
errorCollector = (IdeErrorReporter)errorReporter;
}
}
// Add a strict warning on the last matched token.
void addStrictWarning(String messageId, String messageArg) {
int beg = -1, end = -1;
if (ts != null) {
beg = ts.tokenBeg;
end = ts.tokenEnd - ts.tokenBeg;
}
addStrictWarning(messageId, messageArg, beg, end);
}
void addStrictWarning(String messageId, String messageArg,
int position, int length) {
if (compilerEnv.isStrictMode())
addWarning(messageId, messageArg, position, length);
}
void addWarning(String messageId, String messageArg) {
int beg = -1, end = -1;
if (ts != null) {
beg = ts.tokenBeg;
end = ts.tokenEnd - ts.tokenBeg;
}
addWarning(messageId, messageArg, beg, end);
}
void addWarning(String messageId, int position, int length) {
addWarning(messageId, null, position, length);
}
void addWarning(String messageId, String messageArg,
int position, int length)
{
String message = lookupMessage(messageId, messageArg);
if (compilerEnv.reportWarningAsError()) {
addError(messageId, messageArg, position, length);
} else if (errorCollector != null) {
errorCollector.warning(message, sourceURI, position, length);
} else {
errorReporter.warning(message, sourceURI, ts.getLineno(),
ts.getLine(), ts.getOffset());
}
}
void addError(String messageId) {
addError(messageId, ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
}
void addError(String messageId, int position, int length) {
addError(messageId, null, position, length);
}
void addError(String messageId, String messageArg) {
addError(messageId, messageArg, ts.tokenBeg,
ts.tokenEnd - ts.tokenBeg);
}
void addError(String messageId, String messageArg, int position, int length)
{
++syntaxErrorCount;
String message = lookupMessage(messageId, messageArg);
if (errorCollector != null) {
errorCollector.error(message, sourceURI, position, length);
} else {
int lineno = 1, offset = 1;
String line = "";
if (ts != null) { // happens in some regression tests
lineno = ts.getLineno();
line = ts.getLine();
offset = ts.getOffset();
}
errorReporter.error(message, sourceURI, lineno, line, offset);
}
}
private void addStrictWarning(String messageId, String messageArg,
int position, int length,
int line, String lineSource, int lineOffset) {
if (compilerEnv.isStrictMode()) {
addWarning(messageId, messageArg, position, length, line, lineSource, lineOffset);
}
}
private void addWarning(String messageId, String messageArg,
int position, int length,
int line, String lineSource, int lineOffset) {
String message = lookupMessage(messageId, messageArg);
if (compilerEnv.reportWarningAsError()) {
addError(messageId, messageArg, position, length, line, lineSource, lineOffset);
} else if (errorCollector != null) {
errorCollector.warning(message, sourceURI, position, length);
} else {
errorReporter.warning(message, sourceURI, line, lineSource, lineOffset);
}
}
private void addError(String messageId, String messageArg,
int position, int length,
int line, String lineSource, int lineOffset) {
++syntaxErrorCount;
String message = lookupMessage(messageId, messageArg);
if (errorCollector != null) {
errorCollector.error(message, sourceURI, position, length);
} else {
errorReporter.error(message, sourceURI, line, lineSource, lineOffset);
}
}
String lookupMessage(String messageId) {
return lookupMessage(messageId, null);
}
String lookupMessage(String messageId, String messageArg) {
return messageArg == null
? ScriptRuntime.getMessage0(messageId)
: ScriptRuntime.getMessage1(messageId, messageArg);
}
void reportError(String messageId) {
reportError(messageId, null);
}
void reportError(String messageId, String messageArg) {
if (ts == null) { // happens in some regression tests
reportError(messageId, messageArg, 1, 1);
} else {
reportError(messageId, messageArg, ts.tokenBeg,
ts.tokenEnd - ts.tokenBeg);
}
}
void reportError(String messageId, int position, int length)
{
reportError(messageId, null, position, length);
}
void reportError(String messageId, String messageArg, int position,
int length)
{
addError(messageId, position, length);
if (!compilerEnv.recoverFromErrors()) {
throw new ParserException();
}
}
// Computes the absolute end offset of node N.
// Use with caution! Assumes n.getPosition() is -absolute-, which
// is only true before the node is added to its parent.
private int getNodeEnd(AstNode n) {
return n.getPosition() + n.getLength();
}
private void recordComment(int lineno, String comment) {
if (scannedComments == null) {
scannedComments = new ArrayList();
}
Comment commentNode = new Comment(ts.tokenBeg,
ts.getTokenLength(),
ts.commentType,
comment);
if (ts.commentType == Token.CommentType.JSDOC &&
compilerEnv.isRecordingLocalJsDocComments()) {
currentJsDocComment = commentNode;
}
commentNode.setLineno(lineno);
scannedComments.add(commentNode);
}
private Comment getAndResetJsDoc() {
Comment saved = currentJsDocComment;
currentJsDocComment = null;
return saved;
}
private int getNumberOfEols(String comment) {
int lines = 0;
for (int i = comment.length()-1; i >= 0; i--) {
if (comment.charAt(i) == '\n') {
lines++;
}
}
return lines;
}
// Returns the next token without consuming it.
// If previous token was consumed, calls scanner to get new token.
// If previous token was -not- consumed, returns it (idempotent).
//
// This function will not return a newline (Token.EOL - instead, it
// gobbles newlines until it finds a non-newline token, and flags
// that token as appearing just after a newline.
//
// This function will also not return a Token.COMMENT. Instead, it
// records comments in the scannedComments list. If the token
// returned by this function immediately follows a jsdoc comment,
// the token is flagged as such.
//
// Note that this function always returned the un-flagged token!
// The flags, if any, are saved in currentFlaggedToken.
private int peekToken()
throws IOException
{
// By far the most common case: last token hasn't been consumed,
// so return already-peeked token.
if (currentFlaggedToken != Token.EOF) {
return currentToken;
}
int lineno = ts.getLineno();
int tt = ts.getToken();
boolean sawEOL = false;
// process comments and whitespace
while (tt == Token.EOL || tt == Token.COMMENT) {
if (tt == Token.EOL) {
lineno++;
sawEOL = true;
} else {
if (compilerEnv.isRecordingComments()) {
String comment = ts.getAndResetCurrentComment();
recordComment(lineno, comment);
// Comments may contain multiple lines, get the number
// of EoLs and increase the lineno
lineno += getNumberOfEols(comment);
}
}
tt = ts.getToken();
}
currentToken = tt;
currentFlaggedToken = tt | (sawEOL ? TI_AFTER_EOL : 0);
return currentToken; // return unflagged token
}
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
{
if (peekToken() != toMatch) {
return false;
}
consumeToken();
return true;
}
// Returns Token.EOL if the current token follows a newline, else returns
// the current token. Used in situations where we don't consider certain
// token types valid if they are preceded by a newline. One example is the
// postfix ++ or -- operator, which has to be on the same line as its
// operand.
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 boolean mustMatchToken(int toMatch, String messageId)
throws IOException
{
return mustMatchToken(toMatch, messageId, ts.tokenBeg,
ts.tokenEnd - ts.tokenBeg);
}
private boolean mustMatchToken(int toMatch, String msgId, int pos, int len)
throws IOException
{
if (matchToken(toMatch)) {
return true;
}
reportError(msgId, pos, len);
return false;
}
private void mustHaveXML() {
if (!compilerEnv.isXmlAvailable()) {
reportError("msg.XML.not.available");
}
}
public boolean eof() {
return ts.eof();
}
boolean insideFunction() {
return nestingOfFunction != 0;
}
void pushScope(Scope scope) {
Scope parent = scope.getParentScope();
// During codegen, parent scope chain may already be initialized,
// in which case we just need to set currentScope variable.
if (parent != null) {
if (parent != currentScope)
codeBug();
} else {
currentScope.addChildScope(scope);
}
currentScope = scope;
}
void popScope() {
currentScope = currentScope.getParentScope();
}
private void enterLoop(Loop loop) {
if (loopSet == null)
loopSet = new ArrayList();
loopSet.add(loop);
if (loopAndSwitchSet == null)
loopAndSwitchSet = new ArrayList();
loopAndSwitchSet.add(loop);
pushScope(loop);
if (currentLabel != null) {
currentLabel.setStatement(loop);
currentLabel.getFirstLabel().setLoop(loop);
// This is the only time during parsing that we set a node's parent
// before parsing the children. In order for the child node offsets
// to be correct, we adjust the loop's reported position back to an
// absolute source offset, and restore it when we call exitLoop().
loop.setRelative(-currentLabel.getPosition());
}
}
private void exitLoop() {
Loop loop = loopSet.remove(loopSet.size() - 1);
loopAndSwitchSet.remove(loopAndSwitchSet.size() - 1);
if (loop.getParent() != null) { // see comment in enterLoop
loop.setRelative(loop.getParent().getPosition());
}
popScope();
}
private void enterSwitch(SwitchStatement node) {
if (loopAndSwitchSet == null)
loopAndSwitchSet = new ArrayList();
loopAndSwitchSet.add(node);
}
private void exitSwitch() {
loopAndSwitchSet.remove(loopAndSwitchSet.size() - 1);
}
/**
* Builds a parse tree from the given source string.
*
* @return an {@link AstRoot} object representing the parsed program. If
* the parse fails, {@code null} will be returned. (The parse failure will
* result in a call to the {@link ErrorReporter} from
* {@link CompilerEnvirons}.)
*/
public AstRoot parse(String sourceString, String sourceURI, int lineno)
{
if (parseFinished) throw new IllegalStateException("parser reused");
this.sourceURI = sourceURI;
if (compilerEnv.isIdeMode()) {
this.sourceChars = sourceString.toCharArray();
}
this.ts = new TokenStream(this, null, sourceString, lineno);
try {
return parse();
} catch (IOException iox) {
// Should never happen
throw new IllegalStateException();
} finally {
parseFinished = true;
}
}
/**
* Builds a parse tree from the given sourcereader.
* @see #parse(String,String,int)
* @throws IOException if the {@link Reader} encounters an error
*/
public AstRoot parse(Reader sourceReader, String sourceURI, int lineno)
throws IOException
{
if (parseFinished) throw new IllegalStateException("parser reused");
if (compilerEnv.isIdeMode()) {
return parse(readFully(sourceReader), sourceURI, lineno);
}
try {
this.sourceURI = sourceURI;
ts = new TokenStream(this, sourceReader, null, lineno);
return parse();
} finally {
parseFinished = true;
}
}
private AstRoot parse() throws IOException
{
int pos = 0;
AstRoot root = new AstRoot(pos);
currentScope = currentScriptOrFn = root;
int baseLineno = ts.lineno; // line number where source starts
int end = pos; // in case source is empty
boolean inDirectivePrologue = true;
boolean savedStrictMode = inUseStrictDirective;
// TODO: eval code should get strict mode from invoking code
inUseStrictDirective = false;
try {
for (;;) {
int tt = peekToken();
if (tt <= Token.EOF) {
break;
}
AstNode n;
if (tt == Token.FUNCTION) {
consumeToken();
try {
n = function(calledByCompileFunction
? FunctionNode.FUNCTION_EXPRESSION
: FunctionNode.FUNCTION_STATEMENT);
} catch (ParserException e) {
break;
}
} else {
n = statement();
if (inDirectivePrologue) {
String directive = getDirective(n);
if (directive == null) {
inDirectivePrologue = false;
} else if (directive.equals("use strict")) {
inUseStrictDirective = true;
root.setInStrictMode(true);
}
}
}
end = getNodeEnd(n);
root.addChildToBack(n);
n.setParent(root);
}
} catch (StackOverflowError ex) {
String msg = lookupMessage("msg.too.deep.parser.recursion");
if (!compilerEnv.isIdeMode())
throw Context.reportRuntimeError(msg, sourceURI,
ts.lineno, null, 0);
} finally {
inUseStrictDirective = savedStrictMode;
}
if (this.syntaxErrorCount != 0) {
String msg = String.valueOf(this.syntaxErrorCount);
msg = lookupMessage("msg.got.syntax.errors", msg);
if (!compilerEnv.isIdeMode())
throw errorReporter.runtimeError(msg, sourceURI, baseLineno,
null, 0);
}
// add comments to root in lexical order
if (scannedComments != null) {
// If we find a comment beyond end of our last statement or
// function, extend the root bounds to the end of that comment.
int last = scannedComments.size() - 1;
end = Math.max(end, getNodeEnd(scannedComments.get(last)));
for (Comment c : scannedComments) {
root.addComment(c);
}
}
root.setLength(end - pos);
root.setSourceName(sourceURI);
root.setBaseLineno(baseLineno);
root.setEndLineno(ts.lineno);
return root;
}
private AstNode parseFunctionBody()
throws IOException
{
boolean isExpressionClosure = false;
if (!matchToken(Token.LC)) {
if (compilerEnv.getLanguageVersion() < Context.VERSION_1_8) {
reportError("msg.no.brace.body");
} else {
isExpressionClosure = true;
}
}
++nestingOfFunction;
int pos = ts.tokenBeg;
Block pn = new Block(pos); // starts at LC position
boolean inDirectivePrologue = true;
boolean savedStrictMode = inUseStrictDirective;
// Don't set 'inUseStrictDirective' to false: inherit strict mode.
pn.setLineno(ts.lineno);
try {
if (isExpressionClosure) {
ReturnStatement n = new ReturnStatement(ts.lineno);
n.setReturnValue(assignExpr());
// expression closure flag is required on both nodes
n.putProp(Node.EXPRESSION_CLOSURE_PROP, Boolean.TRUE);
pn.putProp(Node.EXPRESSION_CLOSURE_PROP, Boolean.TRUE);
pn.addStatement(n);
} else {
bodyLoop: for (;;) {
AstNode 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();
if (inDirectivePrologue) {
String directive = getDirective(n);
if (directive == null) {
inDirectivePrologue = false;
} else if (directive.equals("use strict")) {
inUseStrictDirective = true;
}
}
break;
}
pn.addStatement(n);
}
}
} catch (ParserException e) {
// Ignore it
} finally {
--nestingOfFunction;
inUseStrictDirective = savedStrictMode;
}
int end = ts.tokenEnd;
getAndResetJsDoc();
if (!isExpressionClosure && mustMatchToken(Token.RC, "msg.no.brace.after.body"))
end = ts.tokenEnd;
pn.setLength(end - pos);
return pn;
}
private String getDirective(AstNode n) {
if (n instanceof ExpressionStatement) {
AstNode e = ((ExpressionStatement) n).getExpression();
if (e instanceof StringLiteral) {
return ((StringLiteral) e).getValue();
}
}
return null;
}
private void parseFunctionParams(FunctionNode fnNode)
throws IOException
{
if (matchToken(Token.RP)) {
fnNode.setRp(ts.tokenBeg - fnNode.getPosition());
return;
}
// Would prefer not to call createDestructuringAssignment until codegen,
// but the symbol definitions have to happen now, before body is parsed.
Map destructuring = null;
Set paramNames = new HashSet();
do {
int tt = peekToken();
if (tt == Token.LB || tt == Token.LC) {
AstNode expr = destructuringPrimaryExpr();
markDestructuring(expr);
fnNode.addParam(expr);
// 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 HashMap();
}
String pname = currentScriptOrFn.getNextTempName();
defineSymbol(Token.LP, pname, false);
destructuring.put(pname, expr);
} else {
if (mustMatchToken(Token.NAME, "msg.no.parm")) {
fnNode.addParam(createNameNode());
String paramName = ts.getString();
defineSymbol(Token.LP, paramName);
if (this.inUseStrictDirective) {
if ("eval".equals(paramName) ||
"arguments".equals(paramName))
{
reportError("msg.bad.id.strict", paramName);
}
if (paramNames.contains(paramName))
addError("msg.dup.param.strict", paramName);
paramNames.add(paramName);
}
} else {
fnNode.addParam(makeErrorNode());
}
}
} while (matchToken(Token.COMMA));
if (destructuring != null) {
Node destructuringNode = new Node(Token.COMMA);
// Add assignment helper for each destructuring parameter
for (Map.Entry param: destructuring.entrySet()) {
Node assign = createDestructuringAssignment(Token.VAR,
param.getValue(), createName(param.getKey()));
destructuringNode.addChildToBack(assign);
}
fnNode.putProp(Node.DESTRUCTURING_PARAMS, destructuringNode);
}
if (mustMatchToken(Token.RP, "msg.no.paren.after.parms")) {
fnNode.setRp(ts.tokenBeg - fnNode.getPosition());
}
}
private FunctionNode function(int type)
throws IOException
{
int syntheticType = type;
int baseLineno = ts.lineno; // line number where source starts
int functionSourceStart = ts.tokenBeg; // start of "function" kwd
Name name = null;
AstNode memberExprNode = null;
if (matchToken(Token.NAME)) {
name = createNameNode(true, Token.NAME);
if (inUseStrictDirective) {
String id = name.getIdentifier();
if ("eval".equals(id)|| "arguments".equals(id)) {
reportError("msg.bad.id.strict", id);
}
}
if (!matchToken(Token.LP)) {
if (compilerEnv.isAllowMemberExprAsFunctionName()) {
AstNode memberExprHead = name;
name = null;
memberExprNode = memberExprTail(false, memberExprHead);
}
mustMatchToken(Token.LP, "msg.no.paren.parms");
}
} else if (matchToken(Token.LP)) {
// Anonymous function: leave name as null
} else {
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");
}
int lpPos = currentToken == Token.LP ? ts.tokenBeg : -1;
if (memberExprNode != null) {
syntheticType = FunctionNode.FUNCTION_EXPRESSION;
}
if (syntheticType != FunctionNode.FUNCTION_EXPRESSION
&& name != null && name.length() > 0) {
// Function statements define a symbol in the enclosing scope
defineSymbol(Token.FUNCTION, name.getIdentifier());
}
FunctionNode fnNode = new FunctionNode(functionSourceStart, name);
fnNode.setFunctionType(type);
if (lpPos != -1)
fnNode.setLp(lpPos - functionSourceStart);
fnNode.setJsDocNode(getAndResetJsDoc());
PerFunctionVariables savedVars = new PerFunctionVariables(fnNode);
try {
parseFunctionParams(fnNode);
fnNode.setBody(parseFunctionBody());
fnNode.setEncodedSourceBounds(functionSourceStart, ts.tokenEnd);
fnNode.setLength(ts.tokenEnd - functionSourceStart);
if (compilerEnv.isStrictMode()
&& !fnNode.getBody().hasConsistentReturnUsage()) {
String msg = (name != null && name.length() > 0)
? "msg.no.return.value"
: "msg.anon.no.return.value";
addStrictWarning(msg, name == null ? "" : name.getIdentifier());
}
} finally {
savedVars.restore();
}
if (memberExprNode != null) {
// TODO(stevey): fix missing functionality
Kit.codeBug();
fnNode.setMemberExprNode(memberExprNode); // rewrite later
/* old code:
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);
}
}
*/
}
fnNode.setSourceName(sourceURI);
fnNode.setBaseLineno(baseLineno);
fnNode.setEndLineno(ts.lineno);
// Set the parent scope. Needed for finding undeclared vars.
// Have to wait until after parsing the function to set its parent
// scope, since defineSymbol needs the defining-scope check to stop
// at the function boundary when checking for redeclarations.
if (compilerEnv.isIdeMode()) {
fnNode.setParentScope(currentScope);
}
return fnNode;
}
// This function does not match the closing RC: the caller matches
// the RC so it can provide a suitable error message if not matched.
// This means it's up to the caller to set the length of the node to
// include the closing RC. The node start pos is set to the
// absolute buffer start position, and the caller should fix it up
// to be relative to the parent node. All children of this block
// node are given relative start positions and correct lengths.
private AstNode statements(AstNode parent) throws IOException {
if (currentToken != Token.LC // assertion can be invalid in bad code
&& !compilerEnv.isIdeMode()) codeBug();
int pos = ts.tokenBeg;
AstNode block = parent != null ? parent : new Block(pos);
block.setLineno(ts.lineno);
int tt;
while ((tt = peekToken()) > Token.EOF && tt != Token.RC) {
block.addChild(statement());
}
block.setLength(ts.tokenBeg - pos);
return block;
}
private AstNode statements() throws IOException {
return statements(null);
}
private static class ConditionData {
AstNode condition;
int lp = -1;
int rp = -1;
}
// parse and return a parenthesized expression
private ConditionData condition()
throws IOException
{
ConditionData data = new ConditionData();
if (mustMatchToken(Token.LP, "msg.no.paren.cond"))
data.lp = ts.tokenBeg;
data.condition = expr();
if (mustMatchToken(Token.RP, "msg.no.paren.after.cond"))
data.rp = ts.tokenBeg;
// Report strict warning on code like "if (a = 7) ...". Suppress the
// warning if the condition is parenthesized, like "if ((a = 7)) ...".
if (data.condition instanceof Assignment) {
addStrictWarning("msg.equal.as.assign", "",
data.condition.getPosition(),
data.condition.getLength());
}
return data;
}
private AstNode statement()
throws IOException
{
int pos = ts.tokenBeg;
try {
AstNode pn = statementHelper();
if (pn != null) {
if (compilerEnv.isStrictMode() && !pn.hasSideEffects()) {
int beg = pn.getPosition();
beg = Math.max(beg, lineBeginningFor(beg));
addStrictWarning(pn instanceof EmptyStatement
? "msg.extra.trailing.semi"
: "msg.no.side.effects",
"", beg, nodeEnd(pn) - beg);
}
return pn;
}
} catch (ParserException e) {
// an ErrorNode was added to the ErrorReporter
}
// error: skip ahead to a probable statement boundary
guessingStatementEnd: for (;;) {
int tt = peekTokenOrEOL();
consumeToken();
switch (tt) {
case Token.ERROR:
case Token.EOF:
case Token.EOL:
case Token.SEMI:
break guessingStatementEnd;
}
}
// We don't make error nodes explicitly part of the tree;
// they get added to the ErrorReporter. May need to do
// something different here.
return new EmptyStatement(pos, ts.tokenBeg - pos);
}
private AstNode statementHelper()
throws IOException
{
// If the statement is set, then it's been told its label by now.
if (currentLabel != null && currentLabel.getStatement() != null)
currentLabel = null;
AstNode pn = null;
int tt = peekToken(), pos = ts.tokenBeg;
switch (tt) {
case Token.IF:
return ifStatement();
case Token.SWITCH:
return switchStatement();
case Token.WHILE:
return whileLoop();
case Token.DO:
return doLoop();
case Token.FOR:
return forLoop();
case Token.TRY:
return tryStatement();
case Token.THROW:
pn = throwStatement();
break;
case Token.BREAK:
pn = breakStatement();
break;
case Token.CONTINUE:
pn = continueStatement();
break;
case Token.WITH:
if (this.inUseStrictDirective) {
reportError("msg.no.with.strict");
}
return withStatement();
case Token.CONST:
case Token.VAR:
consumeToken();
int lineno = ts.lineno;
pn = variables(currentToken, ts.tokenBeg, true);
pn.setLineno(lineno);
break;
case Token.LET:
pn = letStatement();
if (pn instanceof VariableDeclaration
&& peekToken() == Token.SEMI)
break;
return pn;
case Token.RETURN:
case Token.YIELD:
pn = returnOrYield(tt, false);
break;
case Token.DEBUGGER:
consumeToken();
pn = new KeywordLiteral(ts.tokenBeg,
ts.tokenEnd - ts.tokenBeg, tt);
pn.setLineno(ts.lineno);
break;
case Token.LC:
return block();
case Token.ERROR:
consumeToken();
return makeErrorNode();
case Token.SEMI:
consumeToken();
pos = ts.tokenBeg;
pn = new EmptyStatement(pos, ts.tokenEnd - pos);
pn.setLineno(ts.lineno);
return pn;
case Token.FUNCTION:
consumeToken();
return function(FunctionNode.FUNCTION_EXPRESSION_STATEMENT);
case Token.DEFAULT :
pn = defaultXmlNamespace();
break;
case Token.NAME:
pn = nameOrLabel();
if (pn instanceof ExpressionStatement)
break;
return pn; // LabeledStatement
default:
lineno = ts.lineno;
pn = new ExpressionStatement(expr(), !insideFunction());
pn.setLineno(lineno);
break;
}
autoInsertSemicolon(pn);
return pn;
}
private void autoInsertSemicolon(AstNode pn) throws IOException {
int ttFlagged = peekFlaggedToken();
int pos = pn.getPosition();
switch (ttFlagged & CLEAR_TI_MASK) {
case Token.SEMI:
// Consume ';' as a part of expression
consumeToken();
// extend the node bounds to include the semicolon.
pn.setLength(ts.tokenEnd - pos);
break;
case Token.ERROR:
case Token.EOF:
case Token.RC:
// Autoinsert ;
warnMissingSemi(pos, nodeEnd(pn));
break;
default:
if ((ttFlagged & TI_AFTER_EOL) == 0) {
// Report error if no EOL or autoinsert ; otherwise
reportError("msg.no.semi.stmt");
} else {
warnMissingSemi(pos, nodeEnd(pn));
}
break;
}
}
private IfStatement ifStatement()
throws IOException
{
if (currentToken != Token.IF) codeBug();
consumeToken();
int pos = ts.tokenBeg, lineno = ts.lineno, elsePos = -1;
ConditionData data = condition();
AstNode ifTrue = statement(), ifFalse = null;
if (matchToken(Token.ELSE)) {
elsePos = ts.tokenBeg - pos;
ifFalse = statement();
}
int end = getNodeEnd(ifFalse != null ? ifFalse : ifTrue);
IfStatement pn = new IfStatement(pos, end - pos);
pn.setCondition(data.condition);
pn.setParens(data.lp - pos, data.rp - pos);
pn.setThenPart(ifTrue);
pn.setElsePart(ifFalse);
pn.setElsePosition(elsePos);
pn.setLineno(lineno);
return pn;
}
private SwitchStatement switchStatement()
throws IOException
{
if (currentToken != Token.SWITCH) codeBug();
consumeToken();
int pos = ts.tokenBeg;
SwitchStatement pn = new SwitchStatement(pos);
if (mustMatchToken(Token.LP, "msg.no.paren.switch"))
pn.setLp(ts.tokenBeg - pos);
pn.setLineno(ts.lineno);
AstNode discriminant = expr();
pn.setExpression(discriminant);
enterSwitch(pn);
try {
if (mustMatchToken(Token.RP, "msg.no.paren.after.switch"))
pn.setRp(ts.tokenBeg - pos);
mustMatchToken(Token.LC, "msg.no.brace.switch");
boolean hasDefault = false;
int tt;
switchLoop: for (;;) {
tt = nextToken();
int casePos = ts.tokenBeg;
int caseLineno = ts.lineno;
AstNode caseExpression = null;
switch (tt) {
case Token.RC:
pn.setLength(ts.tokenEnd - pos);
break switchLoop;
case Token.CASE:
caseExpression = expr();
mustMatchToken(Token.COLON, "msg.no.colon.case");
break;
case Token.DEFAULT:
if (hasDefault) {
reportError("msg.double.switch.default");
}
hasDefault = true;
caseExpression = null;
mustMatchToken(Token.COLON, "msg.no.colon.case");
break;
default:
reportError("msg.bad.switch");
break switchLoop;
}
SwitchCase caseNode = new SwitchCase(casePos);
caseNode.setExpression(caseExpression);
caseNode.setLength(ts.tokenEnd - pos); // include colon
caseNode.setLineno(caseLineno);
while ((tt = peekToken()) != Token.RC
&& tt != Token.CASE
&& tt != Token.DEFAULT
&& tt != Token.EOF)
{
caseNode.addStatement(statement()); // updates length
}
pn.addCase(caseNode);
}
} finally {
exitSwitch();
}
return pn;
}
private WhileLoop whileLoop()
throws IOException
{
if (currentToken != Token.WHILE) codeBug();
consumeToken();
int pos = ts.tokenBeg;
WhileLoop pn = new WhileLoop(pos);
pn.setLineno(ts.lineno);
enterLoop(pn);
try {
ConditionData data = condition();
pn.setCondition(data.condition);
pn.setParens(data.lp - pos, data.rp - pos);
AstNode body = statement();
pn.setLength(getNodeEnd(body) - pos);
pn.setBody(body);
} finally {
exitLoop();
}
return pn;
}
private DoLoop doLoop()
throws IOException
{
if (currentToken != Token.DO) codeBug();
consumeToken();
int pos = ts.tokenBeg, end;
DoLoop pn = new DoLoop(pos);
pn.setLineno(ts.lineno);
enterLoop(pn);
try {
AstNode body = statement();
mustMatchToken(Token.WHILE, "msg.no.while.do");
pn.setWhilePosition(ts.tokenBeg - pos);
ConditionData data = condition();
pn.setCondition(data.condition);
pn.setParens(data.lp - pos, data.rp - pos);
end = getNodeEnd(body);
pn.setBody(body);
} finally {
exitLoop();
}
// Always auto-insert semicolon to follow SpiderMonkey:
// It is required by ECMAScript but is ignored by the rest of
// world, see bug 238945
if (matchToken(Token.SEMI)) {
end = ts.tokenEnd;
}
pn.setLength(end - pos);
return pn;
}
private Loop forLoop()
throws IOException
{
if (currentToken != Token.FOR) codeBug();
consumeToken();
int forPos = ts.tokenBeg, lineno = ts.lineno;
boolean isForEach = false, isForIn = false;
int eachPos = -1, inPos = -1, lp = -1, rp = -1;
AstNode init = null; // init is also foo in 'foo in object'
AstNode cond = null; // cond is also object in 'foo in object'
AstNode incr = null;
Loop pn = null;
Scope tempScope = new Scope();
pushScope(tempScope); // decide below what AST class to use
try {
// See if this is a for each () instead of just a for ()
if (matchToken(Token.NAME)) {
if ("each".equals(ts.getString())) {
isForEach = true;
eachPos = ts.tokenBeg - forPos;
} else {
reportError("msg.no.paren.for");
}
}
if (mustMatchToken(Token.LP, "msg.no.paren.for"))
lp = ts.tokenBeg - forPos;
int tt = peekToken();
init = forLoopInit(tt);
if (matchToken(Token.IN)) {
isForIn = true;
inPos = ts.tokenBeg - forPos;
cond = expr(); // object over which we're iterating
} else { // ordinary for-loop
mustMatchToken(Token.SEMI, "msg.no.semi.for");
if (peekToken() == Token.SEMI) {
// no loop condition
cond = new EmptyExpression(ts.tokenBeg, 1);
cond.setLineno(ts.lineno);
} else {
cond = expr();
}
mustMatchToken(Token.SEMI, "msg.no.semi.for.cond");
int tmpPos = ts.tokenEnd;
if (peekToken() == Token.RP) {
incr = new EmptyExpression(tmpPos, 1);
incr.setLineno(ts.lineno);
} else {
incr = expr();
}
}
if (mustMatchToken(Token.RP, "msg.no.paren.for.ctrl"))
rp = ts.tokenBeg - forPos;
if (isForIn) {
ForInLoop fis = new ForInLoop(forPos);
if (init instanceof VariableDeclaration) {
// check that there was only one variable given
if (((VariableDeclaration)init).getVariables().size() > 1) {
reportError("msg.mult.index");
}
}
fis.setIterator(init);
fis.setIteratedObject(cond);
fis.setInPosition(inPos);
fis.setIsForEach(isForEach);
fis.setEachPosition(eachPos);
pn = fis;
} else {
ForLoop fl = new ForLoop(forPos);
fl.setInitializer(init);
fl.setCondition(cond);
fl.setIncrement(incr);
pn = fl;
}
// replace temp scope with the new loop object
currentScope.replaceWith(pn);
popScope();
// We have to parse the body -after- creating the loop node,
// so that the loop node appears in the loopSet, allowing
// break/continue statements to find the enclosing loop.
enterLoop(pn);
try {
AstNode body = statement();
pn.setLength(getNodeEnd(body) - forPos);
pn.setBody(body);
} finally {
exitLoop();
}
} finally {
if (currentScope == tempScope) {
popScope();
}
}
pn.setParens(lp, rp);
pn.setLineno(lineno);
return pn;
}
private AstNode forLoopInit(int tt) throws IOException {
try {
inForInit = true; // checked by variables() and relExpr()
AstNode init = null;
if (tt == Token.SEMI) {
init = new EmptyExpression(ts.tokenBeg, 1);
init.setLineno(ts.lineno);
} else if (tt == Token.VAR || tt == Token.LET) {
consumeToken();
init = variables(tt, ts.tokenBeg, false);
} else {
init = expr();
markDestructuring(init);
}
return init;
} finally {
inForInit = false;
}
}
private TryStatement tryStatement()
throws IOException
{
if (currentToken != Token.TRY) codeBug();
consumeToken();
// Pull out JSDoc info and reset it before recursing.
Comment jsdocNode = getAndResetJsDoc();
int tryPos = ts.tokenBeg, lineno = ts.lineno, finallyPos = -1;
if (peekToken() != Token.LC) {
reportError("msg.no.brace.try");
}
AstNode tryBlock = statement();
int tryEnd = getNodeEnd(tryBlock);
List clauses = null;
boolean sawDefaultCatch = false;
int peek = peekToken();
if (peek == Token.CATCH) {
while (matchToken(Token.CATCH)) {
int catchLineNum = ts.lineno;
if (sawDefaultCatch) {
reportError("msg.catch.unreachable");
}
int catchPos = ts.tokenBeg, lp = -1, rp = -1, guardPos = -1;
if (mustMatchToken(Token.LP, "msg.no.paren.catch"))
lp = ts.tokenBeg;
mustMatchToken(Token.NAME, "msg.bad.catchcond");
Name varName = createNameNode();
String varNameString = varName.getIdentifier();
if (inUseStrictDirective) {
if ("eval".equals(varNameString) ||
"arguments".equals(varNameString))
{
reportError("msg.bad.id.strict", varNameString);
}
}
AstNode catchCond = null;
if (matchToken(Token.IF)) {
guardPos = ts.tokenBeg;
catchCond = expr();
} else {
sawDefaultCatch = true;
}
if (mustMatchToken(Token.RP, "msg.bad.catchcond"))
rp = ts.tokenBeg;
mustMatchToken(Token.LC, "msg.no.brace.catchblock");
Block catchBlock = (Block)statements();
tryEnd = getNodeEnd(catchBlock);
CatchClause catchNode = new CatchClause(catchPos);
catchNode.setVarName(varName);
catchNode.setCatchCondition(catchCond);
catchNode.setBody(catchBlock);
if (guardPos != -1) {
catchNode.setIfPosition(guardPos - catchPos);
}
catchNode.setParens(lp, rp);
catchNode.setLineno(catchLineNum);
if (mustMatchToken(Token.RC, "msg.no.brace.after.body"))
tryEnd = ts.tokenEnd;
catchNode.setLength(tryEnd - catchPos);
if (clauses == null)
clauses = new ArrayList();
clauses.add(catchNode);
}
} else if (peek != Token.FINALLY) {
mustMatchToken(Token.FINALLY, "msg.try.no.catchfinally");
}
AstNode finallyBlock = null;
if (matchToken(Token.FINALLY)) {
finallyPos = ts.tokenBeg;
finallyBlock = statement();
tryEnd = getNodeEnd(finallyBlock);
}
TryStatement pn = new TryStatement(tryPos, tryEnd - tryPos);
pn.setTryBlock(tryBlock);
pn.setCatchClauses(clauses);
pn.setFinallyBlock(finallyBlock);
if (finallyPos != -1) {
pn.setFinallyPosition(finallyPos - tryPos);
}
pn.setLineno(lineno);
if (jsdocNode != null) {
pn.setJsDocNode(jsdocNode);
}
return pn;
}
private ThrowStatement throwStatement()
throws IOException
{
if (currentToken != Token.THROW) codeBug();
consumeToken();
int pos = ts.tokenBeg, lineno = ts.lineno;
if (peekTokenOrEOL() == Token.EOL) {
// ECMAScript does not allow new lines before throw expression,
// see bug 256617
reportError("msg.bad.throw.eol");
}
AstNode expr = expr();
ThrowStatement pn = new ThrowStatement(pos, getNodeEnd(expr), expr);
pn.setLineno(lineno);
return pn;
}
// If we match a NAME, consume the token and return the statement
// with that label. If the name does not match an existing label,
// reports an error. Returns the labeled statement node, or null if
// the peeked token was not a name. Side effect: sets scanner token
// information for the label identifier (tokenBeg, tokenEnd, etc.)
private LabeledStatement matchJumpLabelName()
throws IOException
{
LabeledStatement label = null;
if (peekTokenOrEOL() == Token.NAME) {
consumeToken();
if (labelSet != null) {
label = labelSet.get(ts.getString());
}
if (label == null) {
reportError("msg.undef.label");
}
}
return label;
}
private BreakStatement breakStatement()
throws IOException
{
if (currentToken != Token.BREAK) codeBug();
consumeToken();
int lineno = ts.lineno, pos = ts.tokenBeg, end = ts.tokenEnd;
Name breakLabel = null;
if (peekTokenOrEOL() == Token.NAME) {
breakLabel = createNameNode();
end = getNodeEnd(breakLabel);
}
// matchJumpLabelName only matches if there is one
LabeledStatement labels = matchJumpLabelName();
// always use first label as target
Jump breakTarget = labels == null ? null : labels.getFirstLabel();
if (breakTarget == null && breakLabel == null) {
if (loopAndSwitchSet == null || loopAndSwitchSet.size() == 0) {
if (breakLabel == null) {
reportError("msg.bad.break", pos, end - pos);
}
} else {
breakTarget = loopAndSwitchSet.get(loopAndSwitchSet.size() - 1);
}
}
BreakStatement pn = new BreakStatement(pos, end - pos);
pn.setBreakLabel(breakLabel);
// can be null if it's a bad break in error-recovery mode
if (breakTarget != null)
pn.setBreakTarget(breakTarget);
pn.setLineno(lineno);
return pn;
}
private ContinueStatement continueStatement()
throws IOException
{
if (currentToken != Token.CONTINUE) codeBug();
consumeToken();
int lineno = ts.lineno, pos = ts.tokenBeg, end = ts.tokenEnd;
Name label = null;
if (peekTokenOrEOL() == Token.NAME) {
label = createNameNode();
end = getNodeEnd(label);
}
// matchJumpLabelName only matches if there is one
LabeledStatement labels = matchJumpLabelName();
Loop target = null;
if (labels == null && label == null) {
if (loopSet == null || loopSet.size() == 0) {
reportError("msg.continue.outside");
} else {
target = loopSet.get(loopSet.size() - 1);
}
} else {
if (labels == null || !(labels.getStatement() instanceof Loop)) {
reportError("msg.continue.nonloop", pos, end - pos);
}
target = labels == null ? null : (Loop)labels.getStatement();
}
ContinueStatement pn = new ContinueStatement(pos, end - pos);
if (target != null) // can be null in error-recovery mode
pn.setTarget(target);
pn.setLabel(label);
pn.setLineno(lineno);
return pn;
}
private WithStatement withStatement()
throws IOException
{
if (currentToken != Token.WITH) codeBug();
consumeToken();
Comment withComment = getAndResetJsDoc();
int lineno = ts.lineno, pos = ts.tokenBeg, lp = -1, rp = -1;
if (mustMatchToken(Token.LP, "msg.no.paren.with"))
lp = ts.tokenBeg;
AstNode obj = expr();
if (mustMatchToken(Token.RP, "msg.no.paren.after.with"))
rp = ts.tokenBeg;
AstNode body = statement();
WithStatement pn = new WithStatement(pos, getNodeEnd(body) - pos);
pn.setJsDocNode(withComment);
pn.setExpression(obj);
pn.setStatement(body);
pn.setParens(lp, rp);
pn.setLineno(lineno);
return pn;
}
private AstNode letStatement()
throws IOException
{
if (currentToken != Token.LET) codeBug();
consumeToken();
int lineno = ts.lineno, pos = ts.tokenBeg;
AstNode pn;
if (peekToken() == Token.LP) {
pn = let(true, pos);
} else {
pn = variables(Token.LET, pos, true); // else, e.g.: let x=6, y=7;
}
pn.setLineno(lineno);
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 {@code true} if all the bits in the mask are set in "after"
* but not in "before"
*/
private static final boolean nowAllSet(int before, int after, int mask) {
return ((before & mask) != mask) && ((after & mask) == mask);
}
private AstNode returnOrYield(int tt, boolean exprContext)
throws IOException
{
if (!insideFunction()) {
reportError(tt == Token.RETURN ? "msg.bad.return"
: "msg.bad.yield");
}
consumeToken();
int lineno = ts.lineno, pos = ts.tokenBeg, end = ts.tokenEnd;
AstNode e = null;
// This is ugly, but we don't want to require a semicolon.
switch (peekTokenOrEOL()) {
case Token.SEMI: case Token.RC: case Token.RB: case Token.RP:
case Token.EOF: case Token.EOL: case Token.ERROR: case Token.YIELD:
break;
default:
e = expr();
end = getNodeEnd(e);
}
int before = endFlags;
AstNode ret;
if (tt == Token.RETURN) {
endFlags |= e == null ? Node.END_RETURNS : Node.END_RETURNS_VALUE;
ret = new ReturnStatement(pos, end - pos, e);
// see if we need a strict mode warning
if (nowAllSet(before, endFlags,
Node.END_RETURNS|Node.END_RETURNS_VALUE))
addStrictWarning("msg.return.inconsistent", "", pos, end - pos);
} else {
if (!insideFunction())
reportError("msg.bad.yield");
endFlags |= Node.END_YIELDS;
ret = new Yield(pos, end - pos, e);
setRequiresActivation();
setIsGenerator();
if (!exprContext) {
ret = new ExpressionStatement(ret);
}
}
// see if we are mixing yields and value returns.
if (insideFunction()
&& nowAllSet(before, endFlags,
Node.END_YIELDS|Node.END_RETURNS_VALUE)) {
Name name = ((FunctionNode)currentScriptOrFn).getFunctionName();
if (name == null || name.length() == 0)
addError("msg.anon.generator.returns", "");
else
addError("msg.generator.returns", name.getIdentifier());
}
ret.setLineno(lineno);
return ret;
}
private AstNode block()
throws IOException
{
if (currentToken != Token.LC) codeBug();
consumeToken();
int pos = ts.tokenBeg;
Scope block = new Scope(pos);
block.setLineno(ts.lineno);
pushScope(block);
try {
statements(block);
mustMatchToken(Token.RC, "msg.no.brace.block");
block.setLength(ts.tokenEnd - pos);
return block;
} finally {
popScope();
}
}
private AstNode defaultXmlNamespace()
throws IOException
{
if (currentToken != Token.DEFAULT) codeBug();
consumeToken();
mustHaveXML();
setRequiresActivation();
int lineno = ts.lineno, pos = ts.tokenBeg;
if (!(matchToken(Token.NAME) && "xml".equals(ts.getString()))) {
reportError("msg.bad.namespace");
}
if (!(matchToken(Token.NAME) && "namespace".equals(ts.getString()))) {
reportError("msg.bad.namespace");
}
if (!matchToken(Token.ASSIGN)) {
reportError("msg.bad.namespace");
}
AstNode e = expr();
UnaryExpression dxmln = new UnaryExpression(pos, getNodeEnd(e) - pos);
dxmln.setOperator(Token.DEFAULTNAMESPACE);
dxmln.setOperand(e);
dxmln.setLineno(lineno);
ExpressionStatement es = new ExpressionStatement(dxmln, true);
return es;
}
private void recordLabel(Label label, LabeledStatement bundle)
throws IOException
{
// current token should be colon that primaryExpr left untouched
if (peekToken() != Token.COLON) codeBug();
consumeToken();
String name = label.getName();
if (labelSet == null) {
labelSet = new HashMap();
} else {
LabeledStatement ls = labelSet.get(name);
if (ls != null) {
if (compilerEnv.isIdeMode()) {
Label dup = ls.getLabelByName(name);
reportError("msg.dup.label",
dup.getAbsolutePosition(), dup.getLength());
}
reportError("msg.dup.label",
label.getPosition(), label.getLength());
}
}
bundle.addLabel(label);
labelSet.put(name, bundle);
}
/**
* Found a name in a statement context. If it's a label, we gather
* up any following labels and the next non-label statement into a
* {@link LabeledStatement} "bundle" and return that. Otherwise we parse
* an expression and return it wrapped in an {@link ExpressionStatement}.
*/
private AstNode nameOrLabel()
throws IOException
{
if (currentToken != Token.NAME) throw codeBug();
int pos = ts.tokenBeg;
// set check for label and call down to primaryExpr
currentFlaggedToken |= TI_CHECK_LABEL;
AstNode expr = expr();
if (expr.getType() != Token.LABEL) {
AstNode n = new ExpressionStatement(expr, !insideFunction());
n.lineno = expr.lineno;
return n;
}
LabeledStatement bundle = new LabeledStatement(pos);
recordLabel((Label)expr, bundle);
bundle.setLineno(ts.lineno);
// look for more labels
AstNode stmt = null;
while (peekToken() == Token.NAME) {
currentFlaggedToken |= TI_CHECK_LABEL;
expr = expr();
if (expr.getType() != Token.LABEL) {
stmt = new ExpressionStatement(expr, !insideFunction());
autoInsertSemicolon(stmt);
break;
}
recordLabel((Label)expr, bundle);
}
// no more labels; now parse the labeled statement
try {
currentLabel = bundle;
if (stmt == null) {
stmt = statementHelper();
}
} finally {
currentLabel = null;
// remove the labels for this statement from the global set
for (Label lb : bundle.getLabels()) {
labelSet.remove(lb.getName());
}
}
// If stmt has parent assigned its position already is relative
// (See bug #710225)
bundle.setLength(stmt.getParent() == null
? getNodeEnd(stmt) - pos
: getNodeEnd(stmt));
bundle.setStatement(stmt);
return bundle;
}
/**
* Parse a 'var' or 'const' statement, or a 'var' init list in a for
* statement.
* @param declType A token value: either VAR, CONST, or LET depending on
* context.
* @param pos the position where the node should start. It's sometimes
* the var/const/let keyword, and other times the beginning of the first
* token in the first variable declaration.
* @return the parsed variable list
*/
private VariableDeclaration variables(int declType, int pos, boolean isStatement)
throws IOException
{
int end;
VariableDeclaration pn = new VariableDeclaration(pos);
pn.setType(declType);
pn.setLineno(ts.lineno);
Comment varjsdocNode = getAndResetJsDoc();
if (varjsdocNode != null) {
pn.setJsDocNode(varjsdocNode);
}
// Example:
// var foo = {a: 1, b: 2}, bar = [3, 4];
// var {b: s2, a: s1} = foo, x = 6, y, [s3, s4] = bar;
for (;;) {
AstNode destructuring = null;
Name name = null;
int tt = peekToken(), kidPos = ts.tokenBeg;
end = ts.tokenEnd;
if (tt == Token.LB || tt == Token.LC) {
// Destructuring assignment, e.g., var [a,b] = ...
destructuring = destructuringPrimaryExpr();
end = getNodeEnd(destructuring);
if (!(destructuring instanceof DestructuringForm))
reportError("msg.bad.assign.left", kidPos, end - kidPos);
markDestructuring(destructuring);
} else {
// Simple variable name
mustMatchToken(Token.NAME, "msg.bad.var");
name = createNameNode();
name.setLineno(ts.getLineno());
if (inUseStrictDirective) {
String id = ts.getString();
if ("eval".equals(id) || "arguments".equals(ts.getString()))
{
reportError("msg.bad.id.strict", id);
}
}
defineSymbol(declType, ts.getString(), inForInit);
}
int lineno = ts.lineno;
Comment jsdocNode = getAndResetJsDoc();
AstNode init = null;
if (matchToken(Token.ASSIGN)) {
init = assignExpr();
end = getNodeEnd(init);
}
VariableInitializer vi = new VariableInitializer(kidPos, end - kidPos);
if (destructuring != null) {
if (init == null && !inForInit) {
reportError("msg.destruct.assign.no.init");
}
vi.setTarget(destructuring);
} else {
vi.setTarget(name);
}
vi.setInitializer(init);
vi.setType(declType);
vi.setJsDocNode(jsdocNode);
vi.setLineno(lineno);
pn.addVariable(vi);
if (!matchToken(Token.COMMA))
break;
}
pn.setLength(end - pos);
pn.setIsStatement(isStatement);
return pn;
}
// have to pass in 'let' kwd position to compute kid offsets properly
private AstNode let(boolean isStatement, int pos)
throws IOException
{
LetNode pn = new LetNode(pos);
pn.setLineno(ts.lineno);
if (mustMatchToken(Token.LP, "msg.no.paren.after.let"))
pn.setLp(ts.tokenBeg - pos);
pushScope(pn);
try {
VariableDeclaration vars = variables(Token.LET, ts.tokenBeg, isStatement);
pn.setVariables(vars);
if (mustMatchToken(Token.RP, "msg.no.paren.let")) {
pn.setRp(ts.tokenBeg - pos);
}
if (isStatement && peekToken() == Token.LC) {
// let statement
consumeToken();
int beg = ts.tokenBeg; // position stmt at LC
AstNode stmt = statements();
mustMatchToken(Token.RC, "msg.no.curly.let");
stmt.setLength(ts.tokenEnd - beg);
pn.setLength(ts.tokenEnd - pos);
pn.setBody(stmt);
pn.setType(Token.LET);
} else {
// let expression
AstNode expr = expr();
pn.setLength(getNodeEnd(expr) - pos);
pn.setBody(expr);
if (isStatement) {
// let expression in statement context
ExpressionStatement es =
new ExpressionStatement(pn, !insideFunction());
es.setLineno(pn.getLineno());
return es;
}
}
} finally {
popScope();
}
return pn;
}
void defineSymbol(int declType, String name) {
defineSymbol(declType, name, false);
}
void defineSymbol(int declType, String name, boolean ignoreNotInBlock) {
if (name == null) {
if (compilerEnv.isIdeMode()) { // be robust in IDE-mode
return;
} else {
codeBug();
}
}
Scope definingScope = currentScope.getDefiningScope(name);
Symbol symbol = definingScope != null
? definingScope.getSymbol(name)
: null;
int symDeclType = symbol != null ? symbol.getDeclType() : -1;
if (symbol != null
&& (symDeclType == Token.CONST
|| declType == Token.CONST
|| (definingScope == currentScope && symDeclType == Token.LET)))
{
addError(symDeclType == Token.CONST ? "msg.const.redecl" :
symDeclType == Token.LET ? "msg.let.redecl" :
symDeclType == Token.VAR ? "msg.var.redecl" :
symDeclType == Token.FUNCTION ? "msg.fn.redecl" :
"msg.parm.redecl", name);
return;
}
switch (declType) {
case Token.LET:
if (!ignoreNotInBlock &&
((currentScope.getType() == Token.IF) ||
currentScope instanceof Loop)) {
addError("msg.let.decl.not.in.block");
return;
}
currentScope.putSymbol(new Symbol(declType, name));
return;
case Token.VAR:
case Token.CONST:
case Token.FUNCTION:
if (symbol != null) {
if (symDeclType == Token.VAR)
addStrictWarning("msg.var.redecl", name);
else if (symDeclType == Token.LP) {
addStrictWarning("msg.var.hides.arg", name);
}
} else {
currentScriptOrFn.putSymbol(new Symbol(declType, name));
}
return;
case Token.LP:
if (symbol != null) {
// must be duplicate parameter. Second parameter hides the
// first, so go ahead and add the second parameter
addWarning("msg.dup.parms", name);
}
currentScriptOrFn.putSymbol(new Symbol(declType, name));
return;
default:
throw codeBug();
}
}
private AstNode expr()
throws IOException
{
AstNode pn = assignExpr();
int pos = pn.getPosition();
while (matchToken(Token.COMMA)) {
int opPos = ts.tokenBeg;
if (compilerEnv.isStrictMode() && !pn.hasSideEffects())
addStrictWarning("msg.no.side.effects", "",
pos, nodeEnd(pn) - pos);
if (peekToken() == Token.YIELD)
reportError("msg.yield.parenthesized");
pn = new InfixExpression(Token.COMMA, pn, assignExpr(), opPos);
}
return pn;
}
private AstNode assignExpr()
throws IOException
{
int tt = peekToken();
if (tt == Token.YIELD) {
return returnOrYield(tt, true);
}
AstNode pn = condExpr();
tt = peekToken();
if (Token.FIRST_ASSIGN <= tt && tt <= Token.LAST_ASSIGN) {
consumeToken();
// Pull out JSDoc info and reset it before recursing.
Comment jsdocNode = getAndResetJsDoc();
markDestructuring(pn);
int opPos = ts.tokenBeg;
pn = new Assignment(tt, pn, assignExpr(), opPos);
if (jsdocNode != null) {
pn.setJsDocNode(jsdocNode);
}
} else if (tt == Token.SEMI) {
// This may be dead code added intentionally, for JSDoc purposes.
// For example: /** @type Number */ C.prototype.x;
if (currentJsDocComment != null) {
pn.setJsDocNode(getAndResetJsDoc());
}
}
return pn;
}
private AstNode condExpr()
throws IOException
{
AstNode pn = orExpr();
if (matchToken(Token.HOOK)) {
int line = ts.lineno;
int qmarkPos = ts.tokenBeg, colonPos = -1;
/*
* Always accept the 'in' operator in the middle clause of a ternary,
* where it's unambiguous, even if we might be parsing the init of a
* for statement.
*/
boolean wasInForInit = inForInit;
inForInit = false;
AstNode ifTrue;
try {
ifTrue = assignExpr();
} finally {
inForInit = wasInForInit;
}
if (mustMatchToken(Token.COLON, "msg.no.colon.cond"))
colonPos = ts.tokenBeg;
AstNode ifFalse = assignExpr();
int beg = pn.getPosition(), len = getNodeEnd(ifFalse) - beg;
ConditionalExpression ce = new ConditionalExpression(beg, len);
ce.setLineno(line);
ce.setTestExpression(pn);
ce.setTrueExpression(ifTrue);
ce.setFalseExpression(ifFalse);
ce.setQuestionMarkPosition(qmarkPos - beg);
ce.setColonPosition(colonPos - beg);
pn = ce;
}
return pn;
}
private AstNode orExpr()
throws IOException
{
AstNode pn = andExpr();
if (matchToken(Token.OR)) {
int opPos = ts.tokenBeg;
pn = new InfixExpression(Token.OR, pn, orExpr(), opPos);
}
return pn;
}
private AstNode andExpr()
throws IOException
{
AstNode pn = bitOrExpr();
if (matchToken(Token.AND)) {
int opPos = ts.tokenBeg;
pn = new InfixExpression(Token.AND, pn, andExpr(), opPos);
}
return pn;
}
private AstNode bitOrExpr()
throws IOException
{
AstNode pn = bitXorExpr();
while (matchToken(Token.BITOR)) {
int opPos = ts.tokenBeg;
pn = new InfixExpression(Token.BITOR, pn, bitXorExpr(), opPos);
}
return pn;
}
private AstNode bitXorExpr()
throws IOException
{
AstNode pn = bitAndExpr();
while (matchToken(Token.BITXOR)) {
int opPos = ts.tokenBeg;
pn = new InfixExpression(Token.BITXOR, pn, bitAndExpr(), opPos);
}
return pn;
}
private AstNode bitAndExpr()
throws IOException
{
AstNode pn = eqExpr();
while (matchToken(Token.BITAND)) {
int opPos = ts.tokenBeg;
pn = new InfixExpression(Token.BITAND, pn, eqExpr(), opPos);
}
return pn;
}
private AstNode eqExpr()
throws IOException
{
AstNode pn = relExpr();
for (;;) {
int tt = peekToken(), opPos = ts.tokenBeg;
switch (tt) {
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
consumeToken();
int parseToken = tt;
if (compilerEnv.getLanguageVersion() == Context.VERSION_1_2) {
// JavaScript 1.2 uses shallow equality for == and != .
if (tt == Token.EQ)
parseToken = Token.SHEQ;
else if (tt == Token.NE)
parseToken = Token.SHNE;
}
pn = new InfixExpression(parseToken, pn, relExpr(), opPos);
continue;
}
break;
}
return pn;
}
private AstNode relExpr()
throws IOException
{
AstNode pn = shiftExpr();
for (;;) {
int tt = peekToken(), opPos = ts.tokenBeg;
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();
pn = new InfixExpression(tt, pn, shiftExpr(), opPos);
continue;
}
break;
}
return pn;
}
private AstNode shiftExpr()
throws IOException
{
AstNode pn = addExpr();
for (;;) {
int tt = peekToken(), opPos = ts.tokenBeg;
switch (tt) {
case Token.LSH:
case Token.URSH:
case Token.RSH:
consumeToken();
pn = new InfixExpression(tt, pn, addExpr(), opPos);
continue;
}
break;
}
return pn;
}
private AstNode addExpr()
throws IOException
{
AstNode pn = mulExpr();
for (;;) {
int tt = peekToken(), opPos = ts.tokenBeg;
if (tt == Token.ADD || tt == Token.SUB) {
consumeToken();
pn = new InfixExpression(tt, pn, mulExpr(), opPos);
continue;
}
break;
}
return pn;
}
private AstNode mulExpr()
throws IOException
{
AstNode pn = unaryExpr();
for (;;) {
int tt = peekToken(), opPos = ts.tokenBeg;
switch (tt) {
case Token.MUL:
case Token.DIV:
case Token.MOD:
consumeToken();
pn = new InfixExpression(tt, pn, unaryExpr(), opPos);
continue;
}
break;
}
return pn;
}
private AstNode unaryExpr()
throws IOException
{
AstNode node;
int tt = peekToken();
int line = ts.lineno;
switch(tt) {
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.TYPEOF:
consumeToken();
node = new UnaryExpression(tt, ts.tokenBeg, unaryExpr());
node.setLineno(line);
return node;
case Token.ADD:
consumeToken();
// Convert to special POS token in parse tree
node = new UnaryExpression(Token.POS, ts.tokenBeg, unaryExpr());
node.setLineno(line);
return node;
case Token.SUB:
consumeToken();
// Convert to special NEG token in parse tree
node = new UnaryExpression(Token.NEG, ts.tokenBeg, unaryExpr());
node.setLineno(line);
return node;
case Token.INC:
case Token.DEC:
consumeToken();
UnaryExpression expr = new UnaryExpression(tt, ts.tokenBeg,
memberExpr(true));
expr.setLineno(line);
checkBadIncDec(expr);
return expr;
case Token.DELPROP:
consumeToken();
node = new UnaryExpression(tt, ts.tokenBeg, unaryExpr());
node.setLineno(line);
return node;
case Token.ERROR:
consumeToken();
return makeErrorNode();
case Token.LT:
// XML stream encountered in expression.
if (compilerEnv.isXmlAvailable()) {
consumeToken();
return memberExprTail(true, xmlInitializer());
}
// Fall thru to the default handling of RELOP
default:
AstNode pn = memberExpr(true);
// Don't look across a newline boundary for a postfix incop.
tt = peekTokenOrEOL();
if (!(tt == Token.INC || tt == Token.DEC)) {
return pn;
}
consumeToken();
UnaryExpression uexpr =
new UnaryExpression(tt, ts.tokenBeg, pn, true);
uexpr.setLineno(line);
checkBadIncDec(uexpr);
return uexpr;
}
}
private AstNode xmlInitializer()
throws IOException
{
if (currentToken != Token.LT) codeBug();
int pos = ts.tokenBeg, tt = ts.getFirstXMLToken();
if (tt != Token.XML && tt != Token.XMLEND) {
reportError("msg.syntax");
return makeErrorNode();
}
XmlLiteral pn = new XmlLiteral(pos);
pn.setLineno(ts.lineno);
for (;;tt = ts.getNextXMLToken()) {
switch (tt) {
case Token.XML:
pn.addFragment(new XmlString(ts.tokenBeg, ts.getString()));
mustMatchToken(Token.LC, "msg.syntax");
int beg = ts.tokenBeg;
AstNode expr = (peekToken() == Token.RC)
? new EmptyExpression(beg, ts.tokenEnd - beg)
: expr();
mustMatchToken(Token.RC, "msg.syntax");
XmlExpression xexpr = new XmlExpression(beg, expr);
xexpr.setIsXmlAttribute(ts.isXMLAttribute());
xexpr.setLength(ts.tokenEnd - beg);
pn.addFragment(xexpr);
break;
case Token.XMLEND:
pn.addFragment(new XmlString(ts.tokenBeg, ts.getString()));
return pn;
default:
reportError("msg.syntax");
return makeErrorNode();
}
}
}
private List argumentList()
throws IOException
{
if (matchToken(Token.RP))
return null;
List result = new ArrayList();
boolean wasInForInit = inForInit;
inForInit = false;
try {
do {
if (peekToken() == Token.YIELD) {
reportError("msg.yield.parenthesized");
}
AstNode en = assignExpr();
if (peekToken() == Token.FOR) {
try {
result.add(generatorExpression(en, 0, true));
}
catch(IOException ex) {
// #TODO
}
}
else {
result.add(en);
}
} while (matchToken(Token.COMMA));
} finally {
inForInit = wasInForInit;
}
mustMatchToken(Token.RP, "msg.no.paren.arg");
return result;
}
/**
* Parse a new-expression, or if next token isn't {@link Token#NEW},
* a primary expression.
* @param allowCallSyntax passed down to {@link #memberExprTail}
*/
private AstNode memberExpr(boolean allowCallSyntax)
throws IOException
{
int tt = peekToken(), lineno = ts.lineno;
AstNode pn;
if (tt != Token.NEW) {
pn = primaryExpr();
} else {
consumeToken();
int pos = ts.tokenBeg;
NewExpression nx = new NewExpression(pos);
AstNode target = memberExpr(false);
int end = getNodeEnd(target);
nx.setTarget(target);
int lp = -1;
if (matchToken(Token.LP)) {
lp = ts.tokenBeg;
List args = argumentList();
if (args != null && args.size() > ARGC_LIMIT)
reportError("msg.too.many.constructor.args");
int rp = ts.tokenBeg;
end = ts.tokenEnd;
if (args != null)
nx.setArguments(args);
nx.setParens(lp - pos, rp - pos);
}
// 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.
if (matchToken(Token.LC)) {
ObjectLiteral initializer = objectLiteral();
end = getNodeEnd(initializer);
nx.setInitializer(initializer);
}
nx.setLength(end - pos);
pn = nx;
}
pn.setLineno(lineno);
AstNode tail = memberExprTail(allowCallSyntax, pn);
return tail;
}
/**
* Parse any number of "(expr)", "[expr]" ".expr", "..expr",
* or ".(expr)" constructs trailing the passed expression.
* @param pn the non-null parent node
* @return the outermost (lexically last occurring) expression,
* which will have the passed parent node as a descendant
*/
private AstNode memberExprTail(boolean allowCallSyntax, AstNode pn)
throws IOException
{
// we no longer return null for errors, so this won't be null
if (pn == null) codeBug();
int pos = pn.getPosition();
int lineno;
tailLoop:
for (;;) {
int tt = peekToken();
switch (tt) {
case Token.DOT:
case Token.DOTDOT:
lineno = ts.lineno;
pn = propertyAccess(tt, pn);
pn.setLineno(lineno);
break;
case Token.DOTQUERY:
consumeToken();
int opPos = ts.tokenBeg, rp = -1;
lineno = ts.lineno;
mustHaveXML();
setRequiresActivation();
AstNode filter = expr();
int end = getNodeEnd(filter);
if (mustMatchToken(Token.RP, "msg.no.paren")) {
rp = ts.tokenBeg;
end = ts.tokenEnd;
}
XmlDotQuery q = new XmlDotQuery(pos, end - pos);
q.setLeft(pn);
q.setRight(filter);
q.setOperatorPosition(opPos);
q.setRp(rp - pos);
q.setLineno(lineno);
pn = q;
break;
case Token.LB:
consumeToken();
int lb = ts.tokenBeg, rb = -1;
lineno = ts.lineno;
AstNode expr = expr();
end = getNodeEnd(expr);
if (mustMatchToken(Token.RB, "msg.no.bracket.index")) {
rb = ts.tokenBeg;
end = ts.tokenEnd;
}
ElementGet g = new ElementGet(pos, end - pos);
g.setTarget(pn);
g.setElement(expr);
g.setParens(lb, rb);
g.setLineno(lineno);
pn = g;
break;
case Token.LP:
if (!allowCallSyntax) {
break tailLoop;
}
lineno = ts.lineno;
consumeToken();
checkCallRequiresActivation(pn);
FunctionCall f = new FunctionCall(pos);
f.setTarget(pn);
// Assign the line number for the function call to where
// the paren appeared, not where the name expression started.
f.setLineno(lineno);
f.setLp(ts.tokenBeg - pos);
List args = argumentList();
if (args != null && args.size() > ARGC_LIMIT)
reportError("msg.too.many.function.args");
f.setArguments(args);
f.setRp(ts.tokenBeg - pos);
f.setLength(ts.tokenEnd - pos);
pn = f;
break;
default:
break tailLoop;
}
}
return pn;
}
/**
* Handles any construct following a "." or ".." operator.
* @param pn the left-hand side (target) of the operator. Never null.
* @return a PropertyGet, XmlMemberGet, or ErrorNode
*/
private AstNode propertyAccess(int tt, AstNode pn)
throws IOException
{
if (pn == null) codeBug();
int memberTypeFlags = 0, lineno = ts.lineno, dotPos = ts.tokenBeg;
consumeToken();
if (tt == Token.DOTDOT) {
mustHaveXML();
memberTypeFlags = Node.DESCENDANTS_FLAG;
}
if (!compilerEnv.isXmlAvailable()) {
int maybeName = nextToken();
if (maybeName != Token.NAME
&& !(compilerEnv.isReservedKeywordAsIdentifier()
&& TokenStream.isKeyword(ts.getString()))) {
reportError("msg.no.name.after.dot");
}
Name name = createNameNode(true, Token.GETPROP);
PropertyGet pg = new PropertyGet(pn, name, dotPos);
pg.setLineno(lineno);
return pg;
}
AstNode ref = null; // right side of . or .. operator
int token = nextToken();
switch (token) {
case Token.THROW:
// needed for generator.throw();
saveNameTokenData(ts.tokenBeg, "throw", ts.lineno);
ref = propertyName(-1, "throw", memberTypeFlags);
break;
case Token.NAME:
// handles: name, ns::name, ns::*, ns::[expr]
ref = propertyName(-1, ts.getString(), memberTypeFlags);
break;
case Token.MUL:
// handles: *, *::name, *::*, *::[expr]
saveNameTokenData(ts.tokenBeg, "*", ts.lineno);
ref = propertyName(-1, "*", memberTypeFlags);
break;
case Token.XMLATTR:
// handles: '@attr', '@ns::attr', '@ns::*', '@ns::*',
// '@::attr', '@::*', '@*', '@*::attr', '@*::*'
ref = attributeAccess();
break;
default:
if (compilerEnv.isReservedKeywordAsIdentifier()) {
// allow keywords as property names, e.g. ({if: 1})
String name = Token.keywordToName(token);
if (name != null) {
saveNameTokenData(ts.tokenBeg, name, ts.lineno);
ref = propertyName(-1, name, memberTypeFlags);
break;
}
}
reportError("msg.no.name.after.dot");
return makeErrorNode();
}
boolean xml = ref instanceof XmlRef;
InfixExpression result = xml ? new XmlMemberGet() : new PropertyGet();
if (xml && tt == Token.DOT)
result.setType(Token.DOT);
int pos = pn.getPosition();
result.setPosition(pos);
result.setLength(getNodeEnd(ref) - pos);
result.setOperatorPosition(dotPos - pos);
result.setLineno(pn.getLineno());
result.setLeft(pn); // do this after setting position
result.setRight(ref);
return result;
}
/**
* Xml attribute expression:
* {@code @attr}, {@code @ns::attr}, {@code @ns::*}, {@code @ns::*},
* {@code @*}, {@code @*::attr}, {@code @*::*}, {@code @ns::[expr]},
* {@code @*::[expr]}, {@code @[expr]}
* Called if we peeked an '@' token.
*/
private AstNode attributeAccess()
throws IOException
{
int tt = nextToken(), atPos = ts.tokenBeg;
switch (tt) {
// handles: @name, @ns::name, @ns::*, @ns::[expr]
case Token.NAME:
return propertyName(atPos, ts.getString(), 0);
// handles: @*, @*::name, @*::*, @*::[expr]
case Token.MUL:
saveNameTokenData(ts.tokenBeg, "*", ts.lineno);
return propertyName(atPos, "*", 0);
// handles @[expr]
case Token.LB:
return xmlElemRef(atPos, null, -1);
default:
reportError("msg.no.name.after.xmlAttr");
return makeErrorNode();
}
}
/**
* Check if :: follows name in which case it becomes a qualified name.
*
* @param atPos a natural number if we just read an '@' token, else -1
*
* @param s the name or string that was matched (an identifier, "throw" or
* "*").
*
* @param memberTypeFlags flags tracking whether we're a '.' or '..' child
*
* @return an XmlRef node if it's an attribute access, a child of a
* '..' operator, or the name is followed by ::. For a plain name,
* returns a Name node. Returns an ErrorNode for malformed XML
* expressions. (For now - might change to return a partial XmlRef.)
*/
private AstNode propertyName(int atPos, String s, int memberTypeFlags)
throws IOException
{
int pos = atPos != -1 ? atPos : ts.tokenBeg, lineno = ts.lineno;
int colonPos = -1;
Name name = createNameNode(true, currentToken);
Name ns = null;
if (matchToken(Token.COLONCOLON)) {
ns = name;
colonPos = ts.tokenBeg;
switch (nextToken()) {
// handles name::name
case Token.NAME:
name = createNameNode();
break;
// handles name::*
case Token.MUL:
saveNameTokenData(ts.tokenBeg, "*", ts.lineno);
name = createNameNode(false, -1);
break;
// handles name::[expr] or *::[expr]
case Token.LB:
return xmlElemRef(atPos, ns, colonPos);
default:
reportError("msg.no.name.after.coloncolon");
return makeErrorNode();
}
}
if (ns == null && memberTypeFlags == 0 && atPos == -1) {
return name;
}
XmlPropRef ref = new XmlPropRef(pos, getNodeEnd(name) - pos);
ref.setAtPos(atPos);
ref.setNamespace(ns);
ref.setColonPos(colonPos);
ref.setPropName(name);
ref.setLineno(lineno);
return ref;
}
/**
* Parse the [expr] portion of an xml element reference, e.g.
* @[expr], @*::[expr], or ns::[expr].
*/
private XmlElemRef xmlElemRef(int atPos, Name namespace, int colonPos)
throws IOException
{
int lb = ts.tokenBeg, rb = -1, pos = atPos != -1 ? atPos : lb;
AstNode expr = expr();
int end = getNodeEnd(expr);
if (mustMatchToken(Token.RB, "msg.no.bracket.index")) {
rb = ts.tokenBeg;
end = ts.tokenEnd;
}
XmlElemRef ref = new XmlElemRef(pos, end - pos);
ref.setNamespace(namespace);
ref.setColonPos(colonPos);
ref.setAtPos(atPos);
ref.setExpression(expr);
ref.setBrackets(lb, rb);
return ref;
}
private AstNode destructuringPrimaryExpr()
throws IOException, ParserException
{
try {
inDestructuringAssignment = true;
return primaryExpr();
} finally {
inDestructuringAssignment = false;
}
}
private AstNode primaryExpr()
throws IOException
{
int ttFlagged = nextFlaggedToken();
int tt = ttFlagged & CLEAR_TI_MASK;
switch(tt) {
case Token.FUNCTION:
return function(FunctionNode.FUNCTION_EXPRESSION);
case Token.LB:
return arrayLiteral();
case Token.LC:
return objectLiteral();
case Token.LET:
return let(false, ts.tokenBeg);
case Token.LP:
return parenExpr();
case Token.XMLATTR:
mustHaveXML();
return attributeAccess();
case Token.NAME:
return name(ttFlagged, tt);
case Token.NUMBER: {
String s = ts.getString();
if (this.inUseStrictDirective && ts.isNumberOctal()) {
reportError("msg.no.octal.strict");
}
if (ts.isNumberOctal()) {
s = "0"+s;
}
if (ts.isNumberHex()) {
s = "0x"+s;
}
return new NumberLiteral(ts.tokenBeg,
s,
ts.getNumber());
}
case Token.STRING:
return createStringLiteral();
case Token.DIV:
case Token.ASSIGN_DIV:
// Got / or /= which in this context means a regexp
ts.readRegExp(tt);
int pos = ts.tokenBeg, end = ts.tokenEnd;
RegExpLiteral re = new RegExpLiteral(pos, end - pos);
re.setValue(ts.getString());
re.setFlags(ts.readAndClearRegExpFlags());
return re;
case Token.NULL:
case Token.THIS:
case Token.FALSE:
case Token.TRUE:
pos = ts.tokenBeg; end = ts.tokenEnd;
return new KeywordLiteral(pos, end - pos, 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;
}
// should only be reachable in IDE/error-recovery mode
return makeErrorNode();
}
private AstNode parenExpr() throws IOException {
boolean wasInForInit = inForInit;
inForInit = false;
try {
Comment jsdocNode = getAndResetJsDoc();
int lineno = ts.lineno;
int begin = ts.tokenBeg;
AstNode e = expr();
if (peekToken() == Token.FOR) {
return generatorExpression(e, begin);
}
ParenthesizedExpression pn = new ParenthesizedExpression(e);
if (jsdocNode == null) {
jsdocNode = getAndResetJsDoc();
}
if (jsdocNode != null) {
pn.setJsDocNode(jsdocNode);
}
mustMatchToken(Token.RP, "msg.no.paren");
pn.setLength(ts.tokenEnd - pn.getPosition());
pn.setLineno(lineno);
return pn;
} finally {
inForInit = wasInForInit;
}
}
private AstNode name(int ttFlagged, int tt) throws IOException {
String nameString = ts.getString();
int namePos = ts.tokenBeg, nameLineno = ts.lineno;
if (0 != (ttFlagged & TI_CHECK_LABEL) && peekToken() == Token.COLON) {
// Do not consume colon. It is used as an unwind indicator
// to return to statementHelper.
Label label = new Label(namePos, ts.tokenEnd - namePos);
label.setName(nameString);
label.setLineno(ts.lineno);
return label;
}
// Not a label. Unfortunately peeking the next token to check for
// a colon has biffed ts.tokenBeg, ts.tokenEnd. We store the name's
// bounds in instance vars and createNameNode uses them.
saveNameTokenData(namePos, nameString, nameLineno);
if (compilerEnv.isXmlAvailable()) {
return propertyName(-1, nameString, 0);
} else {
return createNameNode(true, Token.NAME);
}
}
/**
* May return an {@link ArrayLiteral} or {@link ArrayComprehension}.
*/
private AstNode arrayLiteral()
throws IOException
{
if (currentToken != Token.LB) codeBug();
int pos = ts.tokenBeg, end = ts.tokenEnd;
List elements = new ArrayList();
ArrayLiteral pn = new ArrayLiteral(pos);
boolean after_lb_or_comma = true;
int afterComma = -1;
int skipCount = 0;
for (;;) {
int tt = peekToken();
if (tt == Token.COMMA) {
consumeToken();
afterComma = ts.tokenEnd;
if (!after_lb_or_comma) {
after_lb_or_comma = true;
} else {
elements.add(new EmptyExpression(ts.tokenBeg, 1));
skipCount++;
}
} else if (tt == Token.RB) {
consumeToken();
// 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.
end = ts.tokenEnd;
pn.setDestructuringLength(elements.size() +
(after_lb_or_comma ? 1 : 0));
pn.setSkipCount(skipCount);
if (afterComma != -1)
warnTrailingComma(pos, elements, afterComma);
break;
} else if (tt == Token.FOR && !after_lb_or_comma
&& elements.size() == 1) {
return arrayComprehension(elements.get(0), pos);
} else if (tt == Token.EOF) {
reportError("msg.no.bracket.arg");
break;
} else {
if (!after_lb_or_comma) {
reportError("msg.no.bracket.arg");
}
elements.add(assignExpr());
after_lb_or_comma = false;
afterComma = -1;
}
}
for (AstNode e : elements) {
pn.addElement(e);
}
pn.setLength(end - pos);
return pn;
}
/**
* Parse a JavaScript 1.7 Array comprehension.
* @param result the first expression after the opening left-bracket
* @param pos start of LB token that begins the array comprehension
* @return the array comprehension or an error node
*/
private AstNode arrayComprehension(AstNode result, int pos)
throws IOException
{
List loops =
new ArrayList();
while (peekToken() == Token.FOR) {
loops.add(arrayComprehensionLoop());
}
int ifPos = -1;
ConditionData data = null;
if (peekToken() == Token.IF) {
consumeToken();
ifPos = ts.tokenBeg - pos;
data = condition();
}
mustMatchToken(Token.RB, "msg.no.bracket.arg");
ArrayComprehension pn = new ArrayComprehension(pos, ts.tokenEnd - pos);
pn.setResult(result);
pn.setLoops(loops);
if (data != null) {
pn.setIfPosition(ifPos);
pn.setFilter(data.condition);
pn.setFilterLp(data.lp - pos);
pn.setFilterRp(data.rp - pos);
}
return pn;
}
private ArrayComprehensionLoop arrayComprehensionLoop()
throws IOException
{
if (nextToken() != Token.FOR) codeBug();
int pos = ts.tokenBeg;
int eachPos = -1, lp = -1, rp = -1, inPos = -1;
ArrayComprehensionLoop pn = new ArrayComprehensionLoop(pos);
pushScope(pn);
try {
if (matchToken(Token.NAME)) {
if (ts.getString().equals("each")) {
eachPos = ts.tokenBeg - pos;
} else {
reportError("msg.no.paren.for");
}
}
if (mustMatchToken(Token.LP, "msg.no.paren.for")) {
lp = ts.tokenBeg - pos;
}
AstNode iter = null;
switch (peekToken()) {
case Token.LB:
case Token.LC:
// handle destructuring assignment
iter = destructuringPrimaryExpr();
markDestructuring(iter);
break;
case Token.NAME:
consumeToken();
iter = createNameNode();
break;
default:
reportError("msg.bad.var");
}
// Define as a let since we want the scope of the variable to
// be restricted to the array comprehension
if (iter.getType() == Token.NAME) {
defineSymbol(Token.LET, ts.getString(), true);
}
if (mustMatchToken(Token.IN, "msg.in.after.for.name"))
inPos = ts.tokenBeg - pos;
AstNode obj = expr();
if (mustMatchToken(Token.RP, "msg.no.paren.for.ctrl"))
rp = ts.tokenBeg - pos;
pn.setLength(ts.tokenEnd - pos);
pn.setIterator(iter);
pn.setIteratedObject(obj);
pn.setInPosition(inPos);
pn.setEachPosition(eachPos);
pn.setIsForEach(eachPos != -1);
pn.setParens(lp, rp);
return pn;
} finally {
popScope();
}
}
private AstNode generatorExpression(AstNode result, int pos)
throws IOException
{
return generatorExpression(result, pos, false);
}
private AstNode generatorExpression(AstNode result, int pos, boolean inFunctionParams)
throws IOException
{
List loops =
new ArrayList();
while (peekToken() == Token.FOR) {
loops.add(generatorExpressionLoop());
}
int ifPos = -1;
ConditionData data = null;
if (peekToken() == Token.IF) {
consumeToken();
ifPos = ts.tokenBeg - pos;
data = condition();
}
if(!inFunctionParams) {
mustMatchToken(Token.RP, "msg.no.paren.let");
}
GeneratorExpression pn = new GeneratorExpression(pos, ts.tokenEnd - pos);
pn.setResult(result);
pn.setLoops(loops);
if (data != null) {
pn.setIfPosition(ifPos);
pn.setFilter(data.condition);
pn.setFilterLp(data.lp - pos);
pn.setFilterRp(data.rp - pos);
}
return pn;
}
private GeneratorExpressionLoop generatorExpressionLoop()
throws IOException
{
if (nextToken() != Token.FOR) codeBug();
int pos = ts.tokenBeg;
int lp = -1, rp = -1, inPos = -1;
GeneratorExpressionLoop pn = new GeneratorExpressionLoop(pos);
pushScope(pn);
try {
if (mustMatchToken(Token.LP, "msg.no.paren.for")) {
lp = ts.tokenBeg - pos;
}
AstNode iter = null;
switch (peekToken()) {
case Token.LB:
case Token.LC:
// handle destructuring assignment
iter = destructuringPrimaryExpr();
markDestructuring(iter);
break;
case Token.NAME:
consumeToken();
iter = createNameNode();
break;
default:
reportError("msg.bad.var");
}
// Define as a let since we want the scope of the variable to
// be restricted to the array comprehension
if (iter.getType() == Token.NAME) {
defineSymbol(Token.LET, ts.getString(), true);
}
if (mustMatchToken(Token.IN, "msg.in.after.for.name"))
inPos = ts.tokenBeg - pos;
AstNode obj = expr();
if (mustMatchToken(Token.RP, "msg.no.paren.for.ctrl"))
rp = ts.tokenBeg - pos;
pn.setLength(ts.tokenEnd - pos);
pn.setIterator(iter);
pn.setIteratedObject(obj);
pn.setInPosition(inPos);
pn.setParens(lp, rp);
return pn;
} finally {
popScope();
}
}
private static final int PROP_ENTRY = 1;
private static final int GET_ENTRY = 2;
private static final int SET_ENTRY = 4;
private static final int METHOD_ENTRY = 8;
private ObjectLiteral objectLiteral()
throws IOException
{
int pos = ts.tokenBeg, lineno = ts.lineno;
int afterComma = -1;
List elems = new ArrayList();
Set getterNames = null;
Set setterNames = null;
if (this.inUseStrictDirective) {
getterNames = new HashSet();
setterNames = new HashSet();
}
Comment objJsdocNode = getAndResetJsDoc();
commaLoop:
for (;;) {
String propertyName = null;
int entryKind = PROP_ENTRY;
int tt = peekToken();
Comment jsdocNode = getAndResetJsDoc();
if (tt == Token.RC) {
if (afterComma != -1)
warnTrailingComma(pos, elems, afterComma);
break commaLoop;
} else {
AstNode pname = objliteralProperty();
if (pname == null) {
propertyName = null;
reportError("msg.bad.prop");
} else {
propertyName = ts.getString();
int ppos = ts.tokenBeg;
consumeToken();
// This code path needs to handle both destructuring object
// literals like:
// var {get, b} = {get: 1, b: 2};
// and getters like:
// var x = {get 1() { return 2; };
// So we check a whitelist of tokens to check if we're at the
// first case. (Because of keywords, the second case may be
// many tokens.)
int peeked = peekToken();
if (peeked != Token.COMMA
&& peeked != Token.COLON
&& peeked != Token.RC)
{
if (peeked == Token.LP) {
entryKind = METHOD_ENTRY;
} else if (pname.getType() == Token.NAME) {
if ("get".equals(propertyName)) {
entryKind = GET_ENTRY;
} else if ("set".equals(propertyName)) {
entryKind = SET_ENTRY;
}
}
if (entryKind == GET_ENTRY || entryKind == SET_ENTRY) {
pname = objliteralProperty();
if (pname == null) {
reportError("msg.bad.prop");
}
consumeToken();
}
if (pname == null) {
propertyName = null;
} else {
propertyName = ts.getString();
ObjectProperty objectProp = methodDefinition(
ppos, pname, entryKind);
pname.setJsDocNode(jsdocNode);
elems.add(objectProp);
}
} else {
pname.setJsDocNode(jsdocNode);
elems.add(plainProperty(pname, tt));
}
}
}
if (this.inUseStrictDirective && propertyName != null) {
switch (entryKind) {
case PROP_ENTRY:
case METHOD_ENTRY:
if (getterNames.contains(propertyName)
|| setterNames.contains(propertyName)) {
addError("msg.dup.obj.lit.prop.strict", propertyName);
}
getterNames.add(propertyName);
setterNames.add(propertyName);
break;
case GET_ENTRY:
if (getterNames.contains(propertyName)) {
addError("msg.dup.obj.lit.prop.strict", propertyName);
}
getterNames.add(propertyName);
break;
case SET_ENTRY:
if (setterNames.contains(propertyName)) {
addError("msg.dup.obj.lit.prop.strict", propertyName);
}
setterNames.add(propertyName);
break;
}
}
// Eat any dangling jsdoc in the property.
getAndResetJsDoc();
if (matchToken(Token.COMMA)) {
afterComma = ts.tokenEnd;
} else {
break commaLoop;
}
}
mustMatchToken(Token.RC, "msg.no.brace.prop");
ObjectLiteral pn = new ObjectLiteral(pos, ts.tokenEnd - pos);
if (objJsdocNode != null) {
pn.setJsDocNode(objJsdocNode);
}
pn.setElements(elems);
pn.setLineno(lineno);
return pn;
}
private AstNode objliteralProperty() throws IOException {
AstNode pname;
int tt = peekToken();
switch(tt) {
case Token.NAME:
pname = createNameNode();
break;
case Token.STRING:
pname = createStringLiteral();
break;
case Token.NUMBER:
pname = new NumberLiteral(
ts.tokenBeg, ts.getString(), ts.getNumber());
break;
default:
if (compilerEnv.isReservedKeywordAsIdentifier()
&& TokenStream.isKeyword(ts.getString())) {
// convert keyword to property name, e.g. ({if: 1})
pname = createNameNode();
break;
}
return null;
}
return pname;
}
private ObjectProperty plainProperty(AstNode property, int ptt)
throws IOException
{
// Support, e.g., |var {x, y} = o| as destructuring shorthand
// for |var {x: x, y: y} = o|, as implemented in spidermonkey JS 1.8.
int tt = peekToken();
if ((tt == Token.COMMA || tt == Token.RC) && ptt == Token.NAME
&& compilerEnv.getLanguageVersion() >= Context.VERSION_1_8) {
if (!inDestructuringAssignment) {
reportError("msg.bad.object.init");
}
AstNode nn = new Name(property.getPosition(), property.getString());
ObjectProperty pn = new ObjectProperty();
pn.putProp(Node.DESTRUCTURING_SHORTHAND, Boolean.TRUE);
pn.setLeftAndRight(property, nn);
return pn;
}
mustMatchToken(Token.COLON, "msg.no.colon.prop");
ObjectProperty pn = new ObjectProperty();
pn.setOperatorPosition(ts.tokenBeg);
pn.setLeftAndRight(property, assignExpr());
return pn;
}
private ObjectProperty methodDefinition(int pos, AstNode propName, int entryKind)
throws IOException
{
FunctionNode fn = function(FunctionNode.FUNCTION_EXPRESSION);
// We've already parsed the function name, so fn should be anonymous.
Name name = fn.getFunctionName();
if (name != null && name.length() != 0) {
reportError("msg.bad.prop");
}
ObjectProperty pn = new ObjectProperty(pos);
switch (entryKind) {
case GET_ENTRY:
pn.setIsGetterMethod();
fn.setFunctionIsGetterMethod();
break;
case SET_ENTRY:
pn.setIsSetterMethod();
fn.setFunctionIsSetterMethod();
break;
case METHOD_ENTRY:
pn.setIsNormalMethod();
fn.setFunctionIsNormalMethod();
break;
}
int end = getNodeEnd(fn);
pn.setLeft(propName);
pn.setRight(fn);
pn.setLength(end - pos);
return pn;
}
private Name createNameNode() {
return createNameNode(false, Token.NAME);
}
/**
* Create a {@code Name} node using the token info from the
* last scanned name. In some cases we need to either synthesize
* a name node, or we lost the name token information by peeking.
* If the {@code token} parameter is not {@link Token#NAME}, then
* we use token info saved in instance vars.
*/
private Name createNameNode(boolean checkActivation, int token) {
int beg = ts.tokenBeg;
String s = ts.getString();
int lineno = ts.lineno;
if (!"".equals(prevNameTokenString)) {
beg = prevNameTokenStart;
s = prevNameTokenString;
lineno = prevNameTokenLineno;
prevNameTokenStart = 0;
prevNameTokenString = "";
prevNameTokenLineno = 0;
}
if (s == null) {
if (compilerEnv.isIdeMode()) {
s = "";
} else {
codeBug();
}
}
Name name = new Name(beg, s);
name.setLineno(lineno);
if (checkActivation) {
checkActivationName(s, token);
}
return name;
}
private StringLiteral createStringLiteral() {
int pos = ts.tokenBeg, end = ts.tokenEnd;
StringLiteral s = new StringLiteral(pos, end - pos);
s.setLineno(ts.lineno);
s.setValue(ts.getString());
s.setQuoteCharacter(ts.getQuoteChar());
return s;
}
protected void checkActivationName(String name, int token) {
if (!insideFunction()) {
return;
}
boolean activation = false;
if ("arguments".equals(name)
|| (compilerEnv.getActivationNames() != null
&& compilerEnv.getActivationNames().contains(name)))
{
activation = true;
} else if ("length".equals(name)) {
if (token == Token.GETPROP
&& compilerEnv.getLanguageVersion() == Context.VERSION_1_2)
{
// Use of "length" in 1.2 requires an activation object.
activation = true;
}
}
if (activation) {
setRequiresActivation();
}
}
protected void setRequiresActivation() {
if (insideFunction()) {
((FunctionNode)currentScriptOrFn).setRequiresActivation();
}
}
private void checkCallRequiresActivation(AstNode pn) {
if ((pn.getType() == Token.NAME
&& "eval".equals(((Name)pn).getIdentifier()))
|| (pn.getType() == Token.GETPROP &&
"eval".equals(((PropertyGet)pn).getProperty().getIdentifier())))
setRequiresActivation();
}
protected void setIsGenerator() {
if (insideFunction()) {
((FunctionNode)currentScriptOrFn).setIsGenerator();
}
}
private void checkBadIncDec(UnaryExpression expr) {
AstNode op = removeParens(expr.getOperand());
int tt = op.getType();
if (!(tt == Token.NAME
|| tt == Token.GETPROP
|| tt == Token.GETELEM
|| tt == Token.GET_REF
|| tt == Token.CALL))
reportError(expr.getType() == Token.INC
? "msg.bad.incr"
: "msg.bad.decr");
}
private ErrorNode makeErrorNode() {
ErrorNode pn = new ErrorNode(ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
pn.setLineno(ts.lineno);
return pn;
}
// Return end of node. Assumes node does NOT have a parent yet.
private int nodeEnd(AstNode node) {
return node.getPosition() + node.getLength();
}
private void saveNameTokenData(int pos, String name, int lineno) {
prevNameTokenStart = pos;
prevNameTokenString = name;
prevNameTokenLineno = lineno;
}
/**
* Return the file offset of the beginning of the input source line
* containing the passed position.
*
* @param pos an offset into the input source stream. If the offset
* is negative, it's converted to 0, and if it's beyond the end of
* the source buffer, the last source position is used.
*
* @return the offset of the beginning of the line containing pos
* (i.e. 1+ the offset of the first preceding newline). Returns -1
* if the {@link CompilerEnvirons} is not set to ide-mode,
* and {@link #parse(java.io.Reader,String,int)} was used.
*/
private int lineBeginningFor(int pos) {
if (sourceChars == null) {
return -1;
}
if (pos <= 0) {
return 0;
}
char[] buf = sourceChars;
if (pos >= buf.length) {
pos = buf.length - 1;
}
while (--pos >= 0) {
char c = buf[pos];
if (ScriptRuntime.isJSLineTerminator(c)) {
return pos + 1; // want position after the newline
}
}
return 0;
}
private void warnMissingSemi(int pos, int end) {
// Should probably change this to be a CompilerEnvirons setting,
// with an enum Never, Always, Permissive, where Permissive means
// don't warn for 1-line functions like function (s) {return x+2}
if (compilerEnv.isStrictMode()) {
int[] linep = new int[2];
String line = ts.getLine(end, linep);
// this code originally called lineBeginningFor() and in order to
// preserve its different line-offset handling, we need to special
// case ide-mode here
int beg = compilerEnv.isIdeMode()
? Math.max(pos, end - linep[1])
: pos;
if (line != null) {
addStrictWarning("msg.missing.semi", "", beg, end - beg,
linep[0], line, linep[1]);
} else {
// no line information available, report warning at current line
addStrictWarning("msg.missing.semi", "", beg, end - beg);
}
}
}
private void warnTrailingComma(int pos, List> elems, int commaPos) {
if (compilerEnv.getWarnTrailingComma()) {
// back up from comma to beginning of line or array/objlit
if (!elems.isEmpty()) {
pos = ((AstNode)elems.get(0)).getPosition();
}
pos = Math.max(pos, lineBeginningFor(commaPos));
addWarning("msg.extra.trailing.comma", pos, commaPos - pos);
}
}
private String readFully(Reader reader) throws IOException {
BufferedReader in = new BufferedReader(reader);
try {
char[] cbuf = new char[1024];
StringBuilder sb = new StringBuilder(1024);
int bytes_read;
while ((bytes_read = in.read(cbuf, 0, 1024)) != -1) {
sb.append(cbuf, 0, bytes_read);
}
return sb.toString();
} finally {
in.close();
}
}
// helps reduce clutter in the already-large function() method
protected class PerFunctionVariables
{
private ScriptNode savedCurrentScriptOrFn;
private Scope savedCurrentScope;
private int savedEndFlags;
private boolean savedInForInit;
private Map savedLabelSet;
private List savedLoopSet;
private List savedLoopAndSwitchSet;
PerFunctionVariables(FunctionNode fnNode) {
savedCurrentScriptOrFn = Parser.this.currentScriptOrFn;
Parser.this.currentScriptOrFn = fnNode;
savedCurrentScope = Parser.this.currentScope;
Parser.this.currentScope = fnNode;
savedLabelSet = Parser.this.labelSet;
Parser.this.labelSet = null;
savedLoopSet = Parser.this.loopSet;
Parser.this.loopSet = null;
savedLoopAndSwitchSet = Parser.this.loopAndSwitchSet;
Parser.this.loopAndSwitchSet = null;
savedEndFlags = Parser.this.endFlags;
Parser.this.endFlags = 0;
savedInForInit = Parser.this.inForInit;
Parser.this.inForInit = false;
}
void restore() {
Parser.this.currentScriptOrFn = savedCurrentScriptOrFn;
Parser.this.currentScope = savedCurrentScope;
Parser.this.labelSet = savedLabelSet;
Parser.this.loopSet = savedLoopSet;
Parser.this.loopAndSwitchSet = savedLoopAndSwitchSet;
Parser.this.endFlags = savedEndFlags;
Parser.this.inForInit = savedInForInit;
}
}
/**
* Given a destructuring assignment with a left hand side parsed
* as an array or object literal and a right hand side expression,
* rewrite as a series of assignments to the variables defined in
* left from property accesses to the expression on the right.
* @param type declaration type: Token.VAR or Token.LET or -1
* @param left array or object literal containing NAME nodes for
* variables to assign
* @param right expression to assign from
* @return expression that performs a series of assignments to
* the variables defined in left
*/
Node createDestructuringAssignment(int type, Node left, Node right)
{
String tempName = currentScriptOrFn.getNextTempName();
Node result = destructuringAssignmentHelper(type, left, right,
tempName);
Node comma = result.getLastChild();
comma.addChildToBack(createName(tempName));
return result;
}
Node destructuringAssignmentHelper(int variableType, Node left,
Node right, String tempName)
{
Scope result = createScopeNode(Token.LETEXPR, left.getLineno());
result.addChildToFront(new Node(Token.LET,
createName(Token.NAME, tempName, right)));
try {
pushScope(result);
defineSymbol(Token.LET, tempName, true);
} finally {
popScope();
}
Node comma = new Node(Token.COMMA);
result.addChildToBack(comma);
List destructuringNames = new ArrayList();
boolean empty = true;
switch (left.getType()) {
case Token.ARRAYLIT:
empty = destructuringArray((ArrayLiteral)left,
variableType, tempName, comma,
destructuringNames);
break;
case Token.OBJECTLIT:
empty = destructuringObject((ObjectLiteral)left,
variableType, tempName, comma,
destructuringNames);
break;
case Token.GETPROP:
case Token.GETELEM:
switch (variableType) {
case Token.CONST:
case Token.LET:
case Token.VAR:
reportError("msg.bad.assign.left");
}
comma.addChildToBack(simpleAssignment(left, createName(tempName)));
break;
default:
reportError("msg.bad.assign.left");
}
if (empty) {
// Don't want a COMMA node with no children. Just add a zero.
comma.addChildToBack(createNumber(0));
}
result.putProp(Node.DESTRUCTURING_NAMES, destructuringNames);
return result;
}
boolean destructuringArray(ArrayLiteral array,
int variableType,
String tempName,
Node parent,
List destructuringNames)
{
boolean empty = true;
int setOp = variableType == Token.CONST
? Token.SETCONST : Token.SETNAME;
int index = 0;
for (AstNode n : array.getElements()) {
if (n.getType() == Token.EMPTY) {
index++;
continue;
}
Node rightElem = new Node(Token.GETELEM,
createName(tempName),
createNumber(index));
if (n.getType() == Token.NAME) {
String name = n.getString();
parent.addChildToBack(new Node(setOp,
createName(Token.BINDNAME,
name, null),
rightElem));
if (variableType != -1) {
defineSymbol(variableType, name, true);
destructuringNames.add(name);
}
} else {
parent.addChildToBack
(destructuringAssignmentHelper
(variableType, n,
rightElem,
currentScriptOrFn.getNextTempName()));
}
index++;
empty = false;
}
return empty;
}
boolean destructuringObject(ObjectLiteral node,
int variableType,
String tempName,
Node parent,
List destructuringNames)
{
boolean empty = true;
int setOp = variableType == Token.CONST
? Token.SETCONST : Token.SETNAME;
for (ObjectProperty prop : node.getElements()) {
int lineno = 0;
// This function is sometimes called from the IRFactory when
// when executing regression tests, and in those cases the
// tokenStream isn't set. Deal with it.
if (ts != null) {
lineno = ts.lineno;
}
AstNode id = prop.getLeft();
Node rightElem = null;
if (id instanceof Name) {
Node s = Node.newString(((Name)id).getIdentifier());
rightElem = new Node(Token.GETPROP, createName(tempName), s);
} else if (id instanceof StringLiteral) {
Node s = Node.newString(((StringLiteral)id).getValue());
rightElem = new Node(Token.GETPROP, createName(tempName), s);
} else if (id instanceof NumberLiteral) {
Node s = createNumber((int)((NumberLiteral)id).getNumber());
rightElem = new Node(Token.GETELEM, createName(tempName), s);
} else {
throw codeBug();
}
rightElem.setLineno(lineno);
AstNode value = prop.getRight();
if (value.getType() == Token.NAME) {
String name = ((Name)value).getIdentifier();
parent.addChildToBack(new Node(setOp,
createName(Token.BINDNAME,
name, null),
rightElem));
if (variableType != -1) {
defineSymbol(variableType, name, true);
destructuringNames.add(name);
}
} else {
parent.addChildToBack
(destructuringAssignmentHelper
(variableType, value, rightElem,
currentScriptOrFn.getNextTempName()));
}
empty = false;
}
return empty;
}
protected Node createName(String name) {
checkActivationName(name, Token.NAME);
return Node.newString(Token.NAME, name);
}
protected Node createName(int type, String name, Node child) {
Node result = createName(name);
result.setType(type);
if (child != null)
result.addChildToBack(child);
return result;
}
protected Node createNumber(double number) {
return Node.newNumber(number);
}
/**
* Create a node that can be used to hold lexically scoped variable
* definitions (via let declarations).
*
* @param token the token of the node to create
* @param lineno line number of source
* @return the created node
*/
protected Scope createScopeNode(int token, int lineno) {
Scope scope =new Scope();
scope.setType(token);
scope.setLineno(lineno);
return scope;
}
// Quickie tutorial for some of the interpreter bytecodes.
//
// GETPROP - for normal foo.bar prop access; right side is a name
// GETELEM - for normal foo[bar] element access; rhs is an expr
// SETPROP - for assignment when left side is a GETPROP
// SETELEM - for assignment when left side is a GETELEM
// DELPROP - used for delete foo.bar or foo[bar]
//
// GET_REF, SET_REF, DEL_REF - in general, these mean you're using
// get/set/delete on a right-hand side expression (possibly with no
// explicit left-hand side) that doesn't use the normal JavaScript
// Object (i.e. ScriptableObject) get/set/delete functions, but wants
// to provide its own versions instead. It will ultimately implement
// Ref, and currently SpecialRef (for __proto__ etc.) and XmlName
// (for E4X XML objects) are the only implementations. The runtime
// notices these bytecodes and delegates get/set/delete to the object.
//
// BINDNAME: used in assignments. LHS is evaluated first to get a
// specific object containing the property ("binding" the property
// to the object) so that it's always the same object, regardless of
// side effects in the RHS.
protected Node simpleAssignment(Node left, Node right) {
int nodeType = left.getType();
switch (nodeType) {
case Token.NAME:
if (inUseStrictDirective &&
"eval".equals(((Name) left).getIdentifier()))
{
reportError("msg.bad.id.strict",
((Name) left).getIdentifier());
}
left.setType(Token.BINDNAME);
return new Node(Token.SETNAME, left, right);
case Token.GETPROP:
case Token.GETELEM: {
Node obj, id;
// If it's a PropertyGet or ElementGet, we're in the parse pass.
// We could alternately have PropertyGet and ElementGet
// override getFirstChild/getLastChild and return the appropriate
// field, but that seems just as ugly as this casting.
if (left instanceof PropertyGet) {
obj = ((PropertyGet)left).getTarget();
id = ((PropertyGet)left).getProperty();
} else if (left instanceof ElementGet) {
obj = ((ElementGet)left).getTarget();
id = ((ElementGet)left).getElement();
} else {
// This branch is called during IRFactory transform pass.
obj = left.getFirstChild();
id = left.getLastChild();
}
int type;
if (nodeType == Token.GETPROP) {
type = Token.SETPROP;
// TODO(stevey) - see https://bugzilla.mozilla.org/show_bug.cgi?id=492036
// The new AST code generates NAME tokens for GETPROP ids where the old parser
// generated STRING nodes. If we don't set the type to STRING below, this will
// cause java.lang.VerifyError in codegen for code like
// "var obj={p:3};[obj.p]=[9];"
id.setType(Token.STRING);
} else {
type = Token.SETELEM;
}
return new Node(type, obj, id, right);
}
case Token.GET_REF: {
Node ref = left.getFirstChild();
checkMutableReference(ref);
return new Node(Token.SET_REF, ref, right);
}
}
throw codeBug();
}
protected void checkMutableReference(Node n) {
int memberTypeFlags = n.getIntProp(Node.MEMBER_TYPE_PROP, 0);
if ((memberTypeFlags & Node.DESCENDANTS_FLAG) != 0) {
reportError("msg.bad.assign.left");
}
}
// remove any ParenthesizedExpression wrappers
protected AstNode removeParens(AstNode node) {
while (node instanceof ParenthesizedExpression) {
node = ((ParenthesizedExpression)node).getExpression();
}
return node;
}
void markDestructuring(AstNode node) {
if (node instanceof DestructuringForm) {
((DestructuringForm)node).setIsDestructuring(true);
} else if (node instanceof ParenthesizedExpression) {
markDestructuring(((ParenthesizedExpression)node).getExpression());
}
}
// throw a failed-assertion with some helpful debugging info
private RuntimeException codeBug()
throws RuntimeException
{
throw Kit.codeBug("ts.cursor=" + ts.cursor
+ ", ts.tokenBeg=" + ts.tokenBeg
+ ", currentToken=" + currentToken);
}
}