Mercurial > hg > openjdk > jigsaw > nashorn
view src/jdk/nashorn/internal/parser/Parser.java @ 143:4be452026847
8010652: Eliminate non-child references in Block/FunctionNode, and make few node types immutable
Reviewed-by: jlaskey, lagergren
author | attila |
---|---|
date | Sat, 23 Mar 2013 00:58:39 +0100 |
parents | 390d44ba90cf |
children |
line wrap: on
line source
/* * Copyright (c) 2010, 2013, 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.ARGUMENTS; import static jdk.nashorn.internal.codegen.CompilerConstants.EVAL; import static jdk.nashorn.internal.codegen.CompilerConstants.FUNCTION_PREFIX; import static jdk.nashorn.internal.codegen.CompilerConstants.RUN_SCRIPT; 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.COLON; import static jdk.nashorn.internal.parser.TokenType.COMMARIGHT; import static jdk.nashorn.internal.parser.TokenType.DECPOSTFIX; import static jdk.nashorn.internal.parser.TokenType.DECPREFIX; 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.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.INCPOSTFIX; import static jdk.nashorn.internal.parser.TokenType.LBRACE; import static jdk.nashorn.internal.parser.TokenType.LPAREN; 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.TERNARY; import static jdk.nashorn.internal.parser.TokenType.WHILE; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Stack; import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.codegen.Namespace; import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.BreakNode; import jdk.nashorn.internal.ir.BreakableNode; import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.ContinueNode; import jdk.nashorn.internal.ir.DoWhileNode; import jdk.nashorn.internal.ir.EmptyNode; import jdk.nashorn.internal.ir.ExecuteNode; import jdk.nashorn.internal.ir.ForNode; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.FunctionNode.CompilationState; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.IfNode; import jdk.nashorn.internal.ir.IndexNode; import jdk.nashorn.internal.ir.LabelNode; import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.LineNumberNode; import jdk.nashorn.internal.ir.LiteralNode; 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.SwitchNode; 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.runtime.DebugLogger; import jdk.nashorn.internal.runtime.ErrorManager; import jdk.nashorn.internal.runtime.JSErrorType; import jdk.nashorn.internal.runtime.ParserException; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptingFunctions; import jdk.nashorn.internal.runtime.Source; import jdk.nashorn.internal.runtime.Timing; /** * Builds the IR. */ public class Parser extends AbstractParser { /** Current script environment. */ private final ScriptEnvironment env; /** Is scripting mode. */ private final boolean scripting; private final LexicalContext lexicalContext = new LexicalContext(); private List<Node> functionDeclarations; /** Namespace for function names where not explicitly given */ private final Namespace namespace; private static final DebugLogger LOG = new DebugLogger("parser"); /** * 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); } /** * Construct a parser. * * @param env script environment * @param source source to parse * @param errors error manager * @param strict parser created with strict mode enabled. */ public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict) { super(source, errors, strict); this.env = env; this.namespace = new Namespace(env.getNamespace()); this.scripting = env._scripting; } /** * 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 * "runScript" {@link CompilerConstants#RUN_SCRIPT} * * @return function node resulting from successful parse */ public FunctionNode parse() { return parse(RUN_SCRIPT.tag()); } /** * Execute parse and return the resulting function node. * Errors will be thrown and the error manager will contain information * if parsing should fail * * @param scriptName name for the script, given to the parsed FunctionNode * * @return function node resulting from successful parse */ public FunctionNode parse(final String scriptName) { final long t0 = Timing.isEnabled() ? System.currentTimeMillis() : 0L; LOG.info(this + " begin for '" + scriptName + "'"); try { stream = new TokenStream(); lexer = new Lexer(source, stream, scripting && !env._no_syntax_extensions); // Set up first token (skips opening EOL.) k = -1; next(); // Begin parse. return program(scriptName); } catch (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()); } return null; } finally { final String end = this + " end '" + scriptName + "'"; if (Timing.isEnabled()) { Timing.accumulateTime(toString(), System.currentTimeMillis() - t0); LOG.info(end + "' in " + (System.currentTimeMillis() - t0) + " ms"); } else { LOG.info(end); } } } /** * 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 Block newBlock() { final Block block = new Block(source, token, Token.descPosition(token)); lexicalContext.push(block); return block; } /** * Set up a new function block. * * @param ident Name of function. * @return New block. */ private FunctionNode newFunctionBlock(final IdentNode ident) { // Build function name. final StringBuilder sb = new StringBuilder(); final FunctionNode parentFunction = getFunction(); if(parentFunction != null && !parentFunction.isProgram()) { sb.append(parentFunction.getName()).append('$'); } sb.append(ident != null ? ident.getName() : FUNCTION_PREFIX.tag()); final String name = namespace.uniqueName(sb.toString()); assert parentFunction != null || name.equals(RUN_SCRIPT.tag()) : "name = " + name;// must not rename runScript(). // Start new block. final FunctionNode functionBlock = new FunctionNode(source, token, Token.descPosition(token), namespace, ident, name); if(parentFunction == null) { functionBlock.setProgram(); } functionBlock.setStrictMode(isStrictMode); functionBlock.setState(errors.hasErrors() ? CompilationState.PARSE_ERROR : CompilationState.PARSED); lexicalContext.push(functionBlock); return functionBlock; } /** * Restore the current block. */ private void restoreBlock(Block block) { lexicalContext.pop(block); } /** * Get the statements in a block. * @return Block statements. */ private Block getBlock(final boolean needsBraces) { // Set up new block. Captures LBRACE. final Block newBlock = newBlock(); try { pushControlNode(newBlock); // Block opening brace. if (needsBraces) { expect(LBRACE); } try { // Accumulate block statements. statementList(); } finally { popControlNode(); } } finally { restoreBlock(newBlock); } final int possibleEnd = Token.descPosition(token) + Token.descLength(token); // Block closing brace. if (needsBraces) { expect(RBRACE); } newBlock.setFinish(possibleEnd); return newBlock; } /** * Get all the statements generated by a single statement. * @return Statements. */ private Block getStatement() { if (type == LBRACE) { return getBlock(true); } // Set up new block. Captures first token. final Block newBlock = newBlock(); try { // Accumulate statements. statement(); } finally { restoreBlock(newBlock); } return newBlock; } /** * Detect calls to special functions. * @param ident Called function. */ private void detectSpecialFunction(final IdentNode ident) { final String name = ident.getName(); if (EVAL.tag().equals(name)) { final Iterator<FunctionNode> it = lexicalContext.getFunctions(); if(it.hasNext()) { it.next().setHasEval(it); } } } /** * Detect use of special properties. * @param ident Referenced property. */ private void detectSpecialProperty(final IdentNode ident) { final String name = ident.getName(); if (ARGUMENTS.tag().equals(name)) { getFunction().setUsesArguments(); } } /** * 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 Token.descType(ident.getToken()).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 Node verifyAssignment(final long op, final Node lhs, final Node 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 AccessNode || lhs instanceof IndexNode || lhs instanceof IdentNode)) { if (env._early_lvalue_error) { error(JSErrorType.REFERENCE_ERROR, AbstractParser.message("invalid.lvalue"), lhs.getToken()); } return referenceError(lhs, rhs); } if (lhs instanceof IdentNode) { if (!checkIdentLValue((IdentNode)lhs)) { return referenceError(lhs, rhs); } verifyStrictIdent((IdentNode)lhs, "assignment"); } break; default: break; } // Build up node. return new BinaryNode(source, op, lhs, rhs); } /** * 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 Node incDecExpression(final long firstToken, final TokenType tokenType, final Node expression, final boolean isPostfix) { long incDecToken = firstToken; if (isPostfix) { incDecToken = Token.recast(incDecToken, tokenType == DECPREFIX ? DECPOSTFIX : INCPOSTFIX); } final UnaryNode node = new UnaryNode(source, incDecToken, expression); if (isPostfix) { node.setStart(expression.getStart()); node.setFinish(Token.descPosition(incDecToken) + Token.descLength(incDecToken)); } return node; } /** * Find a label node in the label stack. * @param ident Ident to find. * @return null or the found label node. */ private LabelNode findLabel(final IdentNode ident) { for (final LabelNode labelNode : getFunction().getLabelStack()) { if (labelNode.getLabel().equals(ident)) { return labelNode; } } return null; } /** * Add a label to the label stack. * @param labelNode Label to add. */ private void pushLabel(final LabelNode labelNode) { getFunction().getLabelStack().push(labelNode); } /** * Remove a label from the label stack. */ private void popLabel() { getFunction().getLabelStack().pop(); } /** * Track the current nesting of controls for break and continue. * @param node For, while, do or switch node. */ private void pushControlNode(final Node node) { final boolean isLoop = node instanceof WhileNode; final boolean isBreakable = node instanceof BreakableNode || node instanceof Block; final FunctionNode function = getFunction(); function.getControlStack().push(node); for (final LabelNode labelNode : function.getLabelStack()) { if (isBreakable && labelNode.getBreakNode() == null) { labelNode.setBreakNode(node); } if (isLoop && labelNode.getContinueNode() == null) { labelNode.setContinueNode(node); } } } /** * Finish with control. */ private void popControlNode() { // Get control stack. final Stack<Node> controlStack = getFunction().getControlStack(); // Can be empty if missing brace. if (!controlStack.isEmpty()) { controlStack.pop(); } } private void popControlNode(final Node node) { // Get control stack. final Stack<Node> controlStack = getFunction().getControlStack(); // Can be empty if missing brace. if (!controlStack.isEmpty() && controlStack.peek() == node) { controlStack.pop(); } } private boolean isInWithBlock() { final Stack<Node> controlStack = getFunction().getControlStack(); for (int i = controlStack.size() - 1; i >= 0; i--) { final Node node = controlStack.get(i); if (node instanceof WithNode) { return true; } } return false; } private <T extends Node> T findControl(final Class<T> ctype) { final Stack<Node> controlStack = getFunction().getControlStack(); for (int i = controlStack.size() - 1; i >= 0; i--) { final Node node = controlStack.get(i); if (ctype.isAssignableFrom(node.getClass())) { return ctype.cast(node); } } return null; } private <T extends Node> List<T> findControls(final Class<T> ctype, final Node to) { final List<T> nodes = new ArrayList<>(); final Stack<Node> controlStack = getFunction().getControlStack(); for (int i = controlStack.size() - 1; i >= 0; i--) { final Node node = controlStack.get(i); if (to == node) { break; //stop looking } if (ctype.isAssignableFrom(node.getClass())) { nodes.add(ctype.cast(node)); } } return nodes; } private <T extends Node> int countControls(final Class<T> ctype, final Node to) { return findControls(ctype, to).size(); } /** * ----------------------------------------------------------------------- * * 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) { // Make a fake token for the script. final long functionToken = Token.toDesc(FUNCTION, 0, source.getLength()); // Set up the script to append elements. final FunctionNode script = newFunctionBlock(new IdentNode(source, functionToken, Token.descPosition(functionToken), scriptName)); script.setKind(FunctionNode.Kind.SCRIPT); script.setFirstToken(functionToken); functionDeclarations = new ArrayList<>(); sourceElements(); script.prependStatements(functionDeclarations); functionDeclarations = null; expect(EOF); script.setLastToken(token); script.setFinish(source.getLength() - 1); return script; } /** * 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 ExecuteNode) { final Node expr = ((ExecuteNode)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; } /** * Return last node in a statement list. * * @param statements Statement list. * * @return Last (non-debug) statement or null if empty block. */ private static Node lastStatement(final List<Node> statements) { for (int lastIndex = statements.size() - 1; lastIndex >= 0; lastIndex--) { final Node node = statements.get(lastIndex); if (!node.isDebug()) { return node; } } return null; } /** * SourceElements : * SourceElement * SourceElements SourceElement * * See 14 * * Parse the elements of the script or function. */ private void sourceElements() { List<Node> directiveStmts = null; boolean checkDirective = true; 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); // check for directive prologues if (checkDirective) { // skip any debug statement like line number to get actual first line final Node lastStatement = lastStatement(getBlock().getStatements()); // 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 FunctionNode function = getFunction(); function.setStrictMode(true); // 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 preceeding // escaped string directives getValue(statement.getToken()); } // verify that function name as well as parameter names // satisfy strict mode restrictions. verifyStrictIdent(function.getIdent(), "function name"); for (final IdentNode param : function.getParameters()) { verifyStrictIdent(param, "function parameter"); } } } } } } catch (final Exception e) { // Recover parsing. recover(e); } // No backtracking from here on. stream.commit(k); } } finally { isStrictMode = oldStrictMode; } } /** * Statement : * Block * VariableStatement * EmptyStatement * ExpressionStatement * IfStatement * IterationStatement * ContinueStatement * BreakStatement * ReturnStatement * WithStatement * LabelledStatement * SwitchStatement * ThrowStatement * TryStatement * DebuggerStatement * * see 12 * * Parse any of the basic statement types. */ private void statement() { statement(false); } /** * @param topLevel does this statement occur at the "top level" of a script or a function? */ private void statement(final boolean topLevel) { final LineNumberNode lineNumberNode = lineNumber(); if (type == 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 (isStrictMode && !topLevel) { error(AbstractParser.message("strict.no.func.here"), token); } functionExpression(true, topLevel); return; } getBlock().addStatement(lineNumberNode); switch (type) { case LBRACE: block(); break; case RBRACE: break; case VAR: variableStatement(true); 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 YIELD: yieldStatement(); 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; default: if (type == IDENT || isNonStrictModeIdent()) { if (T(k + 1) == COLON) { labelStatement(); return; } } expressionStatement(); break; } } /** * block : * { StatementList? } * * see 12.1 * * Parse a statement block. */ private void block() { // Get statements in block. final Block newBlock = getBlock(true); // Force block execution. final ExecuteNode executeNode = new ExecuteNode(source, newBlock.getToken(), finish, newBlock); getBlock().addStatement(executeNode); } /** * 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 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 */ @SuppressWarnings("fallthrough") private void verifyStrictIdent(final IdentNode ident, final String contextString) { if (isStrictMode) { switch (ident.getName()) { case "eval": case "arguments": error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken()); default: break; } } } /** * 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 List<VarNode> variableStatement(final boolean isStatement) { // VAR tested in caller. next(); final List<VarNode> vars = new ArrayList<>(); while (true) { // Get starting token. final long varToken = token; // Get name of var. final IdentNode name = getIdent(); verifyStrictIdent(name, "variable name"); // Assume no init. Node init = null; // Look for initializer assignment. if (type == ASSIGN) { next(); // Get initializer expression. Suppress IN if not statement. init = assignmentExpression(!isStatement); } // Allocate var node. final VarNode var = new VarNode(source, varToken, finish, name, init); vars.add(var); // Add to current block. getBlock().addStatement(var); if (type != COMMARIGHT) { break; } next(); } // If is a statement then handle end of line. if (isStatement) { boolean semicolon = type == SEMICOLON; endOfLine(); if (semicolon) { getBlock().setFinish(finish); } } return vars; } /** * EmptyStatement : * ; * * See 12.3 * * Parse an empty statement. */ private void emptyStatement() { if (env._empty_statements) { getBlock().addStatement(new EmptyNode(source, 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 long expressionToken = token; // Get expression and add as statement. final Node expression = expression(); ExecuteNode executeNode = null; if (expression != null) { executeNode = new ExecuteNode(source, expressionToken, finish, expression); getBlock().addStatement(executeNode); } else { expect(null); } endOfLine(); if (executeNode != null) { executeNode.setFinish(finish); getBlock().setFinish(finish); } } /** * IfStatement : * if ( Expression ) Statement else Statement * if ( Expression ) Statement * * See 12.5 * * Parse an IF statement. */ private void ifStatement() { // Capture IF token. final long ifToken = token; // IF tested in caller. next(); expect(LPAREN); // Get the test expression. final Node test = expression(); expect(RPAREN); // Get the pass statement. final Block pass = getStatement(); // Assume no else. Block fail = null; if (type == ELSE) { next(); // Get the else block. fail = getStatement(); } // Construct and add new if node. final IfNode ifNode = new IfNode(source, ifToken, fail != null ? fail.getFinish() : pass.getFinish(), test, pass, fail); getBlock().addStatement(ifNode); } /** * ... 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. */ private void forStatement() { // Create FOR node, capturing FOR token. final ForNode forNode = new ForNode(source, token, Token.descPosition(token)); pushControlNode(forNode); // Set up new block for scope of vars. Captures first token. final Block outer = newBlock(); 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())) { forNode.setIsForEach(); next(); } expect(LPAREN); /// Capture control information. forControl(forNode); expect(RPAREN); // Set the for body. final Block body = getStatement(); forNode.setBody(body); forNode.setFinish(body.getFinish()); outer.setFinish(body.getFinish()); // Add for to current block. getBlock().addStatement(forNode); } finally { restoreBlock(outer); popControlNode(); } getBlock().addStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer)); } /** * ... IterationStatement : * ... * Expression[NoIn]?; Expression? ; Expression? * var VariableDeclarationList[NoIn]; Expression? ; Expression? * LeftHandSideExpression in Expression * var VariableDeclaration[NoIn] in Expression * * See 12.6 * * Parse the control section of a FOR statement. Also used for * comprehensions. * @param forNode Owning FOR. */ private void forControl(final ForNode forNode) { List<VarNode> vars = null; switch (type) { case VAR: // Var statements captured in for outer block. vars = variableStatement(false); break; case SEMICOLON: break; default: final Node expression = expression(unaryExpression(), COMMARIGHT.getPrecedence(), true); forNode.setInit(expression); } switch (type) { case SEMICOLON: // for (init; test; modify) expect(SEMICOLON); // Get the test expression. if (type != SEMICOLON) { forNode.setTest(expression()); } expect(SEMICOLON); // Get the modify expression. if (type != RPAREN) { final Node expression = expression(); forNode.setModify(expression); } break; case IN: forNode.setIsForIn(); if (vars != null) { // for (var i in obj) if (vars.size() == 1) { forNode.setInit(new IdentNode(vars.get(0).getName())); } else { // for (var i, j in obj) is invalid error(AbstractParser.message("many.vars.in.for.in.loop"), vars.get(1).getToken()); } } else { // for (expr in obj) final Node init = forNode.getInit(); assert init != null : "for..in init expression can not be null here"; // check if initial expression is a valid L-value if (!(init instanceof AccessNode || init instanceof IndexNode || init instanceof IdentNode)) { error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken()); } if (init instanceof IdentNode) { if (!checkIdentLValue((IdentNode)init)) { error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken()); } verifyStrictIdent((IdentNode)init, "for-in iterator"); } } next(); // Get the collection expression. forNode.setModify(expression()); break; default: expect(SEMICOLON); break; } } /** * ...IterationStatement : * ... * while ( Expression ) Statement * ... * * See 12.6 * * Parse while statement. */ private void whileStatement() { // Capture WHILE token. final long whileToken = token; // WHILE tested in caller. next(); // Construct WHILE node. final WhileNode whileNode = new WhileNode(source, whileToken, Token.descPosition(whileToken)); pushControlNode(whileNode); try { expect(LPAREN); // Get the test expression. final Node test = expression(); whileNode.setTest(test); expect(RPAREN); // Get WHILE body. final Block statements = getStatement(); whileNode.setBody(statements); whileNode.setFinish(statements.getFinish()); // Add WHILE node. getBlock().addStatement(whileNode); } finally { popControlNode(); } } /** * ...IterationStatement : * ... * do Statement while( Expression ) ; * ... * * See 12.6 * * Parse DO WHILE statement. */ private void doStatement() { // Capture DO token. final long doToken = token; // DO tested in the caller. next(); final WhileNode doWhileNode = new DoWhileNode(source, doToken, Token.descPosition(doToken)); pushControlNode(doWhileNode); try { // Get DO body. final Block statements = getStatement(); doWhileNode.setBody(statements); expect(WHILE); expect(LPAREN); // Get the test expression. final Node test = expression(); doWhileNode.setTest(test); expect(RPAREN); if (type == SEMICOLON) { endOfLine(); } doWhileNode.setFinish(finish); // Add DO node. getBlock().addStatement(doWhileNode); } finally { popControlNode(); } } /** * ContinueStatement : * continue Identifier? ; // [no LineTerminator here] * * See 12.7 * * Parse CONTINUE statement. */ private void continueStatement() { // Capture CONTINUE token. final long continueToken = token; // CONTINUE tested in caller. nextOrEOL(); LabelNode labelNode = null; // SEMICOLON or label. switch (type) { case RBRACE: case SEMICOLON: case EOL: break; default: final IdentNode ident = getIdent(); labelNode = findLabel(ident); if (labelNode == null) { error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken()); } break; } final Node targetNode = labelNode != null ? labelNode.getContinueNode() : findControl(WhileNode.class); if (targetNode == null) { error(AbstractParser.message("illegal.continue.stmt"), continueToken); } endOfLine(); // Construct and add CONTINUE node. final ContinueNode continueNode = new ContinueNode(source, continueToken, finish, labelNode, targetNode, findControl(TryNode.class)); continueNode.setScopeNestingLevel(countControls(WithNode.class, targetNode)); getBlock().addStatement(continueNode); } /** * BreakStatement : * break Identifier? ; // [no LineTerminator here] * * See 12.8 * */ private void breakStatement() { // Capture BREAK token. final long breakToken = token; // BREAK tested in caller. nextOrEOL(); LabelNode labelNode = null; // SEMICOLON or label. switch (type) { case RBRACE: case SEMICOLON: case EOL: break; default: final IdentNode ident = getIdent(); labelNode = findLabel(ident); if (labelNode == null) { error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken()); } break; } final Node targetNode = labelNode != null ? labelNode.getBreakNode() : findControl(BreakableNode.class); if (targetNode == null) { error(AbstractParser.message("illegal.break.stmt"), breakToken); } endOfLine(); // Construct and add BREAK node. final BreakNode breakNode = new BreakNode(source, breakToken, finish, labelNode, targetNode, findControl(TryNode.class)); breakNode.setScopeNestingLevel(countControls(WithNode.class, targetNode)); getBlock().addStatement(breakNode); } /** * ReturnStatement : * return Expression? ; // [no LineTerminator here] * * See 12.9 * * Parse RETURN statement. */ private void returnStatement() { // check for return outside function if (getFunction().getKind() == FunctionNode.Kind.SCRIPT) { error(AbstractParser.message("invalid.return")); } // Capture RETURN token. final long returnToken = token; // RETURN tested in caller. nextOrEOL(); Node expression = null; // SEMICOLON or expression. switch (type) { case RBRACE: case SEMICOLON: case EOL: break; default: expression = expression(); break; } endOfLine(); // Construct and add RETURN node. final ReturnNode returnNode = new ReturnNode(source, returnToken, finish, expression, findControl(TryNode.class)); getBlock().addStatement(returnNode); } /** * YieldStatement : * yield Expression? ; // [no LineTerminator here] * * JavaScript 1.8 * * Parse YIELD statement. */ private void yieldStatement() { // Capture YIELD token. final long yieldToken = token; // YIELD tested in caller. nextOrEOL(); Node expression = null; // SEMICOLON or expression. switch (type) { case RBRACE: case SEMICOLON: case EOL: break; default: expression = expression(); break; } endOfLine(); // Construct and add YIELD node. final ReturnNode yieldNode = new ReturnNode(source, yieldToken, finish, expression, findControl(TryNode.class)); getBlock().addStatement(yieldNode); } /** * WithStatement : * with ( Expression ) Statement * * See 12.10 * * Parse WITH statement. */ private void withStatement() { // Capture WITH token. final long withToken = token; // WITH tested in caller. next(); // ECMA 12.10.1 strict mode restrictions if (isStrictMode) { error(AbstractParser.message("strict.no.with"), withToken); } // Get WITH expression. final WithNode withNode = new WithNode(source, withToken, finish, null, null); final Iterator<FunctionNode> it = lexicalContext.getFunctions(); if(it.hasNext()) { it.next().setHasWith(it); } try { pushControlNode(withNode); expect(LPAREN); final Node expression = expression(); withNode.setExpression(expression); expect(RPAREN); // Get WITH body. final Block statements = getStatement(); withNode.setBody(statements); withNode.setFinish(finish); } finally { popControlNode(withNode); } getBlock().addStatement(withNode); } /** * 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() { // Capture SWITCH token. final long switchToken = token; // SWITCH tested in caller. next(); // Create and add switch statement. final SwitchNode switchNode = new SwitchNode(source, switchToken, Token.descPosition(switchToken)); pushControlNode(switchNode); try { expect(LPAREN); // Get switch expression. final Node switchExpression = expression(); switchNode.setExpression(switchExpression); expect(RPAREN); expect(LBRACE); // Prepare to accumulate cases. final List<CaseNode> cases = new ArrayList<>(); CaseNode defaultCase = null; while (type != RBRACE) { // Prepare for next case. Node caseExpression = null; final long caseToken = token; switch (type) { case CASE: next(); // Get case expression. caseExpression = expression(); break; case DEFAULT: if (defaultCase != null) { 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); final CaseNode caseNode = new CaseNode(source, caseToken, finish, caseExpression, statements); statements.setFinish(finish); if (caseExpression == null) { defaultCase = caseNode; } cases.add(caseNode); } switchNode.setCases(cases); switchNode.setDefaultCase(defaultCase); next(); switchNode.setFinish(finish); getBlock().addStatement(switchNode); } finally { popControlNode(); } } /** * 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 (findLabel(ident) != null) { error(AbstractParser.message("duplicate.label", ident.getName()), labelToken); } try { // Create and add label. final LabelNode labelNode = new LabelNode(source, labelToken, finish, ident, null); pushLabel(labelNode); // Get and save body. final Block statements = getStatement(); labelNode.setBody(statements); labelNode.setFinish(finish); getBlock().addStatement(labelNode); } finally { // Remove label. popLabel(); } } /** * ThrowStatement : * throw Expression ; // [no LineTerminator here] * * See 12.13 * * Parse throw statement. */ private void throwStatement() { // Capture THROW token. final long throwToken = token; // THROW tested in caller. nextOrEOL(); Node expression = null; // SEMICOLON or expression. switch (type) { case RBRACE: case SEMICOLON: case EOL: break; default: expression = expression(); break; } if (expression == null) { error(AbstractParser.message("expected.operand", type.getNameOrType())); } endOfLine(); // Construct and add THROW node. final ThrowNode throwNode = new ThrowNode(source, throwToken, finish, expression, findControl(TryNode.class)); getBlock().addStatement(throwNode); } /** * 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 long tryToken = token; // TRY tested in caller. next(); // Container block needed to act as target for labeled break statements final Block outer = newBlock(); pushControlNode(outer); // Create try. final TryNode tryNode = new TryNode(source, tryToken, Token.descPosition(tryToken), findControl(TryNode.class)); pushControlNode(tryNode); try { // Get TRY body. final Block tryBody = getBlock(true); // Prepare to accumulate catches. final List<Block> catchBlocks = new ArrayList<>(); while (type == CATCH) { // Capture CATCH token. final long catchToken = token; next(); expect(LPAREN); // Get exception ident. final IdentNode exception = getIdent(); // ECMA 12.4.1 strict mode restrictions verifyStrictIdent(exception, "catch argument"); // Check for conditional catch. Node ifExpression = null; if (type == IF) { next(); // Get the exception condition. ifExpression = expression(); } expect(RPAREN); final Block catchBlock = newBlock(); try { // Get CATCH body. final Block catchBody = getBlock(true); // Create and add catch. final CatchNode catchNode = new CatchNode(source, catchToken, finish, exception, ifExpression, catchBody); getBlock().addStatement(catchNode); catchBlocks.add(catchBlock); } finally { restoreBlock(catchBlock); } // If unconditional catch then should to be the end. if (ifExpression == null) { break; } } popControlNode(); // Prepare to capture finally statement. Block finallyStatements = null; if (type == FINALLY) { next(); // Get FINALLY body. finallyStatements = getBlock(true); } // Need at least one catch or a finally. if (catchBlocks.isEmpty() && finallyStatements == null) { error(AbstractParser.message("missing.catch.or.finally"), tryToken); } tryNode.setBody(tryBody); tryNode.setCatchBlocks(catchBlocks); tryNode.setFinallyBody(finallyStatements); tryNode.setFinish(finish); outer.setFinish(finish); // Add try. outer.addStatement(tryNode); } finally { popControlNode(tryNode); restoreBlock(outer); popControlNode(outer); } getBlock().addStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer)); } /** * DebuggerStatement : * debugger ; * * See 12.15 * * Parse debugger statement. */ private void debuggerStatement() { // Capture DEBUGGER token. final long debuggerToken = token; // DEBUGGER tested in caller. next(); endOfLine(); final RuntimeNode runtimeNode = new RuntimeNode(source, debuggerToken, finish, RuntimeNode.Request.DEBUGGER, new ArrayList<Node>()); getBlock().addStatement(runtimeNode); } /** * PrimaryExpression : * this * Identifier * Literal * ArrayLiteral * ObjectLiteral * ( Expression ) * * See 11.1 * * Parse primary expression. * @return Expression node. */ @SuppressWarnings("fallthrough") private Node primaryExpression() { // Capture first token. final long primaryToken = token; switch (type) { case THIS: final String name = type.getName(); next(); return new IdentNode(source, primaryToken, finish, name); case IDENT: final IdentNode ident = getIdent(); if (ident == null) { break; } detectSpecialProperty(ident); return ident; case OCTAL: if (isStrictMode) { error(AbstractParser.message("strict.no.octal"), token); } case STRING: case ESCSTRING: case DECIMAL: case HEXADECIMAL: case FLOATING: case REGEX: case XML: return getLiteral(); case EXECSTRING: return execString(primaryToken); case FALSE: next(); return LiteralNode.newInstance(source, primaryToken, finish, false); case TRUE: next(); return LiteralNode.newInstance(source, primaryToken, finish, true); case NULL: next(); return LiteralNode.newInstance(source, primaryToken, finish); case LBRACKET: return arrayLiteral(); case LBRACE: return objectLiteral(); case LPAREN: next(); final Node expression = expression(); expect(RPAREN); return expression; default: // In this context some operator tokens mark the start of a literal. if (lexer.scanLiteral(primaryToken, type)) { 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. */ Node execString(final long primaryToken) { // Synthesize an ident to call $EXEC. final IdentNode execIdent = new IdentNode(source, primaryToken, finish, ScriptingFunctions.EXEC_NAME); // Skip over EXECSTRING. next(); // Set up argument list for call. final List<Node> arguments = new ArrayList<>(); // Skip beginning of edit string expression. expect(LBRACE); // Add the following expression to arguments. arguments.add(expression()); // Skip ending of edit string expression. expect(RBRACE); return new CallNode(source, primaryToken, finish, execIdent, arguments); } /** * 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. */ private Node arrayLiteral() { // Capture LBRACKET token. final long arrayToken = token; // LBRACKET tested in caller. next(); // Prepare to accummulating elements. final List<Node> elements = new ArrayList<>(); // Track elisions. boolean elision = true; loop: while (true) { switch (type) { case RBRACKET: next(); break loop; case COMMARIGHT: next(); // If no prior expression if (elision) { elements.add(null); } elision = true; break; default: if (!elision) { error(AbstractParser.message("expected.comma", type.getNameOrType())); } // Add expression element. final Node expression = assignmentExpression(false); if (expression != null) { elements.add(expression); } else { expect(RBRACKET); } elision = false; break; } } return LiteralNode.newInstance(source, arrayToken, finish, elements); } /** * ObjectLiteral : * { } * { PropertyNameAndValueList } { PropertyNameAndValueList , } * * PropertyNameAndValueList : * PropertyAssignment * PropertyNameAndValueList , PropertyAssignment * * See 11.1.5 * * Parse an object literal. * @return Expression node. */ private Node objectLiteral() { // Capture LBRACE token. final long objectToken = token; // LBRACE tested in caller. next(); // Object context. // Prepare to accumulate elements. final List<Node> elements = new ArrayList<>(); final Map<Object, PropertyNode> 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: next(); commaSeen = true; break; default: if (!commaSeen) { error(AbstractParser.message("expected.comma", type.getNameOrType())); } commaSeen = false; // Get and add the next property. final PropertyNode property = propertyAssignment(); final Object key = property.getKeyName(); final PropertyNode existingProperty = map.get(key); if (existingProperty != null) { // ECMA section 11.1.5 Object Initialiser // point # 4 on property assignment production final Node value = property.getValue(); final Node getter = property.getGetter(); final Node setter = property.getSetter(); final Node prevValue = existingProperty.getValue(); final Node prevGetter = existingProperty.getGetter(); final Node prevSetter = existingProperty.getSetter(); boolean redefinitionOk = true; // ECMA 11.1.5 strict mode restrictions if (isStrictMode) { if (value != null && prevValue != null) { redefinitionOk = false; } } final boolean isPrevAccessor = prevGetter != null || prevSetter != null; final boolean isAccessor = getter != null || setter != null; // data property redefined as accessor property if (prevValue != null && isAccessor) { redefinitionOk = false; } // accessor property redefined as data if (isPrevAccessor && value != null) { redefinitionOk = false; } if (isAccessor && isPrevAccessor) { if (getter != null && prevGetter != null || setter != null && prevSetter != null) { redefinitionOk = false; } } if (!redefinitionOk) { error(AbstractParser.message("property.redefinition", key.toString()), property.getToken()); } if (value != null) { final Node existingValue = existingProperty.getValue(); if (existingValue == null) { existingProperty.setValue(value); } else { final long propertyToken = Token.recast(existingProperty.getToken(), COMMARIGHT); existingProperty.setValue(new BinaryNode(source, propertyToken, existingValue, value)); } existingProperty.setGetter(null); existingProperty.setSetter(null); } if (getter != null) { existingProperty.setGetter(getter); } if (setter != null) { existingProperty.setSetter(setter); } } else { map.put(key, property); elements.add(property); } break; } } return new ObjectNode(source, objectToken, finish, elements); } /** * PropertyName : * IdentifierName * StringLiteral * NumericLiteral * * See 11.1.5 * * @return PropertyName node */ @SuppressWarnings("fallthrough") private PropertyKey propertyName() { switch (type) { case IDENT: return getIdent(); case OCTAL: if (isStrictMode) { error(AbstractParser.message("strict.no.octal"), token); } case STRING: case ESCSTRING: case DECIMAL: case HEXADECIMAL: case FLOATING: return getLiteral(); default: return getIdentifierName(); } } /** * 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; FunctionNode functionNode; List<IdentNode> parameters; PropertyNode propertyNode; PropertyKey propertyName; if (type == IDENT) { // Get IDENT. final String ident = (String)expectValue(IDENT); if (type != COLON) { final long getSetToken = token; switch (ident) { case "get": final PropertyKey getIdent = propertyName(); final String getterName = getIdent.getPropertyName(); final IdentNode getNameNode = new IdentNode(source, ((Node)getIdent).getToken(), finish, "get " + getterName); expect(LPAREN); expect(RPAREN); parameters = new ArrayList<>(); functionNode = functionBody(getSetToken, getNameNode, parameters, FunctionNode.Kind.GETTER); propertyNode = new PropertyNode(source, propertyToken, finish, getIdent, null); propertyNode.setGetter(functionNode); return propertyNode; case "set": final PropertyKey setIdent = propertyName(); final String setterName = setIdent.getPropertyName(); final IdentNode setNameNode = new IdentNode(source, ((Node)setIdent).getToken(), finish, "set " + setterName); expect(LPAREN); final IdentNode argIdent = getIdent(); verifyStrictIdent(argIdent, "setter argument"); expect(RPAREN); parameters = new ArrayList<>(); parameters.add(argIdent); functionNode = functionBody(getSetToken, setNameNode, parameters, FunctionNode.Kind.SETTER); propertyNode = new PropertyNode(source, propertyToken, finish, setIdent, null); propertyNode.setSetter(functionNode); return propertyNode; default: break; } } propertyName = new IdentNode(source, propertyToken, finish, ident); } else { propertyName = propertyName(); } expect(COLON); final Node value = assignmentExpression(false); propertyNode = new PropertyNode(source, propertyToken, finish, propertyName, value); return propertyNode; } /** * LeftHandSideExpression : * NewExpression * CallExpression * * CallExpression : * MemberExpression Arguments * CallExpression Arguments * CallExpression [ Expression ] * CallExpression . IdentifierName * * See 11.2 * * Parse left hand side expression. * @return Expression node. */ private Node leftHandSideExpression() { long callToken = token; Node lhs = memberExpression(); if (type == LPAREN) { final List<Node> arguments = argumentList(); // Catch special functions. if (lhs instanceof IdentNode) { detectSpecialFunction((IdentNode)lhs); } lhs = new CallNode(source, callToken, finish, lhs, arguments); if (isInWithBlock()) { ((CallNode)lhs).setInWithBlock(); } } loop: while (true) { // Capture token. callToken = token; switch (type) { case LPAREN: // Get NEW or FUNCTION arguments. final List<Node> arguments = argumentList(); // Create call node. lhs = new CallNode(source, callToken, finish, lhs, arguments); if (isInWithBlock()) { ((CallNode)lhs).setInWithBlock(); } break; case LBRACKET: next(); // Get array index. final Node rhs = expression(); expect(RBRACKET); // Create indexing node. lhs = new IndexNode(source, callToken, finish, lhs, rhs); break; case PERIOD: next(); final IdentNode property = getIdentifierName(); // Create property access node. lhs = new AccessNode(source, callToken, finish, lhs, property); break; default: break loop; } } return lhs; } /** * NewExpression : * MemberExpression * new NewExpression * * See 11.2 * * Parse new expression. * @return Expression node. */ private Node newExpression() { final long newToken = token; // NEW is tested in caller. next(); // Get function base. final Node constructor = memberExpression(); if (constructor == null) { return null; } // Get arguments. List<Node> 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()" expresssion // 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(source, constructor.getToken(), finish, constructor, arguments); if (isInWithBlock()) { callNode.setInWithBlock(); } return new UnaryNode(source, newToken, callNode); } /** * MemberExpression : * PrimaryExpression * FunctionExpression * MemberExpression [ Expression ] * MemberExpression . IdentifierName * new MemberExpression Arguments * * See 11.2 * * Parse member expression. * @return Expression node. */ private Node memberExpression() { // Prepare to build operation. Node lhs; switch (type) { case NEW: // Get new exppression. lhs = newExpression(); break; case FUNCTION: // Get function expression. lhs = functionExpression(false, false); break; 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 Node index = expression(); expect(RBRACKET); // Create indexing node. lhs = new IndexNode(source, callToken, finish, lhs, index); break; case PERIOD: if (lhs == null) { error(AbstractParser.message("expected.operand", type.getNameOrType())); return null; } next(); final IdentNode property = getIdentifierName(); // Create property access node. lhs = new AccessNode(source, callToken, finish, lhs, property); break; default: break loop; } } return lhs; } /** * Arguments : * ( ) * ( ArgumentList ) * * ArgumentList : * AssignmentExpression * ArgumentList , AssignmentExpression * * See 11.2 * * Parse function call arguments. * @return Argument list. */ private List<Node> argumentList() { // Prepare to accumulate list of arguments. final List<Node> 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; } // Get argument expression. nodeList.add(assignmentExpression(false)); } expect(RPAREN); return nodeList; } /** * 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 Node functionExpression(final boolean isStatement, final boolean topLevel) { final LineNumberNode lineNumber = lineNumber(); final long functionToken = token; // FUNCTION is tested in caller. next(); IdentNode name = null; if (type == IDENT || isNonStrictModeIdent()) { name = getIdent(); verifyStrictIdent(name, "function name"); } else if (isStatement) { // Nashorn extension: anonymous function statements if (env._no_syntax_extensions || !env._anon_functions) { expect(IDENT); } } // name is null, generate anonymous name boolean isAnonymous = false; if (name == null) { final String tmpName = "_L" + source.getLine(Token.descPosition(token)); name = new IdentNode(source, functionToken, Token.descPosition(functionToken), tmpName); isAnonymous = true; } expect(LPAREN); final List<IdentNode> parameters = formalParameterList(); expect(RPAREN); final FunctionNode functionNode = functionBody(functionToken, name, parameters, FunctionNode.Kind.NORMAL); if (isStatement) { if(topLevel) { functionNode.setIsDeclared(); } if(ARGUMENTS.tag().equals(name.getName())) { getFunction().setDefinesArguments(); } } if (isAnonymous) { functionNode.setIsAnonymous(); } final int arity = parameters.size(); final boolean strict = functionNode.isStrictMode(); if (arity > 1) { 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 (ARGUMENTS.tag().equals(parameterName)) { functionNode.setDefinesArguments(); } if (parametersSet.contains(parameterName)) { // redefinition of parameter name if (strict) { error(AbstractParser.message("strict.param.redefinition", parameterName), parameter.getToken()); } else { // rename in non-strict mode parameterName = functionNode.uniqueName(parameterName); final long parameterToken = parameter.getToken(); parameters.set(i, new IdentNode(source, parameterToken, Token.descPosition(parameterToken), functionNode.uniqueName(parameterName))); } } parametersSet.add(parameterName); } } else if (arity == 1) { if (ARGUMENTS.tag().equals(parameters.get(0).getName())) { functionNode.setDefinesArguments(); } } if (isStatement) { final VarNode varNode = new VarNode(source, functionToken, finish, name, functionNode, true); if(topLevel) { functionDeclarations.add(lineNumber); functionDeclarations.add(varNode); } else { final Block block = getBlock(); block.addStatement(lineNumber); block.addStatement(varNode); } } return functionNode; } /** * FormalParameterList : * Identifier * FormalParameterList , Identifier * * See 13 * * Parse function parameter list. * @return List of parameter nodes. */ private List<IdentNode> formalParameterList() { // Prepare to gather parameters. final List<IdentNode> parameters = new ArrayList<>(); // Track commas. boolean first = true; while (type != RPAREN) { // Comma prior to every argument except the first. if (!first) { expect(COMMARIGHT); } else { first = false; } // Get and add parameter. final IdentNode ident = getIdent(); // ECMA 13.1 strict mode restrictions verifyStrictIdent(ident, "function parameter"); parameters.add(ident); } return parameters; } /** * FunctionBody : * SourceElements? * * See 13 * * Parse function body. * @return function node (body.) */ private FunctionNode functionBody(final long firstToken, final IdentNode ident, final List<IdentNode> parameters, final FunctionNode.Kind kind) { FunctionNode functionNode = null; try { // Create a new function block. functionNode = newFunctionBlock(ident); functionNode.setParameters(parameters); functionNode.setKind(kind); functionNode.setFirstToken(firstToken); // Nashorn extension: expression closures if (!env._no_syntax_extensions && type != LBRACE) { /* * Example: * * function square(x) x * x; * print(square(3)); */ // just expression as function body final Node expr = expression(); // create a return statement - this creates code in itself and does not need to be // wrapped into an ExecuteNode final ReturnNode returnNode = new ReturnNode(source, expr.getToken(), finish, expr, null); // add the return statement functionNode.addStatement(returnNode); functionNode.setLastToken(token); functionNode.setFinish(Token.descPosition(token) + Token.descLength(token)); } else { expect(LBRACE); // Gather the function elements. final List<Node> prevFunctionDecls = functionDeclarations; functionDeclarations = new ArrayList<>(); try { sourceElements(); functionNode.prependStatements(functionDeclarations); } finally { functionDeclarations = prevFunctionDecls; } functionNode.setLastToken(token); expect(RBRACE); functionNode.setFinish(finish); } } finally { restoreBlock(functionNode); } return functionNode; } private RuntimeNode referenceError(final Node lhs, final Node rhs) { final ArrayList<Node> args = new ArrayList<>(); args.add(lhs); if (rhs == null) { args.add(LiteralNode.newInstance(source, lhs.getToken(), lhs.getFinish())); } else { args.add(rhs); } args.add(LiteralNode.newInstance(source, lhs.getToken(), lhs.getFinish(), lhs.toString())); final RuntimeNode runtimeNode = new RuntimeNode(source, lhs.getToken(), lhs.getFinish(), RuntimeNode.Request.REFERENCE_ERROR, args); return runtimeNode; } /* * parse LHS [a, b, ..., c]. * * JavaScript 1.8. */ //private Node destructureExpression() { // return null; //} /** * PostfixExpression : * LeftHandSideExpression * LeftHandSideExpression ++ // [no LineTerminator here] * LeftHandSideExpression -- // [no LineTerminator here] * * See 11.3 * * UnaryExpression : * PostfixExpression * delete UnaryExpression * Node UnaryExpression * typeof UnaryExpression * ++ UnaryExpression * -- UnaryExpression * + UnaryExpression * - UnaryExpression * ~ UnaryExpression * ! UnaryExpression * * See 11.4 * * Parse unary expression. * @return Expression node. */ private Node unaryExpression() { final long unaryToken = token; switch (type) { case DELETE: case VOID: case TYPEOF: case ADD: case SUB: case BIT_NOT: case NOT: next(); final Node expr = unaryExpression(); /* // Not sure if "delete <ident>" is a compile-time error or a // runtime error in strict mode. if (isStrictMode) { if (unaryTokenType == DELETE && expr instanceof IdentNode) { error(message("strict.cant.delete.ident", ((IdentNode)expr).getName()), expr.getToken()); } } */ return new UnaryNode(source, unaryToken, expr); case INCPREFIX: case DECPREFIX: final TokenType opType = type; next(); final Node lhs = leftHandSideExpression(); // ++, -- without operand.. if (lhs == null) { // error would have been issued when looking for 'lhs' return null; } if (!(lhs instanceof AccessNode || lhs instanceof IndexNode || lhs instanceof IdentNode)) { return referenceError(lhs, null); } if (lhs instanceof IdentNode) { if (!checkIdentLValue((IdentNode)lhs)) { return referenceError(lhs, null); } verifyStrictIdent((IdentNode)lhs, "operand for " + opType.getName() + " operator"); } return incDecExpression(unaryToken, opType, lhs, false); default: break; } Node expression = leftHandSideExpression(); if (last != EOL) { switch (type) { case INCPREFIX: case DECPREFIX: final TokenType opType = type; final Node lhs = expression; if (!(lhs instanceof AccessNode || lhs instanceof IndexNode || lhs instanceof IdentNode)) { next(); return referenceError(lhs, null); } if (lhs instanceof IdentNode) { if (!checkIdentLValue((IdentNode)lhs)) { next(); return referenceError(lhs, null); } verifyStrictIdent((IdentNode)lhs, "operand for " + opType.getName() + " operator"); } expression = incDecExpression(token, type, expression, true); next(); break; default: break; } } if (expression == null) { error(AbstractParser.message("expected.operand", type.getNameOrType())); } return expression; } /** * 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. */ private Node expression() { // TODO - Destructuring array. // Include commas in expression parsing. return expression(unaryExpression(), COMMARIGHT.getPrecedence(), false); } private Node expression(final Node exprLhs, final int minPrecedence, final boolean noIn) { // Get the precedence of the next operator. int precedence = type.getPrecedence(); Node 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 Node rhs = expression(unaryExpression(), ASSIGN.getPrecedence(), false); expect(COLON); // Fail expression. final Node third = expression(unaryExpression(), ASSIGN.getPrecedence(), noIn); // Build up node. lhs = new TernaryNode(source, op, lhs, rhs, third); } else { // Skip operator. next(); // Get the next primary expression. Node 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(); } lhs = verifyAssignment(op, lhs, rhs); } precedence = type.getPrecedence(); } return lhs; } private Node assignmentExpression(final boolean noIn) { // TODO - Handle decompose. // Exclude commas in expression parsing. return expression(unaryExpression(), ASSIGN.getPrecedence(), noIn); } /** * 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; } } /** * Add a line number node at current position */ private LineNumberNode lineNumber() { if (env._debug_lines) { return new LineNumberNode(source, token, line); } return null; } @Override public String toString() { return "[JavaScript Parsing]"; } private Block getBlock() { return lexicalContext.getCurrentBlock(); } private FunctionNode getFunction() { return lexicalContext.getCurrentFunction(); } }