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}