Skip to main content

codineer_runtime/
json.rs

1use std::collections::BTreeMap;
2use std::fmt::{Display, Formatter};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum JsonValue {
6    Null,
7    Bool(bool),
8    Number(i64),
9    String(String),
10    Array(Vec<JsonValue>),
11    Object(BTreeMap<String, JsonValue>),
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct JsonError {
16    message: String,
17}
18
19impl JsonError {
20    #[must_use]
21    pub fn new(message: impl Into<String>) -> Self {
22        Self {
23            message: message.into(),
24        }
25    }
26}
27
28impl Display for JsonError {
29    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30        write!(f, "{}", self.message)
31    }
32}
33
34impl std::error::Error for JsonError {}
35
36impl JsonValue {
37    #[must_use]
38    pub fn render(&self) -> String {
39        match self {
40            Self::Null => "null".to_string(),
41            Self::Bool(value) => value.to_string(),
42            Self::Number(value) => value.to_string(),
43            Self::String(value) => render_string(value),
44            Self::Array(values) => {
45                let rendered = values
46                    .iter()
47                    .map(Self::render)
48                    .collect::<Vec<_>>()
49                    .join(",");
50                format!("[{rendered}]")
51            }
52            Self::Object(entries) => {
53                let rendered = entries
54                    .iter()
55                    .map(|(key, value)| format!("{}:{}", render_string(key), value.render()))
56                    .collect::<Vec<_>>()
57                    .join(",");
58                format!("{{{rendered}}}")
59            }
60        }
61    }
62
63    pub fn parse(source: &str) -> Result<Self, JsonError> {
64        let mut parser = Parser::new(source);
65        let value = parser.parse_value()?;
66        parser.skip_whitespace();
67        if parser.is_eof() {
68            Ok(value)
69        } else {
70            Err(JsonError::new("unexpected trailing content"))
71        }
72    }
73
74    #[must_use]
75    pub fn as_object(&self) -> Option<&BTreeMap<String, JsonValue>> {
76        match self {
77            Self::Object(value) => Some(value),
78            _ => None,
79        }
80    }
81
82    #[must_use]
83    pub fn as_array(&self) -> Option<&[JsonValue]> {
84        match self {
85            Self::Array(value) => Some(value),
86            _ => None,
87        }
88    }
89
90    #[must_use]
91    pub fn as_str(&self) -> Option<&str> {
92        match self {
93            Self::String(value) => Some(value),
94            _ => None,
95        }
96    }
97
98    #[must_use]
99    pub fn as_bool(&self) -> Option<bool> {
100        match self {
101            Self::Bool(value) => Some(*value),
102            _ => None,
103        }
104    }
105
106    #[must_use]
107    pub fn as_i64(&self) -> Option<i64> {
108        match self {
109            Self::Number(value) => Some(*value),
110            _ => None,
111        }
112    }
113}
114
115fn render_string(value: &str) -> String {
116    let mut rendered = String::with_capacity(value.len() + 2);
117    rendered.push('"');
118    for ch in value.chars() {
119        match ch {
120            '"' => rendered.push_str("\\\""),
121            '\\' => rendered.push_str("\\\\"),
122            '\n' => rendered.push_str("\\n"),
123            '\r' => rendered.push_str("\\r"),
124            '\t' => rendered.push_str("\\t"),
125            '\u{08}' => rendered.push_str("\\b"),
126            '\u{0C}' => rendered.push_str("\\f"),
127            control if control.is_control() => push_unicode_escape(&mut rendered, control),
128            plain => rendered.push(plain),
129        }
130    }
131    rendered.push('"');
132    rendered
133}
134
135fn push_unicode_escape(rendered: &mut String, control: char) {
136    const HEX: &[u8; 16] = b"0123456789abcdef";
137
138    rendered.push_str("\\u");
139    let value = u32::from(control);
140    for shift in [12_u32, 8, 4, 0] {
141        let nibble = ((value >> shift) & 0xF) as usize;
142        rendered.push(char::from(HEX[nibble]));
143    }
144}
145
146struct Parser<'a> {
147    chars: Vec<char>,
148    index: usize,
149    _source: &'a str,
150}
151
152impl<'a> Parser<'a> {
153    fn new(source: &'a str) -> Self {
154        Self {
155            chars: source.chars().collect(),
156            index: 0,
157            _source: source,
158        }
159    }
160
161    fn parse_value(&mut self) -> Result<JsonValue, JsonError> {
162        self.skip_whitespace();
163        match self.peek() {
164            Some('n') => self.parse_literal("null", JsonValue::Null),
165            Some('t') => self.parse_literal("true", JsonValue::Bool(true)),
166            Some('f') => self.parse_literal("false", JsonValue::Bool(false)),
167            Some('"') => self.parse_string().map(JsonValue::String),
168            Some('[') => self.parse_array(),
169            Some('{') => self.parse_object(),
170            Some('-' | '0'..='9') => self.parse_number().map(JsonValue::Number),
171            Some(other) => Err(JsonError::new(format!("unexpected character: {other}"))),
172            None => Err(JsonError::new("unexpected end of input")),
173        }
174    }
175
176    fn parse_literal(&mut self, expected: &str, value: JsonValue) -> Result<JsonValue, JsonError> {
177        for expected_char in expected.chars() {
178            if self.next() != Some(expected_char) {
179                return Err(JsonError::new(format!(
180                    "invalid literal: expected {expected}"
181                )));
182            }
183        }
184        Ok(value)
185    }
186
187    fn parse_string(&mut self) -> Result<String, JsonError> {
188        self.expect('"')?;
189        let mut value = String::new();
190        while let Some(ch) = self.next() {
191            match ch {
192                '"' => return Ok(value),
193                '\\' => value.push(self.parse_escape()?),
194                plain => value.push(plain),
195            }
196        }
197        Err(JsonError::new("unterminated string"))
198    }
199
200    fn parse_escape(&mut self) -> Result<char, JsonError> {
201        match self.next() {
202            Some('"') => Ok('"'),
203            Some('\\') => Ok('\\'),
204            Some('/') => Ok('/'),
205            Some('b') => Ok('\u{08}'),
206            Some('f') => Ok('\u{0C}'),
207            Some('n') => Ok('\n'),
208            Some('r') => Ok('\r'),
209            Some('t') => Ok('\t'),
210            Some('u') => self.parse_unicode_escape(),
211            Some(other) => Err(JsonError::new(format!("invalid escape sequence: {other}"))),
212            None => Err(JsonError::new("unexpected end of input in escape sequence")),
213        }
214    }
215
216    fn parse_unicode_escape(&mut self) -> Result<char, JsonError> {
217        let mut value = 0_u32;
218        for _ in 0..4 {
219            let Some(ch) = self.next() else {
220                return Err(JsonError::new("unexpected end of input in unicode escape"));
221            };
222            value = (value << 4)
223                | ch.to_digit(16)
224                    .ok_or_else(|| JsonError::new("invalid unicode escape"))?;
225        }
226        char::from_u32(value).ok_or_else(|| JsonError::new("invalid unicode scalar value"))
227    }
228
229    fn parse_array(&mut self) -> Result<JsonValue, JsonError> {
230        self.expect('[')?;
231        let mut values = Vec::new();
232        loop {
233            self.skip_whitespace();
234            if self.try_consume(']') {
235                break;
236            }
237            values.push(self.parse_value()?);
238            self.skip_whitespace();
239            if self.try_consume(']') {
240                break;
241            }
242            self.expect(',')?;
243        }
244        Ok(JsonValue::Array(values))
245    }
246
247    fn parse_object(&mut self) -> Result<JsonValue, JsonError> {
248        self.expect('{')?;
249        let mut entries = BTreeMap::new();
250        loop {
251            self.skip_whitespace();
252            if self.try_consume('}') {
253                break;
254            }
255            let key = self.parse_string()?;
256            self.skip_whitespace();
257            self.expect(':')?;
258            let value = self.parse_value()?;
259            entries.insert(key, value);
260            self.skip_whitespace();
261            if self.try_consume('}') {
262                break;
263            }
264            self.expect(',')?;
265        }
266        Ok(JsonValue::Object(entries))
267    }
268
269    fn parse_number(&mut self) -> Result<i64, JsonError> {
270        let mut value = String::new();
271        if self.try_consume('-') {
272            value.push('-');
273        }
274
275        while let Some(ch @ '0'..='9') = self.peek() {
276            value.push(ch);
277            self.index += 1;
278        }
279
280        if value.is_empty() || value == "-" {
281            return Err(JsonError::new("invalid number"));
282        }
283
284        value
285            .parse::<i64>()
286            .map_err(|_| JsonError::new("number out of range"))
287    }
288
289    fn expect(&mut self, expected: char) -> Result<(), JsonError> {
290        match self.next() {
291            Some(actual) if actual == expected => Ok(()),
292            Some(actual) => Err(JsonError::new(format!(
293                "expected '{expected}', found '{actual}'"
294            ))),
295            None => Err(JsonError::new(format!(
296                "expected '{expected}', found end of input"
297            ))),
298        }
299    }
300
301    fn try_consume(&mut self, expected: char) -> bool {
302        if self.peek() == Some(expected) {
303            self.index += 1;
304            true
305        } else {
306            false
307        }
308    }
309
310    fn skip_whitespace(&mut self) {
311        while matches!(self.peek(), Some(' ' | '\n' | '\r' | '\t')) {
312            self.index += 1;
313        }
314    }
315
316    fn peek(&self) -> Option<char> {
317        self.chars.get(self.index).copied()
318    }
319
320    fn next(&mut self) -> Option<char> {
321        let ch = self.peek()?;
322        self.index += 1;
323        Some(ch)
324    }
325
326    fn is_eof(&self) -> bool {
327        self.index >= self.chars.len()
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use super::{render_string, JsonValue};
334    use std::collections::BTreeMap;
335
336    #[test]
337    fn renders_and_parses_json_values() {
338        let mut object = BTreeMap::new();
339        object.insert("flag".to_string(), JsonValue::Bool(true));
340        object.insert(
341            "items".to_string(),
342            JsonValue::Array(vec![
343                JsonValue::Number(4),
344                JsonValue::String("ok".to_string()),
345            ]),
346        );
347
348        let rendered = JsonValue::Object(object).render();
349        let parsed = JsonValue::parse(&rendered).expect("json should parse");
350
351        assert_eq!(parsed.as_object().expect("object").len(), 2);
352    }
353
354    #[test]
355    fn escapes_control_characters() {
356        assert_eq!(render_string("a\n\t\"b"), "\"a\\n\\t\\\"b\"");
357    }
358
359    #[test]
360    fn renders_all_escape_sequences() {
361        assert_eq!(render_string("\r"), "\"\\r\"");
362        assert_eq!(render_string("\u{08}"), "\"\\b\"");
363        assert_eq!(render_string("\u{0C}"), "\"\\f\"");
364        assert_eq!(render_string("\\"), "\"\\\\\"");
365        assert!(render_string("\u{01}").contains("\\u0001"));
366    }
367
368    #[test]
369    fn renders_primitive_types() {
370        assert_eq!(JsonValue::Null.render(), "null");
371        assert_eq!(JsonValue::Bool(true).render(), "true");
372        assert_eq!(JsonValue::Bool(false).render(), "false");
373        assert_eq!(JsonValue::Number(42).render(), "42");
374        assert_eq!(JsonValue::Number(-7).render(), "-7");
375        assert_eq!(JsonValue::String("hi".into()).render(), "\"hi\"");
376        assert_eq!(JsonValue::Array(vec![]).render(), "[]");
377    }
378
379    #[test]
380    fn as_accessors_return_none_for_wrong_types() {
381        let null = JsonValue::Null;
382        assert!(null.as_object().is_none());
383        assert!(null.as_array().is_none());
384        assert!(null.as_str().is_none());
385        assert!(null.as_bool().is_none());
386        assert!(null.as_i64().is_none());
387
388        let num = JsonValue::Number(5);
389        assert!(num.as_str().is_none());
390        assert!(num.as_bool().is_none());
391        assert!(num.as_object().is_none());
392        assert!(num.as_array().is_none());
393        assert_eq!(num.as_i64(), Some(5));
394
395        let b = JsonValue::Bool(true);
396        assert_eq!(b.as_bool(), Some(true));
397        assert!(b.as_i64().is_none());
398    }
399
400    #[test]
401    fn parses_null_true_false_literals() {
402        assert_eq!(JsonValue::parse("null").unwrap(), JsonValue::Null);
403        assert_eq!(JsonValue::parse("true").unwrap(), JsonValue::Bool(true));
404        assert_eq!(JsonValue::parse("false").unwrap(), JsonValue::Bool(false));
405    }
406
407    #[test]
408    fn parses_numbers_including_negative() {
409        assert_eq!(JsonValue::parse("0").unwrap(), JsonValue::Number(0));
410        assert_eq!(JsonValue::parse("-42").unwrap(), JsonValue::Number(-42));
411        assert_eq!(JsonValue::parse("12345").unwrap(), JsonValue::Number(12345));
412    }
413
414    #[test]
415    fn parses_string_escapes() {
416        let parsed = JsonValue::parse(r#""a\nb\t\\\/\"\u0041""#).unwrap();
417        assert_eq!(parsed.as_str().unwrap(), "a\nb\t\\/\"A");
418    }
419
420    #[test]
421    fn parses_arrays_and_nested_objects() {
422        let parsed = JsonValue::parse(r#"[1, "two", [3], {"k": null}]"#).unwrap();
423        let arr = parsed.as_array().unwrap();
424        assert_eq!(arr.len(), 4);
425        assert_eq!(arr[0].as_i64(), Some(1));
426        assert_eq!(arr[2].as_array().unwrap().len(), 1);
427    }
428
429    #[test]
430    fn rejects_trailing_content() {
431        let err = JsonValue::parse("null extra").unwrap_err();
432        assert!(err.to_string().contains("trailing"));
433    }
434
435    #[test]
436    fn rejects_invalid_literal() {
437        assert!(JsonValue::parse("nul").is_err());
438        assert!(JsonValue::parse("tru").is_err());
439        assert!(JsonValue::parse("fals").is_err());
440    }
441
442    #[test]
443    fn rejects_unexpected_character() {
444        let err = JsonValue::parse("@").unwrap_err();
445        assert!(err.to_string().contains("unexpected character"));
446    }
447
448    #[test]
449    fn rejects_empty_input() {
450        let err = JsonValue::parse("").unwrap_err();
451        assert!(err.to_string().contains("end of input"));
452    }
453
454    #[test]
455    fn rejects_unterminated_string() {
456        let err = JsonValue::parse(r#""no end"#).unwrap_err();
457        assert!(err.to_string().contains("unterminated"));
458    }
459
460    #[test]
461    fn rejects_invalid_escape() {
462        let err = JsonValue::parse(r#""\x""#).unwrap_err();
463        assert!(err.to_string().contains("invalid escape"));
464    }
465
466    #[test]
467    fn rejects_truncated_unicode_escape() {
468        let err = JsonValue::parse(r#""\u00""#).unwrap_err();
469        assert!(err.to_string().contains("unicode escape"));
470    }
471
472    #[test]
473    fn rejects_bad_unicode_hex() {
474        let err = JsonValue::parse(r#""\u00GG""#).unwrap_err();
475        assert!(err.to_string().contains("unicode"));
476    }
477
478    #[test]
479    fn rejects_bare_minus() {
480        let err = JsonValue::parse("-").unwrap_err();
481        assert!(err.to_string().contains("invalid number"));
482    }
483
484    #[test]
485    fn rejects_number_overflow() {
486        let err = JsonValue::parse("99999999999999999999").unwrap_err();
487        assert!(err.to_string().contains("out of range"));
488    }
489
490    #[test]
491    fn rejects_bad_array_separator() {
492        let err = JsonValue::parse("[1 2]").unwrap_err();
493        assert!(err.to_string().contains("expected"));
494    }
495
496    #[test]
497    fn rejects_bad_object_separator() {
498        let err = JsonValue::parse(r#"{"a":1 "b":2}"#).unwrap_err();
499        assert!(err.to_string().contains("expected"));
500    }
501
502    #[test]
503    fn json_error_display() {
504        let err = super::JsonError::new("test error");
505        assert_eq!(err.to_string(), "test error");
506    }
507}