blob: e8c2fada5b930b96df876269f2c28260970c05d4 [file] [log] [blame]
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.dev.js;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JPrimitiveType;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
import com.google.gwt.dev.js.ast.JsBinaryOperator;
import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.js.ast.JsExprStmt;
import com.google.gwt.dev.js.ast.JsExpression;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsInvocation;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsNode;
import com.google.gwt.dev.js.ast.JsParameter;
import com.google.gwt.dev.js.ast.JsReturn;
import com.google.gwt.dev.js.ast.JsScope;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.js.ast.JsThisRef;
import com.google.gwt.dev.util.StringInterner;
/**
* Utils for JS AST.
*/
public class JsUtils {
/**
* Given a JsInvocation, determine if it is invoking a JsFunction that is
* specified to be executed only once during the program's lifetime.
*/
public static JsFunction isExecuteOnce(JsInvocation invocation) {
JsFunction f = isFunction(invocation.getQualifier());
if (f != null && f.isClinit()) {
return f;
}
return null;
}
/**
* Given an expression, determine if it is a JsNameRef that refers to a
* statically-defined JsFunction.
*/
public static JsFunction isFunction(JsExpression e) {
if (!(e instanceof JsNameRef)) {
return null;
}
JsNameRef ref = (JsNameRef) e;
// Unravel foo.call(...).
if (!ref.getName().isObfuscatable() && CALL_STRING.equals(ref.getIdent())) {
if (ref.getQualifier() instanceof JsNameRef) {
ref = (JsNameRef) ref.getQualifier();
}
}
JsNode staticRef = ref.getName().getStaticRef();
if (staticRef instanceof JsFunction) {
return (JsFunction) staticRef;
}
return null;
}
public static JsExpression createAssignment(JsExpression lhs, JsExpression rhs) {
return createAssignment(lhs.getSourceInfo(), lhs, rhs);
}
public static JsExpression createAssignment(SourceInfo info, JsExpression lhs, JsExpression rhs) {
return new JsBinaryOperation(info, JsBinaryOperator.ASG, lhs, rhs);
}
public static JsFunction createBridge(JMethod method, JsName polyName, JsScope scope) {
SourceInfo sourceInfo = method.getSourceInfo();
JsFunction bridge = new JsFunction(sourceInfo, scope);
for (JParameter p : method.getParams()) {
JsName name = bridge.getScope().declareName(p.getName());
bridge.getParameters().add(new JsParameter(sourceInfo, name));
}
JsNameRef ref = polyName.makeRef(sourceInfo);
ref.setQualifier(new JsThisRef(sourceInfo));
JsInvocation invocation = new JsInvocation(sourceInfo, ref);
for (JsParameter p : bridge.getParameters()) {
invocation.getArguments().add(p.getName().makeRef(sourceInfo));
}
JsBlock block = new JsBlock(sourceInfo);
if (method.getType() == JPrimitiveType.VOID) {
block.getStatements().add(invocation.makeStmt());
} else {
block.getStatements().add(new JsReturn(sourceInfo, invocation));
}
bridge.setBody(block);
return bridge;
}
public static JsExpression createCommaExpression(JsExpression... expressions) {
return createCommaExpressionHelper(0, expressions);
}
public static JsFunction createEmptyFunctionLiteral(SourceInfo info, JsScope scope, JsName name) {
JsFunction func = new JsFunction(info, scope, name);
func.setBody(new JsBlock(info));
return func;
}
public static JsNameRef createQualifiedNameRef(SourceInfo info, JsName... names) {
JsNameRef result = null;
for (JsName name : names) {
if (result == null) {
result = name.makeRef(info);
continue;
}
result = name.makeQualifiedRef(info, result);
}
return result;
}
/**
* Given a string qualifier such as 'foo.bar.Baz', returns a chain of JsNameRef's representing
* this qualifier.
*/
public static JsNameRef createQualifiedNameRef(String namespace, SourceInfo sourceInfo) {
assert !namespace.isEmpty();
JsNameRef ref = null;
for (String part : namespace.split("\\.")) {
JsNameRef newRef = new JsNameRef(sourceInfo, part);
if (ref != null) {
newRef.setQualifier(ref);
}
ref = newRef;
}
return ref;
}
/**
* Attempts to extract a single expression from a given statement and returns
* it. If no such expression exists, returns <code>null</code>.
*/
public static JsExpression extractExpression(JsStatement stmt) {
if (stmt == null) {
return null;
}
if (stmt instanceof JsExprStmt) {
return ((JsExprStmt) stmt).getExpression();
}
if (stmt instanceof JsBlock && ((JsBlock) stmt).getStatements().size() == 1) {
return extractExpression(((JsBlock) stmt).getStatements().get(0));
}
return null;
}
public static boolean isEmpty(JsStatement stmt) {
if (stmt == null) {
return true;
}
return (stmt instanceof JsBlock && ((JsBlock) stmt).getStatements().isEmpty());
}
/**
* If the statement is a JsExprStmt that declares a function with no other
* side effects, returns that function; otherwise <code>null</code>.
*/
public static JsFunction isFunctionDeclaration(JsStatement stmt) {
if (stmt instanceof JsExprStmt) {
JsExprStmt exprStmt = (JsExprStmt) stmt;
JsExpression expr = exprStmt.getExpression();
if (expr instanceof JsFunction) {
JsFunction func = (JsFunction) expr;
if (func.getName() != null) {
return func;
}
}
}
return null;
}
private static JsExpression createCommaExpressionHelper(int index, JsExpression... expressions) {
int remainingExpressions = expressions.length - index;
assert remainingExpressions >= 2;
JsExpression lhs = expressions[index];
JsExpression rhs = expressions[index + 1];
if (remainingExpressions > 2) {
rhs = createCommaExpressionHelper(index + 1, expressions);
}
// Construct the binary expression
if (rhs == null) {
return lhs;
} else if (lhs == null) {
return rhs;
}
return new JsBinaryOperation(lhs.getSourceInfo(), JsBinaryOperator.COMMA, lhs, rhs);
}
private static final String CALL_STRING = StringInterner.get().intern("call");
private JsUtils() {
}
}