/* * Copyright 2008 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.InternalCompilerException; import com.google.gwt.dev.js.ast.JsArrayAccess; import com.google.gwt.dev.js.ast.JsArrayLiteral; import com.google.gwt.dev.js.ast.JsBinaryOperation; import com.google.gwt.dev.js.ast.JsBooleanLiteral; import com.google.gwt.dev.js.ast.JsConditional; import com.google.gwt.dev.js.ast.JsContext; 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.JsNameOf; import com.google.gwt.dev.js.ast.JsNameRef; import com.google.gwt.dev.js.ast.JsNew; import com.google.gwt.dev.js.ast.JsNullLiteral; import com.google.gwt.dev.js.ast.JsNumberLiteral; import com.google.gwt.dev.js.ast.JsObjectLiteral; import com.google.gwt.dev.js.ast.JsPostfixOperation; import com.google.gwt.dev.js.ast.JsPrefixOperation; import com.google.gwt.dev.js.ast.JsPropertyInitializer; import com.google.gwt.dev.js.ast.JsRegExp; import com.google.gwt.dev.js.ast.JsStringLiteral; import com.google.gwt.dev.js.ast.JsThisRef; import com.google.gwt.dev.js.ast.JsVisitor; import java.util.ArrayList; import java.util.List; import java.util.Stack; /** * A utility class to clone JsExpression AST members for use by * {@link JsInliner}. <b>Not all expressions are necessarily implemented</b>, * only those that are safe to hoist into outer call sites. */ public final class JsHoister { /** * Implements actual cloning logic. We rely on the JsExpressions to provide * traversal logic. The {@link #stack} field is used to accumulate * already-cloned JsExpression instances. One gotcha that falls out of this is * that argument lists are on the stack in reverse order, so lists should be * constructed via inserts, rather than appends. */ public static class Cloner extends JsVisitor { protected final Stack<JsExpression> stack = new Stack<JsExpression>(); private boolean successful = true; @Override public void endVisit(JsArrayAccess x, JsContext ctx) { JsArrayAccess newExpression = new JsArrayAccess(x.getSourceInfo()); newExpression.setIndexExpr(stack.pop()); newExpression.setArrayExpr(stack.pop()); stack.push(newExpression); } @Override public void endVisit(JsArrayLiteral x, JsContext ctx) { JsArrayLiteral toReturn = new JsArrayLiteral(x.getSourceInfo()); List<JsExpression> expressions = toReturn.getExpressions(); int size = x.getExpressions().size(); while (size-- > 0) { expressions.add(0, stack.pop()); } stack.push(toReturn); } @Override public void endVisit(JsBinaryOperation x, JsContext ctx) { JsBinaryOperation toReturn = new JsBinaryOperation(x.getSourceInfo(), x.getOperator()); toReturn.setArg2(stack.pop()); toReturn.setArg1(stack.pop()); stack.push(toReturn); } @Override public void endVisit(JsBooleanLiteral x, JsContext ctx) { stack.push(x); } @Override public void endVisit(JsConditional x, JsContext ctx) { JsConditional toReturn = new JsConditional(x.getSourceInfo()); toReturn.setElseExpression(stack.pop()); toReturn.setThenExpression(stack.pop()); toReturn.setTestExpression(stack.pop()); stack.push(toReturn); } /** * The only functions that would get be visited are those being used as * first-class objects. */ @Override public void endVisit(JsFunction x, JsContext ctx) { // Set a flag to indicate that we cannot continue, and push a null so // we don't run out of elements on the stack. successful = false; stack.push(null); } /** * Cloning the invocation allows us to modify it without damaging other call * sites. */ @Override public void endVisit(JsInvocation x, JsContext ctx) { JsInvocation toReturn = new JsInvocation(x.getSourceInfo()); List<JsExpression> params = toReturn.getArguments(); int size = x.getArguments().size(); while (size-- > 0) { params.add(0, stack.pop()); } toReturn.setQualifier(stack.pop()); stack.push(toReturn); } @Override public void endVisit(JsNameOf x, JsContext ctx) { JsNameOf toReturn = new JsNameOf(x.getSourceInfo(), x.getName()); stack.push(toReturn); } /** * Do a deep clone of a JsNameRef. Because JsNameRef chains are shared * throughout the AST, you can't just go and change their qualifiers when * re-writing an invocation. */ @Override public void endVisit(JsNameRef x, JsContext ctx) { JsNameRef toReturn = new JsNameRef(x.getSourceInfo(), x.getName()); if (x.getQualifier() != null) { toReturn.setQualifier(stack.pop()); } stack.push(toReturn); } @Override public void endVisit(JsNew x, JsContext ctx) { int size = x.getArguments().size(); List<JsExpression> arguments = new ArrayList<JsExpression>(size); while (size-- > 0) { arguments.add(0, stack.pop()); } JsNew toReturn = new JsNew(x.getSourceInfo(), stack.pop()); toReturn.getArguments().addAll(arguments); stack.push(toReturn); } @Override public void endVisit(JsNullLiteral x, JsContext ctx) { stack.push(x); } @Override public void endVisit(JsNumberLiteral x, JsContext ctx) { stack.push(x); } @Override public void endVisit(JsObjectLiteral x, JsContext ctx) { JsObjectLiteral toReturn = new JsObjectLiteral(x.getSourceInfo()); List<JsPropertyInitializer> inits = toReturn.getPropertyInitializers(); int size = x.getPropertyInitializers().size(); while (size-- > 0) { /* * JsPropertyInitializers are the only non-JsExpression objects that we * care about, so we just go ahead and create the objects in the loop, * rather than expecting it to be on the stack and having to perform * narrowing casts at all stack.pop() invocations. */ JsPropertyInitializer newInit = new JsPropertyInitializer( x.getSourceInfo()); newInit.setValueExpr(stack.pop()); newInit.setLabelExpr(stack.pop()); inits.add(0, newInit); } stack.push(toReturn); } @Override public void endVisit(JsPostfixOperation x, JsContext ctx) { JsPostfixOperation toReturn = new JsPostfixOperation(x.getSourceInfo(), x.getOperator()); toReturn.setArg(stack.pop()); stack.push(toReturn); } @Override public void endVisit(JsPrefixOperation x, JsContext ctx) { JsPrefixOperation toReturn = new JsPrefixOperation(x.getSourceInfo(), x.getOperator()); toReturn.setArg(stack.pop()); stack.push(toReturn); } @Override public void endVisit(JsRegExp x, JsContext ctx) { stack.push(x); } @Override public void endVisit(JsStringLiteral x, JsContext ctx) { stack.push(x); } @Override public void endVisit(JsThisRef x, JsContext ctx) { stack.push(new JsThisRef(x.getSourceInfo())); } public JsExpression getExpression() { return (successful && checkStack()) ? stack.peek() : null; } private boolean checkStack() { if (stack.size() > 1) { throw new InternalCompilerException("Too many expressions on stack"); } return stack.size() == 1; } } /** * Given a JsStatement, construct an expression to hoist into the outer * caller. This does not perform any name replacement, nor does it verify the * scope of referenced elements, but simply constructs a mutable copy of the * expression that can be manipulated at-will. * * @return A copy of the original expression, or <code>null</code> if the * expression cannot be hoisted. */ public static JsExpression hoist(JsExpression expression) { if (expression == null) { return null; } Cloner c = new Cloner(); c.accept(expression); return c.getExpression(); } private JsHoister() { } }