cairo_lang_parser/
printer.rs

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    /// The highest SyntaxKind that is interesting. All other kinds, if not under it, are ignored.
41    /// If None, the whole tree is printed.
42    top_level_kind: Option<String>,
43    /// Syntax kinds to ignore when printing. In this context, "ignore" means printing the nodes
44    /// themselves, but not their children.
45    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    /// Create a new printer that is capable of partial printing of the syntax tree.
62    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    /// `under_top_level`: whether we are in a subtree of the top-level kind.
84    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    /// `under_top_level`: whether we are in a subtree of the top-level kind.
143    #[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        // Update under_top_level and indent as needed.
157        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        // Append to string only if we are under the top level kind.
184        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    /// Assumes children and expected children are non-empty of the same length.
247    /// `under_top_level`: whether we are in a subtree of the top-level kind.
248    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    // Color helpers.
273    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
293// TODO(yuval): autogenerate.
294fn 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}