plotnik_lib/query/
printer.rs

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