001    /*
002     * Copyright 2008 Google Inc.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005     * in compliance with the License. You may obtain a copy of the License at
006     * 
007     * http://www.apache.org/licenses/LICENSE-2.0
008     * 
009     * Unless required by applicable law or agreed to in writing, software distributed under the License
010     * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011     * or implied. See the License for the specific language governing permissions and limitations under
012     * the License.
013     */
014    
015    package org.jetbrains.kotlin.js.backend.ast;
016    
017    /**
018     * Taken from GWT project with modifications.
019     * Original:
020     *  repository: https://gwt.googlesource.com/gwt
021     *  revision: e32bf0a95029165d9e6ab457c7ee7ca8b07b908c
022     *  file: dev/core/src/com/google/gwt/dev/js/ast/JsModVisitor.java
023     */
024    
025    import com.intellij.util.SmartList;
026    import org.jetbrains.annotations.NotNull;
027    import org.jetbrains.annotations.Nullable;
028    
029    import java.util.*;
030    
031    /**
032     * A visitor for iterating through and modifying an AST.
033     */
034    public class JsVisitorWithContextImpl extends JsVisitorWithContext {
035    
036        private final Stack<JsContext<JsStatement>> statementContexts = new Stack<JsContext<JsStatement>>();
037    
038        public class ListContext<T extends JsNode> extends JsContext<T> {
039            private List<T> nodes;
040            private int index;
041    
042            // Those are reset in every iteration of traverse()
043            private final List<T> previous = new SmartList<T>();
044            private final List<T> next = new SmartList<T>();
045            private boolean removed = false;
046    
047            @Override
048            public <R extends T> void addPrevious(R node) {
049                previous.add(node);
050            }
051    
052            @Override
053            public <R extends T> void addNext(R node) {
054                next.add(node);
055            }
056    
057            @Override
058            public void removeMe() {
059                removed = true;
060            }
061    
062            @Override
063            public <R extends T> void replaceMe(R node) {
064                checkReplacement(nodes.get(index), node);
065                nodes.set(index, node);
066                removed = false;
067            }
068            
069            @Nullable
070            @Override
071            public T getCurrentNode() {
072                if (!removed && index < nodes.size()) {
073                    return nodes.get(index);
074                }
075    
076                return null;
077            }
078    
079            protected void traverse(List<T> nodes) {
080                assert previous.isEmpty(): "addPrevious() was called before traverse()";
081                assert next.isEmpty(): "addNext() was called before traverse()";
082                this.nodes = nodes;
083    
084                for (index = 0; index < nodes.size(); index++) {
085                    removed = false;
086                    previous.clear();
087                    next.clear();
088                    doTraverse(getCurrentNode(), this);
089    
090                    if (!previous.isEmpty()) {
091                        nodes.addAll(index, previous);
092                        index += previous.size();
093                    }
094    
095                    if (removed) {
096                        nodes.remove(index);
097                        index--;
098                    }
099    
100                    if (!next.isEmpty()) {
101                        nodes.addAll(index + 1, next);
102                        index += next.size();
103                    }
104                }
105            }
106        }
107    
108        private class LvalueContext extends NodeContext<JsExpression> {
109        }
110    
111        private class NodeContext<T extends JsNode> extends JsContext<T> {
112            protected T node;
113    
114            @Override
115            public void removeMe() {
116                throw new UnsupportedOperationException();
117            }
118    
119            @Override
120            public <R extends T> void replaceMe(R node) {
121                checkReplacement(this.node, node);
122                this.node = node;
123            }
124    
125            @Nullable
126            @Override
127            public T getCurrentNode() {
128                return node;
129            }
130    
131            protected T traverse(T node) {
132                this.node = node;
133                doTraverse(node, this);
134                return this.node;
135            }
136        }
137    
138        protected static void checkReplacement(@SuppressWarnings("UnusedParameters") JsNode origNode, JsNode newNode) {
139            if (newNode == null) throw new RuntimeException("Cannot replace with null");
140        }
141    
142        @Override
143        protected <T extends JsNode> T doAccept(T node) {
144            return new NodeContext<T>().traverse(node);
145        }
146    
147        @Override
148        protected JsExpression doAcceptLvalue(JsExpression expr) {
149            return new LvalueContext().traverse(expr);
150        }
151    
152        @Override
153        protected <T extends JsStatement> JsStatement doAcceptStatement(T statement) {
154            List<JsStatement> statements = new SmartList<JsStatement>(statement);
155            doAcceptStatementList(statements);
156    
157            if (statements.size() == 1) {
158                return statements.get(0);
159            }
160    
161            return new JsBlock(statements);
162        }
163    
164        @Override
165        protected void doAcceptStatementList(List<JsStatement> statements) {
166            ListContext<JsStatement> context = new ListContext<JsStatement>();
167            statementContexts.push(context);
168            context.traverse(statements);
169            statementContexts.pop();
170        }
171    
172        @Override
173        protected <T extends JsNode> void doAcceptList(List<T> collection) {
174            new ListContext<T>().traverse(collection);
175        }
176    
177        @NotNull
178        protected JsContext<JsStatement> getLastStatementLevelContext() {
179            return statementContexts.peek();
180        }
181    
182        @Override
183        protected <T extends JsNode> void doTraverse(T node, JsContext ctx) {
184            node.traverse(this, ctx);
185        }
186    
187    }