org.mozilla.javascript.ast.FunctionNode Maven / Gradle / Ivy
/* -*- 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 org.mozilla.javascript.Node;
import org.mozilla.javascript.Token;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 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;
// codegen variables
private int functionType;
private boolean needsActivation;
private boolean isGenerator;
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 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);
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);
}
}
}
}
}