001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.js;
017:
018: import com.google.gwt.dev.jjs.InternalCompilerException;
019: import com.google.gwt.dev.js.ast.JsArrayAccess;
020: import com.google.gwt.dev.js.ast.JsArrayLiteral;
021: import com.google.gwt.dev.js.ast.JsBinaryOperation;
022: import com.google.gwt.dev.js.ast.JsBooleanLiteral;
023: import com.google.gwt.dev.js.ast.JsConditional;
024: import com.google.gwt.dev.js.ast.JsContext;
025: import com.google.gwt.dev.js.ast.JsDecimalLiteral;
026: import com.google.gwt.dev.js.ast.JsExpression;
027: import com.google.gwt.dev.js.ast.JsFunction;
028: import com.google.gwt.dev.js.ast.JsIntegralLiteral;
029: import com.google.gwt.dev.js.ast.JsInvocation;
030: import com.google.gwt.dev.js.ast.JsNameRef;
031: import com.google.gwt.dev.js.ast.JsNew;
032: import com.google.gwt.dev.js.ast.JsNullLiteral;
033: import com.google.gwt.dev.js.ast.JsObjectLiteral;
034: import com.google.gwt.dev.js.ast.JsPostfixOperation;
035: import com.google.gwt.dev.js.ast.JsPrefixOperation;
036: import com.google.gwt.dev.js.ast.JsPropertyInitializer;
037: import com.google.gwt.dev.js.ast.JsRegExp;
038: import com.google.gwt.dev.js.ast.JsStringLiteral;
039: import com.google.gwt.dev.js.ast.JsThisRef;
040: import com.google.gwt.dev.js.ast.JsVisitor;
041:
042: import java.util.List;
043: import java.util.Stack;
044:
045: /**
046: * A utility class to clone JsExpression AST members for use by
047: * {@link JsInliner}. <b>Not all expressions are necessarily
048: * implemented</b>, only those that are safe to hoist into outer call sites.
049: */
050: final class JsHoister {
051: /**
052: * Implements actual cloning logic. We rely on the JsExpressions to provide
053: * traversal logic. The {@link #stack} field is used to accumulate
054: * already-cloned JsExpression instances. One gotcha that falls out of this is
055: * that argument lists are on the stack in reverse order, so lists should be
056: * constructed via inserts, rather than appends.
057: */
058: private static class Cloner extends JsVisitor {
059: private final Stack<JsExpression> stack = new Stack<JsExpression>();
060: private boolean successful = true;
061:
062: @Override
063: public void endVisit(JsArrayAccess x,
064: JsContext<JsExpression> ctx) {
065: JsArrayAccess newExpression = new JsArrayAccess();
066: newExpression.setIndexExpr(stack.pop());
067: newExpression.setArrayExpr(stack.pop());
068: stack.push(newExpression);
069: }
070:
071: @Override
072: public void endVisit(JsArrayLiteral x,
073: JsContext<JsExpression> ctx) {
074: JsArrayLiteral toReturn = new JsArrayLiteral();
075: List<JsExpression> expressions = toReturn.getExpressions();
076: for (JsExpression e : x.getExpressions()) {
077: expressions.add(0, stack.pop());
078: }
079: stack.push(toReturn);
080: }
081:
082: @Override
083: public void endVisit(JsBinaryOperation x,
084: JsContext<JsExpression> ctx) {
085: JsBinaryOperation toReturn = new JsBinaryOperation(x
086: .getOperator());
087: toReturn.setArg2(stack.pop());
088: toReturn.setArg1(stack.pop());
089: stack.push(toReturn);
090: }
091:
092: @Override
093: public void endVisit(JsBooleanLiteral x,
094: JsContext<JsExpression> ctx) {
095: stack.push(x);
096: }
097:
098: @Override
099: public void endVisit(JsConditional x,
100: JsContext<JsExpression> ctx) {
101: JsConditional toReturn = new JsConditional();
102: toReturn.setElseExpression(stack.pop());
103: toReturn.setThenExpression(stack.pop());
104: toReturn.setTestExpression(stack.pop());
105: stack.push(toReturn);
106: }
107:
108: @Override
109: public void endVisit(JsDecimalLiteral x,
110: JsContext<JsExpression> ctx) {
111: stack.push(x);
112: }
113:
114: /**
115: * The only functions that would get be visited are those being used as
116: * first-class objects.
117: */
118: @Override
119: public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
120: // Set a flag to indicate that we cannot continue, and push a null so
121: // we don't run out of elements on the stack.
122: successful = false;
123: stack.push(null);
124: }
125:
126: @Override
127: public void endVisit(JsIntegralLiteral x,
128: JsContext<JsExpression> ctx) {
129: stack.push(x);
130: }
131:
132: /**
133: * Cloning the invocation allows us to modify it without damaging other call
134: * sites.
135: */
136: @Override
137: public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
138: JsInvocation toReturn = new JsInvocation();
139: List<JsExpression> params = toReturn.getArguments();
140: for (JsExpression e : x.getArguments()) {
141: params.add(0, stack.pop());
142: }
143: toReturn.setQualifier(stack.pop());
144: stack.push(toReturn);
145: }
146:
147: /**
148: * Do a deep clone of a JsNameRef. Because JsNameRef chains are shared
149: * throughout the AST, you can't just go and change their qualifiers when
150: * re-writing an invocation.
151: */
152: @Override
153: public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
154: JsNameRef toReturn = new JsNameRef(x.getName());
155:
156: if (x.getQualifier() != null) {
157: toReturn.setQualifier(stack.pop());
158: }
159: stack.push(toReturn);
160: }
161:
162: @Override
163: public void endVisit(JsNew x, JsContext<JsExpression> ctx) {
164: JsNew toReturn = new JsNew();
165:
166: List<JsExpression> arguments = toReturn.getArguments();
167: for (JsExpression a : x.getArguments()) {
168: arguments.add(0, stack.pop());
169: }
170: toReturn.setConstructorExpression(stack.pop());
171: stack.push(toReturn);
172: }
173:
174: @Override
175: public void endVisit(JsNullLiteral x,
176: JsContext<JsExpression> ctx) {
177: stack.push(x);
178: }
179:
180: @Override
181: public void endVisit(JsObjectLiteral x,
182: JsContext<JsExpression> ctx) {
183: JsObjectLiteral toReturn = new JsObjectLiteral();
184: List<JsPropertyInitializer> inits = toReturn
185: .getPropertyInitializers();
186:
187: for (JsPropertyInitializer init : x
188: .getPropertyInitializers()) {
189: /*
190: * JsPropertyInitializers are the only non-JsExpression objects that we
191: * care about, so we just go ahead and create the objects in the loop,
192: * rather than expecting it to be on the stack and having to perform
193: * narrowing casts at all stack.pop() invocations.
194: */
195: JsPropertyInitializer newInit = new JsPropertyInitializer();
196: newInit.setValueExpr(stack.pop());
197: newInit.setLabelExpr(stack.pop());
198:
199: inits.add(0, newInit);
200: }
201: stack.push(toReturn);
202: }
203:
204: @Override
205: public void endVisit(JsPostfixOperation x,
206: JsContext<JsExpression> ctx) {
207: JsPostfixOperation toReturn = new JsPostfixOperation(x
208: .getOperator());
209: toReturn.setArg(stack.pop());
210: stack.push(toReturn);
211: }
212:
213: @Override
214: public void endVisit(JsPrefixOperation x,
215: JsContext<JsExpression> ctx) {
216: JsPrefixOperation toReturn = new JsPrefixOperation(x
217: .getOperator());
218: toReturn.setArg(stack.pop());
219: stack.push(toReturn);
220: }
221:
222: @Override
223: public void endVisit(JsRegExp x, JsContext<JsExpression> ctx) {
224: stack.push(x);
225: }
226:
227: @Override
228: public void endVisit(JsStringLiteral x,
229: JsContext<JsExpression> ctx) {
230: stack.push(x);
231: }
232:
233: /**
234: * A "this" reference can only effectively be hoisted if the call site is
235: * qualified by a JsNameRef, so we'll ignore this for now.
236: */
237: @Override
238: public void endVisit(JsThisRef x, JsContext<JsExpression> ctx) {
239: // Set a flag to indicate that we cannot continue, and push a null so
240: // we don't run out of elements on the stack.
241: successful = false;
242: stack.push(null);
243: }
244:
245: public JsExpression getExpression() {
246: return (successful && checkStack()) ? stack.peek() : null;
247: }
248:
249: private boolean checkStack() {
250: if (stack.size() > 1) {
251: throw new InternalCompilerException(
252: "Too many expressions on stack");
253: }
254:
255: return stack.size() == 1;
256: }
257: }
258:
259: /**
260: * Given a JsStatement, construct an expression to hoist into the outer
261: * caller. This does not perform any name replacement, nor does it verify the
262: * scope of referenced elements, but simply constructs a mutable copy of the
263: * expression that can be manipulated at-will.
264: *
265: * @return A copy of the original expression, or <code>null</code> if the
266: * expression cannot be hoisted.
267: */
268: public static JsExpression hoist(JsExpression expression) {
269: if (expression == null) {
270: return null;
271: }
272:
273: Cloner c = new Cloner();
274: c.accept(expression);
275: return c.getExpression();
276: }
277:
278: private JsHoister() {
279: }
280: }
|