Skip to main content

cjc_runtime/
json.rs

1//! Hand-rolled JSON parser and emitter for CJC.
2//!
3//! Design decisions:
4//! - Uses `BTreeMap` for object keys → deterministic, sorted output
5//! - No external dependencies (no serde)
6//! - Converts JSON ↔ CJC `Value` directly
7//! - Strings always use `\uXXXX` escaping for non-ASCII in output
8
9use std::collections::BTreeMap;
10use std::rc::Rc;
11
12use crate::Value;
13
14// ---------------------------------------------------------------------------
15// JSON Value (intermediate representation)
16// ---------------------------------------------------------------------------
17
18/// A JSON value, using BTreeMap for deterministic key ordering.
19#[derive(Debug, Clone, PartialEq)]
20pub enum JsonValue {
21    Null,
22    Bool(bool),
23    Number(f64),
24    String(String),
25    Array(Vec<JsonValue>),
26    Object(BTreeMap<String, JsonValue>),
27}
28
29// ---------------------------------------------------------------------------
30// Parser
31// ---------------------------------------------------------------------------
32
33/// A simple recursive-descent JSON parser.
34struct JsonParser<'a> {
35    input: &'a [u8],
36    pos: usize,
37}
38
39impl<'a> JsonParser<'a> {
40    fn new(input: &'a str) -> Self {
41        Self {
42            input: input.as_bytes(),
43            pos: 0,
44        }
45    }
46
47    fn peek(&self) -> Option<u8> {
48        self.input.get(self.pos).copied()
49    }
50
51    fn advance(&mut self) -> Option<u8> {
52        let ch = self.input.get(self.pos).copied();
53        if ch.is_some() {
54            self.pos += 1;
55        }
56        ch
57    }
58
59    fn skip_whitespace(&mut self) {
60        while let Some(ch) = self.peek() {
61            if ch == b' ' || ch == b'\t' || ch == b'\n' || ch == b'\r' {
62                self.pos += 1;
63            } else {
64                break;
65            }
66        }
67    }
68
69    fn expect(&mut self, expected: u8) -> Result<(), String> {
70        match self.advance() {
71            Some(ch) if ch == expected => Ok(()),
72            Some(ch) => Err(format!(
73                "expected '{}', found '{}' at position {}",
74                expected as char, ch as char, self.pos - 1
75            )),
76            None => Err(format!("unexpected end of input, expected '{}'", expected as char)),
77        }
78    }
79
80    fn parse_value(&mut self) -> Result<JsonValue, String> {
81        self.skip_whitespace();
82        match self.peek() {
83            None => Err("unexpected end of input".into()),
84            Some(b'"') => self.parse_string().map(JsonValue::String),
85            Some(b'{') => self.parse_object(),
86            Some(b'[') => self.parse_array(),
87            Some(b't') => self.parse_literal("true", JsonValue::Bool(true)),
88            Some(b'f') => self.parse_literal("false", JsonValue::Bool(false)),
89            Some(b'n') => self.parse_literal("null", JsonValue::Null),
90            Some(ch) if ch == b'-' || ch.is_ascii_digit() => self.parse_number(),
91            Some(ch) => Err(format!("unexpected character '{}' at position {}", ch as char, self.pos)),
92        }
93    }
94
95    fn parse_string(&mut self) -> Result<String, String> {
96        self.expect(b'"')?;
97        let mut result = String::new();
98        loop {
99            match self.advance() {
100                None => return Err("unterminated string".into()),
101                Some(b'"') => return Ok(result),
102                Some(b'\\') => {
103                    match self.advance() {
104                        Some(b'"') => result.push('"'),
105                        Some(b'\\') => result.push('\\'),
106                        Some(b'/') => result.push('/'),
107                        Some(b'b') => result.push('\u{0008}'),
108                        Some(b'f') => result.push('\u{000C}'),
109                        Some(b'n') => result.push('\n'),
110                        Some(b'r') => result.push('\r'),
111                        Some(b't') => result.push('\t'),
112                        Some(b'u') => {
113                            let hex = self.parse_hex4()?;
114                            if let Some(ch) = char::from_u32(hex) {
115                                result.push(ch);
116                            } else {
117                                result.push('\u{FFFD}');
118                            }
119                        }
120                        Some(ch) => return Err(format!("invalid escape '\\{}'", ch as char)),
121                        None => return Err("unterminated escape sequence".into()),
122                    }
123                }
124                Some(ch) => result.push(ch as char),
125            }
126        }
127    }
128
129    fn parse_hex4(&mut self) -> Result<u32, String> {
130        let mut value = 0u32;
131        for _ in 0..4 {
132            let ch = self.advance().ok_or("unexpected end in \\uXXXX")?;
133            let digit = match ch {
134                b'0'..=b'9' => (ch - b'0') as u32,
135                b'a'..=b'f' => (ch - b'a' + 10) as u32,
136                b'A'..=b'F' => (ch - b'A' + 10) as u32,
137                _ => return Err(format!("invalid hex digit '{}' in \\uXXXX", ch as char)),
138            };
139            value = value * 16 + digit;
140        }
141        Ok(value)
142    }
143
144    fn parse_number(&mut self) -> Result<JsonValue, String> {
145        let start = self.pos;
146        // Optional minus
147        if self.peek() == Some(b'-') {
148            self.pos += 1;
149        }
150        // Integer part
151        if self.peek() == Some(b'0') {
152            self.pos += 1;
153        } else {
154            if !self.peek().map_or(false, |c| c.is_ascii_digit()) {
155                return Err("expected digit".into());
156            }
157            while self.peek().map_or(false, |c| c.is_ascii_digit()) {
158                self.pos += 1;
159            }
160        }
161        // Fractional part
162        if self.peek() == Some(b'.') {
163            self.pos += 1;
164            while self.peek().map_or(false, |c| c.is_ascii_digit()) {
165                self.pos += 1;
166            }
167        }
168        // Exponent
169        if self.peek() == Some(b'e') || self.peek() == Some(b'E') {
170            self.pos += 1;
171            if self.peek() == Some(b'+') || self.peek() == Some(b'-') {
172                self.pos += 1;
173            }
174            while self.peek().map_or(false, |c| c.is_ascii_digit()) {
175                self.pos += 1;
176            }
177        }
178        let num_str = std::str::from_utf8(&self.input[start..self.pos])
179            .map_err(|_| "invalid UTF-8 in number")?;
180        let value: f64 = num_str
181            .parse()
182            .map_err(|_| format!("invalid number: {}", num_str))?;
183        Ok(JsonValue::Number(value))
184    }
185
186    fn parse_array(&mut self) -> Result<JsonValue, String> {
187        self.expect(b'[')?;
188        self.skip_whitespace();
189        let mut items = Vec::new();
190        if self.peek() == Some(b']') {
191            self.pos += 1;
192            return Ok(JsonValue::Array(items));
193        }
194        loop {
195            items.push(self.parse_value()?);
196            self.skip_whitespace();
197            match self.peek() {
198                Some(b',') => {
199                    self.pos += 1;
200                }
201                Some(b']') => {
202                    self.pos += 1;
203                    return Ok(JsonValue::Array(items));
204                }
205                _ => return Err("expected ',' or ']' in array".into()),
206            }
207        }
208    }
209
210    fn parse_object(&mut self) -> Result<JsonValue, String> {
211        self.expect(b'{')?;
212        self.skip_whitespace();
213        let mut map = BTreeMap::new();
214        if self.peek() == Some(b'}') {
215            self.pos += 1;
216            return Ok(JsonValue::Object(map));
217        }
218        loop {
219            self.skip_whitespace();
220            let key = self.parse_string()?;
221            self.skip_whitespace();
222            self.expect(b':')?;
223            let value = self.parse_value()?;
224            map.insert(key, value);
225            self.skip_whitespace();
226            match self.peek() {
227                Some(b',') => {
228                    self.pos += 1;
229                }
230                Some(b'}') => {
231                    self.pos += 1;
232                    return Ok(JsonValue::Object(map));
233                }
234                _ => return Err("expected ',' or '}' in object".into()),
235            }
236        }
237    }
238
239    fn parse_literal(&mut self, expected: &str, value: JsonValue) -> Result<JsonValue, String> {
240        for byte in expected.as_bytes() {
241            match self.advance() {
242                Some(ch) if ch == *byte => {}
243                _ => return Err(format!("expected '{}'", expected)),
244            }
245        }
246        Ok(value)
247    }
248}
249
250// ---------------------------------------------------------------------------
251// Public API
252// ---------------------------------------------------------------------------
253
254/// Parse a JSON string into a CJC `Value`.
255///
256/// Mapping:
257/// - JSON null → `Value::Void`
258/// - JSON bool → `Value::Bool`
259/// - JSON number → `Value::Float` (or `Value::Int` if integer-valued)
260/// - JSON string → `Value::String`
261/// - JSON array → `Value::Array`
262/// - JSON object → `Value::Struct { name: "Json", fields }` with sorted keys
263pub fn json_parse(input: &str) -> Result<Value, String> {
264    let mut parser = JsonParser::new(input);
265    let json = parser.parse_value()?;
266    parser.skip_whitespace();
267    if parser.pos < parser.input.len() {
268        return Err(format!(
269            "trailing content at position {}",
270            parser.pos
271        ));
272    }
273    Ok(json_to_value(json))
274}
275
276/// Convert a CJC `Value` to a JSON string with sorted keys.
277pub fn json_stringify(value: &Value) -> Result<String, String> {
278    let json = value_to_json(value)?;
279    Ok(emit_json(&json))
280}
281
282// ---------------------------------------------------------------------------
283// Conversions: JsonValue ↔ CJC Value
284// ---------------------------------------------------------------------------
285
286fn json_to_value(json: JsonValue) -> Value {
287    match json {
288        JsonValue::Null => Value::Void,
289        JsonValue::Bool(b) => Value::Bool(b),
290        JsonValue::Number(n) => {
291            // If the number is an exact integer and within i64 range, use Int
292            if n.fract() == 0.0 && n >= i64::MIN as f64 && n <= i64::MAX as f64 {
293                Value::Int(n as i64)
294            } else {
295                Value::Float(n)
296            }
297        }
298        JsonValue::String(s) => Value::String(Rc::new(s)),
299        JsonValue::Array(items) => {
300            let vals: Vec<Value> = items.into_iter().map(json_to_value).collect();
301            Value::Array(Rc::new(vals))
302        }
303        JsonValue::Object(map) => {
304            let mut fields = std::collections::BTreeMap::new();
305            for (key, val) in map {
306                fields.insert(key, json_to_value(val));
307            }
308            Value::Struct {
309                name: "Json".to_string(),
310                fields,
311            }
312        }
313    }
314}
315
316fn value_to_json(value: &Value) -> Result<JsonValue, String> {
317    match value {
318        Value::Void => Ok(JsonValue::Null),
319        Value::Bool(b) => Ok(JsonValue::Bool(*b)),
320        Value::Int(n) => Ok(JsonValue::Number(*n as f64)),
321        Value::Float(n) => {
322            if n.is_nan() || n.is_infinite() {
323                Ok(JsonValue::Null) // JSON has no NaN/Inf
324            } else {
325                Ok(JsonValue::Number(*n))
326            }
327        }
328        Value::String(s) => Ok(JsonValue::String((**s).clone())),
329        Value::Array(arr) => {
330            let items: Result<Vec<JsonValue>, String> =
331                arr.iter().map(value_to_json).collect();
332            Ok(JsonValue::Array(items?))
333        }
334        Value::Struct { fields, .. } => {
335            let mut map = BTreeMap::new();
336            // Use sorted iteration for deterministic output
337            let mut sorted_keys: Vec<&String> = fields.keys().collect();
338            sorted_keys.sort();
339            for key in sorted_keys {
340                if let Some(val) = fields.get(key) {
341                    map.insert(key.clone(), value_to_json(val)?);
342                }
343            }
344            Ok(JsonValue::Object(map))
345        }
346        Value::Tuple(items) => {
347            let json_items: Result<Vec<JsonValue>, String> =
348                items.iter().map(value_to_json).collect();
349            Ok(JsonValue::Array(json_items?))
350        }
351        _ => Err(format!("cannot convert {} to JSON", value.type_name())),
352    }
353}
354
355// ---------------------------------------------------------------------------
356// Emitter
357// ---------------------------------------------------------------------------
358
359fn emit_json(json: &JsonValue) -> String {
360    let mut out = String::new();
361    emit_value(&mut out, json);
362    out
363}
364
365fn emit_value(out: &mut String, json: &JsonValue) {
366    match json {
367        JsonValue::Null => out.push_str("null"),
368        JsonValue::Bool(true) => out.push_str("true"),
369        JsonValue::Bool(false) => out.push_str("false"),
370        JsonValue::Number(n) => {
371            if n.fract() == 0.0 && n.abs() < 1e15 {
372                // Emit integers without decimal point
373                out.push_str(&format!("{}", *n as i64));
374            } else {
375                out.push_str(&format!("{}", n));
376            }
377        }
378        JsonValue::String(s) => emit_string(out, s),
379        JsonValue::Array(items) => {
380            out.push('[');
381            for (i, item) in items.iter().enumerate() {
382                if i > 0 {
383                    out.push(',');
384                }
385                emit_value(out, item);
386            }
387            out.push(']');
388        }
389        JsonValue::Object(map) => {
390            out.push('{');
391            // BTreeMap iterates in sorted order — deterministic!
392            for (i, (key, val)) in map.iter().enumerate() {
393                if i > 0 {
394                    out.push(',');
395                }
396                emit_string(out, key);
397                out.push(':');
398                emit_value(out, val);
399            }
400            out.push('}');
401        }
402    }
403}
404
405fn emit_string(out: &mut String, s: &str) {
406    out.push('"');
407    for ch in s.chars() {
408        match ch {
409            '"' => out.push_str("\\\""),
410            '\\' => out.push_str("\\\\"),
411            '\n' => out.push_str("\\n"),
412            '\r' => out.push_str("\\r"),
413            '\t' => out.push_str("\\t"),
414            '\u{0008}' => out.push_str("\\b"),
415            '\u{000C}' => out.push_str("\\f"),
416            c if c < '\u{0020}' => {
417                out.push_str(&format!("\\u{:04x}", c as u32));
418            }
419            c => out.push(c),
420        }
421    }
422    out.push('"');
423}
424
425// ---------------------------------------------------------------------------
426// Tests
427// ---------------------------------------------------------------------------
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432
433    #[test]
434    fn test_parse_null() {
435        let v = json_parse("null").unwrap();
436        assert!(matches!(v, Value::Void));
437    }
438
439    #[test]
440    fn test_parse_bool() {
441        assert!(matches!(json_parse("true").unwrap(), Value::Bool(true)));
442        assert!(matches!(json_parse("false").unwrap(), Value::Bool(false)));
443    }
444
445    #[test]
446    fn test_parse_integer() {
447        match json_parse("42").unwrap() {
448            Value::Int(n) => assert_eq!(n, 42),
449            other => panic!("expected Int, got {:?}", other),
450        }
451    }
452
453    #[test]
454    fn test_parse_float() {
455        match json_parse("3.14").unwrap() {
456            Value::Float(n) => assert!((n - 3.14).abs() < 1e-10),
457            other => panic!("expected Float, got {:?}", other),
458        }
459    }
460
461    #[test]
462    fn test_parse_string() {
463        match json_parse(r#""hello world""#).unwrap() {
464            Value::String(s) => assert_eq!(&*s, "hello world"),
465            other => panic!("expected String, got {:?}", other),
466        }
467    }
468
469    #[test]
470    fn test_parse_string_escapes() {
471        match json_parse(r#""line\nbreak\ttab""#).unwrap() {
472            Value::String(s) => assert_eq!(&*s, "line\nbreak\ttab"),
473            other => panic!("expected String, got {:?}", other),
474        }
475    }
476
477    #[test]
478    fn test_parse_array() {
479        let v = json_parse("[1, 2, 3]").unwrap();
480        match v {
481            Value::Array(arr) => {
482                assert_eq!(arr.len(), 3);
483                assert!(matches!(arr[0], Value::Int(1)));
484                assert!(matches!(arr[1], Value::Int(2)));
485                assert!(matches!(arr[2], Value::Int(3)));
486            }
487            other => panic!("expected Array, got {:?}", other),
488        }
489    }
490
491    #[test]
492    fn test_parse_object_sorted_keys() {
493        let v = json_parse(r#"{"z": 1, "a": 2, "m": 3}"#).unwrap();
494        match v {
495            Value::Struct { name, fields } => {
496                assert_eq!(name, "Json");
497                assert_eq!(fields.len(), 3);
498                assert!(matches!(fields.get("a"), Some(Value::Int(2))));
499                assert!(matches!(fields.get("z"), Some(Value::Int(1))));
500            }
501            other => panic!("expected Struct, got {:?}", other),
502        }
503    }
504
505    #[test]
506    fn test_parse_nested() {
507        let v = json_parse(r#"{"items": [1, {"nested": true}]}"#).unwrap();
508        match v {
509            Value::Struct { fields, .. } => {
510                match fields.get("items") {
511                    Some(Value::Array(arr)) => {
512                        assert_eq!(arr.len(), 2);
513                    }
514                    other => panic!("expected Array in items, got {:?}", other),
515                }
516            }
517            other => panic!("expected Struct, got {:?}", other),
518        }
519    }
520
521    #[test]
522    fn test_stringify_roundtrip() {
523        let input = r#"{"a":1,"b":"hello","c":[true,null]}"#;
524        let v = json_parse(input).unwrap();
525        let output = json_stringify(&v).unwrap();
526        assert_eq!(output, input);
527    }
528
529    #[test]
530    fn test_stringify_sorted_keys() {
531        // Keys must be alphabetically sorted in output
532        let v = json_parse(r#"{"z":1,"a":2}"#).unwrap();
533        let output = json_stringify(&v).unwrap();
534        assert_eq!(output, r#"{"a":2,"z":1}"#);
535    }
536
537    #[test]
538    fn test_roundtrip_empty_object() {
539        let v = json_parse("{}").unwrap();
540        let output = json_stringify(&v).unwrap();
541        assert_eq!(output, "{}");
542    }
543
544    #[test]
545    fn test_roundtrip_empty_array() {
546        let v = json_parse("[]").unwrap();
547        let output = json_stringify(&v).unwrap();
548        assert_eq!(output, "[]");
549    }
550
551    #[test]
552    fn test_parse_negative_number() {
553        match json_parse("-42").unwrap() {
554            Value::Int(n) => assert_eq!(n, -42),
555            other => panic!("expected Int, got {:?}", other),
556        }
557    }
558
559    #[test]
560    fn test_parse_scientific_notation() {
561        match json_parse("1.5e2").unwrap() {
562            Value::Int(n) => assert_eq!(n, 150),
563            other => panic!("expected Int(150), got {:?}", other),
564        }
565    }
566
567    #[test]
568    fn test_stringify_nan_becomes_null() {
569        let output = json_stringify(&Value::Float(f64::NAN)).unwrap();
570        assert_eq!(output, "null");
571    }
572}