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(Group<'a>),
28 /// A bracket-wrapped sequence.
29 Brackets(Group<'a>),
30 /// A brace-wrapped sequence.
31 Braces(Group<'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/// The location of a node in the source: `length` bytes from byte `offset`.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43pub struct Span {
44 pub offset: u32,
45 pub length: u32,
46}
47
48impl Span {
49 /// The byte offset just past the node.
50 pub fn end(self) -> u32 {
51 self.offset + self.length
52 }
53}
54
55/// The sequence inside a bracket group, with the group's location. A group is
56/// the one node that stores its span: a leaf borrows its text from the source,
57/// so its span follows from where the slice starts (see [`Item::span`]), but an
58/// empty `()` has no slice to measure, and whitespace may stand between a
59/// bracket and the first item.
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct Group<'a> {
62 pub seq: Seq<'a>,
63 /// Covers the brackets, but not the sigils of a prefixed group.
64 pub span: Span,
65}
66
67/// A chain: one or more juxtaposed items (`<chain>`). Never empty.
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct Chain<'a>(pub Vec<Item<'a>>);
70
71/// A sequence: zero or more chains delimited by separators (`<seq>`).
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct Seq<'a>(pub Vec<Chain<'a>>);
74
75/// Tear the tree down iteratively, so dropping a deeply-nested tree (the parser
76/// bounds nesting only by memory) can't overflow the stack the way the default
77/// recursive `Drop` would. `Item` itself has no `Drop`, so leaves — the vast
78/// majority of nodes — drop trivially; the cost falls only on the nesting nodes
79/// walked here. The two recursion points are containers (a nested `Seq`) and
80/// `Prefixed` (a boxed inner `Item`); both are drained onto a heap worklist
81/// before the node holding them drops, so no `Drop` call nests.
82impl Drop for Seq<'_> {
83 fn drop(&mut self) {
84 // Leaves — the vast majority of nodes — drop in place as `collect_nesting`
85 // iterates; only the nesting nodes reach `work`, so a flat tree never
86 // allocates one and a deep tree is torn down a level at a time.
87 let mut work: Vec<Item> = Vec::new();
88 collect_nesting(self, &mut work);
89 while let Some(mut item) = work.pop() {
90 match &mut item {
91 Item::Parens(g) | Item::Brackets(g) | Item::Braces(g) => {
92 collect_nesting(&mut g.seq, &mut work);
93 }
94 Item::Prefixed { item, .. } => {
95 let inner = mem::replace(item.as_mut(), Item::Sym(""));
96 if inner.nests() {
97 work.push(inner);
98 }
99 }
100 Item::Run(members) => {
101 for m in mem::take(members) {
102 if m.nests() {
103 work.push(m);
104 }
105 }
106 }
107 _ => {}
108 }
109 // `item` drops here with its nesting already moved out: a container's
110 // inner `Seq` is empty and a `Prefixed`'s box now holds a leaf, so
111 // neither recurses.
112 }
113 }
114}
115
116/// Drop `seq`'s leaves in place and move its nesting items onto `work`, leaving
117/// `seq` empty.
118fn collect_nesting<'a>(seq: &mut Seq<'a>, work: &mut Vec<Item<'a>>) {
119 for chain in mem::take(&mut seq.0) {
120 for item in chain.0 {
121 if item.nests() {
122 work.push(item);
123 }
124 // else: a leaf, dropped here as the loop advances.
125 }
126 }
127}
128
129impl Seq<'_> {
130 /// Serialize the tree to compact, tagged JSON.
131 pub fn to_json(&self) -> String {
132 let mut out = String::new();
133 self.write_json(&mut out);
134 out
135 }
136
137 fn write_json(&self, out: &mut String) {
138 out.push_str("{\"seq\":[");
139 for (i, chain) in self.0.iter().enumerate() {
140 if i > 0 {
141 out.push(',');
142 }
143 chain.write_json(out);
144 }
145 out.push_str("]}");
146 }
147}
148
149impl Chain<'_> {
150 fn write_json(&self, out: &mut String) {
151 out.push_str("{\"chain\":[");
152 for (i, item) in self.0.iter().enumerate() {
153 if i > 0 {
154 out.push(',');
155 }
156 item.write_json(out);
157 }
158 out.push_str("]}");
159 }
160}
161
162impl Item<'_> {
163 /// Where this item stands in `src`, the text it was parsed from. A string's
164 /// span covers its quotes, a comment's its delimiters, a prefixed item's its
165 /// sigils, and a group's its brackets.
166 ///
167 /// # Panics
168 ///
169 /// If the item was not parsed from `src`.
170 pub fn span(&self, src: &str) -> Span {
171 let leaf = |s: &str| {
172 let range = src
173 .substr_range(s)
174 .expect("the item was not parsed from `src`");
175 Span {
176 offset: range.start as u32,
177 length: s.len() as u32,
178 }
179 };
180 match self {
181 Item::Comm(s) | Item::Sym(s) | Item::Punct(s) => leaf(s),
182 Item::Str(s) => {
183 let inner = leaf(s);
184 Span {
185 offset: inner.offset - 1,
186 length: inner.length + 2,
187 }
188 }
189 Item::Parens(g) | Item::Brackets(g) | Item::Braces(g) => g.span,
190 // A sigil is one ASCII byte, glued to its item.
191 Item::Prefixed { item, .. } => {
192 let inner = item.span(src);
193 Span {
194 offset: inner.offset - 1,
195 length: inner.length + 1,
196 }
197 }
198 Item::Run(members) => {
199 let first = members[0].span(src);
200 let last = members[members.len() - 1].span(src);
201 Span {
202 offset: first.offset,
203 length: last.end() - first.offset,
204 }
205 }
206 }
207 }
208
209 /// Whether this item owns nested items (a container's `Seq` or a `Prefixed`'s
210 /// boxed item) — the nodes the iterative `Drop for Seq` must walk.
211 fn nests(&self) -> bool {
212 matches!(
213 self,
214 Item::Parens(_)
215 | Item::Brackets(_)
216 | Item::Braces(_)
217 | Item::Prefixed { .. }
218 | Item::Run(_)
219 )
220 }
221
222 fn write_json(&self, out: &mut String) {
223 match self {
224 Item::Comm(s) => {
225 out.push_str("{\"comm\":");
226 json_str(s, out);
227 out.push('}');
228 }
229 Item::Str(s) => {
230 out.push_str("{\"str\":");
231 json_str(s, out);
232 out.push('}');
233 }
234 Item::Sym(s) => {
235 out.push_str("{\"sym\":");
236 json_str(s, out);
237 out.push('}');
238 }
239 Item::Punct(s) => {
240 out.push_str("{\"punct\":");
241 json_str(s, out);
242 out.push('}');
243 }
244 Item::Parens(g) => {
245 out.push_str("{\"parens\":");
246 g.seq.write_json(out);
247 out.push('}');
248 }
249 Item::Brackets(g) => {
250 out.push_str("{\"brackets\":");
251 g.seq.write_json(out);
252 out.push('}');
253 }
254 Item::Braces(g) => {
255 out.push_str("{\"braces\":");
256 g.seq.write_json(out);
257 out.push('}');
258 }
259 Item::Prefixed { sigil, item } => {
260 out.push_str("{\"pre\":");
261 let mut buf = [0u8; 4];
262 json_str(sigil.encode_utf8(&mut buf), out);
263 out.push_str(",\"item\":");
264 item.write_json(out);
265 out.push('}');
266 }
267 Item::Run(members) => {
268 out.push_str("{\"run\":[");
269 for (i, m) in members.iter().enumerate() {
270 if i > 0 {
271 out.push(',');
272 }
273 m.write_json(out);
274 }
275 out.push_str("]}");
276 }
277 }
278 }
279}
280
281/// Write `s` as a JSON string literal (quoted and escaped).
282fn json_str(s: &str, out: &mut String) {
283 out.push('"');
284 for ch in s.chars() {
285 match ch {
286 '"' => out.push_str("\\\""),
287 '\\' => out.push_str("\\\\"),
288 '\n' => out.push_str("\\n"),
289 '\r' => out.push_str("\\r"),
290 '\t' => out.push_str("\\t"),
291 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
292 c => out.push(c),
293 }
294 }
295 out.push('"');
296}