1use std::collections::HashMap;
34use std::sync::Arc;
35
36use serde_json::Value as JsonValue;
37use tatara_lisp_eval::ffi::Arity;
38use tatara_lisp_eval::{EvalError, Interpreter, MapKey, Value};
39
40pub fn install_json_stdlib<H: 'static>(interp: &mut Interpreter<H>) {
42 interp.register_fn(
43 "json_parse",
44 Arity::Exact(1),
45 |args: &[Value], _h: &mut H, span| {
46 let s = as_str(&args[0], "json_parse", span)?;
47 let parsed: JsonValue = serde_json::from_str(&s)
48 .map_err(|e| EvalError::native_fn("json_parse", e.to_string(), span))?;
49 Ok(json_to_value(&parsed))
50 },
51 );
52
53 interp.register_fn(
54 "json_stringify",
55 Arity::Exact(1),
56 |args: &[Value], _h: &mut H, span| {
57 let s = serde_json::to_string(&value_to_json(&args[0]))
58 .map_err(|e| EvalError::native_fn("json_stringify", e.to_string(), span))?;
59 Ok(Value::Str(Arc::from(s)))
60 },
61 );
62
63 interp.register_fn(
64 "json_get",
65 Arity::Exact(2),
66 |args: &[Value], _h: &mut H, span| {
67 let doc = &args[0];
68 let key = as_str(&args[1], "json_get", span)?;
69 match json_lookup(doc, &key) {
70 Some(v) => Ok(v),
71 None if is_doc(doc) => Ok(Value::Nil),
72 None => Err(EvalError::native_fn(
73 "json_get",
74 format!("cannot read a field from a {}", doc.type_name()),
75 span,
76 )),
77 }
78 },
79 );
80
81 interp.register_fn(
82 "json_get_or",
83 Arity::Exact(3),
84 |args: &[Value], _h: &mut H, span| {
85 let doc = &args[0];
86 let key = as_str(&args[1], "json_get_or", span)?;
87 match json_lookup(doc, &key) {
88 Some(v) => Ok(v),
89 None if is_doc(doc) => Ok(args[2].clone()),
90 None => Err(EvalError::native_fn(
91 "json_get_or",
92 format!("cannot read a field from a {}", doc.type_name()),
93 span,
94 )),
95 }
96 },
97 );
98}
99
100fn is_doc(v: &Value) -> bool {
104 matches!(v, Value::List(_) | Value::Map(_))
105}
106
107fn as_str(v: &Value, fname: &'static str, span: tatara_lisp::Span) -> Result<String, EvalError> {
108 match v {
109 Value::Str(s) => Ok(s.to_string()),
110 Value::Symbol(s) | Value::Keyword(s) => Ok(s.to_string()),
113 other => {
114 Err(EvalError::type_mismatch("a string", other.type_name(), span).into_native(fname))
115 }
116 }
117}
118
119trait NameError {
122 fn into_native(self, fname: &'static str) -> EvalError;
123}
124
125impl NameError for EvalError {
126 fn into_native(self, fname: &'static str) -> EvalError {
127 match self {
128 EvalError::TypeMismatch { expected, got, at } => {
129 EvalError::native_fn(fname, format!("expected {expected}, got {got}"), at)
130 }
131 other => other,
132 }
133 }
134}
135
136pub fn json_to_value(j: &JsonValue) -> Value {
140 match j {
141 JsonValue::Null => Value::Nil,
142 JsonValue::Bool(b) => Value::Bool(*b),
143 JsonValue::Number(n) => {
144 if let Some(i) = n.as_i64() {
145 Value::Int(i)
146 } else {
147 Value::Float(n.as_f64().unwrap_or(0.0))
148 }
149 }
150 JsonValue::String(s) => Value::Str(Arc::from(s.as_str())),
151 JsonValue::Array(xs) => Value::list(xs.iter().map(json_to_value)),
152 JsonValue::Object(m) if m.is_empty() => Value::map([]),
153 JsonValue::Object(m) => Value::list(
154 m.iter()
155 .map(|(k, v)| Value::list([Value::Str(Arc::from(k.as_str())), json_to_value(v)])),
156 ),
157 }
158}
159
160pub fn value_to_json(v: &Value) -> JsonValue {
163 match v {
164 Value::Nil => JsonValue::Null,
165 Value::Bool(b) => JsonValue::Bool(*b),
166 Value::Int(n) => JsonValue::Number((*n).into()),
167 Value::Float(n) => serde_json::Number::from_f64(*n)
168 .map(JsonValue::Number)
169 .unwrap_or(JsonValue::Null),
170 Value::Str(s) | Value::Symbol(s) | Value::Keyword(s) => {
171 JsonValue::String(s.as_ref().to_owned())
172 }
173 Value::List(xs) => {
178 let looks_like_object = !xs.is_empty()
179 && xs.iter().all(|entry| {
180 if let Value::List(pair) = entry {
181 pair.len() == 2
182 && matches!(
183 pair[0],
184 Value::Str(_) | Value::Symbol(_) | Value::Keyword(_)
185 )
186 } else {
187 false
188 }
189 });
190 if looks_like_object {
191 let mut m = serde_json::Map::with_capacity(xs.len());
192 for entry in xs.iter() {
193 if let Value::List(pair) = entry {
194 let k = match &pair[0] {
195 Value::Str(s) | Value::Symbol(s) | Value::Keyword(s) => {
196 s.as_ref().to_owned()
197 }
198 _ => unreachable!(),
199 };
200 m.insert(k, value_to_json(&pair[1]));
201 }
202 }
203 JsonValue::Object(m)
204 } else {
205 JsonValue::Array(xs.iter().map(value_to_json).collect())
206 }
207 }
208 Value::Map(m) => JsonValue::Object(
213 m.iter()
214 .map(|(k, v)| (map_key_to_json_key(k), value_to_json(v)))
215 .collect(),
216 ),
217 _ => JsonValue::Null,
218 }
219}
220
221fn map_key_to_json_key(k: &MapKey) -> String {
222 match k {
223 MapKey::Str(s) | MapKey::Symbol(s) | MapKey::Keyword(s) => s.as_ref().to_owned(),
224 MapKey::Nil => "null".to_owned(),
225 MapKey::Bool(b) => b.to_string(),
226 MapKey::Int(n) => n.to_string(),
227 MapKey::Float(bits) => f64::from_bits(*bits).to_string(),
228 }
229}
230
231fn json_lookup(doc: &Value, key: &str) -> Option<Value> {
234 match doc {
235 Value::List(entries) => entries.iter().find_map(|entry| {
236 let Value::List(pair) = entry else {
237 return None;
238 };
239 if pair.len() != 2 {
240 return None;
241 }
242 let matches = match &pair[0] {
243 Value::Str(s) | Value::Symbol(s) | Value::Keyword(s) => s.as_ref() == key,
244 _ => false,
245 };
246 matches.then(|| pair[1].clone())
247 }),
248 Value::Map(m) => {
249 let k = MapKey::Str(Arc::from(key));
250 m.get(&k).cloned()
251 }
252 _ => None,
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 fn eval(src: &str) -> Value {
261 crate::run(src)
262 .unwrap_or_else(|e| panic!("{src:?}: {e}"))
263 .value
264 }
265
266 fn s(src: &str) -> String {
267 match eval(src) {
268 Value::Str(v) => v.to_string(),
269 other => panic!("{src:?} produced {other:?}"),
270 }
271 }
272
273 fn assert_round_trips(src: &str) {
277 let parsed: JsonValue = serde_json::from_str(src).expect("fixture is valid JSON");
278 let out = value_to_json(&json_to_value(&parsed));
279 assert_eq!(out, parsed, "round-trip changed the document\nin: {src}");
280 }
281
282 #[test]
283 fn parse_and_get_an_object_field() {
284 let v = eval(r#"json_get(json_parse("{\"outcome\":\"ok\"}"), "outcome")"#);
285 assert!(matches!(v, Value::Str(ref x) if &**x == "ok"), "got {v:?}");
286 }
287
288 #[test]
289 fn a_missing_field_is_nil_not_an_error() {
290 assert!(matches!(
291 eval(r#"json_get(json_parse("{\"a\":1}"), "nope")"#),
292 Value::Nil
293 ));
294 }
295
296 #[test]
297 fn json_get_or_returns_the_default() {
298 assert!(matches!(
299 eval(r#"json_get_or(json_parse("{}"), "nope", 42)"#),
300 Value::Int(42)
301 ));
302 }
303
304 #[test]
305 fn nested_objects_read_by_walking_alists() {
306 let src = r#"json_get(
307 json_get(json_parse("{\"outer\":{\"inner\":7}}"), "outer"),
308 "inner")"#;
309 assert!(matches!(eval(src), Value::Int(7)));
310 }
311
312 #[test]
313 fn numbers_and_bools_keep_their_kinds() {
314 assert!(matches!(
315 eval(r#"json_get(json_parse("{\"n\":1,\"b\":true}"), "n")"#),
316 Value::Int(1)
317 ));
318 assert!(matches!(
319 eval(r#"json_get(json_parse("{\"b\":true}"), "b")"#),
320 Value::Bool(true)
321 ));
322 }
323
324 #[test]
325 fn null_is_nil() {
326 assert!(matches!(
327 eval(r#"json_get(json_parse("{\"x\":null}"), "x")"#),
328 Value::Nil
329 ));
330 }
331
332 #[test]
333 fn stringify_reaches_the_parser_shape_back() {
334 assert_eq!(
335 s(r#"json_stringify(json_parse("{\"a\":1,\"b\":[1,2]}"))"#),
336 r#"{"a":1,"b":[1,2]}"#
337 );
338 }
339
340 #[test]
341 fn empty_object_round_trips() {
342 assert_round_trips("{}");
343 assert_round_trips(r#"{"a":{}}"#);
344 assert_round_trips(r#"{"a":{"b":{}}}"#);
345 assert_round_trips(r#"[{},{}]"#);
346 }
347
348 #[test]
349 fn empty_array_is_not_confused_for_an_object() {
350 assert_round_trips("[]");
351 assert_round_trips(r#"{"a":[]}"#);
352 assert_round_trips(r#"{"obj":{},"arr":[]}"#);
353 }
354
355 #[test]
356 fn non_empty_objects_parse_to_alists() {
357 let v = eval(r#"json_parse("{\"a\":1}")"#);
358 assert!(
359 matches!(v, Value::List(_)),
360 "non-empty object must be an alist"
361 );
362 }
363
364 #[test]
365 fn a_parsed_empty_object_is_a_map_and_reads_as_nil() {
366 let v = eval(r#"json_parse("{}")"#);
367 assert!(
368 matches!(v, Value::Map(_)),
369 "an empty object must be a Map so it round-trips"
370 );
371 assert!(
372 matches!(
373 eval(r#"json_get(json_parse("{}"), "anything")"#),
374 Value::Nil
375 ),
376 "an empty object has no fields to read"
377 );
378 }
379
380 #[test]
381 fn unparseable_json_is_a_named_error() {
382 let err = crate::run(r#"json_parse("{not json")"#).expect_err("must raise");
383 assert!(
384 err.to_string().contains("json_parse"),
385 "the error must name the primitive: {err}"
386 );
387 }
388
389 #[test]
390 fn a_wrong_typed_argument_is_a_type_error() {
391 assert!(crate::run(r#"json_parse(42)"#).is_err());
392 assert!(crate::run(r#"json_get("not a doc", "k")"#).is_err());
393 }
394}