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(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}