/*
* 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 org.jetbrains.kotlin.js.backend.ast;
/**
* Taken from GWT project with modifications.
* Original:
* repository: https://gwt.googlesource.com/gwt
* revision: e32bf0a95029165d9e6ab457c7ee7ca8b07b908c
* file: dev/core/src/com/google/gwt/dev/js/ast/JsModVisitor.java
*/
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* A visitor for iterating through and modifying an AST.
*/
public class JsVisitorWithContextImpl extends JsVisitorWithContext {
private final Stack<JsContext<JsStatement>> statementContexts = new Stack<JsContext<JsStatement>>();
public class ListContext<T extends JsNode> extends JsContext<T> {
private List<T> nodes;
private int index;
// Those are reset in every iteration of traverse()
private final List<T> previous = new SmartList<T>();
private final List<T> next = new SmartList<T>();
private boolean removed = false;
@Override
public <R extends T> void addPrevious(R node) {
previous.add(node);
}
@Override
public <R extends T> void addNext(R node) {
next.add(node);
}
@Override
public void removeMe() {
removed = true;
}
@Override
public <R extends T> void replaceMe(R node) {
checkReplacement(nodes.get(index), node);
nodes.set(index, node);
removed = false;
}
@Nullable
@Override
public T getCurrentNode() {
if (!removed && index < nodes.size()) {
return nodes.get(index);
}
return null;
}
protected void traverse(List<T> nodes) {
assert previous.isEmpty(): "addPrevious() was called before traverse()";
assert next.isEmpty(): "addNext() was called before traverse()";
this.nodes = nodes;
for (index = 0; index < nodes.size(); index++) {
removed = false;
previous.clear();
next.clear();
doTraverse(getCurrentNode(), this);
if (!previous.isEmpty()) {
nodes.addAll(index, previous);
index += previous.size();
}
if (removed) {
nodes.remove(index);
index--;
}
if (!next.isEmpty()) {
nodes.addAll(index + 1, next);
index += next.size();
}
}
}
}
private class LvalueContext extends NodeContext<JsExpression> {
}
private class NodeContext<T extends JsNode> extends JsContext<T> {
protected T node;
@Override
public void removeMe() {
throw new UnsupportedOperationException();
}
@Override
public <R extends T> void replaceMe(R node) {
checkReplacement(this.node, node);
this.node = node;
}
@Nullable
@Override
public T getCurrentNode() {
return node;
}
protected T traverse(T node) {
this.node = node;
doTraverse(node, this);
return this.node;
}
}
protected static void checkReplacement(@SuppressWarnings("UnusedParameters") JsNode origNode, JsNode newNode) {
if (newNode == null) throw new RuntimeException("Cannot replace with null");
}
@Override
protected <T extends JsNode> T doAccept(T node) {
return new NodeContext<T>().traverse(node);
}
@Override
protected JsExpression doAcceptLvalue(JsExpression expr) {
return new LvalueContext().traverse(expr);
}
@Override
protected <T extends JsStatement> JsStatement doAcceptStatement(T statement) {
List<JsStatement> statements = new SmartList<JsStatement>(statement);
doAcceptStatementList(statements);
if (statements.size() == 1) {
return statements.get(0);
}
return new JsBlock(statements);
}
@Override
protected void doAcceptStatementList(List<JsStatement> statements) {
ListContext<JsStatement> context = new ListContext<JsStatement>();
statementContexts.push(context);
context.traverse(statements);
statementContexts.pop();
}
@Override
protected <T extends JsNode> void doAcceptList(List<T> collection) {
new ListContext<T>().traverse(collection);
}
@NotNull
protected JsContext<JsStatement> getLastStatementLevelContext() {
return statementContexts.peek();
}
@Override
protected <T extends JsNode> void doTraverse(T node, JsContext ctx) {
node.traverse(this, ctx);
}
}