com.google.gwt.dev.js.EvalFunctionsAtTopScope Maven / Gradle / Ivy
/*
* 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 dontMove = new HashSet();
private final Deque> itrStack = Queues.newArrayDeque();
private JavaToJavaScriptMap java2jsMap;
private final Deque 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 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 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;
}
}