tatara_lisp_script/stdlib/
toml.rs1use std::sync::Arc;
9
10use tatara_lisp_eval::{Arity, EvalError, Interpreter, Value};
11use toml::Value as TomlValue;
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 "toml-parse",
19 Arity::Exact(1),
20 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
21 let s = str_arg(&args[0], "toml-parse", sp)?;
22 let parsed: TomlValue = s.parse().map_err(|e: toml::de::Error| {
23 EvalError::native_fn("toml-parse", e.to_string(), sp)
24 })?;
25 Ok(toml_to_value(&parsed))
26 },
27 );
28
29 interp.register_fn(
30 "toml-read",
31 Arity::Exact(1),
32 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
33 let path = str_arg(&args[0], "toml-read", sp)?;
34 let body = std::fs::read_to_string(&*path)
35 .map_err(|e| EvalError::native_fn("toml-read", format!("{path}: {e}"), sp))?;
36 let parsed: TomlValue = body.parse().map_err(|e: toml::de::Error| {
37 EvalError::native_fn("toml-read", e.to_string(), sp)
38 })?;
39 Ok(toml_to_value(&parsed))
40 },
41 );
42
43 interp.register_fn(
44 "toml-stringify",
45 Arity::Exact(1),
46 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
47 let tv = value_to_toml(&args[0]).ok_or_else(|| {
48 EvalError::native_fn(
49 "toml-stringify",
50 "TOML requires a table at the root".to_string(),
51 sp,
52 )
53 })?;
54 let s = toml::to_string(&tv)
55 .map_err(|e| EvalError::native_fn("toml-stringify", e.to_string(), sp))?;
56 Ok(Value::Str(Arc::from(s)))
57 },
58 );
59}
60
61fn toml_to_value(t: &TomlValue) -> Value {
62 match t {
63 TomlValue::String(s) => Value::Str(Arc::from(s.as_str())),
64 TomlValue::Integer(n) => Value::Int(*n),
65 TomlValue::Float(f) => Value::Float(*f),
66 TomlValue::Boolean(b) => Value::Bool(*b),
67 TomlValue::Datetime(d) => Value::Str(Arc::from(d.to_string())),
68 TomlValue::Array(xs) => Value::list(xs.iter().map(toml_to_value).collect::<Vec<_>>()),
69 TomlValue::Table(m) => Value::list(
70 m.iter()
71 .map(|(k, v)| {
72 Value::list(vec![Value::Str(Arc::from(k.as_str())), toml_to_value(v)])
73 })
74 .collect::<Vec<_>>(),
75 ),
76 }
77}
78
79fn value_to_toml(v: &Value) -> Option<TomlValue> {
80 match v {
81 Value::Nil => None,
82 Value::Bool(b) => Some(TomlValue::Boolean(*b)),
83 Value::Int(n) => Some(TomlValue::Integer(*n)),
84 Value::Float(f) => Some(TomlValue::Float(*f)),
85 Value::Str(s) | Value::Symbol(s) | Value::Keyword(s) => {
86 Some(TomlValue::String(s.as_ref().to_owned()))
87 }
88 Value::List(xs) => {
89 let looks_like_table = !xs.is_empty()
90 && xs.iter().all(|entry| {
91 if let Value::List(pair) = entry {
92 pair.len() == 2
93 && matches!(
94 pair[0],
95 Value::Str(_) | Value::Symbol(_) | Value::Keyword(_)
96 )
97 } else {
98 false
99 }
100 });
101 if looks_like_table {
102 let mut m = toml::map::Map::new();
103 for entry in xs.iter() {
104 if let Value::List(pair) = entry {
105 let k = match &pair[0] {
106 Value::Str(s) | Value::Symbol(s) | Value::Keyword(s) => {
107 s.as_ref().to_owned()
108 }
109 _ => unreachable!(),
110 };
111 if let Some(v) = value_to_toml(&pair[1]) {
112 m.insert(k, v);
113 }
114 }
115 }
116 Some(TomlValue::Table(m))
117 } else {
118 Some(TomlValue::Array(
119 xs.iter().filter_map(value_to_toml).collect(),
120 ))
121 }
122 }
123 _ => None,
124 }
125}