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 Parens(Seq<'a>),
28 /// A bracket-wrapped sequence.
29 Brackets(Seq<'a>),
30 /// A brace-wrapped sequence.
31 Braces(Seq<'a>),
32 /// An item marked with a leading sigil (`&`, `.`, or `#`).
33 Prefixed { sigil: char, item: Box<Item<'a>> },
34 /// A **run**: two or more atoms written with no whitespace between them, so
35 /// they form one chain item (`Main.A.S`, `f(x)`, `(f x).1`, `3.14`). Members
36 /// are in written order; none is itself a `Run`, a `Punct`, or a `Comm` (a
37 /// punct or comment ends a run). A lone atom is never wrapped in a `Run`.
38 Run(Vec<Item<'a>>),
39}
40
41/// A chain: one or more juxtaposed items (`<chain>`). Never empty.
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct Chain<'a>(pub Vec<Item<'a>>);
44
45/// A sequence: zero or more chains delimited by separators (`<seq>`).
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Seq<'a>(pub Vec<Chain<'a>>);
48
49/// Tear the tree down iteratively, so dropping a deeply-nested tree (the parser
50/// bounds nesting only by memory) can't overflow the stack the way the default
51/// recursive `Drop` would. `Item` itself has no `Drop`, so leaves — the vast
52/// majority of nodes — drop trivially; the cost falls only on the nesting nodes
53/// walked here. The two recursion points are containers (a nested `Seq`) and
54/// `Prefixed` (a boxed inner `Item`); both are drained onto a heap worklist
55/// before the node holding them drops, so no `Drop` call nests.
56impl Drop for Seq<'_> {
57 fn drop(&mut self) {
58 // Leaves — the vast majority of nodes — drop in place as `collect_nesting`
59 // iterates; only the nesting nodes reach `work`, so a flat tree never
60 // allocates one and a deep tree is torn down a level at a time.
61 let mut work: Vec<Item> = Vec::new();
62 collect_nesting(self, &mut work);
63 while let Some(mut item) = work.pop() {
64 match &mut item {
65 Item::Parens(seq) | Item::Brackets(seq) | Item::Braces(seq) => {
66 collect_nesting(seq, &mut work);
67 }
68 Item::Prefixed { item, .. } => {
69 let inner = mem::replace(item.as_mut(), Item::Sym(""));
70 if inner.nests() {
71 work.push(inner);
72 }
73 }
74 Item::Run(members) => {
75 for m in mem::take(members) {
76 if m.nests() {
77 work.push(m);
78 }
79 }
80 }
81 _ => {}
82 }
83 // `item` drops here with its nesting already moved out: a container's
84 // inner `Seq` is empty and a `Prefixed`'s box now holds a leaf, so
85 // neither recurses.
86 }
87 }
88}
89
90/// Drop `seq`'s leaves in place and move its nesting items onto `work`, leaving
91/// `seq` empty.
92fn collect_nesting<'a>(seq: &mut Seq<'a>, work: &mut Vec<Item<'a>>) {
93 for chain in mem::take(&mut seq.0) {
94 for item in chain.0 {
95 if item.nests() {
96 work.push(item);
97 }
98 // else: a leaf, dropped here as the loop advances.
99 }
100 }
101}
102
103impl Seq<'_> {
104 /// Serialize the tree to compact, tagged JSON.
105 pub fn to_json(&self) -> String {
106 let mut out = String::new();
107 self.write_json(&mut out);
108 out
109 }
110
111 fn write_json(&self, out: &mut String) {
112 out.push_str("{\"seq\":[");
113 for (i, chain) in self.0.iter().enumerate() {
114 if i > 0 {
115 out.push(',');
116 }
117 chain.write_json(out);
118 }
119 out.push_str("]}");
120 }
121}
122
123impl Chain<'_> {
124 fn write_json(&self, out: &mut String) {
125 out.push_str("{\"chain\":[");
126 for (i, item) in self.0.iter().enumerate() {
127 if i > 0 {
128 out.push(',');
129 }
130 item.write_json(out);
131 }
132 out.push_str("]}");
133 }
134}
135
136impl Item<'_> {
137 /// Whether this item owns nested items (a container's `Seq` or a `Prefixed`'s
138 /// boxed item) — the nodes the iterative `Drop for Seq` must walk.
139 fn nests(&self) -> bool {
140 matches!(
141 self,
142 Item::Parens(_)
143 | Item::Brackets(_)
144 | Item::Braces(_)
145 | Item::Prefixed { .. }
146 | Item::Run(_)
147 )
148 }
149
150 fn write_json(&self, out: &mut String) {
151 match self {
152 Item::Comm(s) => {
153 out.push_str("{\"comm\":");
154 json_str(s, out);
155 out.push('}');
156 }
157 Item::Str(s) => {
158 out.push_str("{\"str\":");
159 json_str(s, out);
160 out.push('}');
161 }
162 Item::Sym(s) => {
163 out.push_str("{\"sym\":");
164 json_str(s, out);
165 out.push('}');
166 }
167 Item::Punct(s) => {
168 out.push_str("{\"punct\":");
169 json_str(s, out);
170 out.push('}');
171 }
172 Item::Parens(seq) => {
173 out.push_str("{\"parens\":");
174 seq.write_json(out);
175 out.push('}');
176 }
177 Item::Brackets(seq) => {
178 out.push_str("{\"brackets\":");
179 seq.write_json(out);
180 out.push('}');
181 }
182 Item::Braces(seq) => {
183 out.push_str("{\"braces\":");
184 seq.write_json(out);
185 out.push('}');
186 }
187 Item::Prefixed { sigil, item } => {
188 out.push_str("{\"pre\":");
189 let mut buf = [0u8; 4];
190 json_str(sigil.encode_utf8(&mut buf), out);
191 out.push_str(",\"item\":");
192 item.write_json(out);
193 out.push('}');
194 }
195 Item::Run(members) => {
196 out.push_str("{\"run\":[");
197 for (i, m) in members.iter().enumerate() {
198 if i > 0 {
199 out.push(',');
200 }
201 m.write_json(out);
202 }
203 out.push_str("]}");
204 }
205 }
206 }
207}
208
209/// Write `s` as a JSON string literal (quoted and escaped).
210fn json_str(s: &str, out: &mut String) {
211 out.push('"');
212 for ch in s.chars() {
213 match ch {
214 '"' => out.push_str("\\\""),
215 '\\' => out.push_str("\\\\"),
216 '\n' => out.push_str("\\n"),
217 '\r' => out.push_str("\\r"),
218 '\t' => out.push_str("\\t"),
219 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
220 c => out.push(c),
221 }
222 }
223 out.push('"');
224}