cairo_lang_parser/
printer.rs

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