| /* |
| * Copyright 2009 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.impl.JavaToJavaScriptMap; |
| import com.google.gwt.dev.js.ast.JsBlock; |
| import com.google.gwt.dev.js.ast.JsContext; |
| import com.google.gwt.dev.js.ast.JsExprStmt; |
| import com.google.gwt.dev.js.ast.JsFunction; |
| import com.google.gwt.dev.js.ast.JsModVisitor; |
| import com.google.gwt.dev.js.ast.JsProgram; |
| import com.google.gwt.dev.js.ast.JsProgramFragment; |
| import com.google.gwt.dev.js.ast.JsStatement; |
| import com.google.gwt.thirdparty.guava.common.collect.Queues; |
| |
| import java.util.Deque; |
| import java.util.HashSet; |
| import java.util.ListIterator; |
| import java.util.Set; |
| |
| /** |
| * Force all functions to be evaluated at the top of the lexical scope in which |
| * they reside. This makes {@link StaticEvalVisitor} simpler in that we no |
| * longer have to worry about function declarations within expressions. After |
| * this runs, only statements can contain declarations. Moved functions will end |
| * up just before the statement in which they presently reside. |
| */ |
| public class EvalFunctionsAtTopScope extends JsModVisitor { |
| |
| public static void exec(JsProgram jsProgram, JavaToJavaScriptMap map) { |
| EvalFunctionsAtTopScope fev = new EvalFunctionsAtTopScope(map); |
| fev.accept(jsProgram); |
| } |
| |
| private JsStatement currentStatement; |
| |
| private final Set<JsFunction> dontMove = new HashSet<JsFunction>(); |
| |
| private final Deque<ListIterator<JsStatement>> itrStack = Queues.newArrayDeque(); |
| |
| private JavaToJavaScriptMap java2jsMap; |
| |
| private final Deque<JsBlock> scopeStack = Queues.newArrayDeque(); |
| |
| public EvalFunctionsAtTopScope(JavaToJavaScriptMap java2jsMap) { |
| this.java2jsMap = java2jsMap; |
| } |
| |
| @Override |
| public void endVisit(JsExprStmt x, JsContext ctx) { |
| currentStatement = null; |
| } |
| |
| @Override |
| public void endVisit(JsFunction x, JsContext ctx) { |
| scopeStack.pop(); |
| } |
| |
| @Override |
| public void endVisit(JsProgram x, JsContext ctx) { |
| scopeStack.pop(); |
| } |
| |
| @Override |
| public void endVisit(JsProgramFragment x, JsContext ctx) { |
| scopeStack.pop(); |
| } |
| |
| @Override |
| public boolean visit(JsBlock x, JsContext ctx) { |
| if (x == scopeStack.peek()) { |
| ListIterator<JsStatement> itr = x.getStatements().listIterator(); |
| itrStack.push(itr); |
| while (itr.hasNext()) { |
| JsStatement stmt = itr.next(); |
| JsFunction func = JsUtils.isFunctionDeclaration(stmt); |
| // Already at the top level. |
| if (func != null) { |
| dontMove.add(func); |
| } |
| accept(stmt); |
| if (func != null) { |
| dontMove.remove(func); |
| } |
| } |
| itrStack.pop(); |
| // Already visited. |
| return false; |
| } else { |
| // Just do normal visitation. |
| return true; |
| } |
| } |
| |
| @Override |
| public boolean visit(JsExprStmt x, JsContext ctx) { |
| currentStatement = x; |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JsFunction x, JsContext ctx) { |
| /* |
| * We do this during visit() to preserve first-to-last evaluation order. We |
| * check if this function is a vtable declaration and don't move functions |
| * used in other expressions or are in vtable assignments. |
| */ |
| if (x.getName() != null && x.getName().getNamespace() == null && !dontMove.contains(x) |
| && !isMethodDefinition(currentStatement)) { |
| /* |
| * Reinsert this function into the statement immediately before the |
| * current statement. The current statement will have already been |
| * returned from the current iterator's next(), so we have to backshuffle |
| * one step to get in front of it. |
| */ |
| ListIterator<JsStatement> itr = itrStack.peek(); |
| itr.previous(); |
| itr.add(x.makeStmt()); |
| itr.next(); |
| ctx.replaceMe(x.getName().makeRef(x.getSourceInfo().makeChild())); |
| } |
| |
| // Dive into the function itself. |
| scopeStack.push(x.getBody()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JsProgram x, JsContext ctx) { |
| scopeStack.push(x.getGlobalBlock()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JsProgramFragment x, JsContext ctx) { |
| scopeStack.push(x.getGlobalBlock()); |
| return true; |
| } |
| |
| private boolean isMethodDefinition(JsStatement currentStatement) { |
| return java2jsMap.methodForStatement(currentStatement) != null; |
| } |
| } |