| /* |
| * 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; |
| } |
| } |