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
286/// Convert a [`JsonValue`] into a CJC [`Value`].
287///
288/// JSON objects become `Value::Struct` with name `"Json"` and [`BTreeMap`]
289/// fields (sorted keys). JSON arrays become `Value::Array`. Integer-valued
290/// numbers (no fractional part, within `i64` range) become `Value::Int`;
291/// all others become `Value::Float`.
292fn json_to_value(json: JsonValue) -> Value {
293    match json {
294        JsonValue::Null => Value::Void,
295        JsonValue::Bool(b) => Value::Bool(b),
296        JsonValue::Number(n) => {
297            // If the number is an exact integer and within i64 range, use Int
298            if n.fract() == 0.0 && n >= i64::MIN as f64 && n <= i64::MAX as f64 {
299                Value::Int(n as i64)
300            } else {
301                Value::Float(n)
302            }
303        }
304        JsonValue::String(s) => Value::String(Rc::new(s)),
305        JsonValue::Array(items) => {
306            let vals: Vec<Value> = items.into_iter().map(json_to_value).collect();
307            Value::Array(Rc::new(vals))
308        }
309        JsonValue::Object(map) => {
310            let mut fields = std::collections::BTreeMap::new();
311            for (key, val) in map {
312                fields.insert(key, json_to_value(val));
313            }
314            Value::Struct {
315                name: "Json".to_string(),
316                fields,
317            }
318        }
319    }
320}
321
322fn value_to_json(value: &Value) -> Result<JsonValue, String> {
323    match value {
324        Value::Void => Ok(JsonValue::Null),
325        Value::Bool(b) => Ok(JsonValue::Bool(*b)),
326        Value::Int(n) => Ok(JsonValue::Number(*n as f64)),
327        Value::Float(n) => {
328            if n.is_nan() || n.is_infinite() {
329                Ok(JsonValue::Null) // JSON has no NaN/Inf
330            } else {
331                Ok(JsonValue::Number(*n))
332            }
333        }
334        Value::String(s) => Ok(JsonValue::String((**s).clone())),
335        Value::Array(arr) => {
336            let items: Result<Vec<JsonValue>, String> =
337                arr.iter().map(value_to_json).collect();
338            Ok(JsonValue::Array(items?))
339        }
340        Value::Struct { fields, .. } => {
341            let mut map = BTreeMap::new();
342            // Use sorted iteration for deterministic output
343            let mut sorted_keys: Vec<&String> = fields.keys().collect();
344            sorted_keys.sort();
345            for key in sorted_keys {
346                if let Some(val) = fields.get(key) {
347                    map.insert(key.clone(), value_to_json(val)?);
348                }
349            }
350            Ok(JsonValue::Object(map))
351        }
352        Value::Tuple(items) => {
353            let json_items: Result<Vec<JsonValue>, String> =
354                items.iter().map(value_to_json).collect();
355            Ok(JsonValue::Array(json_items?))
356        }
357        _ => Err(format!("cannot convert {} to JSON", value.type_name())),
358    }
359}
360
361// ---------------------------------------------------------------------------
362// Emitter
363// ---------------------------------------------------------------------------
364
365fn emit_json(json: &JsonValue) -> String {
366    let mut out = String::new();
367    emit_value(&mut out, json);
368    out
369}
370
371fn emit_value(out: &mut String, json: &JsonValue) {
372    match json {
373        JsonValue::Null => out.push_str("null"),
374        JsonValue::Bool(true) => out.push_str("true"),
375        JsonValue::Bool(false) => out.push_str("false"),
376        JsonValue::Number(n) => {
377            if n.fract() == 0.0 && n.abs() < 1e15 {
378                // Emit integers without decimal point
379                out.push_str(&format!("{}", *n as i64));
380            } else {
381                out.push_str(&format!("{}", n));
382            }
383        }
384        JsonValue::String(s) => emit_string(out, s),
385        JsonValue::Array(items) => {
386            out.push('[');
387            for (i, item) in items.iter().enumerate() {
388                if i > 0 {
389                    out.push(',');
390                }
391                emit_value(out, item);
392            }
393            out.push(']');
394        }
395        JsonValue::Object(map) => {
396            out.push('{');
397            // BTreeMap iterates in sorted order — deterministic!
398            for (i, (key, val)) in map.iter().enumerate() {
399                if i > 0 {
400                    out.push(',');
401                }
402                emit_string(out, key);
403                out.push(':');
404                emit_value(out, val);
405            }
406            out.push('}');
407        }
408    }
409}
410
411fn emit_string(out: &mut String, s: &str) {
412    out.push('"');
413    for ch in s.chars() {
414        match ch {
415            '"' => out.push_str("\\\""),
416            '\\' => out.push_str("\\\\"),
417            '\n' => out.push_str("\\n"),
418            '\r' => out.push_str("\\r"),
419            '\t' => out.push_str("\\t"),
420            '\u{0008}' => out.push_str("\\b"),
421            '\u{000C}' => out.push_str("\\f"),
422            c if c < '\u{0020}' => {
423                out.push_str(&format!("\\u{:04x}", c as u32));
424            }
425            c => out.push(c),
426        }
427    }
428    out.push('"');
429}
430
431// ---------------------------------------------------------------------------
432// Tests
433// ---------------------------------------------------------------------------
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438
439    #[test]
440    fn test_parse_null() {
441        let v = json_parse("null").unwrap();
442        assert!(matches!(v, Value::Void));
443    }
444
445    #[test]
446    fn test_parse_bool() {
447        assert!(matches!(json_parse("true").unwrap(), Value::Bool(true)));
448        assert!(matches!(json_parse("false").unwrap(), Value::Bool(false)));
449    }
450
451    #[test]
452    fn test_parse_integer() {
453        match json_parse("42").unwrap() {
454            Value::Int(n) => assert_eq!(n, 42),
455            other => panic!("expected Int, got {:?}", other),
456        }
457    }
458
459    #[test]
460    fn test_parse_float() {
461        match json_parse("3.14").unwrap() {
462            Value::Float(n) => assert!((n - 3.14).abs() < 1e-10),
463            other => panic!("expected Float, got {:?}", other),
464        }
465    }
466
467    #[test]
468    fn test_parse_string() {
469        match json_parse(r#""hello world""#).unwrap() {
470            Value::String(s) => assert_eq!(&*s, "hello world"),
471            other => panic!("expected String, got {:?}", other),
472        }
473    }
474
475    #[test]
476    fn test_parse_string_escapes() {
477        match json_parse(r#""line\nbreak\ttab""#).unwrap() {
478            Value::String(s) => assert_eq!(&*s, "line\nbreak\ttab"),
479            other => panic!("expected String, got {:?}", other),
480        }
481    }
482
483    #[test]
484    fn test_parse_array() {
485        let v = json_parse("[1, 2, 3]").unwrap();
486        match v {
487            Value::Array(arr) => {
488                assert_eq!(arr.len(), 3);
489                assert!(matches!(arr[0], Value::Int(1)));
490                assert!(matches!(arr[1], Value::Int(2)));
491                assert!(matches!(arr[2], Value::Int(3)));
492            }
493            other => panic!("expected Array, got {:?}", other),
494        }
495    }
496
497    #[test]
498    fn test_parse_object_sorted_keys() {
499        let v = json_parse(r#"{"z": 1, "a": 2, "m": 3}"#).unwrap();
500        match v {
501            Value::Struct { name, fields } => {
502                assert_eq!(name, "Json");
503                assert_eq!(fields.len(), 3);
504                assert!(matches!(fields.get("a"), Some(Value::Int(2))));
505                assert!(matches!(fields.get("z"), Some(Value::Int(1))));
506            }
507            other => panic!("expected Struct, got {:?}", other),
508        }
509    }
510
511    #[test]
512    fn test_parse_nested() {
513        let v = json_parse(r#"{"items": [1, {"nested": true}]}"#).unwrap();
514        match v {
515            Value::Struct { fields, .. } => {
516                match fields.get("items") {
517                    Some(Value::Array(arr)) => {
518                        assert_eq!(arr.len(), 2);
519                    }
520                    other => panic!("expected Array in items, got {:?}", other),
521                }
522            }
523            other => panic!("expected Struct, got {:?}", other),
524        }
525    }
526
527    #[test]
528    fn test_stringify_roundtrip() {
529        let input = r#"{"a":1,"b":"hello","c":[true,null]}"#;
530        let v = json_parse(input).unwrap();
531        let output = json_stringify(&v).unwrap();
532        assert_eq!(output, input);
533    }
534
535    #[test]
536    fn test_stringify_sorted_keys() {
537        // Keys must be alphabetically sorted in output
538        let v = json_parse(r#"{"z":1,"a":2}"#).unwrap();
539        let output = json_stringify(&v).unwrap();
540        assert_eq!(output, r#"{"a":2,"z":1}"#);
541    }
542
543    #[test]
544    fn test_roundtrip_empty_object() {
545        let v = json_parse("{}").unwrap();
546        let output = json_stringify(&v).unwrap();
547        assert_eq!(output, "{}");
548    }
549
550    #[test]
551    fn test_roundtrip_empty_array() {
552        let v = json_parse("[]").unwrap();
553        let output = json_stringify(&v).unwrap();
554        assert_eq!(output, "[]");
555    }
556
557    #[test]
558    fn test_parse_negative_number() {
559        match json_parse("-42").unwrap() {
560            Value::Int(n) => assert_eq!(n, -42),
561            other => panic!("expected Int, got {:?}", other),
562        }
563    }
564
565    #[test]
566    fn test_parse_scientific_notation() {
567        match json_parse("1.5e2").unwrap() {
568            Value::Int(n) => assert_eq!(n, 150),
569            other => panic!("expected Int(150), got {:?}", other),
570        }
571    }
572
573    #[test]
574    fn test_stringify_nan_becomes_null() {
575        let output = json_stringify(&Value::Float(f64::NAN)).unwrap();
576        assert_eq!(output, "null");
577    }
578}