1use std::fmt;
5use std::io;
6
7use crate::{Node, Value};
8
9impl Node {
10 pub fn write_to<W: io::Write>(&self, writer: W) -> io::Result<()> {
12 let mut adapter = IoAdapter {
13 inner: writer,
14 error: None,
15 };
16 match write(self, &mut adapter) {
17 Ok(()) => Ok(()),
18 Err(fmt::Error) => Err(adapter
19 .error
20 .unwrap_or_else(|| io::Error::other("formatter error"))),
21 }
22 }
23}
24
25impl fmt::Display for Node {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write(self, f)
28 }
29}
30
31impl From<&Node> for String {
32 fn from(node: &Node) -> Self {
33 node.to_string()
34 }
35}
36
37impl From<&Node> for Vec<u8> {
38 fn from(node: &Node) -> Self {
39 node.to_string().into_bytes()
40 }
41}
42
43fn write(root: &Node, out: &mut impl fmt::Write) -> fmt::Result {
45 let mut stack: Vec<(&[Node], usize)> = Vec::new();
47 open(root, out, &mut stack)?;
48 while let Some(top) = stack.last_mut() {
49 let (items, next) = *top;
50 top.1 += 1;
51 if let Some(item) = items.get(next) {
52 if next > 0 {
53 out.write_char(',')?;
54 }
55 if item.is_list() {
56 out.write_str("\r\n")?;
57 }
58 open(item, out, &mut stack)?;
59 } else {
60 if items.last().is_some_and(Node::is_list) {
61 out.write_str("\r\n")?;
62 }
63 out.write_char('}')?;
64 stack.pop();
65 }
66 }
67 Ok(())
68}
69
70fn open<'a>(
72 node: &'a Node,
73 out: &mut impl fmt::Write,
74 stack: &mut Vec<(&'a [Node], usize)>,
75) -> fmt::Result {
76 match node.value() {
77 Value::List(items) => {
78 out.write_char('{')?;
79 stack.push((items, 0));
80 }
81 Value::Str(s) => {
82 out.write_char('"')?;
83 for (i, part) in s.split('"').enumerate() {
84 if i > 0 {
85 out.write_str("\"\"")?;
86 }
87 out.write_str(part)?;
88 }
89 out.write_char('"')?;
90 }
91 Value::Atom(s) => out.write_str(s)?,
92 }
93 Ok(())
94}
95
96struct IoAdapter<W> {
97 inner: W,
98 error: Option<io::Error>,
99}
100
101impl<W: io::Write> fmt::Write for IoAdapter<W> {
102 fn write_str(&mut self, s: &str) -> fmt::Result {
103 self.inner.write_all(s.as_bytes()).map_err(|e| {
104 self.error = Some(e);
105 fmt::Error
106 })
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn round_trips_platform_layout() {
116 let text = "{27,\r\n{18,\r\n{\r\n{1,1,\r\n{\"ru\",\"Форма\"}\r\n},25,4294967295},\
117 \r\n{0}\r\n},\"\",-1,\r\n{2,c2f34349-b1e0-4060-8abf-3c59f38ac69f,}\r\n}";
118 assert_eq!(Node::try_from(text).unwrap().to_string(), text);
119 }
120
121 #[test]
122 fn keeps_base64_with_line_breaks() {
123 let text = "{1,\r\n{#base64:QUJD\r\r\nREVG\r\r\n},2}";
124 assert_eq!(Node::try_from(text).unwrap().to_string(), text);
125 }
126
127 #[test]
128 fn builds_and_writes() {
129 let node = Node::from(vec![
130 Node::from(1),
131 Node::string("say \"hi\""),
132 Node::from(vec![Node::from(true), Node::atom("")]),
133 ]);
134 insta::assert_snapshot!(node.to_string().replace("\r\n", "⏎"), @r#"{1,"say ""hi""",⏎{1,}⏎}"#);
135
136 let mut bytes = Vec::new();
137 node.write_to(&mut bytes).unwrap();
138 assert_eq!(bytes, Vec::<u8>::from(&node));
139 assert_eq!(Node::try_from(bytes.as_slice()).unwrap(), node);
140 }
141
142 #[test]
143 fn deep_nesting_without_recursion() {
144 let depth = 5_000;
145 let text = format!("{}{}", "{".repeat(depth), "}".repeat(depth));
146 let node = Node::try_from(text.as_str()).unwrap();
147 assert_eq!(node.to_string().replace("\r\n", ""), text);
148 }
149}