Skip to main content

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}