tatara_lisp_script/stdlib/
json.rs1use std::sync::Arc;
9
10use serde_json::Value as JsonValue;
11use tatara_lisp_eval::{Arity, EvalError, Interpreter, Value};
12
13use crate::script_ctx::ScriptCtx;
14use crate::stdlib::env::str_arg;
15
16pub fn install(interp: &mut Interpreter<ScriptCtx>) {
17 interp.register_fn(
18 "json-parse",
19 Arity::Exact(1),
20 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
21 let s = str_arg(&args[0], "json-parse", sp)?;
22 let parsed: JsonValue = serde_json::from_str(&s)
23 .map_err(|e| EvalError::native_fn("json-parse", e.to_string(), sp))?;
24 Ok(json_to_value(&parsed))
25 },
26 );
27
28 interp.register_fn(
29 "json-stringify",
30 Arity::Exact(1),
31 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
32 let s = serde_json::to_string(&value_to_json(&args[0]))
33 .map_err(|e| EvalError::native_fn("json-stringify", e.to_string(), sp))?;
34 Ok(Value::Str(Arc::from(s)))
35 },
36 );
37
38 interp.register_fn(
39 "alist-get",
40 Arity::Range(2, 3),
41 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
42 let key = match &args[1] {
43 Value::Str(s) => s.clone(),
44 Value::Symbol(s) | Value::Keyword(s) => s.clone(),
45 other => {
46 return Err(EvalError::native_fn(
47 "alist-get",
48 format!(
49 "key must be string/symbol/keyword, got {}",
50 other.type_name()
51 ),
52 sp,
53 ))
54 }
55 };
56 let default = args.get(2).cloned().unwrap_or(Value::Nil);
57 Ok(alist_lookup(&args[0], &key).unwrap_or(default))
58 },
59 );
60}
61
62pub fn json_to_value(j: &JsonValue) -> Value {
66 match j {
67 JsonValue::Null => Value::Nil,
68 JsonValue::Bool(b) => Value::Bool(*b),
69 JsonValue::Number(n) => {
70 if let Some(i) = n.as_i64() {
71 Value::Int(i)
72 } else {
73 Value::Float(n.as_f64().unwrap_or(0.0))
74 }
75 }
76 JsonValue::String(s) => Value::Str(Arc::from(s.as_str())),
77 JsonValue::Array(xs) => Value::list(xs.iter().map(json_to_value).collect::<Vec<_>>()),
78 JsonValue::Object(m) => Value::list(
79 m.iter()
80 .map(|(k, v)| {
81 Value::list(vec![Value::Str(Arc::from(k.as_str())), json_to_value(v)])
82 })
83 .collect::<Vec<_>>(),
84 ),
85 }
86}
87
88pub fn value_to_json(v: &Value) -> JsonValue {
91 match v {
92 Value::Nil => JsonValue::Null,
93 Value::Bool(b) => JsonValue::Bool(*b),
94 Value::Int(n) => JsonValue::Number((*n).into()),
95 Value::Float(n) => serde_json::Number::from_f64(*n)
96 .map(JsonValue::Number)
97 .unwrap_or(JsonValue::Null),
98 Value::Str(s) | Value::Symbol(s) | Value::Keyword(s) => {
99 JsonValue::String(s.as_ref().to_owned())
100 }
101 Value::List(xs) => {
102 let looks_like_object = !xs.is_empty()
105 && xs.iter().all(|entry| {
106 if let Value::List(pair) = entry {
107 pair.len() == 2
108 && matches!(
109 pair[0],
110 Value::Str(_) | Value::Symbol(_) | Value::Keyword(_)
111 )
112 } else {
113 false
114 }
115 });
116 if looks_like_object {
117 let mut m = serde_json::Map::with_capacity(xs.len());
118 for entry in xs.iter() {
119 if let Value::List(pair) = entry {
120 let k = match &pair[0] {
121 Value::Str(s) | Value::Symbol(s) | Value::Keyword(s) => {
122 s.as_ref().to_owned()
123 }
124 _ => unreachable!(),
125 };
126 m.insert(k, value_to_json(&pair[1]));
127 }
128 }
129 JsonValue::Object(m)
130 } else {
131 JsonValue::Array(xs.iter().map(value_to_json).collect())
132 }
133 }
134 _ => JsonValue::Null,
135 }
136}
137
138fn alist_lookup(alist: &Value, key: &str) -> Option<Value> {
140 let Value::List(entries) = alist else {
141 return None;
142 };
143 for entry in entries.iter() {
144 let Value::List(pair) = entry else { continue };
145 if pair.len() != 2 {
146 continue;
147 }
148 let matches = match &pair[0] {
149 Value::Str(s) | Value::Symbol(s) | Value::Keyword(s) => s.as_ref() == key,
150 _ => false,
151 };
152 if matches {
153 return Some(pair[1].clone());
154 }
155 }
156 None
157}