muon/tree.rs
1//! The Muon tree and its JSON serialization.
2//!
3//! `to_json` emits a compact, tagged JSON dump used by the golden tests so they
4//! read close to `muon-spec.md`. It is deliberately not Muon: other-language
5//! implementations can compare against it with their own JSON parser (see the
6//! language-agnostic-suite item in `docs/TODO.md`).
7
8use alloc::boxed::Box;
9use alloc::format;
10use alloc::string::String;
11use alloc::vec::Vec;
12use core::mem;
13
14/// A single visually-atomic entity in a chain (`<item>` in the spec).
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum Item<'a> {
17 /// A comment, stored raw *including* its `//` or `/* */` delimiters.
18 Comm(&'a str),
19 /// A string literal; the inner slice, *between* the quotes, undecoded
20 /// (escapes are validated but not expanded — see the crate `open` note).
21 Str(&'a str),
22 /// A symbol: a maximal run of symchars (identifier, number, operator, …).
23 Sym(&'a str),
24 /// Punctuation: a maximal run of punctchars standing between items (`:`).
25 Punct(&'a str),
26 /// A parenthesis-wrapped sequence.
27 Tuple(Seq<'a>),
28 /// A bracket-wrapped sequence.
29 List(Seq<'a>),
30 /// A brace-wrapped sequence.
31 Block(Seq<'a>),
32 /// An item marked with a leading sigil (`&` is the only one so far).
33 Prefixed { sigil: char, item: Box<Item<'a>> },
34}
35
36/// A chain: one or more juxtaposed items (`<chain>`). Never empty.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct Chain<'a>(pub Vec<Item<'a>>);
39
40/// A sequence: zero or more chains delimited by separators (`<seq>`).
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Seq<'a>(pub Vec<Chain<'a>>);
43
44/// Tear the tree down iteratively, so dropping a deeply-nested tree (the parser
45/// bounds nesting only by memory) can't overflow the stack the way the default
46/// recursive `Drop` would. `Item` itself has no `Drop`, so leaves — the vast
47/// majority of nodes — drop trivially; the cost falls only on the nesting nodes
48/// walked here. The two recursion points are containers (a nested `Seq`) and
49/// `Prefixed` (a boxed inner `Item`); both are drained onto a heap worklist
50/// before the node holding them drops, so no `Drop` call nests.
51impl Drop for Seq<'_> {
52 fn drop(&mut self) {
53 // Leaves — the vast majority of nodes — drop in place as `collect_nesting`
54 // iterates; only the nesting nodes reach `work`, so a flat tree never
55 // allocates one and a deep tree is torn down a level at a time.
56 let mut work: Vec<Item> = Vec::new();
57 collect_nesting(self, &mut work);
58 while let Some(mut item) = work.pop() {
59 match &mut item {
60 Item::Tuple(seq) | Item::List(seq) | Item::Block(seq) => {
61 collect_nesting(seq, &mut work);
62 }
63 Item::Prefixed { item, .. } => {
64 let inner = mem::replace(item.as_mut(), Item::Sym(""));
65 if inner.nests() {
66 work.push(inner);
67 }
68 }
69 _ => {}
70 }
71 // `item` drops here with its nesting already moved out: a container's
72 // inner `Seq` is empty and a `Prefixed`'s box now holds a leaf, so
73 // neither recurses.
74 }
75 }
76}
77
78/// Drop `seq`'s leaves in place and move its nesting items onto `work`, leaving
79/// `seq` empty.
80fn collect_nesting<'a>(seq: &mut Seq<'a>, work: &mut Vec<Item<'a>>) {
81 for chain in mem::take(&mut seq.0) {
82 for item in chain.0 {
83 if item.nests() {
84 work.push(item);
85 }
86 // else: a leaf, dropped here as the loop advances.
87 }
88 }
89}
90
91impl Seq<'_> {
92 /// Serialize the tree to compact, tagged JSON.
93 pub fn to_json(&self) -> String {
94 let mut out = String::new();
95 self.write_json(&mut out);
96 out
97 }
98
99 fn write_json(&self, out: &mut String) {
100 out.push_str("{\"seq\":[");
101 for (i, chain) in self.0.iter().enumerate() {
102 if i > 0 {
103 out.push(',');
104 }
105 chain.write_json(out);
106 }
107 out.push_str("]}");
108 }
109}
110
111impl Chain<'_> {
112 fn write_json(&self, out: &mut String) {
113 out.push_str("{\"chain\":[");
114 for (i, item) in self.0.iter().enumerate() {
115 if i > 0 {
116 out.push(',');
117 }
118 item.write_json(out);
119 }
120 out.push_str("]}");
121 }
122}
123
124impl Item<'_> {
125 /// Whether this item owns nested items (a container's `Seq` or a `Prefixed`'s
126 /// boxed item) — the nodes the iterative `Drop for Seq` must walk.
127 fn nests(&self) -> bool {
128 matches!(
129 self,
130 Item::Tuple(_) | Item::List(_) | Item::Block(_) | Item::Prefixed { .. }
131 )
132 }
133
134 fn write_json(&self, out: &mut String) {
135 match self {
136 Item::Comm(s) => {
137 out.push_str("{\"comm\":");
138 json_str(s, out);
139 out.push('}');
140 }
141 Item::Str(s) => {
142 out.push_str("{\"str\":");
143 json_str(s, out);
144 out.push('}');
145 }
146 Item::Sym(s) => {
147 out.push_str("{\"sym\":");
148 json_str(s, out);
149 out.push('}');
150 }
151 Item::Punct(s) => {
152 out.push_str("{\"punct\":");
153 json_str(s, out);
154 out.push('}');
155 }
156 Item::Tuple(seq) => {
157 out.push_str("{\"tuple\":");
158 seq.write_json(out);
159 out.push('}');
160 }
161 Item::List(seq) => {
162 out.push_str("{\"list\":");
163 seq.write_json(out);
164 out.push('}');
165 }
166 Item::Block(seq) => {
167 out.push_str("{\"block\":");
168 seq.write_json(out);
169 out.push('}');
170 }
171 Item::Prefixed { sigil, item } => {
172 out.push_str("{\"pre\":");
173 let mut buf = [0u8; 4];
174 json_str(sigil.encode_utf8(&mut buf), out);
175 out.push_str(",\"item\":");
176 item.write_json(out);
177 out.push('}');
178 }
179 }
180 }
181}
182
183/// Write `s` as a JSON string literal (quoted and escaped).
184fn json_str(s: &str, out: &mut String) {
185 out.push('"');
186 for ch in s.chars() {
187 match ch {
188 '"' => out.push_str("\\\""),
189 '\\' => out.push_str("\\\\"),
190 '\n' => out.push_str("\\n"),
191 '\r' => out.push_str("\\r"),
192 '\t' => out.push_str("\\t"),
193 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
194 c => out.push(c),
195 }
196 }
197 out.push('"');
198}