1use core::cell::RefCell;
7use mailang_bytecode::Value;
8use std::rc::Rc;
9
10pub fn builtin_json_parse(args: &[Value]) -> Result<Value, String> {
12 let s = match args.first() {
13 Some(Value::Str(s)) => s.to_string(),
14 _ => return Err("json_parse(s): s must be string".into()),
15 };
16 let mut p = Parser::new(&s);
17 p.skip_ws();
18 let v = p.parse_value()?;
19 p.skip_ws();
20 if p.pos != p.bytes.len() {
21 return Err(format!("json_parse: trailing data at {}", p.pos));
22 }
23 Ok(v)
24}
25
26pub fn builtin_json_stringify(args: &[Value]) -> Result<Value, String> {
28 let v = args.first().ok_or("json_stringify(v): missing value")?;
29 let mut out = String::new();
30 write_json(v, &mut out)?;
31 Ok(Value::Str(out.into()))
32}
33
34fn write_json(v: &Value, out: &mut String) -> Result<(), String> {
35 match v {
36 Value::Null => out.push_str("null"),
37 Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
38 Value::Int(n) => out.push_str(&n.to_string()),
39 Value::Float(n) => {
40 if n.fract() == 0.0 && n.is_finite() && n.abs() < 1e15 {
41 out.push_str(&format!("{}", *n as i64));
42 } else {
43 out.push_str(&n.to_string());
44 }
45 }
46 Value::Str(s) => write_json_string(s, out),
47 Value::Char(c) => write_json_string(&c.to_string(), out),
48 Value::Array(a) => {
49 out.push('[');
50 for (i, item) in a.borrow().iter().enumerate() {
51 if i > 0 {
52 out.push(',');
53 }
54 write_json(item, out)?;
55 }
56 out.push(']');
57 }
58 Value::Tuple(a) => {
59 out.push('[');
60 for (i, item) in a.borrow().iter().enumerate() {
61 if i > 0 {
62 out.push(',');
63 }
64 write_json(item, out)?;
65 }
66 out.push(']');
67 }
68 Value::Map(m) => {
69 out.push('{');
70 for (i, (k, val)) in m.borrow().iter().enumerate() {
71 if i > 0 {
72 out.push(',');
73 }
74 match k {
75 Value::Str(s) => write_json_string(s, out),
76 Value::Char(c) => write_json_string(&c.to_string(), out),
77 Value::Int(n) => write_json_string(&n.to_string(), out),
78 Value::Bool(b) => write_json_string(&b.to_string(), out),
79 other => {
80 return Err(format!("json_stringify: unsupported map key {:?}", other))
81 }
82 }
83 out.push(':');
84 write_json(val, out)?;
85 }
86 out.push('}');
87 }
88 Value::Ok(inner) | Value::Some(inner) => write_json(inner, out)?,
89 other => return Err(format!("json_stringify: unsupported type {:?}", other)),
90 }
91 Ok(())
92}
93
94fn write_json_string(s: &str, out: &mut String) {
95 out.push('"');
96 for c in s.chars() {
97 match c {
98 '"' => out.push_str("\\\""),
99 '\\' => out.push_str("\\\\"),
100 '\n' => out.push_str("\\n"),
101 '\r' => out.push_str("\\r"),
102 '\t' => out.push_str("\\t"),
103 '\u{08}' => out.push_str("\\b"),
104 '\u{0c}' => out.push_str("\\f"),
105 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
106 c => out.push(c),
107 }
108 }
109 out.push('"');
110}
111
112struct Parser<'a> {
113 bytes: &'a [u8],
114 pos: usize,
115}
116
117impl<'a> Parser<'a> {
118 fn new(s: &'a str) -> Self {
119 Self {
120 bytes: s.as_bytes(),
121 pos: 0,
122 }
123 }
124
125 fn peek(&self) -> Option<u8> {
126 self.bytes.get(self.pos).copied()
127 }
128
129 fn skip_ws(&mut self) {
130 while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
131 self.pos += 1;
132 }
133 }
134
135 fn expect(&mut self, b: u8) -> Result<(), String> {
136 if self.peek() == Some(b) {
137 self.pos += 1;
138 Ok(())
139 } else {
140 Err(format!(
141 "json_parse: expected '{}' at {}",
142 b as char, self.pos
143 ))
144 }
145 }
146
147 fn parse_value(&mut self) -> Result<Value, String> {
148 match self.peek() {
149 Some(b'{') => self.parse_object(),
150 Some(b'[') => self.parse_array(),
151 Some(b'"') => Ok(Value::Str(self.parse_string()?.into())),
152 Some(b't') => self.parse_lit("true", Value::Bool(true)),
153 Some(b'f') => self.parse_lit("false", Value::Bool(false)),
154 Some(b'n') => self.parse_lit("null", Value::Null),
155 Some(c) if c == b'-' || c.is_ascii_digit() => self.parse_number(),
156 other => Err(format!(
157 "json_parse: unexpected {:?} at {}",
158 other, self.pos
159 )),
160 }
161 }
162
163 fn parse_lit(&mut self, lit: &str, v: Value) -> Result<Value, String> {
164 if self.bytes[self.pos..].starts_with(lit.as_bytes()) {
165 self.pos += lit.len();
166 Ok(v)
167 } else {
168 Err(format!("json_parse: expected '{}' at {}", lit, self.pos))
169 }
170 }
171
172 fn parse_object(&mut self) -> Result<Value, String> {
173 self.expect(b'{')?;
174 let mut entries: Vec<(Value, Value)> = Vec::new();
175 self.skip_ws();
176 if self.peek() == Some(b'}') {
177 self.pos += 1;
178 return Ok(Value::Map(Rc::new(RefCell::new(entries))));
179 }
180 loop {
181 self.skip_ws();
182 let key = Value::Str(self.parse_string()?.into());
183 self.skip_ws();
184 self.expect(b':')?;
185 self.skip_ws();
186 let val = self.parse_value()?;
187 entries.push((key, val));
188 self.skip_ws();
189 match self.peek() {
190 Some(b',') => {
191 self.pos += 1;
192 }
193 Some(b'}') => {
194 self.pos += 1;
195 break;
196 }
197 other => {
198 return Err(format!(
199 "json_parse: expected ',' or '}}' at {} (got {:?})",
200 self.pos, other
201 ))
202 }
203 }
204 }
205 Ok(Value::Map(Rc::new(RefCell::new(entries))))
206 }
207
208 fn parse_array(&mut self) -> Result<Value, String> {
209 self.expect(b'[')?;
210 let mut items: Vec<Value> = Vec::new();
211 self.skip_ws();
212 if self.peek() == Some(b']') {
213 self.pos += 1;
214 return Ok(Value::Array(Rc::new(RefCell::new(items))));
215 }
216 loop {
217 self.skip_ws();
218 items.push(self.parse_value()?);
219 self.skip_ws();
220 match self.peek() {
221 Some(b',') => {
222 self.pos += 1;
223 }
224 Some(b']') => {
225 self.pos += 1;
226 break;
227 }
228 other => {
229 return Err(format!(
230 "json_parse: expected ',' or ']' at {} (got {:?})",
231 self.pos, other
232 ))
233 }
234 }
235 }
236 Ok(Value::Array(Rc::new(RefCell::new(items))))
237 }
238
239 fn parse_string(&mut self) -> Result<String, String> {
240 self.expect(b'"')?;
241 let mut s = String::new();
242 loop {
243 let c = self.peek().ok_or("json_parse: unterminated string")?;
244 self.pos += 1;
245 match c {
246 b'"' => break,
247 b'\\' => {
248 let e = self.peek().ok_or("json_parse: bad escape")?;
249 self.pos += 1;
250 match e {
251 b'"' => s.push('"'),
252 b'\\' => s.push('\\'),
253 b'/' => s.push('/'),
254 b'n' => s.push('\n'),
255 b'r' => s.push('\r'),
256 b't' => s.push('\t'),
257 b'b' => s.push('\u{08}'),
258 b'f' => s.push('\u{0c}'),
259 b'u' => {
260 let hex = self
261 .bytes
262 .get(self.pos..self.pos + 4)
263 .ok_or("json_parse: short \\u")?;
264 self.pos += 4;
265 let code = u16::from_str_radix(
266 std::str::from_utf8(hex).map_err(|e| e.to_string())?,
267 16,
268 )
269 .map_err(|e| e.to_string())?;
270 s.push(char::from_u32(code as u32).unwrap_or('\u{fffd}'));
272 }
273 other => return Err(format!("json_parse: bad escape \\{}", other as char)),
274 }
275 }
276 c if c >= 0x80 => {
278 let start = self.pos - 1;
279 let extra = match c {
280 0xC0..=0xDF => 1,
281 0xE0..=0xEF => 2,
282 _ => 3,
283 };
284 self.pos = start + 1 + extra;
285 let chunk = self
286 .bytes
287 .get(start..self.pos)
288 .ok_or("json_parse: bad utf-8")?;
289 s.push_str(std::str::from_utf8(chunk).map_err(|e| e.to_string())?);
290 }
291 c => s.push(c as char),
292 }
293 }
294 Ok(s)
295 }
296
297 fn parse_number(&mut self) -> Result<Value, String> {
298 let start = self.pos;
299 if self.peek() == Some(b'-') {
300 self.pos += 1;
301 }
302 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
303 self.pos += 1;
304 }
305 let mut is_float = false;
306 if self.peek() == Some(b'.') {
307 is_float = true;
308 self.pos += 1;
309 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
310 self.pos += 1;
311 }
312 }
313 if matches!(self.peek(), Some(b'e' | b'E')) {
314 is_float = true;
315 self.pos += 1;
316 if matches!(self.peek(), Some(b'+' | b'-')) {
317 self.pos += 1;
318 }
319 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
320 self.pos += 1;
321 }
322 }
323 let text = std::str::from_utf8(&self.bytes[start..self.pos]).map_err(|e| e.to_string())?;
324 if text.is_empty() || text == "-" {
325 return Err(format!("json_parse: bad number at {}", start));
326 }
327 if !is_float {
328 if let Ok(n) = text.parse::<i64>() {
329 return Ok(Value::Int(n));
330 }
331 }
332 text.parse::<f64>()
333 .map(Value::Float)
334 .map_err(|e| e.to_string())
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 fn s(x: &str) -> Value {
343 Value::Str(x.into())
344 }
345
346 #[test]
347 fn parse_scalars() {
348 assert_eq!(builtin_json_parse(&[s("null")]).unwrap(), Value::Null);
349 assert_eq!(builtin_json_parse(&[s("true")]).unwrap(), Value::Bool(true));
350 assert_eq!(
351 builtin_json_parse(&[s("false")]).unwrap(),
352 Value::Bool(false)
353 );
354 assert_eq!(builtin_json_parse(&[s("42")]).unwrap(), Value::Int(42));
355 assert_eq!(builtin_json_parse(&[s("-7")]).unwrap(), Value::Int(-7));
356 assert_eq!(builtin_json_parse(&[s("3.5")]).unwrap(), Value::Float(3.5));
357 assert_eq!(builtin_json_parse(&[s("\"hi\"")]).unwrap(), s("hi"));
358 }
359
360 #[test]
361 fn parse_nested_and_escapes() {
362 let v = builtin_json_parse(&[s(r#"{"a":[1,2,{"b":"x\ny"}],"c":null}"#)]).unwrap();
363 let out = builtin_json_stringify(std::slice::from_ref(&v)).unwrap();
364 assert_eq!(out, s(r#"{"a":[1,2,{"b":"x\ny"}],"c":null}"#));
365 }
366
367 #[test]
368 fn stringify_roundtrip() {
369 let src = r#"[{"k":1,"ok":true},{"n":-2.5,"s":"q\"z"}]"#;
370 let v = builtin_json_parse(&[s(src)]).unwrap();
371 let out = builtin_json_stringify(&[v]).unwrap();
372 assert_eq!(out, s(src));
373 }
374
375 #[test]
376 fn roundtrip_map_from_rust() {
377 let m = Value::Map(Rc::new(RefCell::new(vec![
378 (s("x"), Value::Int(1)),
379 (
380 s("y"),
381 Value::Array(Rc::new(RefCell::new(vec![Value::Bool(false), Value::Null]))),
382 ),
383 ])));
384 let text = builtin_json_stringify(&[m]).unwrap();
385 assert_eq!(text, s(r#"{"x":1,"y":[false,null]}"#));
386 let back = builtin_json_parse(&[text]).unwrap();
387 let again = builtin_json_stringify(&[back]).unwrap();
388 assert_eq!(again, s(r#"{"x":1,"y":[false,null]}"#));
389 }
390
391 #[test]
392 fn parse_rejects_trailing() {
393 assert!(builtin_json_parse(&[s("1 2")]).is_err());
394 }
395}