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[] ruleNamesList = recog ? recog.getRuleNames() : null;
59         return toStringTree(t, ruleNamesList);
60     }
61 
62     /**
63      * @uml
64      * Print out a whole tree in LISP form. {@link #getNodeText} is used on the
65      * node payloads to get the text for the nodes.
66      */
67     public static string toStringTree(Tree t, string[] ruleNames)
68     {
69         string s = Utils.escapeWhitespace(getNodeText(t, ruleNames), false);
70         if ( t.getChildCount()==0 )
71             return s;
72         auto buf = appender!(string);
73         buf.put("(");
74         buf.put(s);
75         buf.put(' ');
76         for (int i = 0; i<t.getChildCount(); i++) {
77             if ( i>0 )
78                 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[] ruleNamesList = recog !is null ? recog.getRuleNames() : null;
88         return getNodeText(t, ruleNamesList);
89     }
90 
91     public static string getNodeText(Tree t, string[] ruleNames)
92     {
93         if (ruleNames) {
94             if (cast(RuleContext)t) {
95                 auto ruleIndex = (cast(RuleContext)t).getRuleContext().getRuleIndex();
96                 string ruleName = ruleNames[ruleIndex];
97                 int altNumber = (cast(RuleContext)t).getAltNumber();
98                 if (altNumber != ATN.INVALID_ALT_NUMBER) {
99                     return ruleName ~ ":" ~ to!string(altNumber);
100                 }
101                 return ruleName;
102             }
103             else if (cast(ErrorNode)t) {
104                 import std.format : format;
105                 return format!"%s"(t);
106             }
107             else if (cast(TerminalNode)t) {
108                 Token symbol = (cast(TerminalNode)t).getSymbol();
109                 if (symbol !is null) {
110                     string s = to!string(symbol.getText);
111                     return s;
112                 }
113             }
114         }
115         // no recog for rule names
116         Object payload = t.getPayload();
117         if (cast(Token)payload) {
118             return to!string((cast(Token)payload).getText);
119         }
120         return t.getPayload().toString();
121     }
122 
123     /**
124      * @uml
125      * Return ordered list of all children of this node
126      */
127     public static Tree[] getChildren(Tree t)
128     {
129         Tree[] kids;
130         for (int i=0; i<t.getChildCount(); i++) {
131             kids ~= t.getChild(i);
132         }
133         return kids;
134     }
135 
136     /**
137      * @uml
138      * Return a list of all ancestors of this node.  The first node of
139      * list is the root and the last is the parent of this node.
140      */
141     public static Tree[] getAncestors(Tree t)
142     {
143         Tree[] emptyTrees;
144         if (t.getParent() is null) return emptyTrees;
145         Tree[] ancestors;
146         t = t.getParent();
147         while (t !is null) {
148             ancestors = t ~ ancestors; // insert at start
149             t = t.getParent();
150         }
151         return ancestors;
152     }
153 
154     /**
155      * @uml
156      * Return true if t is u's parent or a node on path to root from u.
157      * Use == not equals().
158      */
159     public static bool isAncestorOf(Tree t, Tree u)
160     {
161         if (t is null || u is null || t.getParent() is null) return false;
162         Tree p = u.getParent();
163         while (p !is null) {
164             if (t is p) return true;
165             p = p.getParent();
166         }
167         return false;
168     }
169 
170     public static ParseTree[] findAllTokenNodes(ParseTree t, int ttype)
171     {
172         return findAllNodes(t, ttype, true);
173     }
174 
175     public static ParseTree[] findAllRuleNodes(ParseTree t, int ruleIndex)
176     {
177         return findAllNodes(t, ruleIndex, false);
178     }
179 
180     public static ParseTree[] findAllNodes(ParseTree t, int index, bool findTokens)
181     {
182         ParseTree[] nodes;
183         _findAllNodes(t, index, findTokens, nodes);
184         return nodes;
185     }
186 
187     public static void _findAllNodes(ParseTree t, int index, bool findTokens, ParseTree[] nodes)
188     {
189         // check this node (the root) first
190         if ( findTokens && t.classinfo == TerminalNode.classinfo) {
191             TerminalNode tnode = cast(TerminalNode)t;
192             if (tnode.getSymbol().getType() == index) nodes ~= t;
193         }
194         else if (!findTokens && t.classinfo == ParserRuleContext.classinfo) {
195             ParserRuleContext ctx = cast(ParserRuleContext)t;
196             if ( ctx.getRuleIndex() == index ) nodes ~= t;
197         }
198         // check children
199         for (int i = 0; i < t.getChildCount(); i++){
200             _findAllNodes(t.getChild(i), index, findTokens, nodes);
201         }
202     }
203 
204     /**
205      * @uml
206      * Get all descendents; includes t itself.
207      */
208     public static ParseTree[] getDescendants(ParseTree t)
209     {
210         ParseTree[] nodes;
211         nodes ~= t;
212 
213         int n = t.getChildCount();
214         for (int i = 0 ; i < n ; i++){
215             nodes ~= getDescendants(t.getChild(i));
216         }
217         return nodes;
218     }
219 
220     public static ParseTree[] descendants(ParseTree t)
221     {
222         return getDescendants(t);
223     }
224 
225     /**
226      * @uml
227      * Find smallest subtree of t enclosing range startTokenIndex..stopTokenIndex
228      * inclusively using postorder traversal.  Recursive depth-first-search.
229      */
230     public static ParserRuleContext getRootOfSubtreeEnclosingRegion(ParseTree t, int startTokenIndex,
231         int stopTokenIndex)
232     {
233         int n = t.getChildCount();
234         for (int i = 0; i<n; i++) {
235             ParseTree child = t.getChild(i);
236             ParserRuleContext r = getRootOfSubtreeEnclosingRegion(child, startTokenIndex, stopTokenIndex);
237             if (r !is null) return r;
238         }
239         if (t.classinfo == ParserRuleContext.classinfo) {
240             ParserRuleContext r = cast(ParserRuleContext)t;
241             if (startTokenIndex >= r.getStart().getTokenIndex() && // is range fully contained in t?
242                 (r.getStop() is null || stopTokenIndex <= r.getStop().getTokenIndex()) )
243                 {
244                     // note: r.getStop()==null likely implies that we bailed out of parser and there's nothing to the right
245                     return r;
246                 }
247         }
248         return null;
249     }
250 
251     /**
252      * @uml
253      * Replace any subtree siblings of root that are completely to left
254      * or right of lookahead range with a CommonToken(Token.INVALID_TYPE,"...")
255      * node. The source interval for t is not altered to suit smaller range!
256      *
257      * WARNING: destructive to t.
258      */
259     public static void stripChildrenOutOfRange(ParserRuleContext t, ParserRuleContext root,
260         int startIndex, int stopIndex)
261     {
262         if (t is null) return;
263         for (int i = 0; i < t.getChildCount(); i++) {
264             ParseTree child = t.getChild(i);
265             Interval range = child.getSourceInterval();
266             if (child.classinfo == ParserRuleContext.classinfo && (range.b < startIndex || range.a > stopIndex) ) {
267                 if (isAncestorOf(child, root)) { // replace only if subtree doesn't have displayed root
268                     Variant v = "...";
269                     CommonToken abbrev = new CommonToken(TokenConstantDefinition.INVALID_TYPE, v);
270                     t.children[i] =  new TerminalNodeImpl(abbrev);
271                 }
272             }
273         }
274     }
275 
276     /**
277      * @uml
278      * Return first node satisfying the pred
279      */
280     public static Tree findNodeSuchThat(Tree t, Predicate!Tree pred)
281     {
282         if (pred.test(t) ) return t;
283 
284         int n = t.getChildCount();
285         for (int i = 0 ; i < n ; i++){
286             Tree u = findNodeSuchThat(t.getChild(i), pred);
287             if (u !is null ) return u;
288         }
289         return null;
290     }
291 
292 }