Skip to main content

candela_vm/
json.rs

1//! json parse and stringify over the runtime `Data` graph.
2//!
3//! Parsing maps a json document straight onto values candela already has: an
4//! object becomes a map keyed by strings, an array becomes an array, and a
5//! scalar becomes int/float/string/bool/null. No new runtime type is needed.
6//!
7//! Parsed values allocate the way the program's own do, reusing freed slots,
8//! so a parse can start or carry on a collection. The value is built
9//! top-down to stay reachable while it does: the outermost list or map sits on
10//! the recursion stack, a collection root, until the parse ends, and each
11//! entry goes into its parent before anything inside it is allocated. A
12//! string is interned, since a map finds a string key by its slot.
13
14use crate::data::{Data, NULL};
15use crate::gc::GcState;
16use crate::gc::Heap;
17use crate::rt::EnumType;
18use crate::rt::Struct;
19use crate::vm::map_insert;
20use crate::vm::{MapPool, ObjectPool, RegisterFile, StringPool};
21
22/// How deeply objects and arrays may nest. Values are read by recursive
23/// descent, so without a ceiling a long run of `[` exhausts the native stack
24/// and kills the process instead of raising. Real documents stay far below
25/// this.
26const MAX_NESTING_DEPTH: u32 = 128;
27
28struct JsonParser<'a> {
29    bytes: &'a [u8],
30    pos: usize,
31    depth: u32,
32}
33
34impl JsonParser<'_> {
35    fn skip_ws(&mut self) {
36        while self.pos < self.bytes.len() {
37            match self.bytes[self.pos] {
38                b' ' | b'\t' | b'\n' | b'\r' => self.pos += 1,
39                _ => break,
40            }
41        }
42    }
43
44    fn peek(&self) -> Option<u8> {
45        self.bytes.get(self.pos).copied()
46    }
47
48    /// The next value: a scalar, a string, or an empty list or map whose
49    /// opening bracket is consumed, for [`Self::fill`] to fill in once the
50    /// caller has made it reachable.
51    fn parse_value(&mut self, heap: &mut Heap<'_>) -> Result<Data, &'static str> {
52        self.skip_ws();
53        match self.peek() {
54            Some(open @ (b'{' | b'[')) => {
55                self.depth += 1;
56                if self.depth > MAX_NESTING_DEPTH {
57                    return Err("nesting too deep");
58                }
59                self.pos += 1;
60                Ok(if open == b'{' {
61                    heap.map()
62                } else {
63                    heap.array()
64                })
65            }
66            Some(b'"') => {
67                let s = self.parse_string()?;
68                Ok(heap.string(&s))
69            }
70            Some(b't') | Some(b'f') => self.parse_bool(),
71            Some(b'n') => self.parse_null(),
72            Some(c) if c == b'-' || c.is_ascii_digit() => self.parse_number(),
73            Some(_) => Err("unexpected character"),
74            None => Err("unexpected end of input"),
75        }
76    }
77
78    /// Reads the entries of `d`, a value [`Self::parse_value`] answered, up
79    /// to its closing bracket. Nothing to do for a scalar or a string.
80    fn fill(&mut self, heap: &mut Heap<'_>, d: Data) -> Result<(), &'static str> {
81        if d.is_array() {
82            self.fill_array(heap, d.as_array())?;
83        } else if d.is_map() {
84            self.fill_object(heap, d.as_map())?;
85        } else {
86            return Ok(());
87        }
88        self.depth -= 1;
89        Ok(())
90    }
91
92    fn fill_object(&mut self, heap: &mut Heap<'_>, id: usize) -> Result<(), &'static str> {
93        self.skip_ws();
94        if self.peek() == Some(b'}') {
95            self.pos += 1;
96            return Ok(());
97        }
98        loop {
99            self.skip_ws();
100            if self.peek() != Some(b'"') {
101                return Err("object key must be a string");
102            }
103            let key = self.parse_string()?;
104            let key = heap.string(&key);
105            self.skip_ws();
106            if self.peek() != Some(b':') {
107                return Err("expected ':' after object key");
108            }
109            self.pos += 1;
110            // The key goes in first, so the value's allocation cannot free it.
111            // A key the object already had takes the later value, in the place
112            // the first one had.
113            map_insert(&mut heap.maps[id], key, NULL, heap.strings);
114            let val = self.parse_value(heap)?;
115            map_insert(&mut heap.maps[id], key, val, heap.strings);
116            self.fill(heap, val)?;
117            self.skip_ws();
118            match self.peek() {
119                Some(b',') => self.pos += 1,
120                Some(b'}') => {
121                    self.pos += 1;
122                    return Ok(());
123                }
124                _ => return Err("expected ',' or '}' in object"),
125            }
126        }
127    }
128
129    fn fill_array(&mut self, heap: &mut Heap<'_>, id: usize) -> Result<(), &'static str> {
130        self.skip_ws();
131        if self.peek() == Some(b']') {
132            self.pos += 1;
133            return Ok(());
134        }
135        loop {
136            let val = self.parse_value(heap)?;
137            heap.objs[id].push(val);
138            self.fill(heap, val)?;
139            self.skip_ws();
140            match self.peek() {
141                Some(b',') => self.pos += 1,
142                Some(b']') => {
143                    self.pos += 1;
144                    return Ok(());
145                }
146                _ => return Err("expected ',' or ']' in array"),
147            }
148        }
149    }
150
151    fn parse_string(&mut self) -> Result<String, &'static str> {
152        self.pos += 1; // consume opening quote
153        let mut out = String::new();
154        loop {
155            let Some(c) = self.peek() else {
156                return Err("unterminated string");
157            };
158            self.pos += 1;
159            match c {
160                b'"' => return Ok(out),
161                b'\\' => {
162                    let Some(esc) = self.peek() else {
163                        return Err("unterminated escape");
164                    };
165                    self.pos += 1;
166                    match esc {
167                        b'"' => out.push('"'),
168                        b'\\' => out.push('\\'),
169                        b'/' => out.push('/'),
170                        b'b' => out.push('\u{0008}'),
171                        b'f' => out.push('\u{000C}'),
172                        b'n' => out.push('\n'),
173                        b'r' => out.push('\r'),
174                        b't' => out.push('\t'),
175                        b'u' => {
176                            let cp = self.parse_hex4()?;
177                            if (0xD800..=0xDBFF).contains(&cp) {
178                                // High surrogate: a low surrogate must follow.
179                                if self.peek() != Some(b'\\') {
180                                    return Err("unpaired surrogate");
181                                }
182                                self.pos += 1;
183                                if self.peek() != Some(b'u') {
184                                    return Err("unpaired surrogate");
185                                }
186                                self.pos += 1;
187                                let low = self.parse_hex4()?;
188                                if !(0xDC00..=0xDFFF).contains(&low) {
189                                    return Err("invalid low surrogate");
190                                }
191                                let c = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00);
192                                match char::from_u32(c) {
193                                    Some(ch) => out.push(ch),
194                                    None => return Err("invalid unicode escape"),
195                                }
196                            } else {
197                                match char::from_u32(cp) {
198                                    Some(ch) => out.push(ch),
199                                    None => return Err("invalid unicode escape"),
200                                }
201                            }
202                        }
203                        _ => return Err("invalid escape"),
204                    }
205                }
206                c if c.is_ascii() => out.push(c as char),
207                // The lead byte of a multi-byte character: the input is a valid
208                // `&str`, so the whole character is copied through as text.
209                c => {
210                    let start = self.pos - 1;
211                    let len = if c >= 0xF0 {
212                        4
213                    } else if c >= 0xE0 {
214                        3
215                    } else {
216                        2
217                    };
218                    let end = (start + len).min(self.bytes.len());
219                    match std::str::from_utf8(&self.bytes[start..end]) {
220                        Ok(ch) => out.push_str(ch),
221                        Err(_) => return Err("invalid utf-8"),
222                    }
223                    self.pos = end;
224                }
225            }
226        }
227    }
228
229    fn parse_hex4(&mut self) -> Result<u32, &'static str> {
230        if self.pos + 4 > self.bytes.len() {
231            return Err("truncated unicode escape");
232        }
233        let mut v: u32 = 0;
234        for _ in 0..4 {
235            let d = self.bytes[self.pos];
236            self.pos += 1;
237            let n = match d {
238                b'0'..=b'9' => (d - b'0') as u32,
239                b'a'..=b'f' => (d - b'a' + 10) as u32,
240                b'A'..=b'F' => (d - b'A' + 10) as u32,
241                _ => return Err("invalid hex digit"),
242            };
243            v = (v << 4) | n;
244        }
245        Ok(v)
246    }
247
248    fn parse_bool(&mut self) -> Result<Data, &'static str> {
249        if self.bytes[self.pos..].starts_with(b"true") {
250            self.pos += 4;
251            Ok(Data::bool(true))
252        } else if self.bytes[self.pos..].starts_with(b"false") {
253            self.pos += 5;
254            Ok(Data::bool(false))
255        } else {
256            Err("invalid literal")
257        }
258    }
259
260    fn parse_null(&mut self) -> Result<Data, &'static str> {
261        if self.bytes[self.pos..].starts_with(b"null") {
262            self.pos += 4;
263            Ok(NULL)
264        } else {
265            Err("invalid literal")
266        }
267    }
268
269    fn parse_number(&mut self) -> Result<Data, &'static str> {
270        let start = self.pos;
271        let mut is_float = false;
272        if self.peek() == Some(b'-') {
273            self.pos += 1;
274        }
275        while let Some(c) = self.peek() {
276            match c {
277                b'0'..=b'9' => self.pos += 1,
278                b'.' | b'e' | b'E' | b'+' | b'-' => {
279                    is_float = true;
280                    self.pos += 1;
281                }
282                _ => break,
283            }
284        }
285        let text = match std::str::from_utf8(&self.bytes[start..self.pos]) {
286            Ok(t) => t,
287            Err(_) => return Err("invalid number"),
288        };
289        if !is_float && let Ok(n) = text.parse::<i64>() {
290            return Ok(Data::int(n));
291        }
292        match text.parse::<f64>() {
293            Ok(f) => Ok(Data::float(f)),
294            Err(_) => Err("invalid number"),
295        }
296    }
297}
298
299/// Parses a json document into a `Data` graph. A malformed document returns a
300/// short static reason.
301///
302/// A collection the parse starts reads `registers` and `recursion_stack` as
303/// its roots, so every other value the program still holds has to be
304/// reachable from them, and the caller stores the result before it allocates
305/// again.
306pub fn json_parse(
307    input: &str,
308    obj_pool: &mut ObjectPool,
309    map_pool: &mut MapPool,
310    str_pool: &mut StringPool,
311    registers: &RegisterFile,
312    recursion_stack: &mut RegisterFile,
313    gc: &mut GcState,
314) -> Result<Data, &'static str> {
315    let mut heap = Heap {
316        objs: obj_pool,
317        maps: map_pool,
318        strings: str_pool,
319        gc,
320        registers,
321        recursion_stack,
322    };
323    let mut p = JsonParser {
324        bytes: input.as_bytes(),
325        pos: 0,
326        depth: 0,
327    };
328    let v = p.parse_value(&mut heap)?;
329    heap.recursion_stack.0.push(v);
330    let filled = p.fill(&mut heap, v);
331    heap.recursion_stack.0.pop();
332    filled?;
333    p.skip_ws();
334    if p.pos != p.bytes.len() {
335        return Err("trailing characters after value");
336    }
337    Ok(v)
338}
339
340fn escape_into(s: &str, out: &mut String) {
341    out.push('"');
342    for c in s.chars() {
343        match c {
344            '"' => out.push_str("\\\""),
345            '\\' => out.push_str("\\\\"),
346            '\n' => out.push_str("\\n"),
347            '\r' => out.push_str("\\r"),
348            '\t' => out.push_str("\\t"),
349            '\u{0008}' => out.push_str("\\b"),
350            '\u{000C}' => out.push_str("\\f"),
351            c if (c as u32) < 0x20 => {
352                out.push_str(&format!("\\u{:04x}", c as u32));
353            }
354            c => out.push(c),
355        }
356    }
357    out.push('"');
358}
359
360fn write_json(
361    d: Data,
362    out: &mut String,
363    obj_pool: &ObjectPool,
364    map_pool: &MapPool,
365    str_pool: &StringPool,
366    structs: &[Struct],
367    enums: &[EnumType],
368) {
369    if d.is_int() {
370        out.push_str(&d.as_int().to_string());
371    } else if d.is_float() {
372        let f = d.as_float();
373        if f.is_finite() {
374            // Debug formatting keeps a decimal point (`1.0`, not `1`), so a
375            // float round-trips back to a float rather than an int.
376            out.push_str(&format!("{f:?}"));
377        } else {
378            out.push_str("null");
379        }
380    } else if d.is_bool() {
381        out.push_str(if d.as_bool() { "true" } else { "false" });
382    } else if d.is_null() {
383        out.push_str("null");
384    } else if d.is_string() {
385        escape_into(d.as_str(str_pool), out);
386    } else if d.is_array() {
387        out.push('[');
388        for (i, e) in obj_pool[d.as_array()].iter().enumerate() {
389            if i != 0 {
390                out.push(',');
391            }
392            write_json(*e, out, obj_pool, map_pool, str_pool, structs, enums);
393        }
394        out.push(']');
395    } else if d.is_map() {
396        out.push('{');
397        for (i, (k, v)) in map_pool[d.as_map()].iter().enumerate() {
398            if i != 0 {
399                out.push(',');
400            }
401            // json object keys are strings. A string key is emitted directly; a
402            // non-string key is rendered to its text form and quoted.
403            if k.is_string() {
404                escape_into(k.as_str(str_pool), out);
405            } else {
406                let key = k.format(obj_pool, str_pool, map_pool, structs, enums, true);
407                escape_into(&key, out);
408            }
409            out.push(':');
410            write_json(*v, out, obj_pool, map_pool, str_pool, structs, enums);
411        }
412        out.push('}');
413    } else {
414        // struct/enum have no json shape; emit their text form as a string.
415        let s = d.format(obj_pool, str_pool, map_pool, structs, enums, true);
416        escape_into(&s, out);
417    }
418}
419
420/// Serializes a `Data` value to a json string.
421pub fn json_stringify(
422    d: Data,
423    obj_pool: &ObjectPool,
424    map_pool: &MapPool,
425    str_pool: &StringPool,
426    structs: &[Struct],
427    enums: &[EnumType],
428) -> String {
429    let mut out = String::new();
430    write_json(d, &mut out, obj_pool, map_pool, str_pool, structs, enums);
431    out
432}
433
434#[cfg(test)]
435mod json_tests {
436    use super::MAX_NESTING_DEPTH;
437    use crate::data::Data;
438    use crate::gc::GcState;
439    use crate::vm::{MapPool, ObjectPool, Pool, RegisterFile, StringPool};
440
441    fn pools() -> (ObjectPool, MapPool, StringPool) {
442        (Pool(Vec::new()), Pool(Vec::new()), StringPool::default())
443    }
444
445    /// Parses `input` with no roots beside the parse itself.
446    fn json_parse(
447        input: &str,
448        objs: &mut ObjectPool,
449        maps: &mut MapPool,
450        strings: &mut StringPool,
451    ) -> Result<Data, &'static str> {
452        super::json_parse(
453            input,
454            objs,
455            maps,
456            strings,
457            &RegisterFile(Vec::new()),
458            &mut RegisterFile(Vec::new()),
459            &mut GcState::default(),
460        )
461    }
462
463    #[test]
464    fn malformed_input_is_rejected() {
465        for bad in [
466            "nope",
467            "[1,",
468            "{oops",
469            "",
470            "{\"a\":}",
471            "[1] extra",
472            "\"open",
473        ] {
474            let (mut obj, mut map, mut strings) = pools();
475            assert!(
476                json_parse(bad, &mut obj, &mut map, &mut strings).is_err(),
477                "{bad:?} must not parse"
478            );
479        }
480    }
481
482    /// Values are read by recursive descent, so a long run of openers used to
483    /// exhaust the native stack and kill the process instead of raising.
484    #[test]
485    fn nesting_past_the_limit_raises_instead_of_overflowing() {
486        let (mut obj, mut map, mut strings) = pools();
487        assert_eq!(
488            json_parse(&"[".repeat(200_000), &mut obj, &mut map, &mut strings),
489            Err("nesting too deep")
490        );
491
492        let (mut obj, mut map, mut strings) = pools();
493        assert_eq!(
494            json_parse(&"{\"a\":".repeat(200_000), &mut obj, &mut map, &mut strings),
495            Err("nesting too deep")
496        );
497    }
498
499    #[test]
500    fn nesting_up_to_the_limit_parses() {
501        let depth = MAX_NESTING_DEPTH as usize;
502        let doc = format!("{}{}", "[".repeat(depth), "]".repeat(depth));
503        let (mut obj, mut map, mut strings) = pools();
504        assert!(json_parse(&doc, &mut obj, &mut map, &mut strings).is_ok());
505    }
506
507    /// A string over six bytes is boxed as its pool slot, and the same text
508    /// can sit in more than one slot. A parsed key is found by its text, the
509    /// way a program looks it up, whatever slot the program's string is in.
510    #[test]
511    fn object_keys_are_found_by_their_text() {
512        let (mut obj, mut map, mut strings) = pools();
513        let parsed = json_parse(
514            "{\"n\": 7, \"long_key_name\": 42}",
515            &mut obj,
516            &mut map,
517            &mut strings,
518        )
519        .expect("valid object parses");
520        assert!(parsed.is_map());
521
522        let short = Data::p_str("n", &mut strings);
523        let long = Data::p_str("long_key_name", &mut strings);
524        strings.push(String::from("long_key_name"));
525        let other_slot = Data::large_str_id((strings.len() - 1) as u64);
526        let entries = &map[parsed.as_map()];
527        let get = |key: Data| {
528            entries
529                .get(&crate::vm::KeyByText {
530                    key,
531                    strings: &strings,
532                })
533                .copied()
534                .map(Data::as_int)
535        };
536        assert_eq!(get(short), Some(7));
537        assert_eq!(get(long), Some(42));
538        assert_eq!(get(other_slot), Some(42));
539    }
540
541    /// A raw multi-byte character is text, not a run of Latin-1 bytes, so it
542    /// reads back equal to the same string written in a program.
543    #[test]
544    fn raw_non_ascii_characters_parse_as_themselves() {
545        for text in ["\u{e9}", "a\u{e9}b", "\u{4e2d}\u{6587} and \u{1f600} here"] {
546            let (mut obj, mut map, mut strings) = pools();
547            let doc = format!("\"{text}\"");
548            let parsed =
549                json_parse(&doc, &mut obj, &mut map, &mut strings).expect("valid string parses");
550            assert_eq!(parsed, Data::p_str(text, &mut strings), "{text:?}");
551        }
552    }
553
554    /// Equal strings anywhere in a document share one slot, so values compare
555    /// equal as well as keys.
556    #[test]
557    fn equal_strings_share_a_pool_slot() {
558        let (mut obj, mut map, mut strings) = pools();
559        let parsed = json_parse(
560            "[\"a_long_repeated_value\", \"a_long_repeated_value\"]",
561            &mut obj,
562            &mut map,
563            &mut strings,
564        )
565        .expect("valid array parses");
566        let items = &obj[parsed.as_array()];
567        assert_eq!(items[0], items[1]);
568    }
569
570    /// A document with thousands of lists and maps starts a collection
571    /// partway through its parse, which marks and sweeps before the parse
572    /// ends, with a dropped earlier parse on the heap for the sweep to free and
573    /// the parse to reuse. What the parse has built so far is reachable only
574    /// through the recursion stack and its parents, and it all survives.
575    #[test]
576    fn a_collection_mid_parse_keeps_the_partial_value() {
577        let entries: Vec<String> = (0..3000)
578            .map(|i| {
579                format!(
580                    "{{\"id\":{i},\"name\":\"an entry named after {i}\",\"tags\":[{i},\"tag {i} of many\"]}}"
581                )
582            })
583            .collect();
584        let doc = format!("[{}]", entries.join(","));
585        let (mut objs, mut maps, mut strings) = pools();
586        let registers = RegisterFile(Vec::new());
587        let mut stack = RegisterFile(Vec::new());
588        let mut gc = GcState::default();
589        let mut parse = |objs: &mut ObjectPool, maps: &mut MapPool, strings: &mut StringPool| {
590            super::json_parse(&doc, objs, maps, strings, &registers, &mut stack, &mut gc)
591                .expect("the document parses")
592        };
593        parse(&mut objs, &mut maps, &mut strings);
594        let parsed = parse(&mut objs, &mut maps, &mut strings);
595        assert!(gc.cycles() > 0, "the parses never started a collection");
596        assert!(stack.0.is_empty(), "the parse left its root behind");
597        let text = super::json_stringify(parsed, &objs, &maps, &strings, &[], &[]);
598        assert!(
599            text == doc,
600            "the parsed value reads back as a different document"
601        );
602    }
603}