Skip to main content

ironwork_rt/json/
parse.rs

1//! JSON text as JSON PARSE reads it (Language Reference SC27-8713-03, pp. 384-396): RFC 8259's
2//! grammar, with IBM's escape \x for NEXT LINE (U+0085).
3
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub enum Value {
6    /// Name/value pairs in the order of the text.
7    Object(Vec<(String, Value)>),
8    Array(Vec<Value>),
9    String(String),
10    /// A number as written.
11    Number(String),
12    Bool(bool),
13    Null,
14}
15
16/// Why JSON text is refused: JSON-CODE 100, 101 and 102.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum Invalid {
19    Malformed,
20    Empty,
21    Trailing,
22}
23
24impl Invalid {
25    pub fn code(self) -> i64 {
26        match self {
27            Invalid::Malformed => 100,
28            Invalid::Empty => 101,
29            Invalid::Trailing => 102,
30        }
31    }
32}
33
34struct Reader<'t> {
35    text: &'t [char],
36    at: usize,
37}
38
39type Read<T> = Result<T, Invalid>;
40
41fn is_space(c: char) -> bool {
42    matches!(c, ' ' | '\t' | '\n' | '\r')
43}
44
45impl Reader<'_> {
46    fn skip_space(&mut self) {
47        while self.text.get(self.at).is_some_and(|&c| is_space(c)) {
48            self.at += 1;
49        }
50    }
51
52    fn next(&mut self) -> Read<char> {
53        let c = *self.text.get(self.at).ok_or(Invalid::Malformed)?;
54        self.at += 1;
55        Ok(c)
56    }
57
58    fn expect(&mut self, word: &str) -> Read<()> {
59        for w in word.chars() {
60            if self.next()? != w {
61                return Err(Invalid::Malformed);
62            }
63        }
64        Ok(())
65    }
66
67    fn value(&mut self) -> Read<Value> {
68        self.skip_space();
69        let value = match self.text.get(self.at).ok_or(Invalid::Malformed)? {
70            '{' => self.object()?,
71            '[' => self.array()?,
72            '"' => Value::String(self.string()?),
73            't' => {
74                self.expect("true")?;
75                Value::Bool(true)
76            }
77            'f' => {
78                self.expect("false")?;
79                Value::Bool(false)
80            }
81            'n' => {
82                self.expect("null")?;
83                Value::Null
84            }
85            _ => Value::Number(self.number()?),
86        };
87        self.skip_space();
88        Ok(value)
89    }
90
91    fn object(&mut self) -> Read<Value> {
92        self.at += 1;
93        let mut pairs = Vec::new();
94        self.skip_space();
95        if self.text.get(self.at) == Some(&'}') {
96            self.at += 1;
97            return Ok(Value::Object(pairs));
98        }
99        loop {
100            self.skip_space();
101            if self.text.get(self.at) != Some(&'"') {
102                return Err(Invalid::Malformed);
103            }
104            let name = self.string()?;
105            self.skip_space();
106            self.expect(":")?;
107            pairs.push((name, self.value()?));
108            match self.next()? {
109                ',' => {}
110                '}' => return Ok(Value::Object(pairs)),
111                _ => return Err(Invalid::Malformed),
112            }
113        }
114    }
115
116    fn array(&mut self) -> Read<Value> {
117        self.at += 1;
118        let mut elements = Vec::new();
119        self.skip_space();
120        if self.text.get(self.at) == Some(&']') {
121            self.at += 1;
122            return Ok(Value::Array(elements));
123        }
124        loop {
125            elements.push(self.value()?);
126            match self.next()? {
127                ',' => {}
128                ']' => return Ok(Value::Array(elements)),
129                _ => return Err(Invalid::Malformed),
130            }
131        }
132    }
133
134    fn hex4(&mut self) -> Read<u32> {
135        let mut unit = 0;
136        for _ in 0..4 {
137            unit = unit * 16 + self.next()?.to_digit(16).ok_or(Invalid::Malformed)?;
138        }
139        Ok(unit)
140    }
141
142    fn string(&mut self) -> Read<String> {
143        self.at += 1;
144        let mut out = String::new();
145        loop {
146            match self.next()? {
147                '"' => return Ok(out),
148                '\\' => out.push(match self.next()? {
149                    '"' => '"',
150                    '\\' => '\\',
151                    '/' => '/',
152                    'b' => '\u{8}',
153                    'f' => '\u{c}',
154                    'n' => '\n',
155                    'r' => '\r',
156                    't' => '\t',
157                    'x' => '\u{85}',
158                    'u' => {
159                        let unit = self.hex4()?;
160                        let scalar = if (0xD800..0xDC00).contains(&unit) {
161                            self.expect("\\u")?;
162                            let low = self.hex4()?;
163                            if !(0xDC00..0xE000).contains(&low) {
164                                return Err(Invalid::Malformed);
165                            }
166                            0x10000 + ((unit - 0xD800) << 10) + (low - 0xDC00)
167                        } else {
168                            unit
169                        };
170                        char::from_u32(scalar).ok_or(Invalid::Malformed)?
171                    }
172                    _ => return Err(Invalid::Malformed),
173                }),
174                c if (c as u32) < 0x20 => return Err(Invalid::Malformed),
175                c => out.push(c),
176            }
177        }
178    }
179
180    fn digits(&mut self) -> usize {
181        let start = self.at;
182        while self.text.get(self.at).is_some_and(char::is_ascii_digit) {
183            self.at += 1;
184        }
185        self.at - start
186    }
187
188    fn number(&mut self) -> Read<String> {
189        let start = self.at;
190        if self.text.get(self.at) == Some(&'-') {
191            self.at += 1;
192        }
193        let first = self.text.get(self.at).copied();
194        let whole = self.digits();
195        if whole == 0 || (first == Some('0') && whole > 1) {
196            return Err(Invalid::Malformed);
197        }
198        if self.text.get(self.at) == Some(&'.') {
199            self.at += 1;
200            if self.digits() == 0 {
201                return Err(Invalid::Malformed);
202            }
203        }
204        if matches!(self.text.get(self.at), Some('e' | 'E')) {
205            self.at += 1;
206            if matches!(self.text.get(self.at), Some('+' | '-')) {
207                self.at += 1;
208            }
209            if self.digits() == 0 {
210                return Err(Invalid::Malformed);
211            }
212        }
213        Ok(self.text[start..self.at].iter().collect())
214    }
215}
216
217/// The value JSON text holds; nothing but whitespace may follow it.
218pub fn parse(text: &str) -> Result<Value, Invalid> {
219    let chars: Vec<char> = text.chars().collect();
220    if chars.iter().all(|&c| is_space(c)) {
221        return Err(Invalid::Empty);
222    }
223    let mut reader = Reader { text: &chars, at: 0 };
224    let value = reader.value()?;
225    if reader.at < chars.len() {
226        return Err(Invalid::Trailing);
227    }
228    Ok(value)
229}
230
231/// A number's sign, integer digits without leading zeros and fraction digits, its exponent applied.
232pub fn decimal(number: &str) -> (bool, String, String) {
233    let (negative, body) = match number.strip_prefix('-') {
234        Some(rest) => (true, rest),
235        None => (false, number),
236    };
237    let (mantissa, exponent) = match body.find(['e', 'E']) {
238        Some(e) => (&body[..e], body[e + 1..].parse::<i64>().unwrap_or(0)),
239        None => (body, 0),
240    };
241    let (int, frac) = mantissa.split_once('.').unwrap_or((mantissa, ""));
242    let digits = format!("{int}{frac}");
243    let point = int.len() as i64 + exponent;
244    let (int, frac) = if point <= 0 {
245        (String::new(), format!("{}{digits}", "0".repeat(point.unsigned_abs() as usize)))
246    } else if point as usize >= digits.len() {
247        (format!("{digits}{}", "0".repeat(point as usize - digits.len())), String::new())
248    } else {
249        (digits[..point as usize].to_owned(), digits[point as usize..].to_owned())
250    };
251    (negative, int.trim_start_matches('0').to_owned(), frac)
252}
253
254/// A string JSON PARSE takes as a number (p. 396): spaces, a sign, digits with at most one decimal
255/// point among or before them, and spaces; as sign, integer digits and fraction digits.
256pub fn numeric_string(text: &str, point: char) -> Option<(bool, String, String)> {
257    let body = text.trim_matches(' ');
258    let (negative, body) = match body.as_bytes().first() {
259        Some(b'-') => (true, &body[1..]),
260        Some(b'+') => (false, &body[1..]),
261        _ => (false, body),
262    };
263    let (int, frac) = body.split_once(point).unwrap_or((body, ""));
264    let all_digits = |s: &str| s.bytes().all(|b| b.is_ascii_digit());
265    (!(int.is_empty() && frac.is_empty()) && all_digits(int) && all_digits(frac)).then(|| (negative, int.trim_start_matches('0').to_owned(), frac.to_owned()))
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271
272    fn s(t: &str) -> Value {
273        Value::String(t.into())
274    }
275
276    #[test]
277    fn objects_arrays_and_scalars_parse_in_order() {
278        let v = parse(" {\"g\": {\"A\": \"Eh?\", \"3_\": 5, \"t\": [true, false, null, -1.5e2]}} ").unwrap();
279        let inner = Value::Object(vec![
280            ("A".into(), s("Eh?")),
281            ("3_".into(), Value::Number("5".into())),
282            ("t".into(), Value::Array(vec![Value::Bool(true), Value::Bool(false), Value::Null, Value::Number("-1.5e2".into())])),
283        ]);
284        assert_eq!(v, Value::Object(vec![("g".into(), inner)]));
285    }
286
287    #[test]
288    fn escapes_include_ibms_next_line() {
289        assert_eq!(parse(r#"["a\"b\\c\/\né\x😀"]"#).unwrap(), Value::Array(vec![s("a\"b\\c/\né\u{85}😀")]));
290    }
291
292    #[test]
293    fn bad_text_gives_its_code() {
294        assert_eq!(parse("   "), Err(Invalid::Empty));
295        assert_eq!(parse("{\"a\":1} x"), Err(Invalid::Trailing));
296        for bad in ["{\"a\":01}", "{\"a\":1,}", "{a:1}", "[\"\u{1}\"]", "{\"a\":tru}", "{\"a\":.5}", "{\"a\":1."] {
297            assert_eq!(parse(bad), Err(Invalid::Malformed), "{bad}");
298        }
299    }
300
301    #[test]
302    fn numbers_become_digits_either_side_of_the_point() {
303        assert_eq!(decimal("-125.53"), (true, "125".into(), "53".into()));
304        assert_eq!(decimal("1.5E+3"), (false, "1500".into(), String::new()));
305        assert_eq!(decimal("12e-3"), (false, String::new(), "012".into()));
306        assert_eq!(decimal("0"), (false, String::new(), String::new()));
307        assert_eq!(numeric_string(" -1,234 ", ','), Some((true, "1".into(), "234".into())));
308        assert_eq!(numeric_string("0042", '.'), Some((false, "42".into(), String::new())));
309        assert_eq!(numeric_string("4x", '.'), None);
310        assert_eq!(numeric_string(" ", '.'), None);
311    }
312}