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 com.google.dart.compiler.backend.js.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.List;
030    import java.util.Stack;
031    
032    /**
033     * A visitor for iterating through and modifying an AST.
034     */
035    public class JsVisitorWithContextImpl extends JsVisitorWithContext {
036    
037        private final Stack<JsContext> statementContexts = new Stack<JsContext>();
038    
039        public class ListContext<T extends JsNode> implements JsContext {
040            private List<T> collection;
041            private int index;
042    
043            @Override
044            public boolean canInsert() {
045                return true;
046            }
047    
048            @Override
049            public boolean canRemove() {
050                return true;
051            }
052    
053            @Override
054            public void insertAfter(JsNode node) {
055                //noinspection unchecked
056                collection.add(index + 1, (T) node);
057            }
058    
059            @Override
060            public void insertBefore(JsNode node) {
061                //noinspection unchecked
062                collection.add(index++, (T) node);
063            }
064    
065            @Override
066            public boolean isLvalue() {
067                return false;
068            }
069    
070            @Override
071            public void removeMe() {
072                collection.remove(index--);
073            }
074    
075            @Override
076            public void replaceMe(JsNode node) {
077                checkReplacement(collection.get(index), node);
078                //noinspection unchecked
079                collection.set(index, (T) node);
080            }
081            
082            @Nullable
083            @Override
084            public JsNode getCurrentNode() {
085                if (index < collection.size()) {
086                    return collection.get(index);
087                }
088    
089                return null;
090            }
091    
092            protected void traverse(List<T> collection) {
093                this.collection = collection;
094                for (index = 0; index < collection.size(); ++index) {
095                    T node = collection.get(index);
096                    doTraverse(node, this);
097                }
098            }
099        }
100    
101        private class LvalueContext extends NodeContext<JsExpression> {
102            @Override
103            public boolean isLvalue() {
104                return true;
105            }
106        }
107    
108        @SuppressWarnings("unchecked")
109        private class NodeContext<T extends JsNode> implements JsContext {
110            protected T node;
111    
112            @Override
113            public boolean canInsert() {
114                return false;
115            }
116    
117            @Override
118            public boolean canRemove() {
119                return false;
120            }
121    
122            @Override
123            public void insertAfter(JsNode node) {
124                throw new UnsupportedOperationException();
125            }
126    
127            @Override
128            public void insertBefore(JsNode node) {
129                throw new UnsupportedOperationException();
130            }
131    
132            @Override
133            public boolean isLvalue() {
134                return false;
135            }
136    
137            @Override
138            public void removeMe() {
139                throw new UnsupportedOperationException();
140            }
141    
142            @Override
143            public void replaceMe(JsNode node) {
144                checkReplacement(this.node, node);
145                this.node = (T) node;
146            }
147    
148            @Nullable
149            @Override
150            public JsNode getCurrentNode() {
151                return node;
152            }
153    
154            protected T traverse(T node) {
155                this.node = node;
156                doTraverse(node, this);
157                return this.node;
158            }
159        }
160    
161        protected static void checkReplacement(@SuppressWarnings("UnusedParameters") JsNode origNode, JsNode newNode) {
162            if (newNode == null) throw new RuntimeException("Cannot replace with null");
163        }
164    
165        @Override
166        protected <T extends JsNode> T doAccept(T node) {
167            return new NodeContext<T>().traverse(node);
168        }
169    
170        @Override
171        protected JsExpression doAcceptLvalue(JsExpression expr) {
172            return new LvalueContext().traverse(expr);
173        }
174    
175        @Override
176        protected <T extends JsStatement> JsStatement doAcceptStatement(T statement) {
177            List<JsStatement> statements = new SmartList<JsStatement>(statement);
178            doAcceptStatementList(statements);
179    
180            if (statements.size() == 1) {
181                return statements.get(0);
182            }
183    
184            return new JsBlock(statements);
185        }
186    
187        @Override
188        protected <T extends JsStatement> void doAcceptStatementList(List<T> statements) {
189            ListContext<T> context = new ListContext<T>();
190            statementContexts.push(context);
191            context.traverse(statements);
192            statementContexts.pop();
193        }
194    
195        @Override
196        protected <T extends JsNode> void doAcceptList(List<T> collection) {
197            new ListContext<T>().traverse(collection);
198        }
199    
200        @NotNull
201        protected JsContext getLastStatementLevelContext() {
202            return statementContexts.peek();
203        }
204    
205        @Override
206        protected <T extends JsNode> void doTraverse(T node, JsContext ctx) {
207            node.traverse(this, ctx);
208        }
209    
210    }