--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,5587 @@
+/*
+ * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.parser;
+
+import static jdk.nashorn.internal.codegen.CompilerConstants.ANON_FUNCTION_PREFIX;
+import static jdk.nashorn.internal.codegen.CompilerConstants.EVAL;
+import static jdk.nashorn.internal.codegen.CompilerConstants.PROGRAM;
+import static jdk.nashorn.internal.parser.TokenType.ARROW;
+import static jdk.nashorn.internal.parser.TokenType.ASSIGN;
+import static jdk.nashorn.internal.parser.TokenType.CASE;
+import static jdk.nashorn.internal.parser.TokenType.CATCH;
+import static jdk.nashorn.internal.parser.TokenType.CLASS;
+import static jdk.nashorn.internal.parser.TokenType.COLON;
+import static jdk.nashorn.internal.parser.TokenType.COMMARIGHT;
+import static jdk.nashorn.internal.parser.TokenType.COMMENT;
+import static jdk.nashorn.internal.parser.TokenType.CONST;
+import static jdk.nashorn.internal.parser.TokenType.DECPOSTFIX;
+import static jdk.nashorn.internal.parser.TokenType.DECPREFIX;
+import static jdk.nashorn.internal.parser.TokenType.ELLIPSIS;
+import static jdk.nashorn.internal.parser.TokenType.ELSE;
+import static jdk.nashorn.internal.parser.TokenType.EOF;
+import static jdk.nashorn.internal.parser.TokenType.EOL;
+import static jdk.nashorn.internal.parser.TokenType.EQ_STRICT;
+import static jdk.nashorn.internal.parser.TokenType.ESCSTRING;
+import static jdk.nashorn.internal.parser.TokenType.EXPORT;
+import static jdk.nashorn.internal.parser.TokenType.EXTENDS;
+import static jdk.nashorn.internal.parser.TokenType.FINALLY;
+import static jdk.nashorn.internal.parser.TokenType.FUNCTION;
+import static jdk.nashorn.internal.parser.TokenType.IDENT;
+import static jdk.nashorn.internal.parser.TokenType.IF;
+import static jdk.nashorn.internal.parser.TokenType.IMPORT;
+import static jdk.nashorn.internal.parser.TokenType.INCPOSTFIX;
+import static jdk.nashorn.internal.parser.TokenType.LBRACE;
+import static jdk.nashorn.internal.parser.TokenType.LBRACKET;
+import static jdk.nashorn.internal.parser.TokenType.LET;
+import static jdk.nashorn.internal.parser.TokenType.LPAREN;
+import static jdk.nashorn.internal.parser.TokenType.MUL;
+import static jdk.nashorn.internal.parser.TokenType.PERIOD;
+import static jdk.nashorn.internal.parser.TokenType.RBRACE;
+import static jdk.nashorn.internal.parser.TokenType.RBRACKET;
+import static jdk.nashorn.internal.parser.TokenType.RPAREN;
+import static jdk.nashorn.internal.parser.TokenType.SEMICOLON;
+import static jdk.nashorn.internal.parser.TokenType.SPREAD_ARRAY;
+import static jdk.nashorn.internal.parser.TokenType.STATIC;
+import static jdk.nashorn.internal.parser.TokenType.STRING;
+import static jdk.nashorn.internal.parser.TokenType.SUPER;
+import static jdk.nashorn.internal.parser.TokenType.TEMPLATE;
+import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_HEAD;
+import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_MIDDLE;
+import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_TAIL;
+import static jdk.nashorn.internal.parser.TokenType.TERNARY;
+import static jdk.nashorn.internal.parser.TokenType.VAR;
+import static jdk.nashorn.internal.parser.TokenType.VOID;
+import static jdk.nashorn.internal.parser.TokenType.WHILE;
+import static jdk.nashorn.internal.parser.TokenType.YIELD;
+import static jdk.nashorn.internal.parser.TokenType.YIELD_STAR;
+
+import java.io.Serializable;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+import jdk.nashorn.internal.codegen.CompilerConstants;
+import jdk.nashorn.internal.codegen.Namespace;
+import jdk.nashorn.internal.ir.AccessNode;
+import jdk.nashorn.internal.ir.BaseNode;
+import jdk.nashorn.internal.ir.BinaryNode;
+import jdk.nashorn.internal.ir.Block;
+import jdk.nashorn.internal.ir.BlockStatement;
+import jdk.nashorn.internal.ir.BreakNode;
+import jdk.nashorn.internal.ir.CallNode;
+import jdk.nashorn.internal.ir.CaseNode;
+import jdk.nashorn.internal.ir.CatchNode;
+import jdk.nashorn.internal.ir.ClassNode;
+import jdk.nashorn.internal.ir.ContinueNode;
+import jdk.nashorn.internal.ir.DebuggerNode;
+import jdk.nashorn.internal.ir.EmptyNode;
+import jdk.nashorn.internal.ir.ErrorNode;
+import jdk.nashorn.internal.ir.Expression;
+import jdk.nashorn.internal.ir.ExpressionList;
+import jdk.nashorn.internal.ir.ExpressionStatement;
+import jdk.nashorn.internal.ir.ForNode;
+import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.IdentNode;
+import jdk.nashorn.internal.ir.IfNode;
+import jdk.nashorn.internal.ir.IndexNode;
+import jdk.nashorn.internal.ir.JoinPredecessorExpression;
+import jdk.nashorn.internal.ir.LabelNode;
+import jdk.nashorn.internal.ir.LexicalContext;
+import jdk.nashorn.internal.ir.LiteralNode;
+import jdk.nashorn.internal.ir.Module;
+import jdk.nashorn.internal.ir.Node;
+import jdk.nashorn.internal.ir.ObjectNode;
+import jdk.nashorn.internal.ir.PropertyKey;
+import jdk.nashorn.internal.ir.PropertyNode;
+import jdk.nashorn.internal.ir.ReturnNode;
+import jdk.nashorn.internal.ir.RuntimeNode;
+import jdk.nashorn.internal.ir.Statement;
+import jdk.nashorn.internal.ir.SwitchNode;
+import jdk.nashorn.internal.ir.TemplateLiteral;
+import jdk.nashorn.internal.ir.TernaryNode;
+import jdk.nashorn.internal.ir.ThrowNode;
+import jdk.nashorn.internal.ir.TryNode;
+import jdk.nashorn.internal.ir.UnaryNode;
+import jdk.nashorn.internal.ir.VarNode;
+import jdk.nashorn.internal.ir.WhileNode;
+import jdk.nashorn.internal.ir.WithNode;
+import jdk.nashorn.internal.ir.debug.ASTWriter;
+import jdk.nashorn.internal.ir.debug.PrintVisitor;
+import jdk.nashorn.internal.ir.visitor.NodeVisitor;
+import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.ErrorManager;
+import jdk.nashorn.internal.runtime.JSErrorType;
+import jdk.nashorn.internal.runtime.ParserException;
+import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
+import jdk.nashorn.internal.runtime.ScriptEnvironment;
+import jdk.nashorn.internal.runtime.ScriptFunctionData;
+import jdk.nashorn.internal.runtime.ScriptingFunctions;
+import jdk.nashorn.internal.runtime.Source;
+import jdk.nashorn.internal.runtime.Timing;
+import jdk.nashorn.internal.runtime.linker.NameCodec;
+import jdk.nashorn.internal.runtime.logging.DebugLogger;
+import jdk.nashorn.internal.runtime.logging.Loggable;
+import jdk.nashorn.internal.runtime.logging.Logger;
+
+/**
+ * Builds the IR.
+ */
+@Logger(name="parser")
+public class Parser extends AbstractParser implements Loggable {
+ private static final String ARGUMENTS_NAME = CompilerConstants.ARGUMENTS_VAR.symbolName();
+ private static final String CONSTRUCTOR_NAME = "constructor";
+ private static final String GET_NAME = "get";
+ private static final String SET_NAME = "set";
+
+ /** Current env. */
+ private final ScriptEnvironment env;
+
+ /** Is scripting mode. */
+ private final boolean scripting;
+
+ private List<Statement> functionDeclarations;
+
+ private final ParserContext lc;
+ private final Deque<Object> defaultNames;
+
+ /** Namespace for function names where not explicitly given */
+ private final Namespace namespace;
+
+ private final DebugLogger log;
+
+ /** to receive line information from Lexer when scanning multine literals. */
+ protected final Lexer.LineInfoReceiver lineInfoReceiver;
+
+ private RecompilableScriptFunctionData reparsedFunction;
+
+ /**
+ * Constructor
+ *
+ * @param env script environment
+ * @param source source to parse
+ * @param errors error manager
+ */
+ public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors) {
+ this(env, source, errors, env._strict, null);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param env script environment
+ * @param source source to parse
+ * @param errors error manager
+ * @param strict strict
+ * @param log debug logger if one is needed
+ */
+ public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict, final DebugLogger log) {
+ this(env, source, errors, strict, 0, log);
+ }
+
+ /**
+ * Construct a parser.
+ *
+ * @param env script environment
+ * @param source source to parse
+ * @param errors error manager
+ * @param strict parser created with strict mode enabled.
+ * @param lineOffset line offset to start counting lines from
+ * @param log debug logger if one is needed
+ */
+ public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict, final int lineOffset, final DebugLogger log) {
+ super(source, errors, strict, lineOffset);
+ this.lc = new ParserContext();
+ this.defaultNames = new ArrayDeque<>();
+ this.env = env;
+ this.namespace = new Namespace(env.getNamespace());
+ this.scripting = env._scripting;
+ if (this.scripting) {
+ this.lineInfoReceiver = new Lexer.LineInfoReceiver() {
+ @Override
+ public void lineInfo(final int receiverLine, final int receiverLinePosition) {
+ // update the parser maintained line information
+ Parser.this.line = receiverLine;
+ Parser.this.linePosition = receiverLinePosition;
+ }
+ };
+ } else {
+ // non-scripting mode script can't have multi-line literals
+ this.lineInfoReceiver = null;
+ }
+
+ this.log = log == null ? DebugLogger.DISABLED_LOGGER : log;
+ }
+
+ @Override
+ public DebugLogger getLogger() {
+ return log;
+ }
+
+ @Override
+ public DebugLogger initLogger(final Context context) {
+ return context.getLogger(this.getClass());
+ }
+
+ /**
+ * Sets the name for the first function. This is only used when reparsing anonymous functions to ensure they can
+ * preserve their already assigned name, as that name doesn't appear in their source text.
+ * @param name the name for the first parsed function.
+ */
+ public void setFunctionName(final String name) {
+ defaultNames.push(createIdentNode(0, 0, name));
+ }
+
+ /**
+ * Sets the {@link RecompilableScriptFunctionData} representing the function being reparsed (when this
+ * parser instance is used to reparse a previously parsed function, as part of its on-demand compilation).
+ * This will trigger various special behaviors, such as skipping nested function bodies.
+ * @param reparsedFunction the function being reparsed.
+ */
+ public void setReparsedFunction(final RecompilableScriptFunctionData reparsedFunction) {
+ this.reparsedFunction = reparsedFunction;
+ }
+
+ /**
+ * Execute parse and return the resulting function node.
+ * Errors will be thrown and the error manager will contain information
+ * if parsing should fail
+ *
+ * This is the default parse call, which will name the function node
+ * {code :program} {@link CompilerConstants#PROGRAM}
+ *
+ * @return function node resulting from successful parse
+ */
+ public FunctionNode parse() {
+ return parse(PROGRAM.symbolName(), 0, source.getLength(), 0);
+ }
+
+ /**
+ * Set up first token. Skips opening EOL.
+ */
+ private void scanFirstToken() {
+ k = -1;
+ next();
+ }
+
+ /**
+ * Execute parse and return the resulting function node.
+ * Errors will be thrown and the error manager will contain information
+ * if parsing should fail
+ *
+ * This should be used to create one and only one function node
+ *
+ * @param scriptName name for the script, given to the parsed FunctionNode
+ * @param startPos start position in source
+ * @param len length of parse
+ * @param reparseFlags flags provided by {@link RecompilableScriptFunctionData} as context for
+ * the code being reparsed. This allows us to recognize special forms of functions such
+ * as property getters and setters or instances of ES6 method shorthand in object literals.
+ *
+ * @return function node resulting from successful parse
+ */
+ public FunctionNode parse(final String scriptName, final int startPos, final int len, final int reparseFlags) {
+ final boolean isTimingEnabled = env.isTimingEnabled();
+ final long t0 = isTimingEnabled ? System.nanoTime() : 0L;
+ log.info(this, " begin for '", scriptName, "'");
+
+ try {
+ stream = new TokenStream();
+ lexer = new Lexer(source, startPos, len, stream, scripting && !env._no_syntax_extensions, env._es6, reparsedFunction != null);
+ lexer.line = lexer.pendingLine = lineOffset + 1;
+ line = lineOffset;
+
+ scanFirstToken();
+ // Begin parse.
+ return program(scriptName, reparseFlags);
+ } catch (final Exception e) {
+ handleParseException(e);
+
+ return null;
+ } finally {
+ final String end = this + " end '" + scriptName + "'";
+ if (isTimingEnabled) {
+ env._timing.accumulateTime(toString(), System.nanoTime() - t0);
+ log.info(end, "' in ", Timing.toMillisPrint(System.nanoTime() - t0), " ms");
+ } else {
+ log.info(end);
+ }
+ }
+ }
+
+ /**
+ * Parse and return the resulting module.
+ * Errors will be thrown and the error manager will contain information
+ * if parsing should fail
+ *
+ * @param moduleName name for the module, given to the parsed FunctionNode
+ * @param startPos start position in source
+ * @param len length of parse
+ *
+ * @return function node resulting from successful parse
+ */
+ public FunctionNode parseModule(final String moduleName, final int startPos, final int len) {
+ try {
+ stream = new TokenStream();
+ lexer = new Lexer(source, startPos, len, stream, scripting && !env._no_syntax_extensions, env._es6, reparsedFunction != null);
+ lexer.line = lexer.pendingLine = lineOffset + 1;
+ line = lineOffset;
+
+ scanFirstToken();
+ // Begin parse.
+ return module(moduleName);
+ } catch (final Exception e) {
+ handleParseException(e);
+
+ return null;
+ }
+ }
+
+ /**
+ * Entry point for parsing a module.
+ *
+ * @param moduleName the module name
+ * @return the parsed module
+ */
+ public FunctionNode parseModule(final String moduleName) {
+ return parseModule(moduleName, 0, source.getLength());
+ }
+
+ /**
+ * Parse and return the list of function parameter list. A comma
+ * separated list of function parameter identifiers is expected to be parsed.
+ * Errors will be thrown and the error manager will contain information
+ * if parsing should fail. This method is used to check if parameter Strings
+ * passed to "Function" constructor is a valid or not.
+ *
+ * @return the list of IdentNodes representing the formal parameter list
+ */
+ public List<IdentNode> parseFormalParameterList() {
+ try {
+ stream = new TokenStream();
+ lexer = new Lexer(source, stream, scripting && !env._no_syntax_extensions, env._es6);
+
+ scanFirstToken();
+
+ return formalParameterList(TokenType.EOF, false);
+ } catch (final Exception e) {
+ handleParseException(e);
+ return null;
+ }
+ }
+
+ /**
+ * Execute parse and return the resulting function node.
+ * Errors will be thrown and the error manager will contain information
+ * if parsing should fail. This method is used to check if code String
+ * passed to "Function" constructor is a valid function body or not.
+ *
+ * @return function node resulting from successful parse
+ */
+ public FunctionNode parseFunctionBody() {
+ try {
+ stream = new TokenStream();
+ lexer = new Lexer(source, stream, scripting && !env._no_syntax_extensions, env._es6);
+ final int functionLine = line;
+
+ scanFirstToken();
+
+ // Make a fake token for the function.
+ final long functionToken = Token.toDesc(FUNCTION, 0, source.getLength());
+ // Set up the function to append elements.
+
+ final IdentNode ident = new IdentNode(functionToken, Token.descPosition(functionToken), PROGRAM.symbolName());
+ final ParserContextFunctionNode function = createParserContextFunctionNode(ident, functionToken, FunctionNode.Kind.NORMAL, functionLine, Collections.<IdentNode>emptyList());
+ lc.push(function);
+
+ final ParserContextBlockNode body = newBlock();
+
+ functionDeclarations = new ArrayList<>();
+ sourceElements(0);
+ addFunctionDeclarations(function);
+ functionDeclarations = null;
+
+ restoreBlock(body);
+ body.setFlag(Block.NEEDS_SCOPE);
+
+ final Block functionBody = new Block(functionToken, source.getLength() - 1,
+ body.getFlags() | Block.IS_SYNTHETIC, body.getStatements());
+ lc.pop(function);
+
+ expect(EOF);
+
+ final FunctionNode functionNode = createFunctionNode(
+ function,
+ functionToken,
+ ident,
+ Collections.<IdentNode>emptyList(),
+ FunctionNode.Kind.NORMAL,
+ functionLine,
+ functionBody);
+ printAST(functionNode);
+ return functionNode;
+ } catch (final Exception e) {
+ handleParseException(e);
+ return null;
+ }
+ }
+
+ private void handleParseException(final Exception e) {
+ // Extract message from exception. The message will be in error
+ // message format.
+ String message = e.getMessage();
+
+ // If empty message.
+ if (message == null) {
+ message = e.toString();
+ }
+
+ // Issue message.
+ if (e instanceof ParserException) {
+ errors.error((ParserException)e);
+ } else {
+ errors.error(message);
+ }
+
+ if (env._dump_on_error) {
+ e.printStackTrace(env.getErr());
+ }
+ }
+
+ /**
+ * Skip to a good parsing recovery point.
+ */
+ private void recover(final Exception e) {
+ if (e != null) {
+ // Extract message from exception. The message will be in error
+ // message format.
+ String message = e.getMessage();
+
+ // If empty message.
+ if (message == null) {
+ message = e.toString();
+ }
+
+ // Issue message.
+ if (e instanceof ParserException) {
+ errors.error((ParserException)e);
+ } else {
+ errors.error(message);
+ }
+
+ if (env._dump_on_error) {
+ e.printStackTrace(env.getErr());
+ }
+ }
+
+ // Skip to a recovery point.
+ loop:
+ while (true) {
+ switch (type) {
+ case EOF:
+ // Can not go any further.
+ break loop;
+ case EOL:
+ case SEMICOLON:
+ case RBRACE:
+ // Good recovery points.
+ next();
+ break loop;
+ default:
+ // So we can recover after EOL.
+ nextOrEOL();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Set up a new block.
+ *
+ * @return New block.
+ */
+ private ParserContextBlockNode newBlock() {
+ return lc.push(new ParserContextBlockNode(token));
+ }
+
+ private ParserContextFunctionNode createParserContextFunctionNode(final IdentNode ident, final long functionToken, final FunctionNode.Kind kind, final int functionLine, final List<IdentNode> parameters) {
+ // Build function name.
+ final StringBuilder sb = new StringBuilder();
+
+ final ParserContextFunctionNode parentFunction = lc.getCurrentFunction();
+ if (parentFunction != null && !parentFunction.isProgram()) {
+ sb.append(parentFunction.getName()).append(CompilerConstants.NESTED_FUNCTION_SEPARATOR.symbolName());
+ }
+
+ assert ident.getName() != null;
+ sb.append(ident.getName());
+
+ final String name = namespace.uniqueName(sb.toString());
+ assert parentFunction != null || kind == FunctionNode.Kind.MODULE || name.equals(PROGRAM.symbolName()) : "name = " + name;
+
+ int flags = 0;
+ if (isStrictMode) {
+ flags |= FunctionNode.IS_STRICT;
+ }
+ if (parentFunction == null) {
+ flags |= FunctionNode.IS_PROGRAM;
+ }
+
+ final ParserContextFunctionNode functionNode = new ParserContextFunctionNode(functionToken, ident, name, namespace, functionLine, kind, parameters);
+ functionNode.setFlag(flags);
+ return functionNode;
+ }
+
+ private FunctionNode createFunctionNode(final ParserContextFunctionNode function, final long startToken, final IdentNode ident, final List<IdentNode> parameters, final FunctionNode.Kind kind, final int functionLine, final Block body) {
+ // assert body.isFunctionBody() || body.getFlag(Block.IS_PARAMETER_BLOCK) && ((BlockStatement) body.getLastStatement()).getBlock().isFunctionBody();
+ // Start new block.
+ final FunctionNode functionNode =
+ new FunctionNode(
+ source,
+ functionLine,
+ body.getToken(),
+ Token.descPosition(body.getToken()),
+ startToken,
+ function.getLastToken(),
+ namespace,
+ ident,
+ function.getName(),
+ parameters,
+ function.getParameterExpressions(),
+ kind,
+ function.getFlags(),
+ body,
+ function.getEndParserState(),
+ function.getModule(),
+ function.getDebugFlags());
+
+ printAST(functionNode);
+
+ return functionNode;
+ }
+
+ /**
+ * Restore the current block.
+ */
+ private ParserContextBlockNode restoreBlock(final ParserContextBlockNode block) {
+ return lc.pop(block);
+ }
+
+ /**
+ * Get the statements in a block.
+ * @return Block statements.
+ */
+ private Block getBlock(final boolean needsBraces) {
+ final long blockToken = token;
+ final ParserContextBlockNode newBlock = newBlock();
+ try {
+ // Block opening brace.
+ if (needsBraces) {
+ expect(LBRACE);
+ }
+ // Accumulate block statements.
+ statementList();
+
+ } finally {
+ restoreBlock(newBlock);
+ }
+
+ // Block closing brace.
+ if (needsBraces) {
+ expect(RBRACE);
+ }
+
+ final int flags = newBlock.getFlags() | (needsBraces ? 0 : Block.IS_SYNTHETIC);
+ return new Block(blockToken, finish, flags, newBlock.getStatements());
+ }
+
+ /**
+ * Get all the statements generated by a single statement.
+ * @return Statements.
+ */
+ private Block getStatement() {
+ return getStatement(false);
+ }
+
+ private Block getStatement(final boolean labelledStatement) {
+ if (type == LBRACE) {
+ return getBlock(true);
+ }
+ // Set up new block. Captures first token.
+ final ParserContextBlockNode newBlock = newBlock();
+ try {
+ statement(false, 0, true, labelledStatement);
+ } finally {
+ restoreBlock(newBlock);
+ }
+ return new Block(newBlock.getToken(), finish, newBlock.getFlags() | Block.IS_SYNTHETIC, newBlock.getStatements());
+ }
+
+ /**
+ * Detect calls to special functions.
+ * @param ident Called function.
+ */
+ private void detectSpecialFunction(final IdentNode ident) {
+ final String name = ident.getName();
+
+ if (EVAL.symbolName().equals(name)) {
+ markEval(lc);
+ } else if (SUPER.getName().equals(name)) {
+ assert ident.isDirectSuper();
+ markSuperCall(lc);
+ }
+ }
+
+ /**
+ * Detect use of special properties.
+ * @param ident Referenced property.
+ */
+ private void detectSpecialProperty(final IdentNode ident) {
+ if (isArguments(ident)) {
+ // skip over arrow functions, e.g. function f() { return (() => arguments.length)(); }
+ getCurrentNonArrowFunction().setFlag(FunctionNode.USES_ARGUMENTS);
+ }
+ }
+
+ private boolean useBlockScope() {
+ return env._es6;
+ }
+
+ private boolean isES6() {
+ return env._es6;
+ }
+
+ private static boolean isArguments(final String name) {
+ return ARGUMENTS_NAME.equals(name);
+ }
+
+ static boolean isArguments(final IdentNode ident) {
+ return isArguments(ident.getName());
+ }
+
+ /**
+ * Tells whether a IdentNode can be used as L-value of an assignment
+ *
+ * @param ident IdentNode to be checked
+ * @return whether the ident can be used as L-value
+ */
+ private static boolean checkIdentLValue(final IdentNode ident) {
+ return ident.tokenType().getKind() != TokenKind.KEYWORD;
+ }
+
+ /**
+ * Verify an assignment expression.
+ * @param op Operation token.
+ * @param lhs Left hand side expression.
+ * @param rhs Right hand side expression.
+ * @return Verified expression.
+ */
+ private Expression verifyAssignment(final long op, final Expression lhs, final Expression rhs) {
+ final TokenType opType = Token.descType(op);
+
+ switch (opType) {
+ case ASSIGN:
+ case ASSIGN_ADD:
+ case ASSIGN_BIT_AND:
+ case ASSIGN_BIT_OR:
+ case ASSIGN_BIT_XOR:
+ case ASSIGN_DIV:
+ case ASSIGN_MOD:
+ case ASSIGN_MUL:
+ case ASSIGN_SAR:
+ case ASSIGN_SHL:
+ case ASSIGN_SHR:
+ case ASSIGN_SUB:
+ if (lhs instanceof IdentNode) {
+ if (!checkIdentLValue((IdentNode)lhs)) {
+ return referenceError(lhs, rhs, false);
+ }
+ verifyIdent((IdentNode)lhs, "assignment");
+ break;
+ } else if (lhs instanceof AccessNode || lhs instanceof IndexNode) {
+ break;
+ } else if (opType == ASSIGN && isDestructuringLhs(lhs)) {
+ verifyDestructuringAssignmentPattern(lhs, "assignment");
+ break;
+ } else {
+ return referenceError(lhs, rhs, env._early_lvalue_error);
+ }
+ default:
+ break;
+ }
+
+ // Build up node.
+ if(BinaryNode.isLogical(opType)) {
+ return new BinaryNode(op, new JoinPredecessorExpression(lhs), new JoinPredecessorExpression(rhs));
+ }
+ return new BinaryNode(op, lhs, rhs);
+ }
+
+ private boolean isDestructuringLhs(final Expression lhs) {
+ if (lhs instanceof ObjectNode || lhs instanceof LiteralNode.ArrayLiteralNode) {
+ return isES6();
+ }
+ return false;
+ }
+
+ private void verifyDestructuringAssignmentPattern(final Expression pattern, final String contextString) {
+ assert pattern instanceof ObjectNode || pattern instanceof LiteralNode.ArrayLiteralNode;
+ pattern.accept(new VerifyDestructuringPatternNodeVisitor(new LexicalContext()) {
+ @Override
+ protected void verifySpreadElement(final Expression lvalue) {
+ if (!checkValidLValue(lvalue, contextString)) {
+ throw error(AbstractParser.message("invalid.lvalue"), lvalue.getToken());
+ }
+ }
+
+ @Override
+ public boolean enterIdentNode(final IdentNode identNode) {
+ verifyIdent(identNode, contextString);
+ if (!checkIdentLValue(identNode)) {
+ referenceError(identNode, null, true);
+ return false;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean enterAccessNode(final AccessNode accessNode) {
+ return false;
+ }
+
+ @Override
+ public boolean enterIndexNode(final IndexNode indexNode) {
+ return false;
+ }
+
+ @Override
+ protected boolean enterDefault(final Node node) {
+ throw error(String.format("unexpected node in AssignmentPattern: %s", node));
+ }
+ });
+ }
+
+ /**
+ * Reduce increment/decrement to simpler operations.
+ * @param firstToken First token.
+ * @param tokenType Operation token (INCPREFIX/DEC.)
+ * @param expression Left hand side expression.
+ * @param isPostfix Prefix or postfix.
+ * @return Reduced expression.
+ */
+ private static UnaryNode incDecExpression(final long firstToken, final TokenType tokenType, final Expression expression, final boolean isPostfix) {
+ if (isPostfix) {
+ return new UnaryNode(Token.recast(firstToken, tokenType == DECPREFIX ? DECPOSTFIX : INCPOSTFIX), expression.getStart(), Token.descPosition(firstToken) + Token.descLength(firstToken), expression);
+ }
+
+ return new UnaryNode(firstToken, expression);
+ }
+
+ /**
+ * -----------------------------------------------------------------------
+ *
+ * Grammar based on
+ *
+ * ECMAScript Language Specification
+ * ECMA-262 5th Edition / December 2009
+ *
+ * -----------------------------------------------------------------------
+ */
+
+ /**
+ * Program :
+ * SourceElements?
+ *
+ * See 14
+ *
+ * Parse the top level script.
+ */
+ private FunctionNode program(final String scriptName, final int reparseFlags) {
+ // Make a pseudo-token for the script holding its start and length.
+ final long functionToken = Token.toDesc(FUNCTION, Token.descPosition(Token.withDelimiter(token)), source.getLength());
+ final int functionLine = line;
+
+ final IdentNode ident = new IdentNode(functionToken, Token.descPosition(functionToken), scriptName);
+ final ParserContextFunctionNode script = createParserContextFunctionNode(
+ ident,
+ functionToken,
+ FunctionNode.Kind.SCRIPT,
+ functionLine,
+ Collections.<IdentNode>emptyList());
+ lc.push(script);
+ final ParserContextBlockNode body = newBlock();
+
+ functionDeclarations = new ArrayList<>();
+ sourceElements(reparseFlags);
+ addFunctionDeclarations(script);
+ functionDeclarations = null;
+
+ restoreBlock(body);
+ body.setFlag(Block.NEEDS_SCOPE);
+ final Block programBody = new Block(functionToken, finish, body.getFlags() | Block.IS_SYNTHETIC | Block.IS_BODY, body.getStatements());
+ lc.pop(script);
+ script.setLastToken(token);
+
+ expect(EOF);
+
+ return createFunctionNode(script, functionToken, ident, Collections.<IdentNode>emptyList(), FunctionNode.Kind.SCRIPT, functionLine, programBody);
+ }
+
+ /**
+ * Directive value or null if statement is not a directive.
+ *
+ * @param stmt Statement to be checked
+ * @return Directive value if the given statement is a directive
+ */
+ private String getDirective(final Node stmt) {
+ if (stmt instanceof ExpressionStatement) {
+ final Node expr = ((ExpressionStatement)stmt).getExpression();
+ if (expr instanceof LiteralNode) {
+ final LiteralNode<?> lit = (LiteralNode<?>)expr;
+ final long litToken = lit.getToken();
+ final TokenType tt = Token.descType(litToken);
+ // A directive is either a string or an escape string
+ if (tt == TokenType.STRING || tt == TokenType.ESCSTRING) {
+ // Make sure that we don't unescape anything. Return as seen in source!
+ return source.getString(lit.getStart(), Token.descLength(litToken));
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * SourceElements :
+ * SourceElement
+ * SourceElements SourceElement
+ *
+ * See 14
+ *
+ * Parse the elements of the script or function.
+ */
+ private void sourceElements(final int reparseFlags) {
+ List<Node> directiveStmts = null;
+ boolean checkDirective = true;
+ int functionFlags = reparseFlags;
+ final boolean oldStrictMode = isStrictMode;
+
+
+ try {
+ // If is a script, then process until the end of the script.
+ while (type != EOF) {
+ // Break if the end of a code block.
+ if (type == RBRACE) {
+ break;
+ }
+
+ try {
+ // Get the next element.
+ statement(true, functionFlags, false, false);
+ functionFlags = 0;
+
+ // check for directive prologues
+ if (checkDirective) {
+ // skip any debug statement like line number to get actual first line
+ final Statement lastStatement = lc.getLastStatement();
+
+ // get directive prologue, if any
+ final String directive = getDirective(lastStatement);
+
+ // If we have seen first non-directive statement,
+ // no more directive statements!!
+ checkDirective = directive != null;
+
+ if (checkDirective) {
+ if (!oldStrictMode) {
+ if (directiveStmts == null) {
+ directiveStmts = new ArrayList<>();
+ }
+ directiveStmts.add(lastStatement);
+ }
+
+ // handle use strict directive
+ if ("use strict".equals(directive)) {
+ isStrictMode = true;
+ final ParserContextFunctionNode function = lc.getCurrentFunction();
+ function.setFlag(FunctionNode.IS_STRICT);
+
+ // We don't need to check these, if lexical environment is already strict
+ if (!oldStrictMode && directiveStmts != null) {
+ // check that directives preceding this one do not violate strictness
+ for (final Node statement : directiveStmts) {
+ // the get value will force unescape of preceding
+ // escaped string directives
+ getValue(statement.getToken());
+ }
+
+ // verify that function name as well as parameter names
+ // satisfy strict mode restrictions.
+ verifyIdent(function.getIdent(), "function name");
+ for (final IdentNode param : function.getParameters()) {
+ verifyIdent(param, "function parameter");
+ }
+ }
+ } else if (Context.DEBUG) {
+ final int debugFlag = FunctionNode.getDirectiveFlag(directive);
+ if (debugFlag != 0) {
+ final ParserContextFunctionNode function = lc.getCurrentFunction();
+ function.setDebugFlag(debugFlag);
+ }
+ }
+ }
+ }
+ } catch (final Exception e) {
+ final int errorLine = line;
+ final long errorToken = token;
+ //recover parsing
+ recover(e);
+ final ErrorNode errorExpr = new ErrorNode(errorToken, finish);
+ final ExpressionStatement expressionStatement = new ExpressionStatement(errorLine, errorToken, finish, errorExpr);
+ appendStatement(expressionStatement);
+ }
+
+ // No backtracking from here on.
+ stream.commit(k);
+ }
+ } finally {
+ isStrictMode = oldStrictMode;
+ }
+ }
+
+ /**
+ * Parse any of the basic statement types.
+ *
+ * Statement :
+ * BlockStatement
+ * VariableStatement
+ * EmptyStatement
+ * ExpressionStatement
+ * IfStatement
+ * BreakableStatement
+ * ContinueStatement
+ * BreakStatement
+ * ReturnStatement
+ * WithStatement
+ * LabelledStatement
+ * ThrowStatement
+ * TryStatement
+ * DebuggerStatement
+ *
+ * BreakableStatement :
+ * IterationStatement
+ * SwitchStatement
+ *
+ * BlockStatement :
+ * Block
+ *
+ * Block :
+ * { StatementList opt }
+ *
+ * StatementList :
+ * StatementListItem
+ * StatementList StatementListItem
+ *
+ * StatementItem :
+ * Statement
+ * Declaration
+ *
+ * Declaration :
+ * HoistableDeclaration
+ * ClassDeclaration
+ * LexicalDeclaration
+ *
+ * HoistableDeclaration :
+ * FunctionDeclaration
+ * GeneratorDeclaration
+ */
+ private void statement() {
+ statement(false, 0, false, false);
+ }
+
+ /**
+ * @param topLevel does this statement occur at the "top level" of a script or a function?
+ * @param reparseFlags reparse flags to decide whether to allow property "get" and "set" functions or ES6 methods.
+ * @param singleStatement are we in a single statement context?
+ */
+ private void statement(final boolean topLevel, final int reparseFlags, final boolean singleStatement, final boolean labelledStatement) {
+ switch (type) {
+ case LBRACE:
+ block();
+ break;
+ case VAR:
+ variableStatement(type);
+ break;
+ case SEMICOLON:
+ emptyStatement();
+ break;
+ case IF:
+ ifStatement();
+ break;
+ case FOR:
+ forStatement();
+ break;
+ case WHILE:
+ whileStatement();
+ break;
+ case DO:
+ doStatement();
+ break;
+ case CONTINUE:
+ continueStatement();
+ break;
+ case BREAK:
+ breakStatement();
+ break;
+ case RETURN:
+ returnStatement();
+ break;
+ case WITH:
+ withStatement();
+ break;
+ case SWITCH:
+ switchStatement();
+ break;
+ case THROW:
+ throwStatement();
+ break;
+ case TRY:
+ tryStatement();
+ break;
+ case DEBUGGER:
+ debuggerStatement();
+ break;
+ case RPAREN:
+ case RBRACKET:
+ case EOF:
+ expect(SEMICOLON);
+ break;
+ case FUNCTION:
+ // As per spec (ECMA section 12), function declarations as arbitrary statement
+ // is not "portable". Implementation can issue a warning or disallow the same.
+ if (singleStatement) {
+ // ES6 B.3.2 Labelled Function Declarations
+ // It is a Syntax Error if any strict mode source code matches this rule:
+ // LabelledItem : FunctionDeclaration.
+ if (!labelledStatement || isStrictMode) {
+ throw error(AbstractParser.message("expected.stmt", "function declaration"), token);
+ }
+ }
+ functionExpression(true, topLevel || labelledStatement);
+ return;
+ default:
+ if (useBlockScope() && (type == LET && lookaheadIsLetDeclaration(false) || type == CONST)) {
+ if (singleStatement) {
+ throw error(AbstractParser.message("expected.stmt", type.getName() + " declaration"), token);
+ }
+ variableStatement(type);
+ break;
+ } else if (type == CLASS && isES6()) {
+ if (singleStatement) {
+ throw error(AbstractParser.message("expected.stmt", "class declaration"), token);
+ }
+ classDeclaration(false);
+ break;
+ }
+ if (env._const_as_var && type == CONST) {
+ variableStatement(TokenType.VAR);
+ break;
+ }
+
+ if (type == IDENT || isNonStrictModeIdent()) {
+ if (T(k + 1) == COLON) {
+ labelStatement();
+ return;
+ }
+
+ if ((reparseFlags & ScriptFunctionData.IS_PROPERTY_ACCESSOR) != 0) {
+ final String ident = (String) getValue();
+ final long propertyToken = token;
+ final int propertyLine = line;
+ if (GET_NAME.equals(ident)) {
+ next();
+ addPropertyFunctionStatement(propertyGetterFunction(propertyToken, propertyLine));
+ return;
+ } else if (SET_NAME.equals(ident)) {
+ next();
+ addPropertyFunctionStatement(propertySetterFunction(propertyToken, propertyLine));
+ return;
+ }
+ }
+ }
+
+ if ((reparseFlags & ScriptFunctionData.IS_ES6_METHOD) != 0
+ && (type == IDENT || type == LBRACKET || isNonStrictModeIdent())) {
+ final String ident = (String)getValue();
+ final long propertyToken = token;
+ final int propertyLine = line;
+ final Expression propertyKey = propertyName();
+
+ // Code below will need refinement once we fully support ES6 class syntax
+ final int flags = CONSTRUCTOR_NAME.equals(ident) ? FunctionNode.ES6_IS_CLASS_CONSTRUCTOR : FunctionNode.ES6_IS_METHOD;
+ addPropertyFunctionStatement(propertyMethodFunction(propertyKey, propertyToken, propertyLine, false, flags, false));
+ return;
+ }
+
+ expressionStatement();
+ break;
+ }
+ }
+
+ private void addPropertyFunctionStatement(final PropertyFunction propertyFunction) {
+ final FunctionNode fn = propertyFunction.functionNode;
+ functionDeclarations.add(new ExpressionStatement(fn.getLineNumber(), fn.getToken(), finish, fn));
+ }
+
+ /**
+ * ClassDeclaration[Yield, Default] :
+ * class BindingIdentifier[?Yield] ClassTail[?Yield]
+ * [+Default] class ClassTail[?Yield]
+ */
+ private ClassNode classDeclaration(final boolean isDefault) {
+ final int classLineNumber = line;
+
+ final ClassNode classExpression = classExpression(!isDefault);
+
+ if (!isDefault) {
+ final VarNode classVar = new VarNode(classLineNumber, classExpression.getToken(), classExpression.getIdent().getFinish(), classExpression.getIdent(), classExpression, VarNode.IS_CONST);
+ appendStatement(classVar);
+ }
+ return classExpression;
+ }
+
+ /**
+ * ClassExpression[Yield] :
+ * class BindingIdentifier[?Yield]opt ClassTail[?Yield]
+ */
+ private ClassNode classExpression(final boolean isStatement) {
+ assert type == CLASS;
+ final int classLineNumber = line;
+ final long classToken = token;
+ next();
+
+ IdentNode className = null;
+ if (isStatement || type == IDENT) {
+ className = getIdent();
+ }
+
+ return classTail(classLineNumber, classToken, className, isStatement);
+ }
+
+ private static final class ClassElementKey {
+ private final boolean isStatic;
+ private final String propertyName;
+
+ private ClassElementKey(final boolean isStatic, final String propertyName) {
+ this.isStatic = isStatic;
+ this.propertyName = propertyName;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (isStatic ? 1231 : 1237);
+ result = prime * result + ((propertyName == null) ? 0 : propertyName.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof ClassElementKey) {
+ final ClassElementKey other = (ClassElementKey) obj;
+ return this.isStatic == other.isStatic && Objects.equals(this.propertyName, other.propertyName);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Parse ClassTail and ClassBody.
+ *
+ * ClassTail[Yield] :
+ * ClassHeritage[?Yield]opt { ClassBody[?Yield]opt }
+ * ClassHeritage[Yield] :
+ * extends LeftHandSideExpression[?Yield]
+ *
+ * ClassBody[Yield] :
+ * ClassElementList[?Yield]
+ * ClassElementList[Yield] :
+ * ClassElement[?Yield]
+ * ClassElementList[?Yield] ClassElement[?Yield]
+ * ClassElement[Yield] :
+ * MethodDefinition[?Yield]
+ * static MethodDefinition[?Yield]
+ * ;
+ */
+ private ClassNode classTail(final int classLineNumber, final long classToken,
+ final IdentNode className, final boolean isStatement) {
+ final boolean oldStrictMode = isStrictMode;
+ isStrictMode = true;
+ try {
+ Expression classHeritage = null;
+ if (type == EXTENDS) {
+ next();
+ classHeritage = leftHandSideExpression();
+ }
+
+ expect(LBRACE);
+
+ PropertyNode constructor = null;
+ final ArrayList<PropertyNode> classElements = new ArrayList<>();
+ final Map<ClassElementKey, Integer> keyToIndexMap = new HashMap<>();
+ for (;;) {
+ if (type == SEMICOLON) {
+ next();
+ continue;
+ }
+ if (type == RBRACE) {
+ break;
+ }
+ final long classElementToken = token;
+ boolean isStatic = false;
+ if (type == STATIC) {
+ isStatic = true;
+ next();
+ }
+ boolean generator = false;
+ if (isES6() && type == MUL) {
+ generator = true;
+ next();
+ }
+ final PropertyNode classElement = methodDefinition(isStatic, classHeritage != null, generator);
+ if (classElement.isComputed()) {
+ classElements.add(classElement);
+ } else if (!classElement.isStatic() && classElement.getKeyName().equals(CONSTRUCTOR_NAME)) {
+ if (constructor == null) {
+ constructor = classElement;
+ } else {
+ throw error(AbstractParser.message("multiple.constructors"), classElementToken);
+ }
+ } else {
+ // Check for duplicate method definitions and combine accessor methods.
+ // In ES6, a duplicate is never an error regardless of strict mode (in consequence of computed property names).
+
+ final ClassElementKey key = new ClassElementKey(classElement.isStatic(), classElement.getKeyName());
+ final Integer existing = keyToIndexMap.get(key);
+
+ if (existing == null) {
+ keyToIndexMap.put(key, classElements.size());
+ classElements.add(classElement);
+ } else {
+ final PropertyNode existingProperty = classElements.get(existing);
+
+ final Expression value = classElement.getValue();
+ final FunctionNode getter = classElement.getGetter();
+ final FunctionNode setter = classElement.getSetter();
+
+ if (value != null || existingProperty.getValue() != null) {
+ keyToIndexMap.put(key, classElements.size());
+ classElements.add(classElement);
+ } else if (getter != null) {
+ assert existingProperty.getGetter() != null || existingProperty.getSetter() != null;
+ classElements.set(existing, existingProperty.setGetter(getter));
+ } else if (setter != null) {
+ assert existingProperty.getGetter() != null || existingProperty.getSetter() != null;
+ classElements.set(existing, existingProperty.setSetter(setter));
+ }
+ }
+ }
+ }
+
+ final long lastToken = token;
+ expect(RBRACE);
+
+ if (constructor == null) {
+ constructor = createDefaultClassConstructor(classLineNumber, classToken, lastToken, className, classHeritage != null);
+ }
+
+ classElements.trimToSize();
+ return new ClassNode(classLineNumber, classToken, finish, className, classHeritage, constructor, classElements, isStatement);
+ } finally {
+ isStrictMode = oldStrictMode;
+ }
+ }
+
+ private PropertyNode createDefaultClassConstructor(final int classLineNumber, final long classToken, final long lastToken, final IdentNode className, final boolean subclass) {
+ final int ctorFinish = finish;
+ final List<Statement> statements;
+ final List<IdentNode> parameters;
+ final long identToken = Token.recast(classToken, TokenType.IDENT);
+ if (subclass) {
+ final IdentNode superIdent = createIdentNode(identToken, ctorFinish, SUPER.getName()).setIsDirectSuper();
+ final IdentNode argsIdent = createIdentNode(identToken, ctorFinish, "args").setIsRestParameter();
+ final Expression spreadArgs = new UnaryNode(Token.recast(classToken, TokenType.SPREAD_ARGUMENT), argsIdent);
+ final CallNode superCall = new CallNode(classLineNumber, classToken, ctorFinish, superIdent, Collections.singletonList(spreadArgs), false);
+ statements = Collections.singletonList(new ExpressionStatement(classLineNumber, classToken, ctorFinish, superCall));
+ parameters = Collections.singletonList(argsIdent);
+ } else {
+ statements = Collections.emptyList();
+ parameters = Collections.emptyList();
+ }
+
+ final Block body = new Block(classToken, ctorFinish, Block.IS_BODY, statements);
+ final IdentNode ctorName = className != null ? className : createIdentNode(identToken, ctorFinish, CONSTRUCTOR_NAME);
+ final ParserContextFunctionNode function = createParserContextFunctionNode(ctorName, classToken, FunctionNode.Kind.NORMAL, classLineNumber, parameters);
+ function.setLastToken(lastToken);
+
+ function.setFlag(FunctionNode.ES6_IS_METHOD);
+ function.setFlag(FunctionNode.ES6_IS_CLASS_CONSTRUCTOR);
+ if (subclass) {
+ function.setFlag(FunctionNode.ES6_IS_SUBCLASS_CONSTRUCTOR);
+ function.setFlag(FunctionNode.ES6_HAS_DIRECT_SUPER);
+ }
+ if (className == null) {
+ function.setFlag(FunctionNode.IS_ANONYMOUS);
+ }
+
+ final PropertyNode constructor = new PropertyNode(classToken, ctorFinish, ctorName, createFunctionNode(
+ function,
+ classToken,
+ ctorName,
+ parameters,
+ FunctionNode.Kind.NORMAL,
+ classLineNumber,
+ body
+ ), null, null, false, false);
+ return constructor;
+ }
+
+ private PropertyNode methodDefinition(final boolean isStatic, final boolean subclass, final boolean generator) {
+ final long methodToken = token;
+ final int methodLine = line;
+ final boolean computed = type == LBRACKET;
+ final boolean isIdent = type == IDENT;
+ final Expression propertyName = propertyName();
+ int flags = FunctionNode.ES6_IS_METHOD;
+ if (!computed) {
+ final String name = ((PropertyKey)propertyName).getPropertyName();
+ if (!generator && isIdent && type != LPAREN && name.equals(GET_NAME)) {
+ final PropertyFunction methodDefinition = propertyGetterFunction(methodToken, methodLine, flags);
+ verifyAllowedMethodName(methodDefinition.key, isStatic, methodDefinition.computed, generator, true);
+ return new PropertyNode(methodToken, finish, methodDefinition.key, null, methodDefinition.functionNode, null, isStatic, methodDefinition.computed);
+ } else if (!generator && isIdent && type != LPAREN && name.equals(SET_NAME)) {
+ final PropertyFunction methodDefinition = propertySetterFunction(methodToken, methodLine, flags);
+ verifyAllowedMethodName(methodDefinition.key, isStatic, methodDefinition.computed, generator, true);
+ return new PropertyNode(methodToken, finish, methodDefinition.key, null, null, methodDefinition.functionNode, isStatic, methodDefinition.computed);
+ } else {
+ if (!isStatic && !generator && name.equals(CONSTRUCTOR_NAME)) {
+ flags |= FunctionNode.ES6_IS_CLASS_CONSTRUCTOR;
+ if (subclass) {
+ flags |= FunctionNode.ES6_IS_SUBCLASS_CONSTRUCTOR;
+ }
+ }
+ verifyAllowedMethodName(propertyName, isStatic, computed, generator, false);
+ }
+ }
+ final PropertyFunction methodDefinition = propertyMethodFunction(propertyName, methodToken, methodLine, generator, flags, computed);
+ return new PropertyNode(methodToken, finish, methodDefinition.key, methodDefinition.functionNode, null, null, isStatic, computed);
+ }
+
+ /**
+ * ES6 14.5.1 Static Semantics: Early Errors.
+ */
+ private void verifyAllowedMethodName(final Expression key, final boolean isStatic, final boolean computed, final boolean generator, final boolean accessor) {
+ if (!computed) {
+ if (!isStatic && generator && ((PropertyKey) key).getPropertyName().equals(CONSTRUCTOR_NAME)) {
+ throw error(AbstractParser.message("generator.constructor"), key.getToken());
+ }
+ if (!isStatic && accessor && ((PropertyKey) key).getPropertyName().equals(CONSTRUCTOR_NAME)) {
+ throw error(AbstractParser.message("accessor.constructor"), key.getToken());
+ }
+ if (isStatic && ((PropertyKey) key).getPropertyName().equals("prototype")) {
+ throw error(AbstractParser.message("static.prototype.method"), key.getToken());
+ }
+ }
+ }
+
+ /**
+ * block :
+ * { StatementList? }
+ *
+ * see 12.1
+ *
+ * Parse a statement block.
+ */
+ private void block() {
+ appendStatement(new BlockStatement(line, getBlock(true)));
+ }
+
+ /**
+ * StatementList :
+ * Statement
+ * StatementList Statement
+ *
+ * See 12.1
+ *
+ * Parse a list of statements.
+ */
+ private void statementList() {
+ // Accumulate statements until end of list. */
+ loop:
+ while (type != EOF) {
+ switch (type) {
+ case EOF:
+ case CASE:
+ case DEFAULT:
+ case RBRACE:
+ break loop;
+ default:
+ break;
+ }
+
+ // Get next statement.
+ statement();
+ }
+ }
+
+ /**
+ * Make sure that the identifier name used is allowed.
+ *
+ * @param ident Identifier that is verified
+ * @param contextString String used in error message to give context to the user
+ */
+ private void verifyIdent(final IdentNode ident, final String contextString) {
+ verifyStrictIdent(ident, contextString);
+ if (isES6()) {
+ final TokenType tokenType = TokenLookup.lookupKeyword(ident.getName().toCharArray(), 0, ident.getName().length());
+ if (tokenType != IDENT && tokenType.getKind() != TokenKind.FUTURESTRICT) {
+ throw error(expectMessage(IDENT));
+ }
+ }
+ }
+
+ /**
+ * Make sure that in strict mode, the identifier name used is allowed.
+ *
+ * @param ident Identifier that is verified
+ * @param contextString String used in error message to give context to the user
+ */
+ private void verifyStrictIdent(final IdentNode ident, final String contextString) {
+ if (isStrictMode) {
+ switch (ident.getName()) {
+ case "eval":
+ case "arguments":
+ throw error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken());
+ default:
+ break;
+ }
+
+ if (ident.isFutureStrictName()) {
+ throw error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken());
+ }
+ }
+ }
+
+ /*
+ * VariableStatement :
+ * var VariableDeclarationList ;
+ *
+ * VariableDeclarationList :
+ * VariableDeclaration
+ * VariableDeclarationList , VariableDeclaration
+ *
+ * VariableDeclaration :
+ * Identifier Initializer?
+ *
+ * Initializer :
+ * = AssignmentExpression
+ *
+ * See 12.2
+ *
+ * Parse a VAR statement.
+ * @param isStatement True if a statement (not used in a FOR.)
+ */
+ private void variableStatement(final TokenType varType) {
+ variableDeclarationList(varType, true, -1);
+ }
+
+ private static final class ForVariableDeclarationListResult {
+ /** First missing const or binding pattern initializer. */
+ Expression missingAssignment;
+ /** First declaration with an initializer. */
+ long declarationWithInitializerToken;
+ /** Destructuring assignments. */
+ Expression init;
+ Expression firstBinding;
+ Expression secondBinding;
+
+ void recordMissingAssignment(final Expression binding) {
+ if (missingAssignment == null) {
+ missingAssignment = binding;
+ }
+ }
+
+ void recordDeclarationWithInitializer(final long token) {
+ if (declarationWithInitializerToken == 0L) {
+ declarationWithInitializerToken = token;
+ }
+ }
+
+ void addBinding(final Expression binding) {
+ if (firstBinding == null) {
+ firstBinding = binding;
+ } else if (secondBinding == null) {
+ secondBinding = binding;
+ }
+ // ignore the rest
+ }
+
+ void addAssignment(final Expression assignment) {
+ if (init == null) {
+ init = assignment;
+ } else {
+ init = new BinaryNode(Token.recast(init.getToken(), COMMARIGHT), init, assignment);
+ }
+ }
+ }
+
+ /**
+ * @param isStatement {@code true} if a VariableStatement, {@code false} if a {@code for} loop VariableDeclarationList
+ */
+ private ForVariableDeclarationListResult variableDeclarationList(final TokenType varType, final boolean isStatement, final int sourceOrder) {
+ // VAR tested in caller.
+ assert varType == VAR || varType == LET || varType == CONST;
+ final int varLine = line;
+ final long varToken = token;
+
+ next();
+
+ int varFlags = 0;
+ if (varType == LET) {
+ varFlags |= VarNode.IS_LET;
+ } else if (varType == CONST) {
+ varFlags |= VarNode.IS_CONST;
+ }
+
+ final ForVariableDeclarationListResult forResult = isStatement ? null : new ForVariableDeclarationListResult();
+ while (true) {
+ // Get name of var.
+ if (type == YIELD && inGeneratorFunction()) {
+ expect(IDENT);
+ }
+
+ final String contextString = "variable name";
+ final Expression binding = bindingIdentifierOrPattern(contextString);
+ final boolean isDestructuring = !(binding instanceof IdentNode);
+ if (isDestructuring) {
+ final int finalVarFlags = varFlags;
+ verifyDestructuringBindingPattern(binding, new Consumer<IdentNode>() {
+ @Override
+ public void accept(final IdentNode identNode) {
+ verifyIdent(identNode, contextString);
+ if (!env._parse_only) {
+ // don't bother adding a variable if we are just parsing!
+ final VarNode var = new VarNode(varLine, varToken, sourceOrder, identNode.getFinish(), identNode.setIsDeclaredHere(), null, finalVarFlags);
+ appendStatement(var);
+ }
+ }
+ });
+ }
+
+ // Assume no init.
+ Expression init = null;
+
+ // Look for initializer assignment.
+ if (type == ASSIGN) {
+ if (!isStatement) {
+ forResult.recordDeclarationWithInitializer(varToken);
+ }
+ next();
+
+ // Get initializer expression. Suppress IN if not statement.
+ if (!isDestructuring) {
+ defaultNames.push(binding);
+ }
+ try {
+ init = assignmentExpression(!isStatement);
+ } finally {
+ if (!isDestructuring) {
+ defaultNames.pop();
+ }
+ }
+ } else if (isStatement) {
+ if (isDestructuring) {
+ throw error(AbstractParser.message("missing.destructuring.assignment"), token);
+ } else if (varType == CONST) {
+ throw error(AbstractParser.message("missing.const.assignment", ((IdentNode)binding).getName()));
+ }
+ // else, if we are in a for loop, delay checking until we know the kind of loop
+ }
+
+ if (!isDestructuring) {
+ assert init != null || varType != CONST || !isStatement;
+ final IdentNode ident = (IdentNode)binding;
+ if (!isStatement && ident.getName().equals("let")) {
+ throw error(AbstractParser.message("let.binding.for")); //ES6 13.7.5.1
+ }
+ // Only set declaration flag on lexically scoped let/const as it adds runtime overhead.
+ final IdentNode name = varType == LET || varType == CONST ? ident.setIsDeclaredHere() : ident;
+ if (!isStatement) {
+ if (init == null && varType == CONST) {
+ forResult.recordMissingAssignment(name);
+ }
+ forResult.addBinding(new IdentNode(name));
+ }
+ final VarNode var = new VarNode(varLine, varToken, sourceOrder, finish, name, init, varFlags);
+ appendStatement(var);
+ } else {
+ assert init != null || !isStatement;
+ if (init != null) {
+ final Expression assignment = verifyAssignment(Token.recast(varToken, ASSIGN), binding, init);
+ if (isStatement) {
+ appendStatement(new ExpressionStatement(varLine, assignment.getToken(), finish, assignment, varType));
+ } else {
+ forResult.addAssignment(assignment);
+ forResult.addBinding(assignment);
+ }
+ } else if (!isStatement) {
+ forResult.recordMissingAssignment(binding);
+ forResult.addBinding(binding);
+ }
+ }
+
+ if (type != COMMARIGHT) {
+ break;
+ }
+ next();
+ }
+
+ // If is a statement then handle end of line.
+ if (isStatement) {
+ endOfLine();
+ }
+
+ return forResult;
+ }
+
+ private boolean isBindingIdentifier() {
+ return type == IDENT || isNonStrictModeIdent();
+ }
+
+ private IdentNode bindingIdentifier(final String contextString) {
+ final IdentNode name = getIdent();
+ verifyIdent(name, contextString);
+ return name;
+ }
+
+ private Expression bindingPattern() {
+ if (type == LBRACKET) {
+ return arrayLiteral();
+ } else if (type == LBRACE) {
+ return objectLiteral();
+ } else {
+ throw error(AbstractParser.message("expected.binding"));
+ }
+ }
+
+ private Expression bindingIdentifierOrPattern(final String contextString) {
+ if (isBindingIdentifier() || !isES6()) {
+ return bindingIdentifier(contextString);
+ } else {
+ return bindingPattern();
+ }
+ }
+
+ private abstract class VerifyDestructuringPatternNodeVisitor extends NodeVisitor<LexicalContext> {
+ VerifyDestructuringPatternNodeVisitor(final LexicalContext lc) {
+ super(lc);
+ }
+
+ @Override
+ public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
+ if (literalNode.isArray()) {
+ if (((LiteralNode.ArrayLiteralNode)literalNode).hasSpread() && ((LiteralNode.ArrayLiteralNode)literalNode).hasTrailingComma()) {
+ throw error("Rest element must be last", literalNode.getElementExpressions().get(literalNode.getElementExpressions().size() - 1).getToken());
+ }
+ boolean restElement = false;
+ for (final Expression element : literalNode.getElementExpressions()) {
+ if (element != null) {
+ if (restElement) {
+ throw error("Unexpected element after rest element", element.getToken());
+ }
+ if (element.isTokenType(SPREAD_ARRAY)) {
+ restElement = true;
+ final Expression lvalue = ((UnaryNode) element).getExpression();
+ verifySpreadElement(lvalue);
+ }
+ element.accept(this);
+ }
+ }
+ return false;
+ } else {
+ return enterDefault(literalNode);
+ }
+ }
+
+ protected abstract void verifySpreadElement(Expression lvalue);
+
+ @Override
+ public boolean enterObjectNode(final ObjectNode objectNode) {
+ return true;
+ }
+
+ @Override
+ public boolean enterPropertyNode(final PropertyNode propertyNode) {
+ if (propertyNode.getValue() != null) {
+ propertyNode.getValue().accept(this);
+ return false;
+ } else {
+ return enterDefault(propertyNode);
+ }
+ }
+
+ @Override
+ public boolean enterBinaryNode(final BinaryNode binaryNode) {
+ if (binaryNode.isTokenType(ASSIGN)) {
+ binaryNode.lhs().accept(this);
+ // Initializer(rhs) can be any AssignmentExpression
+ return false;
+ } else {
+ return enterDefault(binaryNode);
+ }
+ }
+
+ @Override
+ public boolean enterUnaryNode(final UnaryNode unaryNode) {
+ if (unaryNode.isTokenType(SPREAD_ARRAY)) {
+ // rest element
+ return true;
+ } else {
+ return enterDefault(unaryNode);
+ }
+ }
+ }
+
+ /**
+ * Verify destructuring variable declaration binding pattern and extract bound variable declarations.
+ */
+ private void verifyDestructuringBindingPattern(final Expression pattern, final Consumer<IdentNode> identifierCallback) {
+ assert (pattern instanceof BinaryNode && pattern.isTokenType(ASSIGN)) ||
+ pattern instanceof ObjectNode || pattern instanceof LiteralNode.ArrayLiteralNode;
+ pattern.accept(new VerifyDestructuringPatternNodeVisitor(new LexicalContext()) {
+ @Override
+ protected void verifySpreadElement(final Expression lvalue) {
+ if (lvalue instanceof IdentNode) {
+ // checked in identifierCallback
+ } else if (isDestructuringLhs(lvalue)) {
+ verifyDestructuringBindingPattern(lvalue, identifierCallback);
+ } else {
+ throw error("Expected a valid binding identifier", lvalue.getToken());
+ }
+ }
+
+ @Override
+ public boolean enterIdentNode(final IdentNode identNode) {
+ identifierCallback.accept(identNode);
+ return false;
+ }
+
+ @Override
+ protected boolean enterDefault(final Node node) {
+ throw error(String.format("unexpected node in BindingPattern: %s", node));
+ }
+ });
+ }
+
+ /**
+ * EmptyStatement :
+ * ;
+ *
+ * See 12.3
+ *
+ * Parse an empty statement.
+ */
+ private void emptyStatement() {
+ if (env._empty_statements) {
+ appendStatement(new EmptyNode(line, token, Token.descPosition(token) + Token.descLength(token)));
+ }
+
+ // SEMICOLON checked in caller.
+ next();
+ }
+
+ /**
+ * ExpressionStatement :
+ * Expression ; // [lookahead ~({ or function )]
+ *
+ * See 12.4
+ *
+ * Parse an expression used in a statement block.
+ */
+ private void expressionStatement() {
+ // Lookahead checked in caller.
+ final int expressionLine = line;
+ final long expressionToken = token;
+
+ // Get expression and add as statement.
+ final Expression expression = expression();
+
+ if (expression != null) {
+ final ExpressionStatement expressionStatement = new ExpressionStatement(expressionLine, expressionToken, finish, expression);
+ appendStatement(expressionStatement);
+ } else {
+ expect(null);
+ }
+
+ endOfLine();
+ }
+
+ /**
+ * IfStatement :
+ * if ( Expression ) Statement else Statement
+ * if ( Expression ) Statement
+ *
+ * See 12.5
+ *
+ * Parse an IF statement.
+ */
+ private void ifStatement() {
+ // Capture IF token.
+ final int ifLine = line;
+ final long ifToken = token;
+ // IF tested in caller.
+ next();
+
+ expect(LPAREN);
+ final Expression test = expression();
+ expect(RPAREN);
+ final Block pass = getStatement();
+
+ Block fail = null;
+ if (type == ELSE) {
+ next();
+ fail = getStatement();
+ }
+
+ appendStatement(new IfNode(ifLine, ifToken, fail != null ? fail.getFinish() : pass.getFinish(), test, pass, fail));
+ }
+
+ /**
+ * ... IterationStatement:
+ * ...
+ * for ( Expression[NoIn]?; Expression? ; Expression? ) Statement
+ * for ( var VariableDeclarationList[NoIn]; Expression? ; Expression? ) Statement
+ * for ( LeftHandSideExpression in Expression ) Statement
+ * for ( var VariableDeclaration[NoIn] in Expression ) Statement
+ *
+ * See 12.6
+ *
+ * Parse a FOR statement.
+ */
+ @SuppressWarnings("fallthrough")
+ private void forStatement() {
+ final long forToken = token;
+ final int forLine = line;
+ // start position of this for statement. This is used
+ // for sort order for variables declared in the initializer
+ // part of this 'for' statement (if any).
+ final int forStart = Token.descPosition(forToken);
+ // When ES6 for-let is enabled we create a container block to capture the LET.
+ final ParserContextBlockNode outer = useBlockScope() ? newBlock() : null;
+
+ // Create FOR node, capturing FOR token.
+ final ParserContextLoopNode forNode = new ParserContextLoopNode();
+ lc.push(forNode);
+ Block body = null;
+ Expression init = null;
+ JoinPredecessorExpression test = null;
+ JoinPredecessorExpression modify = null;
+ ForVariableDeclarationListResult varDeclList = null;
+
+ int flags = 0;
+ boolean isForOf = false;
+
+ try {
+ // FOR tested in caller.
+ next();
+
+ // Nashorn extension: for each expression.
+ // iterate property values rather than property names.
+ if (!env._no_syntax_extensions && type == IDENT && "each".equals(getValue())) {
+ flags |= ForNode.IS_FOR_EACH;
+ next();
+ }
+
+ expect(LPAREN);
+
+ TokenType varType = null;
+ switch (type) {
+ case VAR:
+ // Var declaration captured in for outer block.
+ varDeclList = variableDeclarationList(varType = type, false, forStart);
+ break;
+ case SEMICOLON:
+ break;
+ default:
+ if (useBlockScope() && (type == LET && lookaheadIsLetDeclaration(true) || type == CONST)) {
+ flags |= ForNode.PER_ITERATION_SCOPE;
+ // LET/CONST declaration captured in container block created above.
+ varDeclList = variableDeclarationList(varType = type, false, forStart);
+ break;
+ }
+ if (env._const_as_var && type == CONST) {
+ // Var declaration captured in for outer block.
+ varDeclList = variableDeclarationList(varType = TokenType.VAR, false, forStart);
+ break;
+ }
+
+ init = expression(unaryExpression(), COMMARIGHT.getPrecedence(), true);
+ break;
+ }
+
+ switch (type) {
+ case SEMICOLON:
+ // for (init; test; modify)
+ if (varDeclList != null) {
+ assert init == null;
+ init = varDeclList.init;
+ // late check for missing assignment, now we know it's a for (init; test; modify) loop
+ if (varDeclList.missingAssignment != null) {
+ if (varDeclList.missingAssignment instanceof IdentNode) {
+ throw error(AbstractParser.message("missing.const.assignment", ((IdentNode)varDeclList.missingAssignment).getName()));
+ } else {
+ throw error(AbstractParser.message("missing.destructuring.assignment"), varDeclList.missingAssignment.getToken());
+ }
+ }
+ }
+
+ // for each (init; test; modify) is invalid
+ if ((flags & ForNode.IS_FOR_EACH) != 0) {
+ throw error(AbstractParser.message("for.each.without.in"), token);
+ }
+
+ expect(SEMICOLON);
+ if (type != SEMICOLON) {
+ test = joinPredecessorExpression();
+ }
+ expect(SEMICOLON);
+ if (type != RPAREN) {
+ modify = joinPredecessorExpression();
+ }
+ break;
+
+ case IDENT:
+ if (env._es6 && "of".equals(getValue())) {
+ isForOf = true;
+ // fall through
+ } else {
+ expect(SEMICOLON); // fail with expected message
+ break;
+ }
+ case IN:
+ flags |= isForOf ? ForNode.IS_FOR_OF : ForNode.IS_FOR_IN;
+ test = new JoinPredecessorExpression();
+ if (varDeclList != null) {
+ // for (var|let|const ForBinding in|of expression)
+ if (varDeclList.secondBinding != null) {
+ // for (var i, j in obj) is invalid
+ throw error(AbstractParser.message("many.vars.in.for.in.loop", isForOf ? "of" : "in"), varDeclList.secondBinding.getToken());
+ }
+ if (varDeclList.declarationWithInitializerToken != 0 && (isStrictMode || type != TokenType.IN || varType != VAR || varDeclList.init != null)) {
+ // ES5 legacy: for (var i = AssignmentExpressionNoIn in Expression)
+ // Invalid in ES6, but allow it in non-strict mode if no ES6 features used,
+ // i.e., error if strict, for-of, let/const, or destructuring
+ throw error(AbstractParser.message("for.in.loop.initializer", isForOf ? "of" : "in"), varDeclList.declarationWithInitializerToken);
+ }
+ init = varDeclList.firstBinding;
+ assert init instanceof IdentNode || isDestructuringLhs(init);
+ } else {
+ // for (expr in obj)
+ assert init != null : "for..in/of init expression can not be null here";
+
+ // check if initial expression is a valid L-value
+ if (!checkValidLValue(init, isForOf ? "for-of iterator" : "for-in iterator")) {
+ throw error(AbstractParser.message("not.lvalue.for.in.loop", isForOf ? "of" : "in"), init.getToken());
+ }
+ }
+
+ next();
+
+ // For-of only allows AssignmentExpression.
+ modify = isForOf ? new JoinPredecessorExpression(assignmentExpression(false)) : joinPredecessorExpression();
+ break;
+
+ default:
+ expect(SEMICOLON);
+ break;
+ }
+
+ expect(RPAREN);
+
+ // Set the for body.
+ body = getStatement();
+ } finally {
+ lc.pop(forNode);
+
+ for (final Statement var : forNode.getStatements()) {
+ assert var instanceof VarNode;
+ appendStatement(var);
+ }
+ if (body != null) {
+ appendStatement(new ForNode(forLine, forToken, body.getFinish(), body, (forNode.getFlags() | flags), init, test, modify));
+ }
+ if (outer != null) {
+ restoreBlock(outer);
+ if (body != null) {
+ appendStatement(new BlockStatement(forLine, new Block(
+ outer.getToken(),
+ body.getFinish(),
+ outer.getStatements())));
+ }
+ }
+ }
+ }
+
+ private boolean checkValidLValue(final Expression init, final String contextString) {
+ if (init instanceof IdentNode) {
+ if (!checkIdentLValue((IdentNode)init)) {
+ return false;
+ }
+ verifyIdent((IdentNode)init, contextString);
+ return true;
+ } else if (init instanceof AccessNode || init instanceof IndexNode) {
+ return true;
+ } else if (isDestructuringLhs(init)) {
+ verifyDestructuringAssignmentPattern(init, contextString);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("fallthrough")
+ private boolean lookaheadIsLetDeclaration(final boolean ofContextualKeyword) {
+ assert type == LET;
+ for (int i = 1;; i++) {
+ final TokenType t = T(k + i);
+ switch (t) {
+ case EOL:
+ case COMMENT:
+ continue;
+ case IDENT:
+ if (ofContextualKeyword && isES6() && "of".equals(getValue(getToken(k + i)))) {
+ return false;
+ }
+ // fall through
+ case LBRACKET:
+ case LBRACE:
+ return true;
+ default:
+ // accept future strict tokens in non-strict mode (including LET)
+ if (!isStrictMode && t.getKind() == TokenKind.FUTURESTRICT) {
+ return true;
+ }
+ return false;
+ }
+ }
+ }
+
+ /**
+ * ...IterationStatement :
+ * ...
+ * while ( Expression ) Statement
+ * ...
+ *
+ * See 12.6
+ *
+ * Parse while statement.
+ */
+ private void whileStatement() {
+ // Capture WHILE token.
+ final long whileToken = token;
+ final int whileLine = line;
+ // WHILE tested in caller.
+ next();
+
+ final ParserContextLoopNode whileNode = new ParserContextLoopNode();
+ lc.push(whileNode);
+
+ JoinPredecessorExpression test = null;
+ Block body = null;
+
+ try {
+ expect(LPAREN);
+ test = joinPredecessorExpression();
+ expect(RPAREN);
+ body = getStatement();
+ } finally {
+ lc.pop(whileNode);
+ }
+
+ if (body != null) {
+ appendStatement(new WhileNode(whileLine, whileToken, body.getFinish(), false, test, body));
+ }
+ }
+
+ /**
+ * ...IterationStatement :
+ * ...
+ * do Statement while( Expression ) ;
+ * ...
+ *
+ * See 12.6
+ *
+ * Parse DO WHILE statement.
+ */
+ private void doStatement() {
+ // Capture DO token.
+ final long doToken = token;
+ int doLine = 0;
+ // DO tested in the caller.
+ next();
+
+ final ParserContextLoopNode doWhileNode = new ParserContextLoopNode();
+ lc.push(doWhileNode);
+
+ Block body = null;
+ JoinPredecessorExpression test = null;
+
+ try {
+ // Get DO body.
+ body = getStatement();
+
+ expect(WHILE);
+ expect(LPAREN);
+ doLine = line;
+ test = joinPredecessorExpression();
+ expect(RPAREN);
+
+ if (type == SEMICOLON) {
+ endOfLine();
+ }
+ } finally {
+ lc.pop(doWhileNode);
+ }
+
+ appendStatement(new WhileNode(doLine, doToken, finish, true, test, body));
+ }
+
+ /**
+ * ContinueStatement :
+ * continue Identifier? ; // [no LineTerminator here]
+ *
+ * See 12.7
+ *
+ * Parse CONTINUE statement.
+ */
+ private void continueStatement() {
+ // Capture CONTINUE token.
+ final int continueLine = line;
+ final long continueToken = token;
+ // CONTINUE tested in caller.
+ nextOrEOL();
+
+ ParserContextLabelNode labelNode = null;
+
+ // SEMICOLON or label.
+ switch (type) {
+ case RBRACE:
+ case SEMICOLON:
+ case EOL:
+ case EOF:
+ break;
+
+ default:
+ final IdentNode ident = getIdent();
+ labelNode = lc.findLabel(ident.getName());
+
+ if (labelNode == null) {
+ throw error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken());
+ }
+
+ break;
+ }
+
+ final String labelName = labelNode == null ? null : labelNode.getLabelName();
+ final ParserContextLoopNode targetNode = lc.getContinueTo(labelName);
+
+ if (targetNode == null) {
+ throw error(AbstractParser.message("illegal.continue.stmt"), continueToken);
+ }
+
+ endOfLine();
+
+ // Construct and add CONTINUE node.
+ appendStatement(new ContinueNode(continueLine, continueToken, finish, labelName));
+ }
+
+ /**
+ * BreakStatement :
+ * break Identifier? ; // [no LineTerminator here]
+ *
+ * See 12.8
+ *
+ */
+ private void breakStatement() {
+ // Capture BREAK token.
+ final int breakLine = line;
+ final long breakToken = token;
+ // BREAK tested in caller.
+ nextOrEOL();
+
+ ParserContextLabelNode labelNode = null;
+
+ // SEMICOLON or label.
+ switch (type) {
+ case RBRACE:
+ case SEMICOLON:
+ case EOL:
+ case EOF:
+ break;
+
+ default:
+ final IdentNode ident = getIdent();
+ labelNode = lc.findLabel(ident.getName());
+
+ if (labelNode == null) {
+ throw error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken());
+ }
+
+ break;
+ }
+
+ //either an explicit label - then get its node or just a "break" - get first breakable
+ //targetNode is what we are breaking out from.
+ final String labelName = labelNode == null ? null : labelNode.getLabelName();
+ final ParserContextBreakableNode targetNode = lc.getBreakable(labelName);
+
+ if( targetNode instanceof ParserContextBlockNode) {
+ targetNode.setFlag(Block.IS_BREAKABLE);
+ }
+
+ if (targetNode == null) {
+ throw error(AbstractParser.message("illegal.break.stmt"), breakToken);
+ }
+
+ endOfLine();
+
+ // Construct and add BREAK node.
+ appendStatement(new BreakNode(breakLine, breakToken, finish, labelName));
+ }
+
+ /**
+ * ReturnStatement :
+ * return Expression? ; // [no LineTerminator here]
+ *
+ * See 12.9
+ *
+ * Parse RETURN statement.
+ */
+ private void returnStatement() {
+ // check for return outside function
+ if (lc.getCurrentFunction().getKind() == FunctionNode.Kind.SCRIPT || lc.getCurrentFunction().getKind() == FunctionNode.Kind.MODULE) {
+ throw error(AbstractParser.message("invalid.return"));
+ }
+
+ // Capture RETURN token.
+ final int returnLine = line;
+ final long returnToken = token;
+ // RETURN tested in caller.
+ nextOrEOL();
+
+ Expression expression = null;
+
+ // SEMICOLON or expression.
+ switch (type) {
+ case RBRACE:
+ case SEMICOLON:
+ case EOL:
+ case EOF:
+ break;
+
+ default:
+ expression = expression();
+ break;
+ }
+
+ endOfLine();
+
+ // Construct and add RETURN node.
+ appendStatement(new ReturnNode(returnLine, returnToken, finish, expression));
+ }
+
+ /**
+ * Parse YieldExpression.
+ *
+ * YieldExpression[In] :
+ * yield
+ * yield [no LineTerminator here] AssignmentExpression[?In, Yield]
+ * yield [no LineTerminator here] * AssignmentExpression[?In, Yield]
+ */
+ @SuppressWarnings("fallthrough")
+ private Expression yieldExpression(final boolean noIn) {
+ assert inGeneratorFunction();
+ // Capture YIELD token.
+ long yieldToken = token;
+ // YIELD tested in caller.
+ assert type == YIELD;
+ nextOrEOL();
+
+ Expression expression = null;
+
+ boolean yieldAsterisk = false;
+ if (type == MUL) {
+ yieldAsterisk = true;
+ yieldToken = Token.recast(yieldToken, YIELD_STAR);
+ next();
+ }
+
+ switch (type) {
+ case RBRACE:
+ case SEMICOLON:
+ case EOL:
+ case EOF:
+ case COMMARIGHT:
+ case RPAREN:
+ case RBRACKET:
+ case COLON:
+ if (!yieldAsterisk) {
+ // treat (yield) as (yield void 0)
+ expression = newUndefinedLiteral(yieldToken, finish);
+ if (type == EOL) {
+ next();
+ }
+ break;
+ } else {
+ // AssignmentExpression required, fall through
+ }
+
+ default:
+ expression = assignmentExpression(noIn);
+ break;
+ }
+
+ // Construct and add YIELD node.
+ return new UnaryNode(yieldToken, expression);
+ }
+
+ private static UnaryNode newUndefinedLiteral(final long token, final int finish) {
+ return new UnaryNode(Token.recast(token, VOID), LiteralNode.newInstance(token, finish, 0));
+ }
+
+ /**
+ * WithStatement :
+ * with ( Expression ) Statement
+ *
+ * See 12.10
+ *
+ * Parse WITH statement.
+ */
+ private void withStatement() {
+ // Capture WITH token.
+ final int withLine = line;
+ final long withToken = token;
+ // WITH tested in caller.
+ next();
+
+ // ECMA 12.10.1 strict mode restrictions
+ if (isStrictMode) {
+ throw error(AbstractParser.message("strict.no.with"), withToken);
+ }
+
+ expect(LPAREN);
+ final Expression expression = expression();
+ expect(RPAREN);
+ final Block body = getStatement();
+
+ appendStatement(new WithNode(withLine, withToken, finish, expression, body));
+ }
+
+ /**
+ * SwitchStatement :
+ * switch ( Expression ) CaseBlock
+ *
+ * CaseBlock :
+ * { CaseClauses? }
+ * { CaseClauses? DefaultClause CaseClauses }
+ *
+ * CaseClauses :
+ * CaseClause
+ * CaseClauses CaseClause
+ *
+ * CaseClause :
+ * case Expression : StatementList?
+ *
+ * DefaultClause :
+ * default : StatementList?
+ *
+ * See 12.11
+ *
+ * Parse SWITCH statement.
+ */
+ private void switchStatement() {
+ final int switchLine = line;
+ final long switchToken = token;
+
+ // Block to capture variables declared inside the switch statement.
+ final ParserContextBlockNode switchBlock = newBlock();
+
+ // SWITCH tested in caller.
+ next();
+
+ // Create and add switch statement.
+ final ParserContextSwitchNode switchNode = new ParserContextSwitchNode();
+ lc.push(switchNode);
+
+ CaseNode defaultCase = null;
+ // Prepare to accumulate cases.
+ final List<CaseNode> cases = new ArrayList<>();
+
+ Expression expression = null;
+
+ try {
+ expect(LPAREN);
+ expression = expression();
+ expect(RPAREN);
+
+ expect(LBRACE);
+
+
+ while (type != RBRACE) {
+ // Prepare for next case.
+ Expression caseExpression = null;
+ final long caseToken = token;
+
+ switch (type) {
+ case CASE:
+ next();
+ caseExpression = expression();
+ break;
+
+ case DEFAULT:
+ if (defaultCase != null) {
+ throw error(AbstractParser.message("duplicate.default.in.switch"));
+ }
+ next();
+ break;
+
+ default:
+ // Force an error.
+ expect(CASE);
+ break;
+ }
+
+ expect(COLON);
+
+ // Get CASE body.
+ final Block statements = getBlock(false); // TODO: List<Statement> statements = caseStatementList();
+ final CaseNode caseNode = new CaseNode(caseToken, finish, caseExpression, statements);
+
+ if (caseExpression == null) {
+ defaultCase = caseNode;
+ }
+
+ cases.add(caseNode);
+ }
+
+ next();
+ } finally {
+ lc.pop(switchNode);
+ restoreBlock(switchBlock);
+ }
+
+ final SwitchNode switchStatement = new SwitchNode(switchLine, switchToken, finish, expression, cases, defaultCase);
+ appendStatement(new BlockStatement(switchLine, new Block(switchToken, finish, switchBlock.getFlags() | Block.IS_SYNTHETIC | Block.IS_SWITCH_BLOCK, switchStatement)));
+ }
+
+ /**
+ * LabelledStatement :
+ * Identifier : Statement
+ *
+ * See 12.12
+ *
+ * Parse label statement.
+ */
+ private void labelStatement() {
+ // Capture label token.
+ final long labelToken = token;
+ // Get label ident.
+ final IdentNode ident = getIdent();
+
+ expect(COLON);
+
+ if (lc.findLabel(ident.getName()) != null) {
+ throw error(AbstractParser.message("duplicate.label", ident.getName()), labelToken);
+ }
+
+ final ParserContextLabelNode labelNode = new ParserContextLabelNode(ident.getName());
+ Block body = null;
+ try {
+ lc.push(labelNode);
+ body = getStatement(true);
+ } finally {
+ assert lc.peek() instanceof ParserContextLabelNode;
+ lc.pop(labelNode);
+ }
+
+ appendStatement(new LabelNode(line, labelToken, finish, ident.getName(), body));
+ }
+
+ /**
+ * ThrowStatement :
+ * throw Expression ; // [no LineTerminator here]
+ *
+ * See 12.13
+ *
+ * Parse throw statement.
+ */
+ private void throwStatement() {
+ // Capture THROW token.
+ final int throwLine = line;
+ final long throwToken = token;
+ // THROW tested in caller.
+ nextOrEOL();
+
+ Expression expression = null;
+
+ // SEMICOLON or expression.
+ switch (type) {
+ case RBRACE:
+ case SEMICOLON:
+ case EOL:
+ break;
+
+ default:
+ expression = expression();
+ break;
+ }
+
+ if (expression == null) {
+ throw error(AbstractParser.message("expected.operand", type.getNameOrType()));
+ }
+
+ endOfLine();
+
+ appendStatement(new ThrowNode(throwLine, throwToken, finish, expression, false));
+ }
+
+ /**
+ * TryStatement :
+ * try Block Catch
+ * try Block Finally
+ * try Block Catch Finally
+ *
+ * Catch :
+ * catch( Identifier if Expression ) Block
+ * catch( Identifier ) Block
+ *
+ * Finally :
+ * finally Block
+ *
+ * See 12.14
+ *
+ * Parse TRY statement.
+ */
+ private void tryStatement() {
+ // Capture TRY token.
+ final int tryLine = line;
+ final long tryToken = token;
+ // TRY tested in caller.
+ next();
+
+ // Container block needed to act as target for labeled break statements
+ final int startLine = line;
+ final ParserContextBlockNode outer = newBlock();
+ // Create try.
+
+ try {
+ final Block tryBody = getBlock(true);
+ final List<Block> catchBlocks = new ArrayList<>();
+
+ while (type == CATCH) {
+ final int catchLine = line;
+ final long catchToken = token;
+ next();
+ expect(LPAREN);
+
+ // ES6 catch parameter can be a BindingIdentifier or a BindingPattern
+ // http://www.ecma-international.org/ecma-262/6.0/
+ final String contextString = "catch argument";
+ final Expression exception = bindingIdentifierOrPattern(contextString);
+ final boolean isDestructuring = !(exception instanceof IdentNode);
+ if (isDestructuring) {
+ verifyDestructuringBindingPattern(exception, new Consumer<IdentNode>() {
+ @Override
+ public void accept(final IdentNode identNode) {
+ verifyIdent(identNode, contextString);
+ }
+ });
+ } else {
+ // ECMA 12.4.1 strict mode restrictions
+ verifyStrictIdent((IdentNode) exception, "catch argument");
+ }
+
+
+ // Nashorn extension: catch clause can have optional
+ // condition. So, a single try can have more than one
+ // catch clause each with it's own condition.
+ final Expression ifExpression;
+ if (!env._no_syntax_extensions && type == IF) {
+ next();
+ // Get the exception condition.
+ ifExpression = expression();
+ } else {
+ ifExpression = null;
+ }
+
+ expect(RPAREN);
+
+ final ParserContextBlockNode catchBlock = newBlock();
+ try {
+ // Get CATCH body.
+ final Block catchBody = getBlock(true);
+ final CatchNode catchNode = new CatchNode(catchLine, catchToken, finish, exception, ifExpression, catchBody, false);
+ appendStatement(catchNode);
+ } finally {
+ restoreBlock(catchBlock);
+ catchBlocks.add(new Block(catchBlock.getToken(), finish, catchBlock.getFlags() | Block.IS_SYNTHETIC, catchBlock.getStatements()));
+ }
+
+ // If unconditional catch then should to be the end.
+ if (ifExpression == null) {
+ break;
+ }
+ }
+
+ // Prepare to capture finally statement.
+ Block finallyStatements = null;
+
+ if (type == FINALLY) {
+ next();
+ finallyStatements = getBlock(true);
+ }
+
+ // Need at least one catch or a finally.
+ if (catchBlocks.isEmpty() && finallyStatements == null) {
+ throw error(AbstractParser.message("missing.catch.or.finally"), tryToken);
+ }
+
+ final TryNode tryNode = new TryNode(tryLine, tryToken, finish, tryBody, catchBlocks, finallyStatements);
+ // Add try.
+ assert lc.peek() == outer;
+ appendStatement(tryNode);
+ } finally {
+ restoreBlock(outer);
+ }
+
+ appendStatement(new BlockStatement(startLine, new Block(tryToken, finish, outer.getFlags() | Block.IS_SYNTHETIC, outer.getStatements())));
+ }
+
+ /**
+ * DebuggerStatement :
+ * debugger ;
+ *
+ * See 12.15
+ *
+ * Parse debugger statement.
+ */
+ private void debuggerStatement() {
+ // Capture DEBUGGER token.
+ final int debuggerLine = line;
+ final long debuggerToken = token;
+ // DEBUGGER tested in caller.
+ next();
+ endOfLine();
+ appendStatement(new DebuggerNode(debuggerLine, debuggerToken, finish));
+ }
+
+ /**
+ * PrimaryExpression :
+ * this
+ * IdentifierReference
+ * Literal
+ * ArrayLiteral
+ * ObjectLiteral
+ * RegularExpressionLiteral
+ * TemplateLiteral
+ * CoverParenthesizedExpressionAndArrowParameterList
+ *
+ * CoverParenthesizedExpressionAndArrowParameterList :
+ * ( Expression )
+ * ( )
+ * ( ... BindingIdentifier )
+ * ( Expression , ... BindingIdentifier )
+ *
+ * Parse primary expression.
+ * @return Expression node.
+ */
+ @SuppressWarnings("fallthrough")
+ private Expression primaryExpression() {
+ // Capture first token.
+ final int primaryLine = line;
+ final long primaryToken = token;
+
+ switch (type) {
+ case THIS:
+ final String name = type.getName();
+ next();
+ markThis(lc);
+ return new IdentNode(primaryToken, finish, name);
+ case IDENT:
+ final IdentNode ident = getIdent();
+ if (ident == null) {
+ break;
+ }
+ detectSpecialProperty(ident);
+ return ident;
+ case OCTAL_LEGACY:
+ if (isStrictMode) {
+ throw error(AbstractParser.message("strict.no.octal"), token);
+ }
+ case STRING:
+ case ESCSTRING:
+ case DECIMAL:
+ case HEXADECIMAL:
+ case OCTAL:
+ case BINARY_NUMBER:
+ case FLOATING:
+ case REGEX:
+ case XML:
+ return getLiteral();
+ case EXECSTRING:
+ return execString(primaryLine, primaryToken);
+ case FALSE:
+ next();
+ return LiteralNode.newInstance(primaryToken, finish, false);
+ case TRUE:
+ next();
+ return LiteralNode.newInstance(primaryToken, finish, true);
+ case NULL:
+ next();
+ return LiteralNode.newInstance(primaryToken, finish);
+ case LBRACKET:
+ return arrayLiteral();
+ case LBRACE:
+ return objectLiteral();
+ case LPAREN:
+ next();
+
+ if (isES6()) {
+ if (type == RPAREN) {
+ // ()
+ nextOrEOL();
+ expectDontAdvance(ARROW);
+ return new ExpressionList(primaryToken, finish, Collections.emptyList());
+ } else if (type == ELLIPSIS) {
+ // (...rest)
+ final IdentNode restParam = formalParameterList(false).get(0);
+ expectDontAdvance(RPAREN);
+ nextOrEOL();
+ expectDontAdvance(ARROW);
+ return new ExpressionList(primaryToken, finish, Collections.singletonList(restParam));
+ }
+ }
+
+ final Expression expression = expression();
+
+ expect(RPAREN);
+
+ return expression;
+ case TEMPLATE:
+ case TEMPLATE_HEAD:
+ return templateLiteral();
+
+ default:
+ // In this context some operator tokens mark the start of a literal.
+ if (lexer.scanLiteral(primaryToken, type, lineInfoReceiver)) {
+ next();
+ return getLiteral();
+ }
+ if (isNonStrictModeIdent()) {
+ return getIdent();
+ }
+ break;
+ }
+
+ return null;
+ }
+
+ /**
+ * Convert execString to a call to $EXEC.
+ *
+ * @param primaryToken Original string token.
+ * @return callNode to $EXEC.
+ */
+ CallNode execString(final int primaryLine, final long primaryToken) {
+ // Synthesize an ident to call $EXEC.
+ final IdentNode execIdent = new IdentNode(primaryToken, finish, ScriptingFunctions.EXEC_NAME);
+ // Skip over EXECSTRING.
+ next();
+ // Set up argument list for call.
+ // Skip beginning of edit string expression.
+ expect(LBRACE);
+ // Add the following expression to arguments.
+ final List<Expression> arguments = Collections.singletonList(expression());
+ // Skip ending of edit string expression.
+ expect(RBRACE);
+
+ return new CallNode(primaryLine, primaryToken, finish, execIdent, arguments, false);
+ }
+
+ /**
+ * ArrayLiteral :
+ * [ Elision? ]
+ * [ ElementList ]
+ * [ ElementList , Elision? ]
+ * [ expression for (LeftHandExpression in expression) ( (if ( Expression ) )? ]
+ *
+ * ElementList : Elision? AssignmentExpression
+ * ElementList , Elision? AssignmentExpression
+ *
+ * Elision :
+ * ,
+ * Elision ,
+ *
+ * See 12.1.4
+ * JavaScript 1.8
+ *
+ * Parse array literal.
+ * @return Expression node.
+ */
+ @SuppressWarnings("fallthrough")
+ private LiteralNode<Expression[]> arrayLiteral() {
+ // Capture LBRACKET token.
+ final long arrayToken = token;
+ // LBRACKET tested in caller.
+ next();
+
+ // Prepare to accumulate elements.
+ final List<Expression> elements = new ArrayList<>();
+ // Track elisions.
+ boolean elision = true;
+ boolean hasSpread = false;
+ loop:
+ while (true) {
+ long spreadToken = 0;
+ switch (type) {
+ case RBRACKET:
+ next();
+
+ break loop;
+
+ case COMMARIGHT:
+ next();
+
+ // If no prior expression
+ if (elision) {
+ elements.add(null);
+ }
+
+ elision = true;
+
+ break;
+
+ case ELLIPSIS:
+ if (isES6()) {
+ hasSpread = true;
+ spreadToken = token;
+ next();
+ }
+ // fall through
+
+ default:
+ if (!elision) {
+ throw error(AbstractParser.message("expected.comma", type.getNameOrType()));
+ }
+
+ // Add expression element.
+ Expression expression = assignmentExpression(false);
+ if (expression != null) {
+ if (spreadToken != 0) {
+ expression = new UnaryNode(Token.recast(spreadToken, SPREAD_ARRAY), expression);
+ }
+ elements.add(expression);
+ } else {
+ expect(RBRACKET);
+ }
+
+ elision = false;
+ break;
+ }
+ }
+
+ return LiteralNode.newInstance(arrayToken, finish, elements, hasSpread, elision);
+ }
+
+ /**
+ * ObjectLiteral :
+ * { }
+ * { PropertyNameAndValueList } { PropertyNameAndValueList , }
+ *
+ * PropertyNameAndValueList :
+ * PropertyAssignment
+ * PropertyNameAndValueList , PropertyAssignment
+ *
+ * See 11.1.5
+ *
+ * Parse an object literal.
+ * @return Expression node.
+ */
+ private ObjectNode objectLiteral() {
+ // Capture LBRACE token.
+ final long objectToken = token;
+ // LBRACE tested in caller.
+ next();
+
+ // Object context.
+ // Prepare to accumulate elements.
+ final List<PropertyNode> elements = new ArrayList<>();
+ final Map<String, Integer> map = new HashMap<>();
+
+ // Create a block for the object literal.
+ boolean commaSeen = true;
+ loop:
+ while (true) {
+ switch (type) {
+ case RBRACE:
+ next();
+ break loop;
+
+ case COMMARIGHT:
+ if (commaSeen) {
+ throw error(AbstractParser.message("expected.property.id", type.getNameOrType()));
+ }
+ next();
+ commaSeen = true;
+ break;
+
+ default:
+ if (!commaSeen) {
+ throw error(AbstractParser.message("expected.comma", type.getNameOrType()));
+ }
+
+ commaSeen = false;
+ // Get and add the next property.
+ final PropertyNode property = propertyAssignment();
+
+ if (property.isComputed()) {
+ elements.add(property);
+ break;
+ }
+
+ final String key = property.getKeyName();
+ final Integer existing = map.get(key);
+
+ if (existing == null) {
+ map.put(key, elements.size());
+ elements.add(property);
+ break;
+ }
+
+ final PropertyNode existingProperty = elements.get(existing);
+
+ // ECMA section 11.1.5 Object Initialiser
+ // point # 4 on property assignment production
+ final Expression value = property.getValue();
+ final FunctionNode getter = property.getGetter();
+ final FunctionNode setter = property.getSetter();
+
+ final Expression prevValue = existingProperty.getValue();
+ final FunctionNode prevGetter = existingProperty.getGetter();
+ final FunctionNode prevSetter = existingProperty.getSetter();
+
+ if (!isES6()) {
+ checkPropertyRedefinition(property, value, getter, setter, prevValue, prevGetter, prevSetter);
+ } else {
+ if (property.getKey() instanceof IdentNode && ((IdentNode)property.getKey()).isProtoPropertyName() &&
+ existingProperty.getKey() instanceof IdentNode && ((IdentNode)existingProperty.getKey()).isProtoPropertyName()) {
+ throw error(AbstractParser.message("multiple.proto.key"), property.getToken());
+ }
+ }
+
+ if (value != null || prevValue != null) {
+ map.put(key, elements.size());
+ elements.add(property);
+ } else if (getter != null) {
+ assert prevGetter != null || prevSetter != null;
+ elements.set(existing, existingProperty.setGetter(getter));
+ } else if (setter != null) {
+ assert prevGetter != null || prevSetter != null;
+ elements.set(existing, existingProperty.setSetter(setter));
+ }
+ break;
+ }
+ }
+
+ return new ObjectNode(objectToken, finish, elements);
+ }
+
+ private void checkPropertyRedefinition(final PropertyNode property, final Expression value, final FunctionNode getter, final FunctionNode setter, final Expression prevValue, final FunctionNode prevGetter, final FunctionNode prevSetter) {
+ // ECMA 11.1.5 strict mode restrictions
+ if (isStrictMode && value != null && prevValue != null) {
+ throw error(AbstractParser.message("property.redefinition", property.getKeyName()), property.getToken());
+ }
+
+ final boolean isPrevAccessor = prevGetter != null || prevSetter != null;
+ final boolean isAccessor = getter != null || setter != null;
+
+ // data property redefined as accessor property
+ if (prevValue != null && isAccessor) {
+ throw error(AbstractParser.message("property.redefinition", property.getKeyName()), property.getToken());
+ }
+
+ // accessor property redefined as data
+ if (isPrevAccessor && value != null) {
+ throw error(AbstractParser.message("property.redefinition", property.getKeyName()), property.getToken());
+ }
+
+ if (isAccessor && isPrevAccessor) {
+ if (getter != null && prevGetter != null ||
+ setter != null && prevSetter != null) {
+ throw error(AbstractParser.message("property.redefinition", property.getKeyName()), property.getToken());
+ }
+ }
+ }
+
+ /**
+ * LiteralPropertyName :
+ * IdentifierName
+ * StringLiteral
+ * NumericLiteral
+ *
+ * @return PropertyName node
+ */
+ @SuppressWarnings("fallthrough")
+ private PropertyKey literalPropertyName() {
+ switch (type) {
+ case IDENT:
+ return getIdent().setIsPropertyName();
+ case OCTAL_LEGACY:
+ if (isStrictMode) {
+ throw error(AbstractParser.message("strict.no.octal"), token);
+ }
+ case STRING:
+ case ESCSTRING:
+ case DECIMAL:
+ case HEXADECIMAL:
+ case OCTAL:
+ case BINARY_NUMBER:
+ case FLOATING:
+ return getLiteral();
+ default:
+ return getIdentifierName().setIsPropertyName();
+ }
+ }
+
+ /**
+ * ComputedPropertyName :
+ * AssignmentExpression
+ *
+ * @return PropertyName node
+ */
+ private Expression computedPropertyName() {
+ expect(LBRACKET);
+ final Expression expression = assignmentExpression(false);
+ expect(RBRACKET);
+ return expression;
+ }
+
+ /**
+ * PropertyName :
+ * LiteralPropertyName
+ * ComputedPropertyName
+ *
+ * @return PropertyName node
+ */
+ private Expression propertyName() {
+ if (type == LBRACKET && isES6()) {
+ return computedPropertyName();
+ } else {
+ return (Expression)literalPropertyName();
+ }
+ }
+
+ /**
+ * PropertyAssignment :
+ * PropertyName : AssignmentExpression
+ * get PropertyName ( ) { FunctionBody }
+ * set PropertyName ( PropertySetParameterList ) { FunctionBody }
+ *
+ * PropertySetParameterList :
+ * Identifier
+ *
+ * PropertyName :
+ * IdentifierName
+ * StringLiteral
+ * NumericLiteral
+ *
+ * See 11.1.5
+ *
+ * Parse an object literal property.
+ * @return Property or reference node.
+ */
+ private PropertyNode propertyAssignment() {
+ // Capture firstToken.
+ final long propertyToken = token;
+ final int functionLine = line;
+
+ final Expression propertyName;
+ final boolean isIdentifier;
+
+ boolean generator = false;
+ if (type == MUL && isES6()) {
+ generator = true;
+ next();
+ }
+
+ final boolean computed = type == LBRACKET;
+ if (type == IDENT) {
+ // Get IDENT.
+ final String ident = (String)expectValue(IDENT);
+
+ if (type != COLON && (type != LPAREN || !isES6())) {
+ final long getSetToken = propertyToken;
+
+ switch (ident) {
+ case GET_NAME:
+ final PropertyFunction getter = propertyGetterFunction(getSetToken, functionLine);
+ return new PropertyNode(propertyToken, finish, getter.key, null, getter.functionNode, null, false, getter.computed);
+
+ case SET_NAME:
+ final PropertyFunction setter = propertySetterFunction(getSetToken, functionLine);
+ return new PropertyNode(propertyToken, finish, setter.key, null, null, setter.functionNode, false, setter.computed);
+ default:
+ break;
+ }
+ }
+
+ isIdentifier = true;
+ IdentNode identNode = createIdentNode(propertyToken, finish, ident).setIsPropertyName();
+ if (type == COLON && ident.equals("__proto__")) {
+ identNode = identNode.setIsProtoPropertyName();
+ }
+ propertyName = identNode;
+ } else {
+ isIdentifier = isNonStrictModeIdent();
+ propertyName = propertyName();
+ }
+
+ Expression propertyValue;
+
+ if (generator) {
+ expectDontAdvance(LPAREN);
+ }
+
+ if (type == LPAREN && isES6()) {
+ propertyValue = propertyMethodFunction(propertyName, propertyToken, functionLine, generator, FunctionNode.ES6_IS_METHOD, computed).functionNode;
+ } else if (isIdentifier && (type == COMMARIGHT || type == RBRACE || type == ASSIGN) && isES6()) {
+ propertyValue = createIdentNode(propertyToken, finish, ((IdentNode) propertyName).getPropertyName());
+ if (type == ASSIGN && isES6()) {
+ // TODO if not destructuring, this is a SyntaxError
+ final long assignToken = token;
+ next();
+ final Expression rhs = assignmentExpression(false);
+ propertyValue = verifyAssignment(assignToken, propertyValue, rhs);
+ }
+ } else {
+ expect(COLON);
+
+ defaultNames.push(propertyName);
+ try {
+ propertyValue = assignmentExpression(false);
+ } finally {
+ defaultNames.pop();
+ }
+ }
+
+ return new PropertyNode(propertyToken, finish, propertyName, propertyValue, null, null, false, computed);
+ }
+
+ private PropertyFunction propertyGetterFunction(final long getSetToken, final int functionLine) {
+ return propertyGetterFunction(getSetToken, functionLine, FunctionNode.ES6_IS_METHOD);
+ }
+
+ private PropertyFunction propertyGetterFunction(final long getSetToken, final int functionLine, final int flags) {
+ final boolean computed = type == LBRACKET;
+ final Expression propertyName = propertyName();
+ final String getterName = propertyName instanceof PropertyKey ? ((PropertyKey) propertyName).getPropertyName() : getDefaultValidFunctionName(functionLine, false);
+ final IdentNode getNameNode = createIdentNode((propertyName).getToken(), finish, NameCodec.encode("get " + getterName));
+ expect(LPAREN);
+ expect(RPAREN);
+
+ final ParserContextFunctionNode functionNode = createParserContextFunctionNode(getNameNode, getSetToken, FunctionNode.Kind.GETTER, functionLine, Collections.<IdentNode>emptyList());
+ functionNode.setFlag(flags);
+ if (computed) {
+ functionNode.setFlag(FunctionNode.IS_ANONYMOUS);
+ }
+ lc.push(functionNode);
+
+ Block functionBody;
+
+
+ try {
+ functionBody = functionBody(functionNode);
+ } finally {
+ lc.pop(functionNode);
+ }
+
+ final FunctionNode function = createFunctionNode(
+ functionNode,
+ getSetToken,
+ getNameNode,
+ Collections.<IdentNode>emptyList(),
+ FunctionNode.Kind.GETTER,
+ functionLine,
+ functionBody);
+
+ return new PropertyFunction(propertyName, function, computed);
+ }
+
+ private PropertyFunction propertySetterFunction(final long getSetToken, final int functionLine) {
+ return propertySetterFunction(getSetToken, functionLine, FunctionNode.ES6_IS_METHOD);
+ }
+
+ private PropertyFunction propertySetterFunction(final long getSetToken, final int functionLine, final int flags) {
+ final boolean computed = type == LBRACKET;
+ final Expression propertyName = propertyName();
+ final String setterName = propertyName instanceof PropertyKey ? ((PropertyKey) propertyName).getPropertyName() : getDefaultValidFunctionName(functionLine, false);
+ final IdentNode setNameNode = createIdentNode((propertyName).getToken(), finish, NameCodec.encode("set " + setterName));
+ expect(LPAREN);
+ // be sloppy and allow missing setter parameter even though
+ // spec does not permit it!
+ final IdentNode argIdent;
+ if (isBindingIdentifier()) {
+ argIdent = getIdent();
+ verifyIdent(argIdent, "setter argument");
+ } else {
+ argIdent = null;
+ }
+ expect(RPAREN);
+ final List<IdentNode> parameters = new ArrayList<>();
+ if (argIdent != null) {
+ parameters.add(argIdent);
+ }
+
+
+ final ParserContextFunctionNode functionNode = createParserContextFunctionNode(setNameNode, getSetToken, FunctionNode.Kind.SETTER, functionLine, parameters);
+ functionNode.setFlag(flags);
+ if (computed) {
+ functionNode.setFlag(FunctionNode.IS_ANONYMOUS);
+ }
+ lc.push(functionNode);
+
+ Block functionBody;
+ try {
+ functionBody = functionBody(functionNode);
+ } finally {
+ lc.pop(functionNode);
+ }
+
+
+ final FunctionNode function = createFunctionNode(
+ functionNode,
+ getSetToken,
+ setNameNode,
+ parameters,
+ FunctionNode.Kind.SETTER,
+ functionLine,
+ functionBody);
+
+ return new PropertyFunction(propertyName, function, computed);
+ }
+
+ private PropertyFunction propertyMethodFunction(final Expression key, final long methodToken, final int methodLine, final boolean generator, final int flags, final boolean computed) {
+ final String methodName = key instanceof PropertyKey ? ((PropertyKey) key).getPropertyName() : getDefaultValidFunctionName(methodLine, false);
+ final IdentNode methodNameNode = createIdentNode(((Node)key).getToken(), finish, methodName);
+
+ final FunctionNode.Kind functionKind = generator ? FunctionNode.Kind.GENERATOR : FunctionNode.Kind.NORMAL;
+ final ParserContextFunctionNode functionNode = createParserContextFunctionNode(methodNameNode, methodToken, functionKind, methodLine, null);
+ functionNode.setFlag(flags);
+ if (computed) {
+ functionNode.setFlag(FunctionNode.IS_ANONYMOUS);
+ }
+ lc.push(functionNode);
+
+ try {
+ final ParserContextBlockNode parameterBlock = newBlock();
+ final List<IdentNode> parameters;
+ try {
+ expect(LPAREN);
+ parameters = formalParameterList(generator);
+ functionNode.setParameters(parameters);
+ expect(RPAREN);
+ } finally {
+ restoreBlock(parameterBlock);
+ }
+
+ Block functionBody = functionBody(functionNode);
+
+ functionBody = maybeWrapBodyInParameterBlock(functionBody, parameterBlock);
+
+ final FunctionNode function = createFunctionNode(
+ functionNode,
+ methodToken,
+ methodNameNode,
+ parameters,
+ functionKind,
+ methodLine,
+ functionBody);
+ return new PropertyFunction(key, function, computed);
+ } finally {
+ lc.pop(functionNode);
+ }
+ }
+
+ private static class PropertyFunction {
+ final Expression key;
+ final FunctionNode functionNode;
+ final boolean computed;
+
+ PropertyFunction(final Expression key, final FunctionNode function, final boolean computed) {
+ this.key = key;
+ this.functionNode = function;
+ this.computed = computed;
+ }
+ }
+
+ /**
+ * LeftHandSideExpression :
+ * NewExpression
+ * CallExpression
+ *
+ * CallExpression :
+ * MemberExpression Arguments
+ * SuperCall
+ * CallExpression Arguments
+ * CallExpression [ Expression ]
+ * CallExpression . IdentifierName
+ *
+ * SuperCall :
+ * super Arguments
+ *
+ * See 11.2
+ *
+ * Parse left hand side expression.
+ * @return Expression node.
+ */
+ private Expression leftHandSideExpression() {
+ int callLine = line;
+ long callToken = token;
+
+ Expression lhs = memberExpression();
+
+ if (type == LPAREN) {
+ final List<Expression> arguments = optimizeList(argumentList());
+
+ // Catch special functions.
+ if (lhs instanceof IdentNode) {
+ detectSpecialFunction((IdentNode)lhs);
+ }
+
+ lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false);
+ }
+
+ loop:
+ while (true) {
+ // Capture token.
+ callLine = line;
+ callToken = token;
+
+ switch (type) {
+ case LPAREN: {
+ // Get NEW or FUNCTION arguments.
+ final List<Expression> arguments = optimizeList(argumentList());
+
+ // Create call node.
+ lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false);
+
+ break;
+ }
+ case LBRACKET: {
+ next();
+
+ // Get array index.
+ final Expression rhs = expression();
+
+ expect(RBRACKET);
+
+ // Create indexing node.
+ lhs = new IndexNode(callToken, finish, lhs, rhs);
+
+ break;
+ }
+ case PERIOD: {
+ next();
+
+ final IdentNode property = getIdentifierName();
+
+ // Create property access node.
+ lhs = new AccessNode(callToken, finish, lhs, property.getName());
+
+ break;
+ }
+ case TEMPLATE:
+ case TEMPLATE_HEAD: {
+ // tagged template literal
+ final List<Expression> arguments = templateLiteralArgumentList();
+
+ // Create call node.
+ lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false);
+
+ break;
+ }
+ default:
+ break loop;
+ }
+ }
+
+ return lhs;
+ }
+
+ /**
+ * NewExpression :
+ * MemberExpression
+ * new NewExpression
+ *
+ * See 11.2
+ *
+ * Parse new expression.
+ * @return Expression node.
+ */
+ private Expression newExpression() {
+ final long newToken = token;
+ // NEW is tested in caller.
+ next();
+
+ if (type == PERIOD && isES6()) {
+ next();
+ if (type == IDENT && "target".equals(getValue())) {
+ if (lc.getCurrentFunction().isProgram()) {
+ throw error(AbstractParser.message("new.target.in.function"), token);
+ }
+ next();
+ markNewTarget(lc);
+ return new IdentNode(newToken, finish, "new.target");
+ } else {
+ throw error(AbstractParser.message("expected.target"), token);
+ }
+ }
+
+ // Get function base.
+ final int callLine = line;
+ final Expression constructor = memberExpression();
+ if (constructor == null) {
+ return null;
+ }
+ // Get arguments.
+ ArrayList<Expression> arguments;
+
+ // Allow for missing arguments.
+ if (type == LPAREN) {
+ arguments = argumentList();
+ } else {
+ arguments = new ArrayList<>();
+ }
+
+ // Nashorn extension: This is to support the following interface implementation
+ // syntax:
+ //
+ // var r = new java.lang.Runnable() {
+ // run: function() { println("run"); }
+ // };
+ //
+ // The object literal following the "new Constructor()" expression
+ // is passed as an additional (last) argument to the constructor.
+ if (!env._no_syntax_extensions && type == LBRACE) {
+ arguments.add(objectLiteral());
+ }
+
+ final CallNode callNode = new CallNode(callLine, constructor.getToken(), finish, constructor, optimizeList(arguments), true);
+
+ return new UnaryNode(newToken, callNode);
+ }
+
+ /**
+ * MemberExpression :
+ * PrimaryExpression
+ * FunctionExpression
+ * ClassExpression
+ * GeneratorExpression
+ * MemberExpression [ Expression ]
+ * MemberExpression . IdentifierName
+ * MemberExpression TemplateLiteral
+ * SuperProperty
+ * MetaProperty
+ * new MemberExpression Arguments
+ *
+ * SuperProperty :
+ * super [ Expression ]
+ * super . IdentifierName
+ *
+ * MetaProperty :
+ * NewTarget
+ *
+ * Parse member expression.
+ * @return Expression node.
+ */
+ @SuppressWarnings("fallthrough")
+ private Expression memberExpression() {
+ // Prepare to build operation.
+ Expression lhs;
+ boolean isSuper = false;
+
+ switch (type) {
+ case NEW:
+ // Get new expression.
+ lhs = newExpression();
+ break;
+
+ case FUNCTION:
+ // Get function expression.
+ lhs = functionExpression(false, false);
+ break;
+
+ case CLASS:
+ if (isES6()) {
+ lhs = classExpression(false);
+ break;
+ } else {
+ // fall through
+ }
+
+ case SUPER:
+ if (isES6()) {
+ final ParserContextFunctionNode currentFunction = getCurrentNonArrowFunction();
+ if (currentFunction.isMethod()) {
+ final long identToken = Token.recast(token, IDENT);
+ next();
+ lhs = createIdentNode(identToken, finish, SUPER.getName());
+
+ switch (type) {
+ case LBRACKET:
+ case PERIOD:
+ getCurrentNonArrowFunction().setFlag(FunctionNode.ES6_USES_SUPER);
+ isSuper = true;
+ break;
+ case LPAREN:
+ if (currentFunction.isSubclassConstructor()) {
+ lhs = ((IdentNode)lhs).setIsDirectSuper();
+ break;
+ } else {
+ // fall through to throw error
+ }
+ default:
+ throw error(AbstractParser.message("invalid.super"), identToken);
+ }
+ break;
+ } else {
+ // fall through
+ }
+ } else {
+ // fall through
+ }
+
+ default:
+ // Get primary expression.
+ lhs = primaryExpression();
+ break;
+ }
+
+ loop:
+ while (true) {
+ // Capture token.
+ final long callToken = token;
+
+ switch (type) {
+ case LBRACKET: {
+ next();
+
+ // Get array index.
+ final Expression index = expression();
+
+ expect(RBRACKET);
+
+ // Create indexing node.
+ lhs = new IndexNode(callToken, finish, lhs, index);
+
+ if (isSuper) {
+ isSuper = false;
+ lhs = ((BaseNode) lhs).setIsSuper();
+ }
+
+ break;
+ }
+ case PERIOD: {
+ if (lhs == null) {
+ throw error(AbstractParser.message("expected.operand", type.getNameOrType()));
+ }
+
+ next();
+
+ final IdentNode property = getIdentifierName();
+
+ // Create property access node.
+ lhs = new AccessNode(callToken, finish, lhs, property.getName());
+
+ if (isSuper) {
+ isSuper = false;
+ lhs = ((BaseNode) lhs).setIsSuper();
+ }
+
+ break;
+ }
+ case TEMPLATE:
+ case TEMPLATE_HEAD: {
+ // tagged template literal
+ final int callLine = line;
+ final List<Expression> arguments = templateLiteralArgumentList();
+
+ lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false);
+
+ break;
+ }
+ default:
+ break loop;
+ }
+ }
+
+ return lhs;
+ }
+
+ /**
+ * Arguments :
+ * ( )
+ * ( ArgumentList )
+ *
+ * ArgumentList :
+ * AssignmentExpression
+ * ... AssignmentExpression
+ * ArgumentList , AssignmentExpression
+ * ArgumentList , ... AssignmentExpression
+ *
+ * See 11.2
+ *
+ * Parse function call arguments.
+ * @return Argument list.
+ */
+ private ArrayList<Expression> argumentList() {
+ // Prepare to accumulate list of arguments.
+ final ArrayList<Expression> nodeList = new ArrayList<>();
+ // LPAREN tested in caller.
+ next();
+
+ // Track commas.
+ boolean first = true;
+
+ while (type != RPAREN) {
+ // Comma prior to every argument except the first.
+ if (!first) {
+ expect(COMMARIGHT);
+ } else {
+ first = false;
+ }
+
+ long spreadToken = 0;
+ if (type == ELLIPSIS && isES6()) {
+ spreadToken = token;
+ next();
+ }
+
+ // Get argument expression.
+ Expression expression = assignmentExpression(false);
+ if (spreadToken != 0) {
+ expression = new UnaryNode(Token.recast(spreadToken, TokenType.SPREAD_ARGUMENT), expression);
+ }
+ nodeList.add(expression);
+ }
+
+ expect(RPAREN);
+ return nodeList;
+ }
+
+ private static <T> List<T> optimizeList(final ArrayList<T> list) {
+ switch(list.size()) {
+ case 0: {
+ return Collections.emptyList();
+ }
+ case 1: {
+ return Collections.singletonList(list.get(0));
+ }
+ default: {
+ list.trimToSize();
+ return list;
+ }
+ }
+ }
+
+ /**
+ * FunctionDeclaration :
+ * function Identifier ( FormalParameterList? ) { FunctionBody }
+ *
+ * FunctionExpression :
+ * function Identifier? ( FormalParameterList? ) { FunctionBody }
+ *
+ * See 13
+ *
+ * Parse function declaration.
+ * @param isStatement True if for is a statement.
+ *
+ * @return Expression node.
+ */
+ private Expression functionExpression(final boolean isStatement, final boolean topLevel) {
+ final long functionToken = token;
+ final int functionLine = line;
+ // FUNCTION is tested in caller.
+ assert type == FUNCTION;
+ next();
+
+ boolean generator = false;
+ if (type == MUL && isES6()) {
+ generator = true;
+ next();
+ }
+
+ IdentNode name = null;
+
+ if (isBindingIdentifier()) {
+ if (type == YIELD && ((!isStatement && generator) || (isStatement && inGeneratorFunction()))) {
+ // 12.1.1 Early SyntaxError if:
+ // GeneratorExpression with BindingIdentifier yield
+ // HoistableDeclaration with BindingIdentifier yield in generator function body
+ expect(IDENT);
+ }
+ name = getIdent();
+ verifyStrictIdent(name, "function name");
+ } else if (isStatement) {
+ // Nashorn extension: anonymous function statements.
+ // Do not allow anonymous function statement if extensions
+ // are now allowed. But if we are reparsing then anon function
+ // statement is possible - because it was used as function
+ // expression in surrounding code.
+ if (env._no_syntax_extensions && reparsedFunction == null) {
+ expect(IDENT);
+ }
+ }
+
+ // name is null, generate anonymous name
+ boolean isAnonymous = false;
+ if (name == null) {
+ final String tmpName = getDefaultValidFunctionName(functionLine, isStatement);
+ name = new IdentNode(functionToken, Token.descPosition(functionToken), tmpName);
+ isAnonymous = true;
+ }
+
+ final FunctionNode.Kind functionKind = generator ? FunctionNode.Kind.GENERATOR : FunctionNode.Kind.NORMAL;
+ List<IdentNode> parameters = Collections.emptyList();
+ final ParserContextFunctionNode functionNode = createParserContextFunctionNode(name, functionToken, functionKind, functionLine, parameters);
+ lc.push(functionNode);
+
+ Block functionBody = null;
+ // Hide the current default name across function boundaries. E.g. "x3 = function x1() { function() {}}"
+ // If we didn't hide the current default name, then the innermost anonymous function would receive "x3".
+ hideDefaultName();
+ try {
+ final ParserContextBlockNode parameterBlock = newBlock();
+ try {
+ expect(LPAREN);
+ parameters = formalParameterList(generator);
+ functionNode.setParameters(parameters);
+ expect(RPAREN);
+ } finally {
+ restoreBlock(parameterBlock);
+ }
+
+ functionBody = functionBody(functionNode);
+
+ functionBody = maybeWrapBodyInParameterBlock(functionBody, parameterBlock);
+ } finally {
+ defaultNames.pop();
+ lc.pop(functionNode);
+ }
+
+ if (isStatement) {
+ if (topLevel || useBlockScope() || (!isStrictMode && env._function_statement == ScriptEnvironment.FunctionStatementBehavior.ACCEPT)) {
+ functionNode.setFlag(FunctionNode.IS_DECLARED);
+ } else if (isStrictMode) {
+ throw error(JSErrorType.SYNTAX_ERROR, AbstractParser.message("strict.no.func.decl.here"), functionToken);
+ } else if (env._function_statement == ScriptEnvironment.FunctionStatementBehavior.ERROR) {
+ throw error(JSErrorType.SYNTAX_ERROR, AbstractParser.message("no.func.decl.here"), functionToken);
+ } else if (env._function_statement == ScriptEnvironment.FunctionStatementBehavior.WARNING) {
+ warning(JSErrorType.SYNTAX_ERROR, AbstractParser.message("no.func.decl.here.warn"), functionToken);
+ }
+ if (isArguments(name)) {
+ lc.getCurrentFunction().setFlag(FunctionNode.DEFINES_ARGUMENTS);
+ }
+ }
+
+ if (isAnonymous) {
+ functionNode.setFlag(FunctionNode.IS_ANONYMOUS);
+ }
+
+ verifyParameterList(parameters, functionNode);
+
+ final FunctionNode function = createFunctionNode(
+ functionNode,
+ functionToken,
+ name,
+ parameters,
+ functionKind,
+ functionLine,
+ functionBody);
+
+ if (isStatement) {
+ if (isAnonymous) {
+ appendStatement(new ExpressionStatement(functionLine, functionToken, finish, function));
+ return function;
+ }
+
+ // mark ES6 block functions as lexically scoped
+ final int varFlags = (topLevel || !useBlockScope()) ? 0 : VarNode.IS_LET;
+ final VarNode varNode = new VarNode(functionLine, functionToken, finish, name, function, varFlags);
+ if (topLevel) {
+ functionDeclarations.add(varNode);
+ } else if (useBlockScope()) {
+ prependStatement(varNode); // Hoist to beginning of current block
+ } else {
+ appendStatement(varNode);
+ }
+ }
+
+ return function;
+ }
+
+ private void verifyParameterList(final List<IdentNode> parameters, final ParserContextFunctionNode functionNode) {
+ final IdentNode duplicateParameter = functionNode.getDuplicateParameterBinding();
+ if (duplicateParameter != null) {
+ if (functionNode.isStrict() || functionNode.getKind() == FunctionNode.Kind.ARROW || !functionNode.isSimpleParameterList()) {
+ throw error(AbstractParser.message("strict.param.redefinition", duplicateParameter.getName()), duplicateParameter.getToken());
+ }
+
+ final int arity = parameters.size();
+ final HashSet<String> parametersSet = new HashSet<>(arity);
+
+ for (int i = arity - 1; i >= 0; i--) {
+ final IdentNode parameter = parameters.get(i);
+ String parameterName = parameter.getName();
+
+ if (parametersSet.contains(parameterName)) {
+ // redefinition of parameter name, rename in non-strict mode
+ parameterName = functionNode.uniqueName(parameterName);
+ final long parameterToken = parameter.getToken();
+ parameters.set(i, new IdentNode(parameterToken, Token.descPosition(parameterToken), functionNode.uniqueName(parameterName)));
+ }
+ parametersSet.add(parameterName);
+ }
+ }
+ }
+
+ private static Block maybeWrapBodyInParameterBlock(final Block functionBody, final ParserContextBlockNode parameterBlock) {
+ assert functionBody.isFunctionBody();
+ if (!parameterBlock.getStatements().isEmpty()) {
+ parameterBlock.appendStatement(new BlockStatement(functionBody));
+ return new Block(parameterBlock.getToken(), functionBody.getFinish(), (functionBody.getFlags() | Block.IS_PARAMETER_BLOCK) & ~Block.IS_BODY, parameterBlock.getStatements());
+ }
+ return functionBody;
+ }
+
+ private String getDefaultValidFunctionName(final int functionLine, final boolean isStatement) {
+ final String defaultFunctionName = getDefaultFunctionName();
+ if (isValidIdentifier(defaultFunctionName)) {
+ if (isStatement) {
+ // The name will be used as the LHS of a symbol assignment. We add the anonymous function
+ // prefix to ensure that it can't clash with another variable.
+ return ANON_FUNCTION_PREFIX.symbolName() + defaultFunctionName;
+ }
+ return defaultFunctionName;
+ }
+ return ANON_FUNCTION_PREFIX.symbolName() + functionLine;
+ }
+
+ private static boolean isValidIdentifier(final String name) {
+ if (name == null || name.isEmpty()) {
+ return false;
+ }
+ if (!Character.isJavaIdentifierStart(name.charAt(0))) {
+ return false;
+ }
+ for (int i = 1; i < name.length(); ++i) {
+ if (!Character.isJavaIdentifierPart(name.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private String getDefaultFunctionName() {
+ if (!defaultNames.isEmpty()) {
+ final Object nameExpr = defaultNames.peek();
+ if (nameExpr instanceof PropertyKey) {
+ markDefaultNameUsed();
+ return ((PropertyKey)nameExpr).getPropertyName();
+ } else if (nameExpr instanceof AccessNode) {
+ markDefaultNameUsed();
+ return ((AccessNode)nameExpr).getProperty();
+ }
+ }
+ return null;
+ }
+
+ private void markDefaultNameUsed() {
+ defaultNames.pop();
+ hideDefaultName();
+ }
+
+ private void hideDefaultName() {
+ // Can be any value as long as getDefaultFunctionName doesn't recognize it as something it can extract a value
+ // from. Can't be null
+ defaultNames.push("");
+ }
+
+ /**
+ * FormalParameterList :
+ * Identifier
+ * FormalParameterList , Identifier
+ *
+ * See 13
+ *
+ * Parse function parameter list.
+ * @return List of parameter nodes.
+ */
+ private List<IdentNode> formalParameterList(final boolean yield) {
+ return formalParameterList(RPAREN, yield);
+ }
+
+ /**
+ * Same as the other method of the same name - except that the end
+ * token type expected is passed as argument to this method.
+ *
+ * FormalParameterList :
+ * Identifier
+ * FormalParameterList , Identifier
+ *
+ * See 13
+ *
+ * Parse function parameter list.
+ * @return List of parameter nodes.
+ */
+ private List<IdentNode> formalParameterList(final TokenType endType, final boolean yield) {
+ // Prepare to gather parameters.
+ final ArrayList<IdentNode> parameters = new ArrayList<>();
+ // Track commas.
+ boolean first = true;
+
+ while (type != endType) {
+ // Comma prior to every argument except the first.
+ if (!first) {
+ expect(COMMARIGHT);
+ } else {
+ first = false;
+ }
+
+ boolean restParameter = false;
+ if (type == ELLIPSIS && isES6()) {
+ next();
+ restParameter = true;
+ }
+
+ if (type == YIELD && yield) {
+ expect(IDENT);
+ }
+
+ final long paramToken = token;
+ final int paramLine = line;
+ final String contextString = "function parameter";
+ IdentNode ident;
+ if (isBindingIdentifier() || restParameter || !isES6()) {
+ ident = bindingIdentifier(contextString);
+
+ if (restParameter) {
+ ident = ident.setIsRestParameter();
+ // rest parameter must be last
+ expectDontAdvance(endType);
+ parameters.add(ident);
+ break;
+ } else if (type == ASSIGN && isES6()) {
+ next();
+ ident = ident.setIsDefaultParameter();
+
+ if (type == YIELD && yield) {
+ // error: yield in default expression
+ expect(IDENT);
+ }
+
+ // default parameter
+ final Expression initializer = assignmentExpression(false);
+
+ final ParserContextFunctionNode currentFunction = lc.getCurrentFunction();
+ if (currentFunction != null) {
+ if (env._parse_only) {
+ // keep what is seen in source "as is" and save it as parameter expression
+ final BinaryNode assignment = new BinaryNode(Token.recast(paramToken, ASSIGN), ident, initializer);
+ currentFunction.addParameterExpression(ident, assignment);
+ } else {
+ // desugar to: param = (param === undefined) ? initializer : param;
+ // possible alternative: if (param === undefined) param = initializer;
+ final BinaryNode test = new BinaryNode(Token.recast(paramToken, EQ_STRICT), ident, newUndefinedLiteral(paramToken, finish));
+ final TernaryNode value = new TernaryNode(Token.recast(paramToken, TERNARY), test, new JoinPredecessorExpression(initializer), new JoinPredecessorExpression(ident));
+ final BinaryNode assignment = new BinaryNode(Token.recast(paramToken, ASSIGN), ident, value);
+ lc.getFunctionBody(currentFunction).appendStatement(new ExpressionStatement(paramLine, assignment.getToken(), assignment.getFinish(), assignment));
+ }
+ }
+ }
+
+ final ParserContextFunctionNode currentFunction = lc.getCurrentFunction();
+ if (currentFunction != null) {
+ currentFunction.addParameterBinding(ident);
+ if (ident.isRestParameter() || ident.isDefaultParameter()) {
+ currentFunction.setSimpleParameterList(false);
+ }
+ }
+ } else {
+ final Expression pattern = bindingPattern();
+ // Introduce synthetic temporary parameter to capture the object to be destructured.
+ ident = createIdentNode(paramToken, pattern.getFinish(), String.format("arguments[%d]", parameters.size())).setIsDestructuredParameter();
+ verifyDestructuringParameterBindingPattern(pattern, paramToken, paramLine, contextString);
+
+ Expression value = ident;
+ if (type == ASSIGN) {
+ next();
+ ident = ident.setIsDefaultParameter();
+
+ // binding pattern with initializer. desugar to: (param === undefined) ? initializer : param
+ final Expression initializer = assignmentExpression(false);
+
+ if (env._parse_only) {
+ // we don't want the synthetic identifier in parse only mode
+ value = initializer;
+ } else {
+ // TODO initializer must not contain yield expression if yield=true (i.e. this is generator function's parameter list)
+ final BinaryNode test = new BinaryNode(Token.recast(paramToken, EQ_STRICT), ident, newUndefinedLiteral(paramToken, finish));
+ value = new TernaryNode(Token.recast(paramToken, TERNARY), test, new JoinPredecessorExpression(initializer), new JoinPredecessorExpression(ident));
+ }
+ }
+
+ final ParserContextFunctionNode currentFunction = lc.getCurrentFunction();
+ if (currentFunction != null) {
+ // destructuring assignment
+ final BinaryNode assignment = new BinaryNode(Token.recast(paramToken, ASSIGN), pattern, value);
+ if (env._parse_only) {
+ // in parse-only mode, represent source tree "as is"
+ if (ident.isDefaultParameter()) {
+ currentFunction.addParameterExpression(ident, assignment);
+ } else {
+ currentFunction.addParameterExpression(ident, pattern);
+ }
+ } else {
+ lc.getFunctionBody(currentFunction).appendStatement(new ExpressionStatement(paramLine, assignment.getToken(), assignment.getFinish(), assignment));
+ }
+ }
+ }
+ parameters.add(ident);
+ }
+
+ parameters.trimToSize();
+ return parameters;
+ }
+
+ private void verifyDestructuringParameterBindingPattern(final Expression pattern, final long paramToken, final int paramLine, final String contextString) {
+ verifyDestructuringBindingPattern(pattern, new Consumer<IdentNode>() {
+ public void accept(final IdentNode identNode) {
+ verifyIdent(identNode, contextString);
+
+ final ParserContextFunctionNode currentFunction = lc.getCurrentFunction();
+ if (currentFunction != null) {
+ // declare function-scope variables for destructuring bindings
+ if (!env._parse_only) {
+ lc.getFunctionBody(currentFunction).appendStatement(new VarNode(paramLine, Token.recast(paramToken, VAR), pattern.getFinish(), identNode, null));
+ }
+ // detect duplicate bounds names in parameter list
+ currentFunction.addParameterBinding(identNode);
+ currentFunction.setSimpleParameterList(false);
+ }
+ }
+ });
+ }
+
+ /**
+ * FunctionBody :
+ * SourceElements?
+ *
+ * See 13
+ *
+ * Parse function body.
+ * @return function node (body.)
+ */
+ private Block functionBody(final ParserContextFunctionNode functionNode) {
+ long lastToken = 0L;
+ ParserContextBlockNode body = null;
+ final long bodyToken = token;
+ Block functionBody;
+ int bodyFinish = 0;
+
+ final boolean parseBody;
+ Object endParserState = null;
+ try {
+ // Create a new function block.
+ body = newBlock();
+ if (env._debug_scopes) {
+ // debug scope options forces everything to be in scope
+ markEval(lc);
+ }
+ assert functionNode != null;
+ final int functionId = functionNode.getId();
+ parseBody = reparsedFunction == null || functionId <= reparsedFunction.getFunctionNodeId();
+ // Nashorn extension: expression closures
+ if ((!env._no_syntax_extensions || functionNode.getKind() == FunctionNode.Kind.ARROW) && type != LBRACE) {
+ /*
+ * Example:
+ *
+ * function square(x) x * x;
+ * print(square(3));
+ */
+
+ // just expression as function body
+ final Expression expr = assignmentExpression(false);
+ lastToken = previousToken;
+ functionNode.setLastToken(previousToken);
+ assert lc.getCurrentBlock() == lc.getFunctionBody(functionNode);
+ // EOL uses length field to store the line number
+ final int lastFinish = Token.descPosition(lastToken) + (Token.descType(lastToken) == EOL ? 0 : Token.descLength(lastToken));
+ // Only create the return node if we aren't skipping nested functions. Note that we aren't
+ // skipping parsing of these extended functions; they're considered to be small anyway. Also,
+ // they don't end with a single well known token, so it'd be very hard to get correctly (see
+ // the note below for reasoning on skipping happening before instead of after RBRACE for
+ // details).
+ if (parseBody) {
+ functionNode.setFlag(FunctionNode.HAS_EXPRESSION_BODY);
+ final ReturnNode returnNode = new ReturnNode(functionNode.getLineNumber(), expr.getToken(), lastFinish, expr);
+ appendStatement(returnNode);
+ }
+ // bodyFinish = finish;
+ } else {
+ expectDontAdvance(LBRACE);
+ if (parseBody || !skipFunctionBody(functionNode)) {
+ next();
+ // Gather the function elements.
+ final List<Statement> prevFunctionDecls = functionDeclarations;
+ functionDeclarations = new ArrayList<>();
+ try {
+ sourceElements(0);
+ addFunctionDeclarations(functionNode);
+ } finally {
+ functionDeclarations = prevFunctionDecls;
+ }
+
+ lastToken = token;
+ if (parseBody) {
+ // Since the lexer can read ahead and lexify some number of tokens in advance and have
+ // them buffered in the TokenStream, we need to produce a lexer state as it was just
+ // before it lexified RBRACE, and not whatever is its current (quite possibly well read
+ // ahead) state.
+ endParserState = new ParserState(Token.descPosition(token), line, linePosition);
+
+ // NOTE: you might wonder why do we capture/restore parser state before RBRACE instead of
+ // after RBRACE; after all, we could skip the below "expect(RBRACE);" if we captured the
+ // state after it. The reason is that RBRACE is a well-known token that we can expect and
+ // will never involve us getting into a weird lexer state, and as such is a great reparse
+ // point. Typical example of a weird lexer state after RBRACE would be:
+ // function this_is_skipped() { ... } "use strict";
+ // because lexer is doing weird off-by-one maneuvers around string literal quotes. Instead
+ // of compensating for the possibility of a string literal (or similar) after RBRACE,
+ // we'll rather just restart parsing from this well-known, friendly token instead.
+ }
+ }
+ bodyFinish = finish;
+ functionNode.setLastToken(token);
+ expect(RBRACE);
+ }
+ } finally {
+ restoreBlock(body);
+ }
+
+ // NOTE: we can only do alterations to the function node after restoreFunctionNode.
+
+ if (parseBody) {
+ functionNode.setEndParserState(endParserState);
+ } else if (!body.getStatements().isEmpty()){
+ // This is to ensure the body is empty when !parseBody but we couldn't skip parsing it (see
+ // skipFunctionBody() for possible reasons). While it is not strictly necessary for correctness to
+ // enforce empty bodies in nested functions that were supposed to be skipped, we do assert it as
+ // an invariant in few places in the compiler pipeline, so for consistency's sake we'll throw away
+ // nested bodies early if we were supposed to skip 'em.
+ body.setStatements(Collections.<Statement>emptyList());
+ }
+
+ if (reparsedFunction != null) {
+ // We restore the flags stored in the function's ScriptFunctionData that we got when we first
+ // eagerly parsed the code. We're doing it because some flags would be set based on the
+ // content of the function, or even content of its nested functions, most of which are normally
+ // skipped during an on-demand compilation.
+ final RecompilableScriptFunctionData data = reparsedFunction.getScriptFunctionData(functionNode.getId());
+ if (data != null) {
+ // Data can be null if when we originally parsed the file, we removed the function declaration
+ // as it was dead code.
+ functionNode.setFlag(data.getFunctionFlags());
+ // This compensates for missing markEval() in case the function contains an inner function
+ // that contains eval(), that now we didn't discover since we skipped the inner function.
+ if (functionNode.hasNestedEval()) {
+ assert functionNode.hasScopeBlock();
+ body.setFlag(Block.NEEDS_SCOPE);
+ }
+ }
+ }
+ functionBody = new Block(bodyToken, bodyFinish, body.getFlags() | Block.IS_BODY, body.getStatements());
+ return functionBody;
+ }
+
+ private boolean skipFunctionBody(final ParserContextFunctionNode functionNode) {
+ if (reparsedFunction == null) {
+ // Not reparsing, so don't skip any function body.
+ return false;
+ }
+ // Skip to the RBRACE of this function, and continue parsing from there.
+ final RecompilableScriptFunctionData data = reparsedFunction.getScriptFunctionData(functionNode.getId());
+ if (data == null) {
+ // Nested function is not known to the reparsed function. This can happen if the FunctionNode was
+ // in dead code that was removed. Both FoldConstants and Lower prune dead code. In that case, the
+ // FunctionNode was dropped before a RecompilableScriptFunctionData could've been created for it.
+ return false;
+ }
+ final ParserState parserState = (ParserState)data.getEndParserState();
+ assert parserState != null;
+
+ if (k < stream.last() && start < parserState.position && parserState.position <= Token.descPosition(stream.get(stream.last()))) {
+ // RBRACE is already in the token stream, so fast forward to it
+ for (; k < stream.last(); k++) {
+ final long nextToken = stream.get(k + 1);
+ if (Token.descPosition(nextToken) == parserState.position && Token.descType(nextToken) == RBRACE) {
+ token = stream.get(k);
+ type = Token.descType(token);
+ next();
+ assert type == RBRACE && start == parserState.position;
+ return true;
+ }
+ }
+ }
+
+ stream.reset();
+ lexer = parserState.createLexer(source, lexer, stream, scripting && !env._no_syntax_extensions, env._es6);
+ line = parserState.line;
+ linePosition = parserState.linePosition;
+ // Doesn't really matter, but it's safe to treat it as if there were a semicolon before
+ // the RBRACE.
+ type = SEMICOLON;
+ scanFirstToken();
+
+ return true;
+ }
+
+ /**
+ * Encapsulates part of the state of the parser, enough to reconstruct the state of both parser and lexer
+ * for resuming parsing after skipping a function body.
+ */
+ private static class ParserState implements Serializable {
+ private final int position;
+ private final int line;
+ private final int linePosition;
+
+ private static final long serialVersionUID = -2382565130754093694L;
+
+ ParserState(final int position, final int line, final int linePosition) {
+ this.position = position;
+ this.line = line;
+ this.linePosition = linePosition;
+ }
+
+ Lexer createLexer(final Source source, final Lexer lexer, final TokenStream stream, final boolean scripting, final boolean es6) {
+ final Lexer newLexer = new Lexer(source, position, lexer.limit - position, stream, scripting, es6, true);
+ newLexer.restoreState(new Lexer.State(position, Integer.MAX_VALUE, line, -1, linePosition, SEMICOLON));
+ return newLexer;
+ }
+ }
+
+ private void printAST(final FunctionNode functionNode) {
+ if (functionNode.getDebugFlag(FunctionNode.DEBUG_PRINT_AST)) {
+ env.getErr().println(new ASTWriter(functionNode));
+ }
+
+ if (functionNode.getDebugFlag(FunctionNode.DEBUG_PRINT_PARSE)) {
+ env.getErr().println(new PrintVisitor(functionNode, true, false));
+ }
+ }
+
+ private void addFunctionDeclarations(final ParserContextFunctionNode functionNode) {
+ VarNode lastDecl = null;
+ for (int i = functionDeclarations.size() - 1; i >= 0; i--) {
+ Statement decl = functionDeclarations.get(i);
+ if (lastDecl == null && decl instanceof VarNode) {
+ decl = lastDecl = ((VarNode)decl).setFlag(VarNode.IS_LAST_FUNCTION_DECLARATION);
+ functionNode.setFlag(FunctionNode.HAS_FUNCTION_DECLARATIONS);
+ }
+ prependStatement(decl);
+ }
+ }
+
+ private RuntimeNode referenceError(final Expression lhs, final Expression rhs, final boolean earlyError) {
+ if (env._parse_only || earlyError) {
+ throw error(JSErrorType.REFERENCE_ERROR, AbstractParser.message("invalid.lvalue"), lhs.getToken());
+ }
+ final ArrayList<Expression> args = new ArrayList<>();
+ args.add(lhs);
+ if (rhs == null) {
+ args.add(LiteralNode.newInstance(lhs.getToken(), lhs.getFinish()));
+ } else {
+ args.add(rhs);
+ }
+ args.add(LiteralNode.newInstance(lhs.getToken(), lhs.getFinish(), lhs.toString()));
+ return new RuntimeNode(lhs.getToken(), lhs.getFinish(), RuntimeNode.Request.REFERENCE_ERROR, args);
+ }
+
+ /**
+ * PostfixExpression :
+ * LeftHandSideExpression
+ * LeftHandSideExpression ++ // [no LineTerminator here]
+ * LeftHandSideExpression -- // [no LineTerminator here]
+ *
+ * See 11.3
+ *
+ * UnaryExpression :
+ * PostfixExpression
+ * delete UnaryExpression
+ * void UnaryExpression
+ * typeof UnaryExpression
+ * ++ UnaryExpression
+ * -- UnaryExpression
+ * + UnaryExpression
+ * - UnaryExpression
+ * ~ UnaryExpression
+ * ! UnaryExpression
+ *
+ * See 11.4
+ *
+ * Parse unary expression.
+ * @return Expression node.
+ */
+ private Expression unaryExpression() {
+ final int unaryLine = line;
+ final long unaryToken = token;
+
+ switch (type) {
+ case DELETE: {
+ next();
+ final Expression expr = unaryExpression();
+ if (expr instanceof BaseNode || expr instanceof IdentNode) {
+ return new UnaryNode(unaryToken, expr);
+ }
+ appendStatement(new ExpressionStatement(unaryLine, unaryToken, finish, expr));
+ return LiteralNode.newInstance(unaryToken, finish, true);
+ }
+ case ADD:
+ case SUB: {
+ final TokenType opType = type;
+ next();
+ final Expression expr = unaryExpression();
+ return new UnaryNode(Token.recast(unaryToken, (opType == TokenType.ADD) ? TokenType.POS : TokenType.NEG), expr);
+ }
+ case VOID:
+ case TYPEOF:
+ case BIT_NOT:
+ case NOT:
+ next();
+ final Expression expr = unaryExpression();
+ return new UnaryNode(unaryToken, expr);
+
+ case INCPREFIX:
+ case DECPREFIX:
+ final TokenType opType = type;
+ next();
+
+ final Expression lhs = leftHandSideExpression();
+ // ++, -- without operand..
+ if (lhs == null) {
+ throw error(AbstractParser.message("expected.lvalue", type.getNameOrType()));
+ }
+
+ return verifyIncDecExpression(unaryToken, opType, lhs, false);
+
+ default:
+ break;
+ }
+
+ final Expression expression = leftHandSideExpression();
+
+ if (last != EOL) {
+ switch (type) {
+ case INCPREFIX:
+ case DECPREFIX:
+ final long opToken = token;
+ final TokenType opType = type;
+ final Expression lhs = expression;
+ // ++, -- without operand..
+ if (lhs == null) {
+ throw error(AbstractParser.message("expected.lvalue", type.getNameOrType()));
+ }
+ next();
+
+ return verifyIncDecExpression(opToken, opType, lhs, true);
+ default:
+ break;
+ }
+ }
+
+ if (expression == null) {
+ throw error(AbstractParser.message("expected.operand", type.getNameOrType()));
+ }
+
+ return expression;
+ }
+
+ private Expression verifyIncDecExpression(final long unaryToken, final TokenType opType, final Expression lhs, final boolean isPostfix) {
+ assert lhs != null;
+
+ if (!(lhs instanceof AccessNode ||
+ lhs instanceof IndexNode ||
+ lhs instanceof IdentNode)) {
+ return referenceError(lhs, null, env._early_lvalue_error);
+ }
+
+ if (lhs instanceof IdentNode) {
+ if (!checkIdentLValue((IdentNode)lhs)) {
+ return referenceError(lhs, null, false);
+ }
+ verifyIdent((IdentNode)lhs, "operand for " + opType.getName() + " operator");
+ }
+
+ return incDecExpression(unaryToken, opType, lhs, isPostfix);
+ }
+
+ /**
+ * {@code
+ * MultiplicativeExpression :
+ * UnaryExpression
+ * MultiplicativeExpression * UnaryExpression
+ * MultiplicativeExpression / UnaryExpression
+ * MultiplicativeExpression % UnaryExpression
+ *
+ * See 11.5
+ *
+ * AdditiveExpression :
+ * MultiplicativeExpression
+ * AdditiveExpression + MultiplicativeExpression
+ * AdditiveExpression - MultiplicativeExpression
+ *
+ * See 11.6
+ *
+ * ShiftExpression :
+ * AdditiveExpression
+ * ShiftExpression << AdditiveExpression
+ * ShiftExpression >> AdditiveExpression
+ * ShiftExpression >>> AdditiveExpression
+ *
+ * See 11.7
+ *
+ * RelationalExpression :
+ * ShiftExpression
+ * RelationalExpression < ShiftExpression
+ * RelationalExpression > ShiftExpression
+ * RelationalExpression <= ShiftExpression
+ * RelationalExpression >= ShiftExpression
+ * RelationalExpression instanceof ShiftExpression
+ * RelationalExpression in ShiftExpression // if !noIf
+ *
+ * See 11.8
+ *
+ * RelationalExpression
+ * EqualityExpression == RelationalExpression
+ * EqualityExpression != RelationalExpression
+ * EqualityExpression === RelationalExpression
+ * EqualityExpression !== RelationalExpression
+ *
+ * See 11.9
+ *
+ * BitwiseANDExpression :
+ * EqualityExpression
+ * BitwiseANDExpression & EqualityExpression
+ *
+ * BitwiseXORExpression :
+ * BitwiseANDExpression
+ * BitwiseXORExpression ^ BitwiseANDExpression
+ *
+ * BitwiseORExpression :
+ * BitwiseXORExpression
+ * BitwiseORExpression | BitwiseXORExpression
+ *
+ * See 11.10
+ *
+ * LogicalANDExpression :
+ * BitwiseORExpression
+ * LogicalANDExpression && BitwiseORExpression
+ *
+ * LogicalORExpression :
+ * LogicalANDExpression
+ * LogicalORExpression || LogicalANDExpression
+ *
+ * See 11.11
+ *
+ * ConditionalExpression :
+ * LogicalORExpression
+ * LogicalORExpression ? AssignmentExpression : AssignmentExpression
+ *
+ * See 11.12
+ *
+ * AssignmentExpression :
+ * ConditionalExpression
+ * LeftHandSideExpression AssignmentOperator AssignmentExpression
+ *
+ * AssignmentOperator :
+ * = *= /= %= += -= <<= >>= >>>= &= ^= |=
+ *
+ * See 11.13
+ *
+ * Expression :
+ * AssignmentExpression
+ * Expression , AssignmentExpression
+ *
+ * See 11.14
+ * }
+ *
+ * Parse expression.
+ * @return Expression node.
+ */
+ protected Expression expression() {
+ // This method is protected so that subclass can get details
+ // at expression start point!
+
+ // Include commas in expression parsing.
+ return expression(false);
+ }
+
+ private Expression expression(final boolean noIn) {
+ Expression assignmentExpression = assignmentExpression(noIn);
+ while (type == COMMARIGHT) {
+ final long commaToken = token;
+ next();
+
+ boolean rhsRestParameter = false;
+ if (type == ELLIPSIS && isES6()) {
+ // (a, b, ...rest) is not a valid expression, unless we're parsing the parameter list of an arrow function (we need to throw the right error).
+ // But since the rest parameter is always last, at least we know that the expression has to end here and be followed by RPAREN and ARROW, so peek ahead.
+ if (isRestParameterEndOfArrowFunctionParameterList()) {
+ next();
+ rhsRestParameter = true;
+ }
+ }
+
+ Expression rhs = assignmentExpression(noIn);
+
+ if (rhsRestParameter) {
+ rhs = ((IdentNode)rhs).setIsRestParameter();
+ // Our only valid move is to end Expression here and continue with ArrowFunction.
+ // We've already checked that this is the parameter list of an arrow function (see above).
+ // RPAREN is next, so we'll finish the binary expression and drop out of the loop.
+ assert type == RPAREN;
+ }
+
+ assignmentExpression = new BinaryNode(commaToken, assignmentExpression, rhs);
+ }
+ return assignmentExpression;
+ }
+
+ private Expression expression(final int minPrecedence, final boolean noIn) {
+ return expression(unaryExpression(), minPrecedence, noIn);
+ }
+
+ private JoinPredecessorExpression joinPredecessorExpression() {
+ return new JoinPredecessorExpression(expression());
+ }
+
+ private Expression expression(final Expression exprLhs, final int minPrecedence, final boolean noIn) {
+ // Get the precedence of the next operator.
+ int precedence = type.getPrecedence();
+ Expression lhs = exprLhs;
+
+ // While greater precedence.
+ while (type.isOperator(noIn) && precedence >= minPrecedence) {
+ // Capture the operator token.
+ final long op = token;
+
+ if (type == TERNARY) {
+ // Skip operator.
+ next();
+
+ // Pass expression. Middle expression of a conditional expression can be a "in"
+ // expression - even in the contexts where "in" is not permitted.
+ final Expression trueExpr = expression(unaryExpression(), ASSIGN.getPrecedence(), false);
+
+ expect(COLON);
+
+ // Fail expression.
+ final Expression falseExpr = expression(unaryExpression(), ASSIGN.getPrecedence(), noIn);
+
+ // Build up node.
+ lhs = new TernaryNode(op, lhs, new JoinPredecessorExpression(trueExpr), new JoinPredecessorExpression(falseExpr));
+ } else {
+ // Skip operator.
+ next();
+
+ // Get the next primary expression.
+ Expression rhs;
+ final boolean isAssign = Token.descType(op) == ASSIGN;
+ if(isAssign) {
+ defaultNames.push(lhs);
+ }
+ try {
+ rhs = unaryExpression();
+ // Get precedence of next operator.
+ int nextPrecedence = type.getPrecedence();
+
+ // Subtask greater precedence.
+ while (type.isOperator(noIn) &&
+ (nextPrecedence > precedence ||
+ nextPrecedence == precedence && !type.isLeftAssociative())) {
+ rhs = expression(rhs, nextPrecedence, noIn);
+ nextPrecedence = type.getPrecedence();
+ }
+ } finally {
+ if(isAssign) {
+ defaultNames.pop();
+ }
+ }
+ lhs = verifyAssignment(op, lhs, rhs);
+ }
+
+ precedence = type.getPrecedence();
+ }
+
+ return lhs;
+ }
+
+ /**
+ * AssignmentExpression.
+ *
+ * AssignmentExpression[In, Yield] :
+ * ConditionalExpression[?In, ?Yield]
+ * [+Yield] YieldExpression[?In]
+ * ArrowFunction[?In, ?Yield]
+ * LeftHandSideExpression[?Yield] = AssignmentExpression[?In, ?Yield]
+ * LeftHandSideExpression[?Yield] AssignmentOperator AssignmentExpression[?In, ?Yield]
+ *
+ * @param noIn {@code true} if IN operator should be ignored.
+ * @return the assignment expression
+ */
+ protected Expression assignmentExpression(final boolean noIn) {
+ // This method is protected so that subclass can get details
+ // at assignment expression start point!
+
+ if (type == YIELD && inGeneratorFunction() && isES6()) {
+ return yieldExpression(noIn);
+ }
+
+ final long startToken = token;
+ final int startLine = line;
+ final Expression exprLhs = conditionalExpression(noIn);
+
+ if (type == ARROW && isES6()) {
+ if (checkNoLineTerminator()) {
+ final Expression paramListExpr;
+ if (exprLhs instanceof ExpressionList) {
+ paramListExpr = (((ExpressionList)exprLhs).getExpressions().isEmpty() ? null : ((ExpressionList)exprLhs).getExpressions().get(0));
+ } else {
+ paramListExpr = exprLhs;
+ }
+ return arrowFunction(startToken, startLine, paramListExpr);
+ }
+ }
+ assert !(exprLhs instanceof ExpressionList);
+
+ if (isAssignmentOperator(type)) {
+ final boolean isAssign = type == ASSIGN;
+ if (isAssign) {
+ defaultNames.push(exprLhs);
+ }
+ try {
+ final long assignToken = token;
+ next();
+ final Expression exprRhs = assignmentExpression(noIn);
+ return verifyAssignment(assignToken, exprLhs, exprRhs);
+ } finally {
+ if (isAssign) {
+ defaultNames.pop();
+ }
+ }
+ } else {
+ return exprLhs;
+ }
+ }
+
+ /**
+ * Is type one of {@code = *= /= %= += -= <<= >>= >>>= &= ^= |=}?
+ */
+ private static boolean isAssignmentOperator(final TokenType type) {
+ switch (type) {
+ case ASSIGN:
+ case ASSIGN_ADD:
+ case ASSIGN_BIT_AND:
+ case ASSIGN_BIT_OR:
+ case ASSIGN_BIT_XOR:
+ case ASSIGN_DIV:
+ case ASSIGN_MOD:
+ case ASSIGN_MUL:
+ case ASSIGN_SAR:
+ case ASSIGN_SHL:
+ case ASSIGN_SHR:
+ case ASSIGN_SUB:
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * ConditionalExpression.
+ */
+ private Expression conditionalExpression(final boolean noIn) {
+ return expression(TERNARY.getPrecedence(), noIn);
+ }
+
+ /**
+ * ArrowFunction.
+ *
+ * @param startToken start token of the ArrowParameters expression
+ * @param functionLine start line of the arrow function
+ * @param paramListExpr ArrowParameters expression or {@code null} for {@code ()} (empty list)
+ */
+ private Expression arrowFunction(final long startToken, final int functionLine, final Expression paramListExpr) {
+ // caller needs to check that there's no LineTerminator between parameter list and arrow
+ assert type != ARROW || checkNoLineTerminator();
+ expect(ARROW);
+
+ final long functionToken = Token.recast(startToken, ARROW);
+ final IdentNode name = new IdentNode(functionToken, Token.descPosition(functionToken), NameCodec.encode("=>:") + functionLine);
+ final ParserContextFunctionNode functionNode = createParserContextFunctionNode(name, functionToken, FunctionNode.Kind.ARROW, functionLine, null);
+ functionNode.setFlag(FunctionNode.IS_ANONYMOUS);
+
+ lc.push(functionNode);
+ try {
+ final ParserContextBlockNode parameterBlock = newBlock();
+ final List<IdentNode> parameters;
+ try {
+ parameters = convertArrowFunctionParameterList(paramListExpr, functionLine);
+ functionNode.setParameters(parameters);
+
+ if (!functionNode.isSimpleParameterList()) {
+ markEvalInArrowParameterList(parameterBlock);
+ }
+ } finally {
+ restoreBlock(parameterBlock);
+ }
+ Block functionBody = functionBody(functionNode);
+
+ functionBody = maybeWrapBodyInParameterBlock(functionBody, parameterBlock);
+
+ verifyParameterList(parameters, functionNode);
+
+ final FunctionNode function = createFunctionNode(
+ functionNode,
+ functionToken,
+ name,
+ parameters,
+ FunctionNode.Kind.ARROW,
+ functionLine,
+ functionBody);
+ return function;
+ } finally {
+ lc.pop(functionNode);
+ }
+ }
+
+ private void markEvalInArrowParameterList(final ParserContextBlockNode parameterBlock) {
+ final Iterator<ParserContextFunctionNode> iter = lc.getFunctions();
+ final ParserContextFunctionNode current = iter.next();
+ final ParserContextFunctionNode parent = iter.next();
+
+ if (parent.getFlag(FunctionNode.HAS_EVAL) != 0) {
+ // we might have flagged has-eval in the parent function during parsing the parameter list,
+ // if the parameter list contains eval; must tag arrow function as has-eval.
+ for (final Statement st : parameterBlock.getStatements()) {
+ st.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
+ @Override
+ public boolean enterCallNode(final CallNode callNode) {
+ if (callNode.getFunction() instanceof IdentNode && ((IdentNode) callNode.getFunction()).getName().equals("eval")) {
+ current.setFlag(FunctionNode.HAS_EVAL);
+ }
+ return true;
+ }
+ });
+ }
+ // TODO: function containing the arrow function should not be flagged has-eval
+ }
+ }
+
+ private List<IdentNode> convertArrowFunctionParameterList(final Expression paramListExpr, final int functionLine) {
+ final List<IdentNode> parameters;
+ if (paramListExpr == null) {
+ // empty parameter list, i.e. () =>
+ parameters = Collections.emptyList();
+ } else if (paramListExpr instanceof IdentNode || paramListExpr.isTokenType(ASSIGN) || isDestructuringLhs(paramListExpr)) {
+ parameters = Collections.singletonList(verifyArrowParameter(paramListExpr, 0, functionLine));
+ } else if (paramListExpr instanceof BinaryNode && Token.descType(paramListExpr.getToken()) == COMMARIGHT) {
+ parameters = new ArrayList<>();
+ Expression car = paramListExpr;
+ do {
+ final Expression cdr = ((BinaryNode) car).rhs();
+ parameters.add(0, verifyArrowParameter(cdr, parameters.size(), functionLine));
+ car = ((BinaryNode) car).lhs();
+ } while (car instanceof BinaryNode && Token.descType(car.getToken()) == COMMARIGHT);
+ parameters.add(0, verifyArrowParameter(car, parameters.size(), functionLine));
+ } else {
+ throw error(AbstractParser.message("expected.arrow.parameter"), paramListExpr.getToken());
+ }
+ return parameters;
+ }
+
+ private IdentNode verifyArrowParameter(final Expression param, final int index, final int paramLine) {
+ final String contextString = "function parameter";
+ if (param instanceof IdentNode) {
+ final IdentNode ident = (IdentNode)param;
+ verifyStrictIdent(ident, contextString);
+ final ParserContextFunctionNode currentFunction = lc.getCurrentFunction();
+ if (currentFunction != null) {
+ currentFunction.addParameterBinding(ident);
+ }
+ return ident;
+ }
+
+ if (param.isTokenType(ASSIGN)) {
+ final Expression lhs = ((BinaryNode) param).lhs();
+ final long paramToken = lhs.getToken();
+ final Expression initializer = ((BinaryNode) param).rhs();
+ if (lhs instanceof IdentNode) {
+ // default parameter
+ final IdentNode ident = (IdentNode) lhs;
+
+ final ParserContextFunctionNode currentFunction = lc.getCurrentFunction();
+ if (currentFunction != null) {
+ if (env._parse_only) {
+ currentFunction.addParameterExpression(ident, param);
+ } else {
+ final BinaryNode test = new BinaryNode(Token.recast(paramToken, EQ_STRICT), ident, newUndefinedLiteral(paramToken, finish));
+ final TernaryNode value = new TernaryNode(Token.recast(paramToken, TERNARY), test, new JoinPredecessorExpression(initializer), new JoinPredecessorExpression(ident));
+ final BinaryNode assignment = new BinaryNode(Token.recast(paramToken, ASSIGN), ident, value);
+ lc.getFunctionBody(currentFunction).appendStatement(new ExpressionStatement(paramLine, assignment.getToken(), assignment.getFinish(), assignment));
+ }
+
+ currentFunction.addParameterBinding(ident);
+ currentFunction.setSimpleParameterList(false);
+ }
+ return ident;
+ } else if (isDestructuringLhs(lhs)) {
+ // binding pattern with initializer
+ // Introduce synthetic temporary parameter to capture the object to be destructured.
+ final IdentNode ident = createIdentNode(paramToken, param.getFinish(), String.format("arguments[%d]", index)).setIsDestructuredParameter().setIsDefaultParameter();
+ verifyDestructuringParameterBindingPattern(param, paramToken, paramLine, contextString);
+
+ final ParserContextFunctionNode currentFunction = lc.getCurrentFunction();
+ if (currentFunction != null) {
+ if (env._parse_only) {
+ currentFunction.addParameterExpression(ident, param);
+ } else {
+ final BinaryNode test = new BinaryNode(Token.recast(paramToken, EQ_STRICT), ident, newUndefinedLiteral(paramToken, finish));
+ final TernaryNode value = new TernaryNode(Token.recast(paramToken, TERNARY), test, new JoinPredecessorExpression(initializer), new JoinPredecessorExpression(ident));
+ final BinaryNode assignment = new BinaryNode(Token.recast(paramToken, ASSIGN), param, value);
+ lc.getFunctionBody(currentFunction).appendStatement(new ExpressionStatement(paramLine, assignment.getToken(), assignment.getFinish(), assignment));
+ }
+ }
+ return ident;
+ }
+ } else if (isDestructuringLhs(param)) {
+ // binding pattern
+ final long paramToken = param.getToken();
+
+ // Introduce synthetic temporary parameter to capture the object to be destructured.
+ final IdentNode ident = createIdentNode(paramToken, param.getFinish(), String.format("arguments[%d]", index)).setIsDestructuredParameter();
+ verifyDestructuringParameterBindingPattern(param, paramToken, paramLine, contextString);
+
+ final ParserContextFunctionNode currentFunction = lc.getCurrentFunction();
+ if (currentFunction != null) {
+ if (env._parse_only) {
+ currentFunction.addParameterExpression(ident, param);
+ } else {
+ final BinaryNode assignment = new BinaryNode(Token.recast(paramToken, ASSIGN), param, ident);
+ lc.getFunctionBody(currentFunction).appendStatement(new ExpressionStatement(paramLine, assignment.getToken(), assignment.getFinish(), assignment));
+ }
+ }
+ return ident;
+ }
+ throw error(AbstractParser.message("invalid.arrow.parameter"), param.getToken());
+ }
+
+ private boolean checkNoLineTerminator() {
+ assert type == ARROW;
+ if (last == RPAREN) {
+ return true;
+ } else if (last == IDENT) {
+ return true;
+ }
+ for (int i = k - 1; i >= 0; i--) {
+ final TokenType t = T(i);
+ switch (t) {
+ case RPAREN:
+ case IDENT:
+ return true;
+ case EOL:
+ return false;
+ case COMMENT:
+ continue;
+ default:
+ if (t.getKind() == TokenKind.FUTURESTRICT) {
+ return true;
+ }
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Peek ahead to see if what follows after the ellipsis is a rest parameter
+ * at the end of an arrow function parameter list.
+ */
+ private boolean isRestParameterEndOfArrowFunctionParameterList() {
+ assert type == ELLIPSIS;
+ // find IDENT, RPAREN, ARROW, in that order, skipping over EOL (where allowed) and COMMENT
+ int i = 1;
+ for (;;) {
+ final TokenType t = T(k + i++);
+ if (t == IDENT) {
+ break;
+ } else if (t == EOL || t == COMMENT) {
+ continue;
+ } else {
+ return false;
+ }
+ }
+ for (;;) {
+ final TokenType t = T(k + i++);
+ if (t == RPAREN) {
+ break;
+ } else if (t == EOL || t == COMMENT) {
+ continue;
+ } else {
+ return false;
+ }
+ }
+ for (;;) {
+ final TokenType t = T(k + i++);
+ if (t == ARROW) {
+ break;
+ } else if (t == COMMENT) {
+ continue;
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Parse an end of line.
+ */
+ private void endOfLine() {
+ switch (type) {
+ case SEMICOLON:
+ case EOL:
+ next();
+ break;
+ case RPAREN:
+ case RBRACKET:
+ case RBRACE:
+ case EOF:
+ break;
+ default:
+ if (last != EOL) {
+ expect(SEMICOLON);
+ }
+ break;
+ }
+ }
+
+ /**
+ * Parse untagged template literal as string concatenation.
+ */
+ private Expression templateLiteral() {
+ assert type == TEMPLATE || type == TEMPLATE_HEAD;
+ final boolean noSubstitutionTemplate = type == TEMPLATE;
+ long lastLiteralToken = token;
+ LiteralNode<?> literal = getLiteral();
+ if (noSubstitutionTemplate) {
+ return literal;
+ }
+
+ if (env._parse_only) {
+ final List<Expression> exprs = new ArrayList<>();
+ exprs.add(literal);
+ TokenType lastLiteralType;
+ do {
+ final Expression expression = expression();
+ if (type != TEMPLATE_MIDDLE && type != TEMPLATE_TAIL) {
+ throw error(AbstractParser.message("unterminated.template.expression"), token);
+ }
+ exprs.add(expression);
+ lastLiteralType = type;
+ literal = getLiteral();
+ exprs.add(literal);
+ } while (lastLiteralType == TEMPLATE_MIDDLE);
+ return new TemplateLiteral(exprs);
+ } else {
+ Expression concat = literal;
+ TokenType lastLiteralType;
+ do {
+ final Expression expression = expression();
+ if (type != TEMPLATE_MIDDLE && type != TEMPLATE_TAIL) {
+ throw error(AbstractParser.message("unterminated.template.expression"), token);
+ }
+ concat = new BinaryNode(Token.recast(lastLiteralToken, TokenType.ADD), concat, expression);
+ lastLiteralType = type;
+ lastLiteralToken = token;
+ literal = getLiteral();
+ concat = new BinaryNode(Token.recast(lastLiteralToken, TokenType.ADD), concat, literal);
+ } while (lastLiteralType == TEMPLATE_MIDDLE);
+ return concat;
+ }
+ }
+
+ /**
+ * Parse tagged template literal as argument list.
+ * @return argument list for a tag function call (template object, ...substitutions)
+ */
+ private List<Expression> templateLiteralArgumentList() {
+ assert type == TEMPLATE || type == TEMPLATE_HEAD;
+ final ArrayList<Expression> argumentList = new ArrayList<>();
+ final ArrayList<Expression> rawStrings = new ArrayList<>();
+ final ArrayList<Expression> cookedStrings = new ArrayList<>();
+ argumentList.add(null); // filled at the end
+
+ final long templateToken = token;
+ final boolean hasSubstitutions = type == TEMPLATE_HEAD;
+ addTemplateLiteralString(rawStrings, cookedStrings);
+
+ if (hasSubstitutions) {
+ TokenType lastLiteralType;
+ do {
+ final Expression expression = expression();
+ if (type != TEMPLATE_MIDDLE && type != TEMPLATE_TAIL) {
+ throw error(AbstractParser.message("unterminated.template.expression"), token);
+ }
+ argumentList.add(expression);
+
+ lastLiteralType = type;
+ addTemplateLiteralString(rawStrings, cookedStrings);
+ } while (lastLiteralType == TEMPLATE_MIDDLE);
+ }
+
+ final LiteralNode<Expression[]> rawStringArray = LiteralNode.newInstance(templateToken, finish, rawStrings);
+ final LiteralNode<Expression[]> cookedStringArray = LiteralNode.newInstance(templateToken, finish, cookedStrings);
+ final RuntimeNode templateObject = new RuntimeNode(templateToken, finish, RuntimeNode.Request.GET_TEMPLATE_OBJECT, rawStringArray, cookedStringArray);
+ argumentList.set(0, templateObject);
+ return optimizeList(argumentList);
+ }
+
+ private void addTemplateLiteralString(final ArrayList<Expression> rawStrings, final ArrayList<Expression> cookedStrings) {
+ final long stringToken = token;
+ final String rawString = lexer.valueOfRawString(stringToken);
+ final String cookedString = (String) getValue();
+ next();
+ rawStrings.add(LiteralNode.newInstance(stringToken, finish, rawString));
+ cookedStrings.add(LiteralNode.newInstance(stringToken, finish, cookedString));
+ }
+
+
+ /**
+ * Parse a module.
+ *
+ * Module :
+ * ModuleBody?
+ *
+ * ModuleBody :
+ * ModuleItemList
+ */
+ private FunctionNode module(final String moduleName) {
+ final boolean oldStrictMode = isStrictMode;
+ try {
+ isStrictMode = true; // Module code is always strict mode code. (ES6 10.2.1)
+
+ // Make a pseudo-token for the script holding its start and length.
+ final int functionStart = Math.min(Token.descPosition(Token.withDelimiter(token)), finish);
+ final long functionToken = Token.toDesc(FUNCTION, functionStart, source.getLength() - functionStart);
+ final int functionLine = line;
+
+ final IdentNode ident = new IdentNode(functionToken, Token.descPosition(functionToken), moduleName);
+ final ParserContextFunctionNode script = createParserContextFunctionNode(
+ ident,
+ functionToken,
+ FunctionNode.Kind.MODULE,
+ functionLine,
+ Collections.<IdentNode>emptyList());
+ lc.push(script);
+
+ final ParserContextModuleNode module = new ParserContextModuleNode(moduleName);
+ lc.push(module);
+
+ final ParserContextBlockNode body = newBlock();
+
+ functionDeclarations = new ArrayList<>();
+ moduleBody();
+ addFunctionDeclarations(script);
+ functionDeclarations = null;
+
+ restoreBlock(body);
+ body.setFlag(Block.NEEDS_SCOPE);
+ final Block programBody = new Block(functionToken, finish, body.getFlags() | Block.IS_SYNTHETIC | Block.IS_BODY, body.getStatements());
+ lc.pop(module);
+ lc.pop(script);
+ script.setLastToken(token);
+
+ expect(EOF);
+
+ script.setModule(module.createModule());
+ return createFunctionNode(script, functionToken, ident, Collections.<IdentNode>emptyList(), FunctionNode.Kind.MODULE, functionLine, programBody);
+ } finally {
+ isStrictMode = oldStrictMode;
+ }
+ }
+
+ /**
+ * Parse module body.
+ *
+ * ModuleBody :
+ * ModuleItemList
+ *
+ * ModuleItemList :
+ * ModuleItem
+ * ModuleItemList ModuleItem
+ *
+ * ModuleItem :
+ * ImportDeclaration
+ * ExportDeclaration
+ * StatementListItem
+ */
+ private void moduleBody() {
+ loop:
+ while (type != EOF) {
+ switch (type) {
+ case EOF:
+ break loop;
+ case IMPORT:
+ importDeclaration();
+ break;
+ case EXPORT:
+ exportDeclaration();
+ break;
+ default:
+ // StatementListItem
+ statement(true, 0, false, false);
+ break;
+ }
+ }
+ }
+
+
+ /**
+ * Parse import declaration.
+ *
+ * ImportDeclaration :
+ * import ImportClause FromClause ;
+ * import ModuleSpecifier ;
+ * ImportClause :
+ * ImportedDefaultBinding
+ * NameSpaceImport
+ * NamedImports
+ * ImportedDefaultBinding , NameSpaceImport
+ * ImportedDefaultBinding , NamedImports
+ * ImportedDefaultBinding :
+ * ImportedBinding
+ * ModuleSpecifier :
+ * StringLiteral
+ * ImportedBinding :
+ * BindingIdentifier
+ */
+ private void importDeclaration() {
+ final int startPosition = start;
+ expect(IMPORT);
+ final ParserContextModuleNode module = lc.getCurrentModule();
+ if (type == STRING || type == ESCSTRING) {
+ // import ModuleSpecifier ;
+ final IdentNode moduleSpecifier = createIdentNode(token, finish, (String) getValue());
+ next();
+ module.addModuleRequest(moduleSpecifier);
+ } else {
+ // import ImportClause FromClause ;
+ List<Module.ImportEntry> importEntries;
+ if (type == MUL) {
+ importEntries = Collections.singletonList(nameSpaceImport(startPosition));
+ } else if (type == LBRACE) {
+ importEntries = namedImports(startPosition);
+ } else if (isBindingIdentifier()) {
+ // ImportedDefaultBinding
+ final IdentNode importedDefaultBinding = bindingIdentifier("ImportedBinding");
+ final Module.ImportEntry defaultImport = Module.ImportEntry.importSpecifier(importedDefaultBinding, startPosition, finish);
+
+ if (type == COMMARIGHT) {
+ next();
+ importEntries = new ArrayList<>();
+ if (type == MUL) {
+ importEntries.add(nameSpaceImport(startPosition));
+ } else if (type == LBRACE) {
+ importEntries.addAll(namedImports(startPosition));
+ } else {
+ throw error(AbstractParser.message("expected.named.import"));
+ }
+ } else {
+ importEntries = Collections.singletonList(defaultImport);
+ }
+ } else {
+ throw error(AbstractParser.message("expected.import"));
+ }
+
+ final IdentNode moduleSpecifier = fromClause();
+ module.addModuleRequest(moduleSpecifier);
+ for (int i = 0; i < importEntries.size(); i++) {
+ module.addImportEntry(importEntries.get(i).withFrom(moduleSpecifier, finish));
+ }
+ }
+ expect(SEMICOLON);
+ }
+
+ /**
+ * NameSpaceImport :
+ * * as ImportedBinding
+ *
+ * @param startPosition the start of the import declaration
+ * @return imported binding identifier
+ */
+ private Module.ImportEntry nameSpaceImport(final int startPosition) {
+ assert type == MUL;
+ final IdentNode starName = createIdentNode(Token.recast(token, IDENT), finish, Module.STAR_NAME);
+ next();
+ final long asToken = token;
+ final String as = (String) expectValue(IDENT);
+ if (!"as".equals(as)) {
+ throw error(AbstractParser.message("expected.as"), asToken);
+ }
+ final IdentNode localNameSpace = bindingIdentifier("ImportedBinding");
+ return Module.ImportEntry.importSpecifier(starName, localNameSpace, startPosition, finish);
+ }
+
+ /**
+ * NamedImports :
+ * { }
+ * { ImportsList }
+ * { ImportsList , }
+ * ImportsList :
+ * ImportSpecifier
+ * ImportsList , ImportSpecifier
+ * ImportSpecifier :
+ * ImportedBinding
+ * IdentifierName as ImportedBinding
+ * ImportedBinding :
+ * BindingIdentifier
+ */
+ private List<Module.ImportEntry> namedImports(final int startPosition) {
+ assert type == LBRACE;
+ next();
+ final List<Module.ImportEntry> importEntries = new ArrayList<>();
+ while (type != RBRACE) {
+ final boolean bindingIdentifier = isBindingIdentifier();
+ final long nameToken = token;
+ final IdentNode importName = getIdentifierName();
+ if (type == IDENT && "as".equals(getValue())) {
+ next();
+ final IdentNode localName = bindingIdentifier("ImportedBinding");
+ importEntries.add(Module.ImportEntry.importSpecifier(importName, localName, startPosition, finish));
+ } else if (!bindingIdentifier) {
+ throw error(AbstractParser.message("expected.binding.identifier"), nameToken);
+ } else {
+ importEntries.add(Module.ImportEntry.importSpecifier(importName, startPosition, finish));
+ }
+ if (type == COMMARIGHT) {
+ next();
+ } else {
+ break;
+ }
+ }
+ expect(RBRACE);
+ return importEntries;
+ }
+
+ /**
+ * FromClause :
+ * from ModuleSpecifier
+ */
+ private IdentNode fromClause() {
+ final long fromToken = token;
+ final String name = (String) expectValue(IDENT);
+ if (!"from".equals(name)) {
+ throw error(AbstractParser.message("expected.from"), fromToken);
+ }
+ if (type == STRING || type == ESCSTRING) {
+ final IdentNode moduleSpecifier = createIdentNode(Token.recast(token, IDENT), finish, (String) getValue());
+ next();
+ return moduleSpecifier;
+ } else {
+ throw error(expectMessage(STRING));
+ }
+ }
+
+ /**
+ * Parse export declaration.
+ *
+ * ExportDeclaration :
+ * export * FromClause ;
+ * export ExportClause FromClause ;
+ * export ExportClause ;
+ * export VariableStatement
+ * export Declaration
+ * export default HoistableDeclaration[Default]
+ * export default ClassDeclaration[Default]
+ * export default [lookahead !in {function, class}] AssignmentExpression[In] ;
+ */
+ private void exportDeclaration() {
+ expect(EXPORT);
+ final int startPosition = start;
+ final ParserContextModuleNode module = lc.getCurrentModule();
+ switch (type) {
+ case MUL: {
+ final IdentNode starName = createIdentNode(Token.recast(token, IDENT), finish, Module.STAR_NAME);
+ next();
+ final IdentNode moduleRequest = fromClause();
+ expect(SEMICOLON);
+ module.addModuleRequest(moduleRequest);
+ module.addStarExportEntry(Module.ExportEntry.exportStarFrom(starName, moduleRequest, startPosition, finish));
+ break;
+ }
+ case LBRACE: {
+ final List<Module.ExportEntry> exportEntries = exportClause(startPosition);
+ if (type == IDENT && "from".equals(getValue())) {
+ final IdentNode moduleRequest = fromClause();
+ module.addModuleRequest(moduleRequest);
+ for (final Module.ExportEntry exportEntry : exportEntries) {
+ module.addIndirectExportEntry(exportEntry.withFrom(moduleRequest, finish));
+ }
+ } else {
+ for (final Module.ExportEntry exportEntry : exportEntries) {
+ module.addLocalExportEntry(exportEntry);
+ }
+ }
+ expect(SEMICOLON);
+ break;
+ }
+ case DEFAULT:
+ final IdentNode defaultName = createIdentNode(Token.recast(token, IDENT), finish, Module.DEFAULT_NAME);
+ next();
+ final Expression assignmentExpression;
+ IdentNode ident;
+ final int lineNumber = line;
+ final long rhsToken = token;
+ final boolean declaration;
+ switch (type) {
+ case FUNCTION:
+ assignmentExpression = functionExpression(false, true);
+ ident = ((FunctionNode) assignmentExpression).getIdent();
+ declaration = true;
+ break;
+ case CLASS:
+ assignmentExpression = classDeclaration(true);
+ ident = ((ClassNode) assignmentExpression).getIdent();
+ declaration = true;
+ break;
+ default:
+ assignmentExpression = assignmentExpression(false);
+ ident = null;
+ declaration = false;
+ break;
+ }
+ if (ident != null) {
+ module.addLocalExportEntry(Module.ExportEntry.exportDefault(defaultName, ident, startPosition, finish));
+ } else {
+ ident = createIdentNode(Token.recast(rhsToken, IDENT), finish, Module.DEFAULT_EXPORT_BINDING_NAME);
+ lc.appendStatementToCurrentNode(new VarNode(lineNumber, Token.recast(rhsToken, LET), finish, ident, assignmentExpression));
+ if (!declaration) {
+ expect(SEMICOLON);
+ }
+ module.addLocalExportEntry(Module.ExportEntry.exportDefault(defaultName, ident, startPosition, finish));
+ }
+ break;
+ case VAR:
+ case LET:
+ case CONST:
+ final List<Statement> statements = lc.getCurrentBlock().getStatements();
+ final int previousEnd = statements.size();
+ variableStatement(type);
+ for (final Statement statement : statements.subList(previousEnd, statements.size())) {
+ if (statement instanceof VarNode) {
+ module.addLocalExportEntry(Module.ExportEntry.exportSpecifier(((VarNode) statement).getName(), startPosition, finish));
+ }
+ }
+ break;
+ case CLASS: {
+ final ClassNode classDeclaration = classDeclaration(false);
+ module.addLocalExportEntry(Module.ExportEntry.exportSpecifier(classDeclaration.getIdent(), startPosition, finish));
+ break;
+ }
+ case FUNCTION: {
+ final FunctionNode functionDeclaration = (FunctionNode) functionExpression(true, true);
+ module.addLocalExportEntry(Module.ExportEntry.exportSpecifier(functionDeclaration.getIdent(), startPosition, finish));
+ break;
+ }
+ default:
+ throw error(AbstractParser.message("invalid.export"), token);
+ }
+ }
+
+ /**
+ * ExportClause :
+ * { }
+ * { ExportsList }
+ * { ExportsList , }
+ * ExportsList :
+ * ExportSpecifier
+ * ExportsList , ExportSpecifier
+ * ExportSpecifier :
+ * IdentifierName
+ * IdentifierName as IdentifierName
+ *
+ * @return a list of ExportSpecifiers
+ */
+ private List<Module.ExportEntry> exportClause(final int startPosition) {
+ assert type == LBRACE;
+ next();
+ final List<Module.ExportEntry> exports = new ArrayList<>();
+ while (type != RBRACE) {
+ final IdentNode localName = getIdentifierName();
+ if (type == IDENT && "as".equals(getValue())) {
+ next();
+ final IdentNode exportName = getIdentifierName();
+ exports.add(Module.ExportEntry.exportSpecifier(exportName, localName, startPosition, finish));
+ } else {
+ exports.add(Module.ExportEntry.exportSpecifier(localName, startPosition, finish));
+ }
+ if (type == COMMARIGHT) {
+ next();
+ } else {
+ break;
+ }
+ }
+ expect(RBRACE);
+ return exports;
+ }
+
+ @Override
+ public String toString() {
+ return "'JavaScript Parsing'";
+ }
+
+ private static void markEval(final ParserContext lc) {
+ final Iterator<ParserContextFunctionNode> iter = lc.getFunctions();
+ boolean flaggedCurrentFn = false;
+ while (iter.hasNext()) {
+ final ParserContextFunctionNode fn = iter.next();
+ if (!flaggedCurrentFn) {
+ fn.setFlag(FunctionNode.HAS_EVAL);
+ flaggedCurrentFn = true;
+ if (fn.getKind() == FunctionNode.Kind.ARROW) {
+ // possible use of this in an eval that's nested in an arrow function, e.g.:
+ // function fun(){ return (() => eval("this"))(); };
+ markThis(lc);
+ markNewTarget(lc);
+ }
+ } else {
+ fn.setFlag(FunctionNode.HAS_NESTED_EVAL);
+ }
+ final ParserContextBlockNode body = lc.getFunctionBody(fn);
+ // NOTE: it is crucial to mark the body of the outer function as needing scope even when we skip
+ // parsing a nested function. functionBody() contains code to compensate for the lack of invoking
+ // this method when the parser skips a nested function.
+ body.setFlag(Block.NEEDS_SCOPE);
+ fn.setFlag(FunctionNode.HAS_SCOPE_BLOCK);
+ }
+ }
+
+ private void prependStatement(final Statement statement) {
+ lc.prependStatementToCurrentNode(statement);
+ }
+
+ private void appendStatement(final Statement statement) {
+ lc.appendStatementToCurrentNode(statement);
+ }
+
+ private static void markSuperCall(final ParserContext lc) {
+ final Iterator<ParserContextFunctionNode> iter = lc.getFunctions();
+ while (iter.hasNext()) {
+ final ParserContextFunctionNode fn = iter.next();
+ if (fn.getKind() != FunctionNode.Kind.ARROW) {
+ assert fn.isSubclassConstructor();
+ fn.setFlag(FunctionNode.ES6_HAS_DIRECT_SUPER);
+ break;
+ }
+ }
+ }
+
+ private ParserContextFunctionNode getCurrentNonArrowFunction() {
+ final Iterator<ParserContextFunctionNode> iter = lc.getFunctions();
+ while (iter.hasNext()) {
+ final ParserContextFunctionNode fn = iter.next();
+ if (fn.getKind() != FunctionNode.Kind.ARROW) {
+ return fn;
+ }
+ }
+ return null;
+ }
+
+ private static void markThis(final ParserContext lc) {
+ final Iterator<ParserContextFunctionNode> iter = lc.getFunctions();
+ while (iter.hasNext()) {
+ final ParserContextFunctionNode fn = iter.next();
+ fn.setFlag(FunctionNode.USES_THIS);
+ if (fn.getKind() != FunctionNode.Kind.ARROW) {
+ break;
+ }
+ }
+ }
+
+ private static void markNewTarget(final ParserContext lc) {
+ final Iterator<ParserContextFunctionNode> iter = lc.getFunctions();
+ while (iter.hasNext()) {
+ final ParserContextFunctionNode fn = iter.next();
+ if (fn.getKind() != FunctionNode.Kind.ARROW) {
+ if (!fn.isProgram()) {
+ fn.setFlag(FunctionNode.ES6_USES_NEW_TARGET);
+ }
+ break;
+ }
+ }
+ }
+
+ private boolean inGeneratorFunction() {
+ return lc.getCurrentFunction().getKind() == FunctionNode.Kind.GENERATOR;
+ }
+}