Skip to main content

hara_native/
json.rs

1use crate::core::Value;
2use crate::lang::data::{OrderedMap, Vector};
3use num_bigint::BigInt;
4
5const MAX_DEPTH: usize = 256;
6
7pub fn read(source: &str) -> Result<Value, String> {
8    let mut parser = Parser::new(source);
9    let value = parser.value(0)?;
10    parser.whitespace();
11    if parser.peek().is_some() {
12        return Err(parser.error("trailing content after JSON value"));
13    }
14    Ok(value)
15}
16
17pub fn write(value: &Value) -> Result<String, String> {
18    let mut out = String::new();
19    encode(&mut out, value, 0, false)?;
20    Ok(out)
21}
22
23pub fn write_pretty(value: &Value) -> Result<String, String> {
24    let mut out = String::new();
25    encode(&mut out, value, 0, true)?;
26    Ok(out)
27}
28
29fn encode(out: &mut String, value: &Value, depth: usize, pretty: bool) -> Result<(), String> {
30    match value {
31        Value::Nil => out.push_str("null"),
32        Value::Bool(value) => out.push_str(if *value { "true" } else { "false" }),
33        Value::Number(value) => out.push_str(&value.to_string()),
34        Value::BigInteger(value) => out.push_str(&value.to_string()),
35        Value::String(value) => string(out, value),
36        Value::Vector(values) => {
37            out.push('[');
38            for (index, value) in values.iter().enumerate() {
39                if index > 0 {
40                    out.push(',');
41                }
42                if pretty {
43                    newline(out, depth + 1);
44                }
45                encode(out, value, depth + 1, pretty)?;
46            }
47            if pretty && values.len() > 0 {
48                newline(out, depth);
49            }
50            out.push(']');
51        }
52        Value::Tuple(values) => {
53            out.push('[');
54            for (index, value) in values.iter().enumerate() {
55                if index > 0 {
56                    out.push(',');
57                }
58                if pretty {
59                    newline(out, depth + 1);
60                }
61                encode(out, value, depth + 1, pretty)?;
62            }
63            if pretty && !values.is_empty() {
64                newline(out, depth);
65            }
66            out.push(']');
67        }
68        value @ (Value::Map(_) | Value::OrderedMap(_) | Value::SortedMap(_) | Value::Trie(_)) => {
69            let values = crate::core::map_entries(value).expect("map values have entries");
70            out.push('{');
71            for (index, (key, value)) in values.iter().enumerate() {
72                let Value::String(key) = key else {
73                    return Err("json/write expects maps with string keys".into());
74                };
75                if index > 0 {
76                    out.push(',');
77                }
78                if pretty {
79                    newline(out, depth + 1);
80                }
81                string(out, key);
82                out.push_str(if pretty { ": " } else { ":" });
83                encode(out, value, depth + 1, pretty)?;
84            }
85            if pretty && !values.is_empty() {
86                newline(out, depth);
87            }
88            out.push('}');
89        }
90        _ => {
91            return Err(
92                "json/write accepts nil, booleans, integers, strings, vectors, and string-key maps"
93                    .into(),
94            )
95        }
96    }
97    Ok(())
98}
99
100fn newline(out: &mut String, depth: usize) {
101    out.push('\n');
102    out.push_str(&"  ".repeat(depth));
103}
104
105fn string(out: &mut String, value: &str) {
106    out.push('"');
107    for character in value.chars() {
108        match character {
109            '"' => out.push_str("\\\""),
110            '\\' => out.push_str("\\\\"),
111            '\u{08}' => out.push_str("\\b"),
112            '\u{0c}' => out.push_str("\\f"),
113            '\n' => out.push_str("\\n"),
114            '\r' => out.push_str("\\r"),
115            '\t' => out.push_str("\\t"),
116            character if character <= '\u{1f}' => {
117                out.push_str(&format!("\\u{:04x}", character as u32))
118            }
119            character => out.push(character),
120        }
121    }
122    out.push('"');
123}
124
125struct Parser {
126    input: Vec<char>,
127    offset: usize,
128}
129
130impl Parser {
131    fn new(source: &str) -> Self {
132        Self {
133            input: source.chars().collect(),
134            offset: 0,
135        }
136    }
137
138    fn peek(&self) -> Option<char> {
139        self.input.get(self.offset).copied()
140    }
141    fn take(&mut self) -> Option<char> {
142        let value = self.peek();
143        if value.is_some() {
144            self.offset += 1;
145        }
146        value
147    }
148    fn error(&self, message: &str) -> String {
149        format!("json/read: {message} at character {}", self.offset)
150    }
151    fn whitespace(&mut self) {
152        while matches!(self.peek(), Some(' ' | '\t' | '\n' | '\r')) {
153            self.offset += 1;
154        }
155    }
156
157    fn expect(&mut self, expected: char) -> Result<(), String> {
158        if self.take() == Some(expected) {
159            Ok(())
160        } else {
161            Err(self.error(&format!("expected '{expected}'")))
162        }
163    }
164    fn value(&mut self, depth: usize) -> Result<Value, String> {
165        if depth > MAX_DEPTH {
166            return Err(self.error("JSON nesting exceeds 256"));
167        }
168        self.whitespace();
169        match self.peek() {
170            Some('n') => {
171                self.literal("null")?;
172                Ok(Value::Nil)
173            }
174            Some('t') => {
175                self.literal("true")?;
176                Ok(Value::Bool(true))
177            }
178            Some('f') => {
179                self.literal("false")?;
180                Ok(Value::Bool(false))
181            }
182            Some('"') => self.string().map(Value::String),
183            Some('[') => self.array(depth + 1),
184            Some('{') => self.object(depth + 1),
185            Some(_) => self.number(),
186            None => Err(self.error("expected a JSON value")),
187        }
188    }
189    fn literal(&mut self, literal: &str) -> Result<(), String> {
190        for expected in literal.chars() {
191            if self.take() != Some(expected) {
192                return Err(self.error("invalid JSON token"));
193            }
194        }
195        Ok(())
196    }
197    fn number(&mut self) -> Result<Value, String> {
198        let start = self.offset;
199        if self.peek() == Some('-') {
200            self.offset += 1;
201        }
202        match self.peek() {
203            Some('0') => {
204                self.offset += 1;
205                if matches!(self.peek(), Some('0'..='9')) {
206                    return Err(self.error("leading zero in JSON number"));
207                }
208            }
209            Some('1'..='9') => {
210                while matches!(self.peek(), Some('0'..='9')) {
211                    self.offset += 1;
212                }
213            }
214            _ => return Err(self.error("expected a JSON value")),
215        }
216        if matches!(self.peek(), Some('.' | 'e' | 'E')) {
217            return Err(self.error("JSON numbers must be integers"));
218        }
219        let text = self.input[start..self.offset].iter().collect::<String>();
220        let value = BigInt::parse_bytes(text.as_bytes(), 10)
221            .ok_or_else(|| self.error("invalid JSON integer"))?;
222        Ok(crate::numeric::compact_integer(value))
223    }
224    fn array(&mut self, depth: usize) -> Result<Value, String> {
225        self.expect('[')?;
226        self.whitespace();
227        let mut values = Vec::new();
228        if self.peek() == Some(']') {
229            self.offset += 1;
230            return Ok(Value::Vector(Vector::from_iter(values)));
231        }
232        loop {
233            values.push(self.value(depth)?);
234            self.whitespace();
235            if self.peek() == Some(']') {
236                self.offset += 1;
237                return Ok(Value::Vector(Vector::from_iter(values)));
238            }
239            self.expect(',')?;
240            self.whitespace();
241            if self.peek() == Some(']') {
242                return Err(self.error("trailing commas are not valid JSON"));
243            }
244        }
245    }
246    fn object(&mut self, depth: usize) -> Result<Value, String> {
247        self.expect('{')?;
248        self.whitespace();
249        let mut values: Vec<(Value, Value)> = Vec::new();
250        if self.peek() == Some('}') {
251            self.offset += 1;
252            return Ok(Value::OrderedMap(Box::new(OrderedMap::from_iter(values))));
253        }
254        loop {
255            if self.peek() != Some('"') {
256                return Err(self.error("JSON object keys must be strings"));
257            }
258            let key = self.string()?;
259            self.whitespace();
260            self.expect(':')?;
261            let value = self.value(depth)?;
262            if values.iter().any(
263                |(existing, _)| matches!(existing, Value::String(existing) if existing == &key),
264            ) {
265                return Err(self.error("duplicate JSON object key"));
266            }
267            values.push((Value::String(key), value));
268            self.whitespace();
269            if self.peek() == Some('}') {
270                self.offset += 1;
271                return Ok(Value::OrderedMap(Box::new(OrderedMap::from_iter(values))));
272            }
273            self.expect(',')?;
274            self.whitespace();
275            if self.peek() == Some('}') {
276                return Err(self.error("trailing commas are not valid JSON"));
277            }
278        }
279    }
280    fn string(&mut self) -> Result<String, String> {
281        self.expect('"')?;
282        let mut out = String::new();
283        loop {
284            let Some(character) = self.take() else {
285                return Err(self.error("unterminated JSON string"));
286            };
287            match character {
288                '"' => return Ok(out),
289                character if character < '\u{20}' => {
290                    return Err(self.error("unescaped control character"))
291                }
292                '\\' => match self.take() {
293                    Some('"') => out.push('"'),
294                    Some('\\') => out.push('\\'),
295                    Some('/') => out.push('/'),
296                    Some('b') => out.push('\u{08}'),
297                    Some('f') => out.push('\u{0c}'),
298                    Some('n') => out.push('\n'),
299                    Some('r') => out.push('\r'),
300                    Some('t') => out.push('\t'),
301                    Some('u') => out.push(self.unicode_escape()?),
302                    _ => return Err(self.error("invalid JSON escape")),
303                },
304                character => out.push(character),
305            }
306        }
307    }
308    fn unicode_escape(&mut self) -> Result<char, String> {
309        let mut value = 0u32;
310        for _ in 0..4 {
311            let Some(character) = self.take() else {
312                return Err(self.error("incomplete Unicode escape"));
313            };
314            value = value
315                .checked_mul(16)
316                .and_then(|value| character.to_digit(16).map(|digit| value + digit))
317                .ok_or_else(|| self.error("invalid Unicode escape"))?;
318        }
319        char::from_u32(value).ok_or_else(|| self.error("invalid Unicode escape"))
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn integer_json_round_trips_canonical_boundaries() {
329        for (source, expected) in [
330            (i64::MIN.to_string(), Value::Number(i64::MIN)),
331            (i64::MAX.to_string(), Value::Number(i64::MAX)),
332            (
333                (BigInt::from(i64::MIN) - BigInt::from(1_i64)).to_string(),
334                Value::BigInteger(BigInt::from(i64::MIN) - BigInt::from(1_i64)),
335            ),
336            (
337                (BigInt::from(i64::MAX) + BigInt::from(1_i64)).to_string(),
338                Value::BigInteger(BigInt::from(i64::MAX) + BigInt::from(1_i64)),
339            ),
340        ] {
341            assert_eq!(read(&source).unwrap(), expected);
342            assert_eq!(read(&write(&expected).unwrap()).unwrap(), expected);
343        }
344    }
345}