org.mozilla.javascript.ast.FunctionNode Maven / Gradle / Ivy
Show all versions of rhino-runtime Show documentation
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript.ast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mozilla.javascript.Node;
import org.mozilla.javascript.Token;
/**
* A JavaScript function declaration or expression.
*
* Node type is {@link Token#FUNCTION}.
*
*
FunctionDeclaration :
* function Identifier ( FormalParameterListopt ) { FunctionBody }
* FunctionExpression :
* function Identifieropt ( FormalParameterListopt ) { FunctionBody }
* FormalParameterList :
* Identifier
* FormalParameterList , Identifier
* FunctionBody :
* SourceElements
* Program :
* SourceElements
* SourceElements :
* SourceElement
* SourceElements SourceElement
* SourceElement :
* Statement
* FunctionDeclaration
*
* JavaScript 1.8 introduces "function closures" of the form
*
* function ([params] ) Expression
*
* In this case the FunctionNode node will have no body but will have an expression.
*/
public class FunctionNode extends ScriptNode {
/**
* There are three types of functions that can be defined. The first is a function statement.
* This is a function appearing as a top-level statement (i.e., not nested inside some other
* statement) in either a script or a function.
*
* The second is a function expression, which is a function appearing in an expression except
* for the third type, which is...
*
*
The third type is a function expression where the expression is the top-level expression
* in an expression statement.
*
*
The three types of functions have different treatment and must be distinguished.
*/
public static final int FUNCTION_STATEMENT = 1;
public static final int FUNCTION_EXPRESSION = 2;
public static final int FUNCTION_EXPRESSION_STATEMENT = 3;
public static final int ARROW_FUNCTION = 4;
public static enum Form {
FUNCTION,
GETTER,
SETTER,
METHOD
}
private static final List NO_PARAMS = Collections.unmodifiableList(new ArrayList<>());
private Name functionName;
private List params;
private AstNode body;
private boolean isExpressionClosure;
private Form functionForm = Form.FUNCTION;
private int lp = -1;
private int rp = -1;
private boolean hasRestParameter;
// codegen variables
private int functionType;
private boolean needsActivation;
private boolean isGenerator;
private boolean isES6Generator;
private List generatorResumePoints;
private Map liveLocals;
private AstNode memberExprNode;
{
type = Token.FUNCTION;
}
public FunctionNode() {}
public FunctionNode(int pos) {
super(pos);
}
public FunctionNode(int pos, Name name) {
super(pos);
setFunctionName(name);
}
/**
* Returns function name
*
* @return function name, {@code null} for anonymous functions
*/
public Name getFunctionName() {
return functionName;
}
/**
* Sets function name, and sets its parent to this node.
*
* @param name function name, {@code null} for anonymous functions
*/
public void setFunctionName(Name name) {
functionName = name;
if (name != null) name.setParent(this);
}
/**
* Returns the function name as a string
*
* @return the function name, {@code ""} if anonymous
*/
public String getName() {
return functionName != null ? functionName.getIdentifier() : "";
}
/**
* Returns the function parameter list
*
* @return the function parameter list. Returns an immutable empty list if there are no
* parameters.
*/
public List getParams() {
return params != null ? params : NO_PARAMS;
}
/**
* Sets the function parameter list, and sets the parent for each element of the list.
*
* @param params the function parameter list, or {@code null} if no params
*/
public void setParams(List params) {
if (params == null) {
this.params = null;
} else {
if (this.params != null) this.params.clear();
for (AstNode param : params) addParam(param);
}
}
/**
* Adds a parameter to the function parameter list. Sets the parent of the param node to this
* node.
*
* @param param the parameter
* @throws IllegalArgumentException if param is {@code null}
*/
public void addParam(AstNode param) {
assertNotNull(param);
if (params == null) {
params = new ArrayList<>();
}
params.add(param);
param.setParent(this);
}
/**
* Returns true if the specified {@link AstNode} node is a parameter of this Function node. This
* provides a way during AST traversal to disambiguate the function name node from the parameter
* nodes.
*/
public boolean isParam(AstNode node) {
return params == null ? false : params.contains(node);
}
/**
* Returns function body. Normally a {@link Block}, but can be a plain {@link AstNode} if it's a
* function closure.
*
* @return the body. Can be {@code null} only if the AST is malformed.
*/
public AstNode getBody() {
return body;
}
/**
* Sets function body, and sets its parent to this node. Also sets the encoded source bounds
* based on the body bounds. Assumes the function node absolute position has already been set,
* and the body node's absolute position and length are set.
*
*
*
* @param body function body. Its parent is set to this node, and its position is updated to be
* relative to this node.
* @throws IllegalArgumentException if body is {@code null}
*/
public void setBody(AstNode body) {
assertNotNull(body);
this.body = body;
if (Boolean.TRUE.equals(body.getProp(Node.EXPRESSION_CLOSURE_PROP))) {
setIsExpressionClosure(true);
}
int absEnd = body.getPosition() + body.getLength();
body.setParent(this);
this.setLength(absEnd - this.position);
setEncodedSourceBounds(this.position, absEnd);
}
/** Returns left paren position, -1 if missing */
public int getLp() {
return lp;
}
/** Sets left paren position */
public void setLp(int lp) {
this.lp = lp;
}
/** Returns right paren position, -1 if missing */
public int getRp() {
return rp;
}
/** Sets right paren position */
public void setRp(int rp) {
this.rp = rp;
}
/** Sets both paren positions */
public void setParens(int lp, int rp) {
this.lp = lp;
this.rp = rp;
}
/** Returns whether this is a 1.8 function closure */
public boolean isExpressionClosure() {
return isExpressionClosure;
}
/** Sets whether this is a 1.8 function closure */
public void setIsExpressionClosure(boolean isExpressionClosure) {
this.isExpressionClosure = isExpressionClosure;
}
/**
* Return true if this function requires an Ecma-262 Activation object. The Activation object is
* implemented by {@link org.mozilla.javascript.NativeCall}, and is fairly expensive to create,
* so when possible, the interpreter attempts to use a plain call frame instead.
*
* @return true if this function needs activation. It could be needed if there is a lexical
* closure, or in a number of other situations.
*/
public boolean requiresActivation() {
return needsActivation;
}
public void setRequiresActivation() {
needsActivation = true;
}
public boolean isGenerator() {
return isGenerator;
}
public void setIsGenerator() {
isGenerator = true;
}
public boolean isES6Generator() {
return isES6Generator;
}
public void setIsES6Generator() {
isES6Generator = true;
isGenerator = true;
}
@Override
public boolean hasRestParameter() {
return hasRestParameter;
}
public void setHasRestParameter(boolean hasRestParameter) {
this.hasRestParameter = hasRestParameter;
}
public void addResumptionPoint(Node target) {
if (generatorResumePoints == null) generatorResumePoints = new ArrayList<>();
generatorResumePoints.add(target);
}
public List getResumptionPoints() {
return generatorResumePoints;
}
public Map getLiveLocals() {
return liveLocals;
}
public void addLiveLocals(Node node, int[] locals) {
if (liveLocals == null) liveLocals = new HashMap<>();
liveLocals.put(node, locals);
}
@Override
public int addFunction(FunctionNode fnNode) {
int result = super.addFunction(fnNode);
if (getFunctionCount() > 0) {
needsActivation = true;
}
return result;
}
/** Returns the function type (statement, expr, statement expr) */
public int getFunctionType() {
return functionType;
}
public void setFunctionType(int type) {
functionType = type;
}
public boolean isMethod() {
return functionForm == Form.GETTER
|| functionForm == Form.SETTER
|| functionForm == Form.METHOD;
}
public boolean isGetterMethod() {
return functionForm == Form.GETTER;
}
public boolean isSetterMethod() {
return functionForm == Form.SETTER;
}
public boolean isNormalMethod() {
return functionForm == Form.METHOD;
}
public void setFunctionIsGetterMethod() {
functionForm = Form.GETTER;
}
public void setFunctionIsSetterMethod() {
functionForm = Form.SETTER;
}
public void setFunctionIsNormalMethod() {
functionForm = Form.METHOD;
}
/**
* Rhino supports a nonstandard Ecma extension that allows you to say, for instance, function
* a.b.c(arg1, arg) {...}, and it will be rewritten at codegen time to: a.b.c = function(arg1,
* arg2) {...} If we detect an expression other than a simple Name in the position where a
* function name was expected, we record that expression here.
*
* This extension is only available by setting the CompilerEnv option
* "isAllowMemberExprAsFunctionName" in the Parser.
*/
public void setMemberExprNode(AstNode node) {
memberExprNode = node;
if (node != null) node.setParent(this);
}
public AstNode getMemberExprNode() {
return memberExprNode;
}
@Override
public String toSource(int depth) {
StringBuilder sb = new StringBuilder();
boolean isArrow = functionType == ARROW_FUNCTION;
if (!isMethod()) {
sb.append(makeIndent(depth));
if (!isArrow) {
sb.append("function");
}
}
if (functionName != null) {
sb.append(" ");
sb.append(functionName.toSource(0));
}
if (params == null) {
sb.append("() ");
} else if (isArrow && lp == -1) {
// no paren
printList(params, sb);
sb.append(" ");
} else {
sb.append("(");
printList(params, sb);
if (getIntProp(TRAILING_COMMA, 0) == 1) {
sb.append(", ");
}
sb.append(") ");
}
if (isArrow) {
sb.append("=> ");
}
if (isExpressionClosure) {
AstNode body = getBody();
if (body.getLastChild() instanceof ReturnStatement) {
// omit "return" keyword, just print the expression
body = ((ReturnStatement) body.getLastChild()).getReturnValue();
sb.append(body.toSource(0));
if (functionType == FUNCTION_STATEMENT) {
sb.append(";");
}
} else {
// should never happen
sb.append(" ");
sb.append(body.toSource(0));
}
} else {
sb.append(getBody().toSource(depth).trim());
}
if (functionType == FUNCTION_STATEMENT || isMethod()) {
sb.append("\n");
}
return sb.toString();
}
/**
* Visits this node, the function name node if supplied, the parameters, and the body. If there
* is a member-expr node, it is visited last.
*/
@Override
public void visit(NodeVisitor v) {
if (v.visit(this)) {
if (functionName != null) {
functionName.visit(v);
}
for (AstNode param : getParams()) {
param.visit(v);
}
getBody().visit(v);
if (!isExpressionClosure) {
if (memberExprNode != null) {
memberExprNode.visit(v);
}
}
}
}
}