1use cairo_lang_syntax as syntax;
2use cairo_lang_syntax::node::SyntaxNode;
3use cairo_lang_syntax::node::db::SyntaxGroup;
4use cairo_lang_syntax::node::kind::SyntaxKind;
5use cairo_lang_syntax_codegen::cairo_spec::get_spec;
6use cairo_lang_syntax_codegen::spec::{Member, Node, NodeKind};
7use colored::{ColoredString, Colorize};
8use itertools::zip_eq;
9use smol_str::SmolStr;
10
11pub fn print_tree(
12 db: &dyn SyntaxGroup,
13 syntax_root: &SyntaxNode,
14 print_colors: bool,
15 print_trivia: bool,
16) -> String {
17 let mut printer = Printer::new(db, print_colors, print_trivia);
18 printer.print_tree("root", syntax_root, "", true, true);
19 printer.result
20}
21
22pub fn print_partial_tree(
23 db: &dyn SyntaxGroup,
24 syntax_root: &SyntaxNode,
25 top_level_kind: &str,
26 ignored_kinds: Vec<&str>,
27 print_trivia: bool,
28) -> String {
29 let mut printer = Printer::new_partial(db, top_level_kind, ignored_kinds, print_trivia);
30 let under_top_level = printer.top_level_kind.is_none();
31 printer.print_tree("root", syntax_root, "", true, under_top_level);
32 printer.result
33}
34
35struct Printer<'a> {
36 db: &'a dyn SyntaxGroup,
37 spec: Vec<Node>,
38 print_colors: bool,
39 print_trivia: bool,
40 top_level_kind: Option<String>,
43 ignored_kinds: Vec<String>,
46 result: String,
47}
48impl<'a> Printer<'a> {
49 fn new(db: &'a dyn SyntaxGroup, print_colors: bool, print_trivia: bool) -> Self {
50 Self {
51 db,
52 spec: get_spec(),
53 print_colors,
54 print_trivia,
55 top_level_kind: None,
56 ignored_kinds: Vec::new(),
57 result: String::new(),
58 }
59 }
60
61 fn new_partial(
63 db: &'a dyn SyntaxGroup,
64 top_level_kind: &str,
65 ignored_kinds: Vec<&str>,
66 print_trivia: bool,
67 ) -> Self {
68 Self {
69 db,
70 spec: get_spec(),
71 print_colors: false,
72 print_trivia,
73 top_level_kind: if top_level_kind.trim().is_empty() {
74 None
75 } else {
76 Some(top_level_kind.to_string())
77 },
78 ignored_kinds: ignored_kinds.into_iter().map(|x| x.to_string()).collect(),
79 result: String::new(),
80 }
81 }
82
83 fn print_tree(
85 &mut self,
86 field_description: &str,
87 syntax_node: &SyntaxNode,
88 indent: &str,
89 is_last: bool,
90 under_top_level: bool,
91 ) {
92 let extra_head_indent = if is_last { "└── " } else { "├── " };
93 let green_node = syntax_node.green_node(self.db);
94 match &green_node.details {
95 syntax::node::green::GreenNodeDetails::Token(text) => {
96 if under_top_level {
97 self.print_token_node(
98 field_description,
99 indent,
100 extra_head_indent,
101 text.clone(),
102 green_node.kind,
103 )
104 }
105 }
106 syntax::node::green::GreenNodeDetails::Node { .. } => {
107 self.print_internal_node(
108 field_description,
109 indent,
110 extra_head_indent,
111 is_last,
112 syntax_node,
113 green_node.kind,
114 under_top_level,
115 );
116 }
117 }
118 }
119
120 fn print_token_node(
121 &mut self,
122 field_description: &str,
123 indent: &str,
124 extra_head_indent: &str,
125 text: SmolStr,
126 kind: SyntaxKind,
127 ) {
128 let text = if kind == SyntaxKind::TokenMissing {
129 format!("{}: {}", self.blue(field_description.into()), self.red("Missing".into()))
130 } else {
131 let token_text = match kind {
132 SyntaxKind::TokenWhitespace
133 | SyntaxKind::TokenNewline
134 | SyntaxKind::TokenEndOfFile => ".".to_string(),
135 _ => format!(": '{}'", self.green(self.bold(text.as_str().into()))),
136 };
137 format!("{} (kind: {:?}){token_text}", self.blue(field_description.into()), kind)
138 };
139 self.result.push_str(format!("{indent}{extra_head_indent}{text}\n").as_str());
140 }
141
142 #[allow(clippy::too_many_arguments)]
144 fn print_internal_node(
145 &mut self,
146 field_description: &str,
147 indent: &str,
148 extra_head_indent: &str,
149 is_last: bool,
150 syntax_node: &SyntaxNode,
151 kind: SyntaxKind,
152 under_top_level: bool,
153 ) {
154 let current_is_top_level =
155 !under_top_level && self.top_level_kind == Some(format!("{kind:?}"));
156 let (under_top_level, indent) =
158 if current_is_top_level { (true, "") } else { (under_top_level, indent) };
159
160 if !self.print_trivia {
161 if let Some(token_node) = syntax_node.get_terminal_token(self.db) {
162 self.print_tree(field_description, &token_node, indent, is_last, under_top_level);
163 return;
164 }
165 }
166
167 let extra_info = if is_missing_kind(kind) {
168 format!(": {}", self.red("Missing".into()))
169 } else {
170 format!(" (kind: {kind:?})")
171 };
172
173 let children = self.db.get_children(syntax_node.clone());
174 let num_children = children.len();
175 let suffix = if self.ignored_kinds.contains(&format!("{kind:?}")) {
176 " <ignored>".to_string()
177 } else if num_children == 0 {
178 self.bright_purple(" []".into()).to_string()
179 } else {
180 String::new()
181 };
182
183 if under_top_level {
185 if current_is_top_level {
186 self.result.push_str(format!("└── Top level kind: {kind:?}{suffix}\n").as_str());
187 } else {
188 self.result.push_str(
189 format!(
190 "{indent}{extra_head_indent}{}{extra_info}{suffix}\n",
191 self.cyan(field_description.into())
192 )
193 .as_str(),
194 );
195 }
196 }
197
198 if under_top_level && self.ignored_kinds.contains(&format!("{kind:?}")) {
199 return;
200 }
201
202 if num_children == 0 {
203 return;
204 }
205
206 let extra_indent = if is_last || current_is_top_level { " " } else { "│ " };
207 let indent = String::from(indent) + extra_indent;
208 let node_kind = self.get_node_kind(kind.to_string());
209 match node_kind {
210 NodeKind::Struct { members: expected_children }
211 | NodeKind::Terminal { members: expected_children, .. } => {
212 self.print_internal_struct(
213 &children,
214 &expected_children,
215 indent.as_str(),
216 under_top_level,
217 );
218 }
219 NodeKind::List { .. } => {
220 for (i, child) in children.iter().enumerate() {
221 self.print_tree(
222 format!("child #{i}").as_str(),
223 child,
224 indent.as_str(),
225 i == num_children - 1,
226 under_top_level,
227 );
228 }
229 }
230 NodeKind::SeparatedList { .. } => {
231 for (i, child) in children.iter().enumerate() {
232 let description = if i % 2 == 0 { "item" } else { "separator" };
233 self.print_tree(
234 format!("{description} #{}", i / 2).as_str(),
235 child,
236 indent.as_str(),
237 i == num_children - 1,
238 under_top_level,
239 );
240 }
241 }
242 _ => panic!("This should never happen"),
243 }
244 }
245
246 fn print_internal_struct(
249 &mut self,
250 children: &[SyntaxNode],
251 expected_children: &[Member],
252 indent: &str,
253 under_top_level: bool,
254 ) {
255 let (last_child, non_last_children) = children.split_last().unwrap();
256 let (last_expected_child, non_last_expected_children) =
257 expected_children.split_last().unwrap();
258 for (child, expected_child) in zip_eq(non_last_children, non_last_expected_children) {
259 self.print_tree(&expected_child.name, child, indent, false, under_top_level);
260 }
261 self.print_tree(&last_expected_child.name, last_child, indent, true, under_top_level);
262 }
263
264 fn get_node_kind(&self, name: String) -> NodeKind {
265 if let Some(node) = self.spec.iter().find(|x| x.name == name) {
266 node.kind.clone()
267 } else {
268 panic!("Could not find spec for {name}")
269 }
270 }
271
272 fn bold(&self, text: ColoredString) -> ColoredString {
274 if self.print_colors { text.bold() } else { text }
275 }
276 fn green(&self, text: ColoredString) -> ColoredString {
277 if self.print_colors { text.green() } else { text }
278 }
279 fn red(&self, text: ColoredString) -> ColoredString {
280 if self.print_colors { text.red() } else { text }
281 }
282 fn cyan(&self, text: ColoredString) -> ColoredString {
283 if self.print_colors { text.cyan() } else { text }
284 }
285 fn blue(&self, text: ColoredString) -> ColoredString {
286 if self.print_colors { text.blue() } else { text }
287 }
288 fn bright_purple(&self, text: ColoredString) -> ColoredString {
289 if self.print_colors { text.bright_purple() } else { text }
290 }
291}
292
293fn is_missing_kind(kind: SyntaxKind) -> bool {
295 matches!(
296 kind,
297 SyntaxKind::ExprMissing
298 | SyntaxKind::WrappedArgListMissing
299 | SyntaxKind::StatementMissing
300 | SyntaxKind::ModuleItemMissing
301 | SyntaxKind::TraitItemMissing
302 | SyntaxKind::ImplItemMissing
303 )
304}