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