blob: 2fb9af9a25215a4f8ad05942e1def8930d003f1e [file] [log] [blame]
/*
* 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.jjs.ast;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.util.collect.Lists;
import java.util.List;
/**
* A visitor for iterating through and modifying an AST.
*/
public class JModVisitor extends JVisitor {
/**
* Context for traversing a mutable list.
*/
@SuppressWarnings("unchecked")
private class ListContext<T extends JNode> implements Context {
int index;
final List<T> list;
boolean removed;
boolean replaced;
public ListContext(List<T> list) {
this.list = list;
}
public boolean canInsert() {
return true;
}
public boolean canRemove() {
return true;
}
public void insertAfter(JNode node) {
checkRemoved();
list.add(index + 1, (T) node);
++numVisitorChanges;
}
public void insertBefore(JNode node) {
checkRemoved();
list.add(index++, (T) node);
++numVisitorChanges;
}
public boolean isLvalue() {
return false;
}
public void removeMe() {
checkState();
list.remove(index--);
removed = true;
++numVisitorChanges;
}
public void replaceMe(JNode node) {
checkState();
checkReplacement(list.get(index), node);
list.set(index, (T) node);
replaced = true;
++numVisitorChanges;
}
/**
* Cause my list to be traversed by this context.
*/
protected List<T> traverse() {
try {
for (index = 0; index < list.size(); ++index) {
removed = replaced = false;
list.get(index).traverse(JModVisitor.this, this);
}
return list;
} catch (Throwable e) {
throw translateException(list.get(index), e);
}
}
private void checkRemoved() {
if (removed) {
throw new InternalCompilerException("Node was already removed");
}
}
private void checkState() {
checkRemoved();
if (replaced) {
throw new InternalCompilerException("Node was already replaced");
}
}
}
/**
* Context for traversing an immutable list.
*/
@SuppressWarnings("unchecked")
private class ListContextImmutable<T extends JNode> implements Context {
int index;
List<T> list;
boolean removed;
boolean replaced;
public ListContextImmutable(List<T> list) {
this.list = list;
}
public boolean canInsert() {
return true;
}
public boolean canRemove() {
return true;
}
public void insertAfter(JNode node) {
checkRemoved();
list = Lists.add(list, index + 1, (T) node);
++numVisitorChanges;
}
public void insertBefore(JNode node) {
checkRemoved();
list = Lists.add(list, index++, (T) node);
++numVisitorChanges;
}
public boolean isLvalue() {
return false;
}
public void removeMe() {
checkState();
list = Lists.remove(list, index--);
removed = true;
++numVisitorChanges;
}
public void replaceMe(JNode node) {
checkState();
checkReplacement(list.get(index), node);
list = Lists.set(list, index, (T) node);
replaced = true;
++numVisitorChanges;
}
/**
* Cause my list to be traversed by this context.
*/
protected List<T> traverse() {
try {
for (index = 0; index < list.size(); ++index) {
removed = replaced = false;
list.get(index).traverse(JModVisitor.this, this);
}
return list;
} catch (Throwable e) {
throw translateException(list.get(index), e);
}
}
private void checkRemoved() {
if (removed) {
throw new InternalCompilerException("Node was already removed");
}
}
private void checkState() {
checkRemoved();
if (replaced) {
throw new InternalCompilerException("Node was already replaced");
}
}
}
private class LvalueContext extends NodeContext {
public LvalueContext() {
super(false);
}
@Override
public boolean isLvalue() {
return true;
}
}
private class NodeContext implements Context {
boolean canRemove;
JNode node;
boolean replaced;
public NodeContext(boolean canRemove) {
this.canRemove = canRemove;
}
public boolean canInsert() {
return false;
}
public boolean canRemove() {
return this.canRemove;
}
public void insertAfter(JNode node) {
throw new UnsupportedOperationException("Can't insert after " + node);
}
public void insertBefore(JNode node) {
throw new UnsupportedOperationException("Can't insert before " + node);
}
public boolean isLvalue() {
return false;
}
public void removeMe() {
if (!canRemove) {
throw new UnsupportedOperationException("Can't remove " + node);
}
this.node = null;
++numVisitorChanges;
}
public void replaceMe(JNode node) {
if (replaced) {
throw new InternalCompilerException("Node was already replaced");
}
checkReplacement(this.node, node);
this.node = node;
replaced = true;
++numVisitorChanges;
}
}
protected static void checkReplacement(JNode origNode, JNode newNode) {
if (newNode == null) {
throw new InternalCompilerException("Cannot replace with null");
}
if (newNode == origNode) {
throw new InternalCompilerException("The replacement is the same as the original");
}
}
private int numVisitorChanges = 0;
@Override
public JNode accept(JNode node) {
return accept(node, false);
}
@Override
public JNode accept(JNode node, boolean allowRemove) {
NodeContext ctx = new NodeContext(allowRemove);
try {
ctx.node = node;
traverse(node, ctx);
return ctx.node;
} catch (Throwable e) {
throw translateException(node, e);
}
}
@Override
@SuppressWarnings("unchecked")
public <T extends JNode> void accept(List<T> list) {
NodeContext ctx = new NodeContext(false);
try {
for (int i = 0, c = list.size(); i < c; ++i) {
ctx.node = list.get(i);
traverse(ctx.node, ctx);
if (ctx.replaced) {
list.set(i, (T) ctx.node);
ctx.replaced = false;
}
}
} catch (Throwable e) {
throw translateException(ctx.node, e);
}
}
@SuppressWarnings("unchecked")
@Override
public <T extends JNode> List<T> acceptImmutable(List<T> list) {
NodeContext ctx = new NodeContext(false);
try {
for (int i = 0, c = list.size(); i < c; ++i) {
ctx.node = list.get(i);
traverse(ctx.node, ctx);
if (ctx.replaced) {
list = Lists.set(list, i, (T) ctx.node);
ctx.replaced = false;
}
}
return list;
} catch (Throwable e) {
throw translateException(ctx.node, e);
}
}
@Override
public JExpression acceptLvalue(JExpression expr) {
LvalueContext ctx = new LvalueContext();
try {
ctx.node = expr;
traverse(expr, ctx);
return (JExpression) ctx.node;
} catch (Throwable e) {
throw translateException(expr, e);
}
}
@Override
public <T extends JNode> void acceptWithInsertRemove(List<T> list) {
new ListContext<T>(list).traverse();
}
@Override
public <T extends JNode> List<T> acceptWithInsertRemoveImmutable(List<T> list) {
return new ListContextImmutable<T>(list).traverse();
}
@Override
public final boolean didChange() {
return numVisitorChanges > 0;
}
/**
* Returns the number of times the tree was changed since this visitor was
* instantiated.
*/
public int getNumMods() {
return numVisitorChanges;
}
/**
* Call this method to indicate that a visitor has made a change to the tree.
* Used to determine when the optimization pass can exit.
*/
protected void madeChanges() {
++numVisitorChanges;
}
protected void traverse(JNode node, Context context) {
node.traverse(this, context);
}
}