Skip to main content

ronin_core/
printer.rs

1//! The printer: a [`CstDocument`] → its exact source bytes.
2//!
3//! [`print`] walks the CST in source order and concatenates every token's
4//! verbatim text. For an unmodified tree this reproduces the input byte-for-byte
5//! (round-trip identity, INV-2 / TR-003). Because [`print`] reads only token
6//! text — which the lexer captured verbatim and the parser never normalized —
7//! printing is also idempotent: `print(parse(print(parse(x)))) == print(parse(x))`
8//! (TR-017 / INV-2).
9
10use crate::parser::CstDocument;
11use crate::syntax::SyntaxNode;
12
13/// Print a [`CstDocument`] back to source text by concatenating all token text.
14///
15/// For an unmodified tree the result equals the original source bytes exactly.
16#[must_use]
17pub fn print(doc: &CstDocument) -> String {
18    print_node(&doc.root())
19}
20
21/// Print an arbitrary [`SyntaxNode`] subtree to text (concatenated token text).
22///
23/// Useful for printing a fragment of a tree (e.g. for edit primitives in OBJ4).
24#[must_use]
25pub fn print_node(node: &SyntaxNode) -> String {
26    // `SyntaxNode::text()` already concatenates the verbatim text of every
27    // descendant token in source order; this is the canonical lossless print.
28    node.text()
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34    use crate::parser::parse;
35
36    fn assert_roundtrip(src: &str) {
37        let doc = parse(src);
38        assert_eq!(print(&doc), src, "round-trip identity for {src:?}");
39    }
40
41    fn assert_idempotent(src: &str) {
42        let first = print(&parse(src));
43        let second = print(&parse(&first));
44        assert_eq!(second, first, "idempotent print for {src:?}");
45    }
46
47    #[test]
48    fn round_trip_identity() {
49        for src in [
50            "",
51            "   ",
52            "// c\n",
53            "Foo(x: 1, y: 2.0) // trailing\n",
54            "[1, 2, 3,]",
55            "{ 'a': 1, 2: \"b\", }",
56            "r#\"raw\"#",
57            "#![enable(implicit_some)]\nSome(())",
58            "\u{FEFF}true",
59            "1\r\n2",
60        ] {
61            assert_roundtrip(src);
62        }
63    }
64
65    #[test]
66    fn idempotent_print() {
67        for src in [
68            "Foo(x: 1)",
69            "[1, 2, 3]",
70            "{ \"k\": 'v' }",
71            "#![enable(implicit_some)]\nSome(5)",
72            "  spaced  out  ",
73        ] {
74            assert_idempotent(src);
75        }
76    }
77}