1 /*
2  * Copyright (c) 2012-2020 The ANTLR Project. All rights reserved.
3  * Use of this file is governed by the BSD 3-clause license that
4  * can be found in the LICENSE.txt file in the project root.
5  */
6 
7 module antlr.v4.runtime.tree.Trees;
8 
9 import std.array;
10 import std.conv;
11 import std.variant;
12 import antlr.v4.runtime.InterfaceRecognizer;
13 import antlr.v4.runtime.ParserRuleContext;
14 import antlr.v4.runtime.RuleContext;
15 import antlr.v4.runtime.CommonToken;
16 import antlr.v4.runtime.Token;
17 import antlr.v4.runtime.TokenConstantDefinition;
18 import antlr.v4.runtime.atn.ATN;
19 import antlr.v4.runtime.tree.TerminalNode;
20 import antlr.v4.runtime.tree.ErrorNode;
21 import antlr.v4.runtime.tree.Tree;
22 import antlr.v4.runtime.tree.TerminalNodeImpl;
23 import antlr.v4.runtime.tree.ParseTree;
24 import antlr.v4.runtime.misc.Utils;
25 import antlr.v4.runtime.misc.Interval;
26 import antlr.v4.runtime.misc.Predicate;
27 
28 /**
29  * @uml
30  * A set of utility routines useful for all kinds of ANTLR trees
31  */
32 class Trees
33 {
34 
35     private this()
36     {
37     }
38 
39     /**
40      * @uml
41      * Print out a whole tree in LISP form. {@link #getNodeText} is used on the
42      * node payloads to get the text for the nodes.  Detect
43      * parse trees and extract data appropriately.
44      */
45     public static string toStringTree(Tree t)
46     {
47         return toStringTree(t, cast(string[])null);
48     }
49 
50     /**
51      * @uml
52      * Print out a whole tree in LISP form. {@link #getNodeText} is used on the
53      * node payloads to get the text for the nodes.  Detect
54      * parse trees and extract data appropriately.
55      */
56     public static string toStringTree(Tree t, InterfaceRecognizer recog)
57     {
58         string[] ruleNames = recog !is null ? recog.getRuleNames() : null;
59         string[] ruleNamesList = ruleNames !is null ? ruleNames : null;
60         return toStringTree(t, ruleNamesList);
61     }
62 
63     /**
64      * @uml
65      * rint out a whole tree in LISP form. {@link #getNodeText} is used on the
66      * node payloads to get the text for the nodes.
67      */
68     public static string toStringTree(Tree t, string[] ruleNames)
69     {
70         string s = Utils.escapeWhitespace(getNodeText(t, ruleNames), false);
71         if ( t.getChildCount()==0 ) return s;
72         auto buf = appender!(string);
73         buf.put("(");
74         s = Utils.escapeWhitespace(getNodeText(t, ruleNames), false);
75         buf.put(s);
76         buf.put(' ');
77         for (int i = 0; i<t.getChildCount(); i++) {
78             if ( i>0 ) buf.put(' ');
79             buf.put(toStringTree(t.getChild(i), ruleNames));
80         }
81         buf.put(")");
82         return buf.data;
83     }
84 
85     public static string getNodeText(Tree t, InterfaceRecognizer recog)
86     {
87         string[] ruleNames = recog !is null ? recog.getRuleNames() : null;
88         string[] ruleNamesList = ruleNames !is null ? ruleNames : null;
89         return getNodeText(t, ruleNamesList);
90 
91     }
92 
93     public static string getNodeText(Tree t, string[] ruleNames)
94     {
95         if (ruleNames) {
96             if (cast(RuleContext)t) {
97                 auto ruleIndex = (cast(RuleContext)t).getRuleContext().getRuleIndex();
98                 string ruleName = ruleNames[ruleIndex];
99                 int altNumber = (cast(RuleContext)t).getAltNumber();
100                 if (altNumber != ATN.INVALID_ALT_NUMBER) {
101                     return ruleName ~ ":" ~ to!string(altNumber);
102                 }
103                 return ruleName;
104             }
105             else if (cast(ErrorNode)t) {
106                 return "..."; // t.toString();
107             }
108             else if (cast(TerminalNode)t) {
109                 Token symbol = (cast(TerminalNode)t).getSymbol();
110                 if (symbol !is null) {
111                     string s = to!string(symbol.getText);
112                     return s;
113                 }
114             }
115         }
116         // no recog for rule names
117         Object payload = t.getPayload();
118         if (cast(Token)payload) {
119             return to!string((cast(Token)payload).getText);
120         }
121         return t.getPayload().toString();
122     }
123 
124     /**
125      * @uml
126      * Return ordered list of all children of this node
127      */
128     public static Tree[] getChildren(Tree t)
129     {
130         Tree[] kids;
131         for (int i=0; i<t.getChildCount(); i++) {
132             kids ~= t.getChild(i);
133         }
134         return kids;
135     }
136 
137     /**
138      * @uml
139      * Return a list of all ancestors of this node.  The first node of
140      * list is the root and the last is the parent of this node.
141      */
142     public static Tree[] getAncestors(Tree t)
143     {
144         Tree[] emptyTrees;
145         if (t.getParent() is null) return emptyTrees;
146         Tree[] ancestors;
147         t = t.getParent();
148         while (t !is null) {
149             ancestors = t ~ ancestors; // insert at start
150             t = t.getParent();
151         }
152         return ancestors;
153     }
154 
155     /**
156      * @uml
157      * Return true if t is u's parent or a node on path to root from u.
158      * Use == not equals().
159      */
160     public static bool isAncestorOf(Tree t, Tree u)
161     {
162         if (t is null || u is null || t.getParent() is null) return false;
163         Tree p = u.getParent();
164         while (p !is null) {
165             if (t is p) return true;
166             p = p.getParent();
167         }
168         return false;
169     }
170 
171     public static ParseTree[] findAllTokenNodes(ParseTree t, int ttype)
172     {
173         return findAllNodes(t, ttype, true);
174     }
175 
176     public static ParseTree[] findAllRuleNodes(ParseTree t, int ruleIndex)
177     {
178         return findAllNodes(t, ruleIndex, false);
179     }
180 
181     public static ParseTree[] findAllNodes(ParseTree t, int index, bool findTokens)
182     {
183         ParseTree[] nodes;
184         _findAllNodes(t, index, findTokens, nodes);
185         return nodes;
186     }
187 
188     public static void _findAllNodes(ParseTree t, int index, bool findTokens, ParseTree[] nodes)
189     {
190         // check this node (the root) first
191         if ( findTokens && t.classinfo == TerminalNode.classinfo) {
192             TerminalNode tnode = cast(TerminalNode)t;
193             if (tnode.getSymbol().getType() == index) nodes ~= t;
194         }
195         else if (!findTokens && t.classinfo == ParserRuleContext.classinfo) {
196             ParserRuleContext ctx = cast(ParserRuleContext)t;
197             if ( ctx.getRuleIndex() == index ) nodes ~= t;
198         }
199         // check children
200         for (int i = 0; i < t.getChildCount(); i++){
201             _findAllNodes(t.getChild(i), index, findTokens, nodes);
202         }
203     }
204 
205     /**
206      * @uml
207      * Get all descendents; includes t itself.
208      */
209     public static ParseTree[] getDescendants(ParseTree t)
210     {
211         ParseTree[] nodes;
212         nodes ~= t;
213 
214         int n = t.getChildCount();
215         for (int i = 0 ; i < n ; i++){
216             nodes ~= getDescendants(t.getChild(i));
217         }
218         return nodes;
219     }
220 
221     public static ParseTree[] descendants(ParseTree t)
222     {
223         return getDescendants(t);
224     }
225 
226     /**
227      * @uml
228      * Find smallest subtree of t enclosing range startTokenIndex..stopTokenIndex
229      * inclusively using postorder traversal.  Recursive depth-first-search.
230      */
231     public static ParserRuleContext getRootOfSubtreeEnclosingRegion(ParseTree t, int startTokenIndex,
232         int stopTokenIndex)
233     {
234         int n = t.getChildCount();
235         for (int i = 0; i<n; i++) {
236             ParseTree child = t.getChild(i);
237             ParserRuleContext r = getRootOfSubtreeEnclosingRegion(child, startTokenIndex, stopTokenIndex);
238             if (r !is null) return r;
239         }
240         if (t.classinfo == ParserRuleContext.classinfo) {
241             ParserRuleContext r = cast(ParserRuleContext)t;
242             if (startTokenIndex >= r.getStart().getTokenIndex() && // is range fully contained in t?
243                 (r.getStop() is null || stopTokenIndex <= r.getStop().getTokenIndex()) )
244                 {
245                     // note: r.getStop()==null likely implies that we bailed out of parser and there's nothing to the right
246                     return r;
247                 }
248         }
249         return null;
250     }
251 
252     /**
253      * @uml
254      * Replace any subtree siblings of root that are completely to left
255      * or right of lookahead range with a CommonToken(Token.INVALID_TYPE,"...")
256      * node. The source interval for t is not altered to suit smaller range!
257      *
258      * WARNING: destructive to t.
259      */
260     public static void stripChildrenOutOfRange(ParserRuleContext t, ParserRuleContext root,
261         int startIndex, int stopIndex)
262     {
263         if (t is null) return;
264         for (int i = 0; i < t.getChildCount(); i++) {
265             ParseTree child = t.getChild(i);
266             Interval range = child.getSourceInterval();
267             if (child.classinfo == ParserRuleContext.classinfo && (range.b < startIndex || range.a > stopIndex) ) {
268                 if (isAncestorOf(child, root)) { // replace only if subtree doesn't have displayed root
269                     Variant v = "...";
270                     CommonToken abbrev = new CommonToken(TokenConstantDefinition.INVALID_TYPE, v);
271                     t.children[i] =  new TerminalNodeImpl(abbrev);
272                 }
273             }
274         }
275     }
276 
277     /**
278      * @uml
279      * Return first node satisfying the pred
280      */
281     public static Tree findNodeSuchThat(Tree t, Predicate!Tree pred)
282     {
283         if (pred.test(t) ) return t;
284 
285         int n = t.getChildCount();
286         for (int i = 0 ; i < n ; i++){
287             Tree u = findNodeSuchThat(t.getChild(i), pred);
288             if (u !is null ) return u;
289         }
290         return null;
291     }
292 
293 }