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}