Line data Source code
1 : // Copyright 2026 The Authors. See the AUTHORS file for details.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // https://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : import 'parse.dart';
16 :
17 : /// A visitor function that is called for each node in the parse tree.
18 : /// Return false to stop traversal, true to continue.
19 : typedef ParseTreeVisitor = bool Function(ParseTreeNode node);
20 :
21 : /// Represents a node in the parse tree with its full context
22 : class ParseTreeNode {
23 : /// The parse result for this node
24 : final ParseResult result;
25 :
26 : /// The parent node, null for root
27 : final ParseTreeNode? parent;
28 :
29 : /// The depth of this node in the tree (0 = root)
30 : final int depth;
31 :
32 2 : ParseTreeNode(this.result, {this.parent, this.depth = 0});
33 :
34 : /// Get the lexeme (matched text) for this node
35 3 : String? get lexeme => result.lexeme;
36 :
37 : /// Get the rule name that created this node
38 6 : String? get ruleName => result.ruleName;
39 :
40 : /// Get the parser element name
41 3 : String? get element => result.element;
42 :
43 : /// Get all child nodes
44 2 : List<ParseTreeNode> get children {
45 4 : return result.stack
46 2 : .toList()
47 10 : .map((r) => ParseTreeNode(r, parent: this, depth: depth + 1))
48 2 : .toList();
49 : }
50 :
51 : /// Returns true if this node has any children
52 0 : bool get hasChildren => result.stack.isNotEmpty;
53 :
54 1 : @override
55 : String toString() {
56 1 : final sb = StringBuffer();
57 3 : sb.write(' ' * depth);
58 4 : if (ruleName != null) sb.write('$ruleName: ');
59 4 : if (lexeme != null) sb.write('"$lexeme"');
60 4 : if (element != null) sb.write(' ($element)');
61 2 : if (!result.success) sb.write(' <FAILED>');
62 1 : return sb.toString();
63 : }
64 : }
65 :
66 : /// A walker that traverses a parse tree created by a successful parse
67 : class ParseTreeWalker {
68 : /// The root node of the parse tree
69 : final ParseTreeNode root;
70 :
71 : /// Create a new walker from a successful parse result
72 : /// Throws if the parse result indicates failure
73 4 : ParseTreeWalker(ParseResult parseResult) : root = ParseTreeNode(parseResult);
74 :
75 : /// Visit all nodes in the tree in pre-order (parent before children)
76 : /// Returns false if traversal was stopped by the visitor
77 1 : bool visitPreOrder(ParseTreeVisitor visitor) {
78 2 : return _visitPreOrder(root, visitor);
79 : }
80 :
81 1 : bool _visitPreOrder(ParseTreeNode node, ParseTreeVisitor visitor) {
82 1 : if (!visitor(node)) return false;
83 :
84 2 : for (final child in node.children) {
85 1 : if (!_visitPreOrder(child, visitor)) return false;
86 : }
87 :
88 : return true;
89 : }
90 :
91 : /// Visit all nodes in the tree in post-order (children before parent)
92 : /// Returns false if traversal was stopped by the visitor
93 1 : bool visitPostOrder(ParseTreeVisitor visitor) {
94 2 : return _visitPostOrder(root, visitor);
95 : }
96 :
97 1 : bool _visitPostOrder(ParseTreeNode node, ParseTreeVisitor visitor) {
98 2 : for (final child in node.children) {
99 1 : if (!_visitPostOrder(child, visitor)) return false;
100 : }
101 :
102 1 : return visitor(node);
103 : }
104 :
105 : /// Find all nodes matching a given rule name
106 0 : List<ParseTreeNode> findByRuleName(String ruleName) {
107 0 : final matches = <ParseTreeNode>[];
108 0 : visitPreOrder((node) {
109 0 : if (node.ruleName == ruleName) matches.add(node);
110 : return true;
111 : });
112 : return matches;
113 : }
114 :
115 : /// Find the first node matching a given rule name
116 0 : ParseTreeNode? findFirstByRuleName(String ruleName) {
117 : ParseTreeNode? match;
118 0 : visitPreOrder((node) {
119 0 : if (node.ruleName == ruleName) {
120 : match = node;
121 : return false;
122 : }
123 : return true;
124 : });
125 : return match;
126 : }
127 :
128 : /// Get a string representation of the parse tree
129 1 : @override
130 : String toString() {
131 1 : final sb = StringBuffer();
132 2 : visitPreOrder((node) {
133 2 : sb.writeln(node.toString());
134 : return true;
135 : });
136 1 : return sb.toString();
137 : }
138 : }
|