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