plotnik_compiler/query/
printer.rs

1//! AST/CST pretty-printer for debugging and test snapshots.
2
3use std::collections::HashMap;
4use std::fmt::Write;
5
6use indexmap::IndexSet;
7use rowan::NodeOrToken;
8
9use crate::parser::{self as ast, SyntaxNode};
10
11use super::Query;
12use super::SourceKind;
13use crate::analyze::type_check::Arity;
14
15/// Returns indentation string for the given level.
16fn indent(level: usize) -> String {
17    "  ".repeat(level)
18}
19
20pub struct QueryPrinter<'q> {
21    query: &'q Query,
22    raw: bool,
23    trivia: bool,
24    arities: bool,
25    spans: bool,
26    symbols: bool,
27}
28
29impl<'q> QueryPrinter<'q> {
30    pub fn new(query: &'q Query) -> Self {
31        Self {
32            query,
33            raw: false,
34            trivia: false,
35            arities: false,
36            spans: false,
37            symbols: false,
38        }
39    }
40
41    pub fn raw(mut self, value: bool) -> Self {
42        self.raw = value;
43        self
44    }
45
46    pub fn with_trivia(mut self, value: bool) -> Self {
47        self.trivia = value;
48        self
49    }
50
51    pub fn with_arities(mut self, value: bool) -> Self {
52        self.arities = value;
53        self
54    }
55
56    pub fn with_spans(mut self, value: bool) -> Self {
57        self.spans = value;
58        self
59    }
60
61    pub fn only_symbols(mut self, value: bool) -> Self {
62        self.symbols = value;
63        self
64    }
65
66    pub fn dump(&self) -> String {
67        let mut out = String::new();
68        self.format(&mut out).expect("String write never fails");
69        out
70    }
71
72    pub fn format(&self, w: &mut impl Write) -> std::fmt::Result {
73        if self.symbols {
74            return self.format_symbols(w);
75        }
76
77        let source_map = self.query.source_map();
78        let ast_map = self.query.asts();
79        let show_headers = self.should_show_headers(source_map);
80        let mut first = true;
81
82        for source in source_map.iter() {
83            let Some(root) = ast_map.get(&source.id) else {
84                continue;
85            };
86
87            if show_headers {
88                if !first {
89                    writeln!(w)?;
90                }
91                writeln!(w, "# {}", source.kind.display_name())?;
92            }
93
94            if self.raw {
95                self.format_cst(root.as_cst(), 0, w)?;
96            } else {
97                self.format_root(root, w)?;
98            }
99
100            first = false;
101        }
102
103        Ok(())
104    }
105
106    fn should_show_headers(&self, source_map: &super::source_map::SourceMap) -> bool {
107        source_map.len() > 1
108            || source_map
109                .iter()
110                .next()
111                .is_some_and(|s| !matches!(s.kind, SourceKind::OneLiner))
112    }
113
114    fn format_symbols(&self, w: &mut impl Write) -> std::fmt::Result {
115        let symbols = &self.query.symbol_table;
116        if symbols.is_empty() {
117            return Ok(());
118        }
119
120        let defined: IndexSet<&str> = symbols.keys().collect();
121
122        // Collect body nodes from all files
123        let mut body_nodes: HashMap<String, SyntaxNode> = HashMap::new();
124        for root in self.query.asts().values() {
125            for def in root.defs() {
126                if let (Some(name_tok), Some(body)) = (def.name(), def.body()) {
127                    body_nodes.insert(name_tok.text().to_string(), body.as_cst().clone());
128                }
129            }
130        }
131
132        for name in symbols.keys() {
133            let mut visited = IndexSet::new();
134            self.format_symbol_tree(name, 0, &defined, &body_nodes, &mut visited, w)?;
135        }
136        Ok(())
137    }
138
139    fn format_symbol_tree(
140        &self,
141        name: &str,
142        depth: usize,
143        defined: &indexmap::IndexSet<&str>,
144        body_nodes: &std::collections::HashMap<String, SyntaxNode>,
145        visited: &mut indexmap::IndexSet<String>,
146        w: &mut impl Write,
147    ) -> std::fmt::Result {
148        let prefix = indent(depth);
149
150        if visited.contains(name) {
151            writeln!(w, "{}{} (cycle)", prefix, name)?;
152            return Ok(());
153        }
154
155        let is_broken = !defined.contains(name);
156        if is_broken {
157            writeln!(w, "{}{}?", prefix, name)?;
158            return Ok(());
159        }
160
161        let card = body_nodes
162            .get(name)
163            .map(|n| self.arity_mark(n))
164            .unwrap_or("");
165        writeln!(w, "{}{}{}", prefix, name, card)?;
166        visited.insert(name.to_string());
167
168        if let Some(body) = self.query.symbol_table.get(name) {
169            let refs_set = crate::analyze::refs::ref_names(body);
170            let mut refs: Vec<_> = refs_set.iter().map(|s| s.as_str()).collect();
171            refs.sort();
172            for r in refs {
173                self.format_symbol_tree(r, depth + 1, defined, body_nodes, visited, w)?;
174            }
175        }
176
177        visited.shift_remove(name);
178        Ok(())
179    }
180
181    fn format_cst(&self, node: &SyntaxNode, depth: usize, w: &mut impl Write) -> std::fmt::Result {
182        let prefix = indent(depth);
183        let card = self.arity_mark(node);
184        let span = self.span_str(node.text_range());
185
186        writeln!(w, "{}{:?}{}{}", prefix, node.kind(), card, span)?;
187
188        for child in node.children_with_tokens() {
189            match child {
190                NodeOrToken::Node(n) => self.format_cst(&n, depth + 1, w)?,
191                NodeOrToken::Token(t) => {
192                    if !self.trivia && t.kind().is_trivia() {
193                        continue;
194                    }
195                    let child_prefix = indent(depth + 1);
196                    let child_span = self.span_str(t.text_range());
197                    writeln!(
198                        w,
199                        "{}{:?}{} {:?}",
200                        child_prefix,
201                        t.kind(),
202                        child_span,
203                        t.text()
204                    )?;
205                }
206            }
207        }
208        Ok(())
209    }
210
211    fn format_root(&self, root: &ast::Root, w: &mut impl Write) -> std::fmt::Result {
212        let card = self.arity_mark(root.as_cst());
213        let span = self.span_str(root.text_range());
214        writeln!(w, "Root{}{}", card, span)?;
215
216        for def in root.defs() {
217            self.format_def(&def, 1, w)?;
218        }
219        // Parser wraps all top-level exprs in Def nodes, so this should be empty
220        assert!(
221            root.exprs().next().is_none(),
222            "printer: unexpected bare Expr in Root (parser should wrap in Def)"
223        );
224        Ok(())
225    }
226
227    fn format_def(&self, def: &ast::Def, depth: usize, w: &mut impl Write) -> std::fmt::Result {
228        let prefix = indent(depth);
229        let card = self.arity_mark(def.as_cst());
230        let span = self.span_str(def.text_range());
231        let name = def.name().map(|t| t.text().to_string());
232
233        match name {
234            Some(n) => writeln!(w, "{}Def{}{} {}", prefix, card, span, n)?,
235            None => writeln!(w, "{}Def{}{}", prefix, card, span)?,
236        }
237
238        let Some(body) = def.body() else {
239            return Ok(());
240        };
241        self.format_expr(&body, depth + 1, w)
242    }
243
244    fn format_expr(&self, expr: &ast::Expr, depth: usize, w: &mut impl Write) -> std::fmt::Result {
245        let prefix = indent(depth);
246        let card = self.arity_mark(expr.as_cst());
247        let span = self.span_str(expr.text_range());
248
249        match expr {
250            ast::Expr::NamedNode(n) => {
251                if n.is_any() {
252                    writeln!(w, "{}NamedNode{}{} (any)", prefix, card, span)?;
253                } else {
254                    let node_type = n.node_type().map(|tok| tok.text().to_string());
255                    match node_type {
256                        Some(ty) => writeln!(w, "{}NamedNode{}{} {}", prefix, card, span, ty)?,
257                        None => writeln!(w, "{}NamedNode{}{}", prefix, card, span)?,
258                    }
259                }
260                self.format_tree_children(n.as_cst(), depth + 1, w)?;
261            }
262            ast::Expr::Ref(r) => {
263                let name = r.name().map(|t| t.text().to_string()).unwrap_or_default();
264                writeln!(w, "{}Ref{}{} {}", prefix, card, span, name)?;
265            }
266            ast::Expr::AnonymousNode(a) => {
267                if a.is_any() {
268                    writeln!(w, "{}AnonymousNode{}{} (any)", prefix, card, span)?;
269                } else {
270                    let value = a.value().map(|t| t.text().to_string()).unwrap_or_default();
271                    writeln!(w, "{}AnonymousNode{}{} \"{}\"", prefix, card, span, value)?;
272                }
273            }
274            ast::Expr::AltExpr(a) => {
275                writeln!(w, "{}Alt{}{}", prefix, card, span)?;
276                for branch in a.branches() {
277                    self.format_branch(&branch, depth + 1, w)?;
278                }
279                for expr in a.exprs() {
280                    self.format_expr(&expr, depth + 1, w)?;
281                }
282            }
283            ast::Expr::SeqExpr(s) => {
284                writeln!(w, "{}Seq{}{}", prefix, card, span)?;
285                self.format_tree_children(s.as_cst(), depth + 1, w)?;
286            }
287            ast::Expr::CapturedExpr(c) => {
288                let name = c
289                    .name()
290                    .map(|t| t.text()[1..].to_string()) // Strip @ prefix
291                    .unwrap_or_default();
292                let type_ann = c
293                    .type_annotation()
294                    .and_then(|t| t.name())
295                    .map(|t| t.text().to_string());
296                match type_ann {
297                    Some(ty) => writeln!(
298                        w,
299                        "{}CapturedExpr{}{} @{} :: {}",
300                        prefix, card, span, name, ty
301                    )?,
302                    None => writeln!(w, "{}CapturedExpr{}{} @{}", prefix, card, span, name)?,
303                }
304                let Some(inner) = c.inner() else {
305                    return Ok(());
306                };
307                self.format_expr(&inner, depth + 1, w)?;
308            }
309            ast::Expr::QuantifiedExpr(q) => {
310                let op = q
311                    .operator()
312                    .map(|t| t.text().to_string())
313                    .unwrap_or_default();
314                writeln!(w, "{}QuantifiedExpr{}{} {}", prefix, card, span, op)?;
315                let Some(inner) = q.inner() else {
316                    return Ok(());
317                };
318                self.format_expr(&inner, depth + 1, w)?;
319            }
320            ast::Expr::FieldExpr(f) => {
321                let name = f.name().map(|t| t.text().to_string()).unwrap_or_default();
322                writeln!(w, "{}FieldExpr{}{} {}:", prefix, card, span, name)?;
323                let Some(value) = f.value() else {
324                    return Ok(());
325                };
326                self.format_expr(&value, depth + 1, w)?;
327            }
328        }
329        Ok(())
330    }
331
332    fn format_tree_children(
333        &self,
334        node: &SyntaxNode,
335        depth: usize,
336        w: &mut impl Write,
337    ) -> std::fmt::Result {
338        use crate::parser::SyntaxKind;
339        for child in node.children() {
340            if child.kind() == SyntaxKind::Anchor {
341                self.mark_anchor(depth, w)?;
342            } else if child.kind() == SyntaxKind::NegatedField {
343                self.format_negated_field(&ast::NegatedField::cast(child).unwrap(), depth, w)?;
344            } else if let Some(expr) = ast::Expr::cast(child) {
345                self.format_expr(&expr, depth, w)?;
346            }
347        }
348        Ok(())
349    }
350
351    fn mark_anchor(&self, depth: usize, w: &mut impl Write) -> std::fmt::Result {
352        writeln!(w, "{}.", indent(depth))
353    }
354
355    fn format_negated_field(
356        &self,
357        nf: &ast::NegatedField,
358        depth: usize,
359        w: &mut impl Write,
360    ) -> std::fmt::Result {
361        let prefix = indent(depth);
362        let span = self.span_str(nf.text_range());
363        let name = nf.name().map(|t| t.text().to_string()).unwrap_or_default();
364        writeln!(w, "{}NegatedField{} -{}", prefix, span, name)
365    }
366
367    fn format_branch(
368        &self,
369        branch: &ast::Branch,
370        depth: usize,
371        w: &mut impl Write,
372    ) -> std::fmt::Result {
373        let prefix = indent(depth);
374        let card = self.arity_mark(branch.as_cst());
375        let span = self.span_str(branch.text_range());
376        let label = branch.label().map(|t| t.text().to_string());
377
378        match label {
379            Some(l) => writeln!(w, "{}Branch{}{} {}:", prefix, card, span, l)?,
380            None => writeln!(w, "{}Branch{}{}", prefix, card, span)?,
381        }
382
383        let Some(body) = branch.body() else {
384            return Ok(());
385        };
386        self.format_expr(&body, depth + 1, w)
387    }
388
389    fn arity_mark(&self, node: &SyntaxNode) -> &'static str {
390        if !self.arities {
391            return "";
392        }
393        match self.query.get_arity(node) {
394            Some(Arity::One) => "¹",
395            Some(Arity::Many) => "⁺",
396            None => "ˣ",
397        }
398    }
399
400    fn span_str(&self, range: rowan::TextRange) -> String {
401        if !self.spans {
402            return String::new();
403        }
404        format!(
405            " [{}..{}]",
406            u32::from(range.start()),
407            u32::from(range.end())
408        )
409    }
410}
411
412impl Query {
413    pub fn printer(&self) -> QueryPrinter<'_> {
414        QueryPrinter::new(self)
415    }
416}