1use crate::module_exports::{ModuleContext, ModuleExports, ModuleFunction, ModuleParam};
6use shape_value::ValueWord;
7use std::sync::Arc;
8
9fn toml_value_to_nanboxed(value: toml::Value) -> ValueWord {
11 match value {
12 toml::Value::Boolean(b) => ValueWord::from_bool(b),
13 toml::Value::Integer(n) => ValueWord::from_i64(n),
14 toml::Value::Float(f) => ValueWord::from_f64(f),
15 toml::Value::String(s) => ValueWord::from_string(Arc::new(s)),
16 toml::Value::Datetime(dt) => ValueWord::from_string(Arc::new(dt.to_string())),
17 toml::Value::Array(arr) => {
18 let items: Vec<ValueWord> = arr.into_iter().map(toml_value_to_nanboxed).collect();
19 ValueWord::from_array(Arc::new(items))
20 }
21 toml::Value::Table(map) => {
22 let mut keys = Vec::with_capacity(map.len());
23 let mut values = Vec::with_capacity(map.len());
24 for (k, v) in map.into_iter() {
25 keys.push(ValueWord::from_string(Arc::new(k)));
26 values.push(toml_value_to_nanboxed(v));
27 }
28 ValueWord::from_hashmap_pairs(keys, values)
29 }
30 }
31}
32
33fn nanboxed_to_toml_value(nb: &ValueWord) -> toml::Value {
35 use shape_value::heap_value::HeapValue;
36
37 if nb.is_none() {
38 return toml::Value::String("null".to_string());
39 }
40 if let Some(b) = nb.as_bool() {
41 return toml::Value::Boolean(b);
42 }
43 if let Some(n) = nb.as_i64() {
44 return toml::Value::Integer(n);
45 }
46 if let Some(f) = nb.as_f64() {
47 return toml::Value::Float(f);
48 }
49 if let Some(s) = nb.as_str() {
50 return toml::Value::String(s.to_string());
51 }
52 if let Some(arr) = nb.as_any_array() {
53 let items: Vec<toml::Value> = arr
54 .to_generic()
55 .iter()
56 .map(nanboxed_to_toml_value)
57 .collect();
58 return toml::Value::Array(items);
59 }
60 if let Some((keys, values, _index)) = nb.as_hashmap() {
61 let mut map = toml::map::Map::new();
62 for (k, v) in keys.iter().zip(values.iter()) {
63 if let Some(key) = k.as_str() {
64 map.insert(key.to_string(), nanboxed_to_toml_value(v));
65 }
66 }
67 return toml::Value::Table(map);
68 }
69 if let Some(heap) = nb.as_heap_ref() {
71 if let HeapValue::TypedObject { slots, .. } = heap {
72 let _ = slots;
74 return toml::Value::String(format!("{}", nb));
75 }
76 }
77 toml::Value::String(format!("{}", nb))
78}
79
80pub fn create_toml_module() -> ModuleExports {
82 let mut module = ModuleExports::new("std::core::toml");
83 module.description = "TOML parsing and serialization".to_string();
84
85 module.add_function_with_schema(
87 "parse",
88 |args: &[ValueWord], _ctx: &ModuleContext| {
89 let text = args
90 .first()
91 .and_then(|a| a.as_str())
92 .ok_or_else(|| "toml.parse() requires a string argument".to_string())?;
93
94 let parsed: toml::Value =
95 toml::from_str(text).map_err(|e| format!("toml.parse() failed: {}", e))?;
96
97 let result = toml_value_to_nanboxed(parsed);
98 Ok(ValueWord::from_ok(result))
99 },
100 ModuleFunction {
101 description: "Parse a TOML string into Shape values".to_string(),
102 params: vec![ModuleParam {
103 name: "text".to_string(),
104 type_name: "string".to_string(),
105 required: true,
106 description: "TOML string to parse".to_string(),
107 ..Default::default()
108 }],
109 return_type: Some("Result<HashMap>".to_string()),
110 },
111 );
112
113 module.add_function_with_schema(
115 "stringify",
116 |args: &[ValueWord], _ctx: &ModuleContext| {
117 let value = args
118 .first()
119 .ok_or_else(|| "toml.stringify() requires a value argument".to_string())?;
120
121 let toml_value = nanboxed_to_toml_value(value);
122 let output = toml::to_string(&toml_value)
123 .map_err(|e| format!("toml.stringify() failed: {}", e))?;
124
125 Ok(ValueWord::from_ok(ValueWord::from_string(Arc::new(output))))
126 },
127 ModuleFunction {
128 description: "Serialize a Shape value to a TOML string".to_string(),
129 params: vec![ModuleParam {
130 name: "value".to_string(),
131 type_name: "any".to_string(),
132 required: true,
133 description: "Value to serialize".to_string(),
134 ..Default::default()
135 }],
136 return_type: Some("Result<string>".to_string()),
137 },
138 );
139
140 module.add_function_with_schema(
142 "is_valid",
143 |args: &[ValueWord], _ctx: &ModuleContext| {
144 let text = args
145 .first()
146 .and_then(|a| a.as_str())
147 .ok_or_else(|| "toml.is_valid() requires a string argument".to_string())?;
148
149 let valid = toml::from_str::<toml::Value>(text).is_ok();
150 Ok(ValueWord::from_bool(valid))
151 },
152 ModuleFunction {
153 description: "Check if a string is valid TOML".to_string(),
154 params: vec![ModuleParam {
155 name: "text".to_string(),
156 type_name: "string".to_string(),
157 required: true,
158 description: "String to validate as TOML".to_string(),
159 ..Default::default()
160 }],
161 return_type: Some("bool".to_string()),
162 },
163 );
164
165 module
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 fn test_ctx() -> crate::module_exports::ModuleContext<'static> {
173 let registry = Box::leak(Box::new(crate::type_schema::TypeSchemaRegistry::new()));
174 crate::module_exports::ModuleContext {
175 schemas: registry,
176 invoke_callable: None,
177 raw_invoker: None,
178 function_hashes: None,
179 vm_state: None,
180 granted_permissions: None,
181 scope_constraints: None,
182 set_pending_resume: None,
183 set_pending_frame_resume: None,
184 }
185 }
186
187 #[test]
188 fn test_toml_module_creation() {
189 let module = create_toml_module();
190 assert_eq!(module.name, "std::core::toml");
191 assert!(module.has_export("parse"));
192 assert!(module.has_export("stringify"));
193 assert!(module.has_export("is_valid"));
194 }
195
196 #[test]
197 fn test_toml_parse_simple_table() {
198 let module = create_toml_module();
199 let parse_fn = module.get_export("parse").unwrap();
200 let ctx = test_ctx();
201 let input = ValueWord::from_string(Arc::new(
202 r#"
203[server]
204host = "localhost"
205port = 8080
206"#
207 .to_string(),
208 ));
209 let result = parse_fn(&[input], &ctx).unwrap();
210 let inner = result.as_ok_inner().expect("should be Ok");
211 let (keys, _values, _index) = inner.as_hashmap().expect("should be hashmap");
212 assert_eq!(keys.len(), 1); }
214
215 #[test]
216 fn test_toml_parse_basic_types() {
217 let module = create_toml_module();
218 let parse_fn = module.get_export("parse").unwrap();
219 let ctx = test_ctx();
220 let input = ValueWord::from_string(Arc::new(
221 r#"
222name = "test"
223version = 42
224pi = 3.14
225active = true
226"#
227 .to_string(),
228 ));
229 let result = parse_fn(&[input], &ctx).unwrap();
230 let inner = result.as_ok_inner().expect("should be Ok");
231 assert!(inner.as_hashmap().is_some());
232 }
233
234 #[test]
235 fn test_toml_parse_array() {
236 let module = create_toml_module();
237 let parse_fn = module.get_export("parse").unwrap();
238 let ctx = test_ctx();
239 let input = ValueWord::from_string(Arc::new(r#"values = [1, 2, 3]"#.to_string()));
240 let result = parse_fn(&[input], &ctx).unwrap();
241 let inner = result.as_ok_inner().expect("should be Ok");
242 assert!(inner.as_hashmap().is_some());
243 }
244
245 #[test]
246 fn test_toml_parse_invalid() {
247 let module = create_toml_module();
248 let parse_fn = module.get_export("parse").unwrap();
249 let ctx = test_ctx();
250 let input = ValueWord::from_string(Arc::new("= invalid toml [".to_string()));
251 let result = parse_fn(&[input], &ctx);
252 assert!(result.is_err());
253 }
254
255 #[test]
256 fn test_toml_parse_requires_string() {
257 let module = create_toml_module();
258 let parse_fn = module.get_export("parse").unwrap();
259 let ctx = test_ctx();
260 let result = parse_fn(&[ValueWord::from_f64(42.0)], &ctx);
261 assert!(result.is_err());
262 }
263
264 #[test]
265 fn test_toml_stringify_table() {
266 let module = create_toml_module();
267 let stringify_fn = module.get_export("stringify").unwrap();
268 let ctx = test_ctx();
269 let keys = vec![ValueWord::from_string(Arc::new("name".to_string()))];
270 let values = vec![ValueWord::from_string(Arc::new("test".to_string()))];
271 let hm = ValueWord::from_hashmap_pairs(keys, values);
272 let result = stringify_fn(&[hm], &ctx).unwrap();
273 let inner = result.as_ok_inner().expect("should be Ok");
274 let s = inner.as_str().expect("should be string");
275 assert!(s.contains("name"));
276 assert!(s.contains("test"));
277 }
278
279 #[test]
280 fn test_toml_is_valid_true() {
281 let module = create_toml_module();
282 let is_valid_fn = module.get_export("is_valid").unwrap();
283 let ctx = test_ctx();
284 let result = is_valid_fn(
285 &[ValueWord::from_string(Arc::new(
286 r#"key = "value""#.to_string(),
287 ))],
288 &ctx,
289 )
290 .unwrap();
291 assert_eq!(result.as_bool(), Some(true));
292 }
293
294 #[test]
295 fn test_toml_is_valid_false() {
296 let module = create_toml_module();
297 let is_valid_fn = module.get_export("is_valid").unwrap();
298 let ctx = test_ctx();
299 let result = is_valid_fn(
300 &[ValueWord::from_string(Arc::new(
301 "= not valid toml".to_string(),
302 ))],
303 &ctx,
304 )
305 .unwrap();
306 assert_eq!(result.as_bool(), Some(false));
307 }
308
309 #[test]
310 fn test_toml_roundtrip() {
311 let module = create_toml_module();
312 let parse_fn = module.get_export("parse").unwrap();
313 let stringify_fn = module.get_export("stringify").unwrap();
314 let ctx = test_ctx();
315
316 let toml_str = r#"name = "test"
317version = 42
318"#;
319 let parsed = parse_fn(
320 &[ValueWord::from_string(Arc::new(toml_str.to_string()))],
321 &ctx,
322 )
323 .unwrap();
324 let inner = parsed.as_ok_inner().expect("should be Ok");
325 let re_stringified = stringify_fn(&[inner.clone()], &ctx).unwrap();
326 let re_str = re_stringified.as_ok_inner().expect("should be Ok");
327 assert!(re_str.as_str().is_some());
328 }
329
330 #[test]
331 fn test_toml_schemas() {
332 let module = create_toml_module();
333
334 let parse_schema = module.get_schema("parse").unwrap();
335 assert_eq!(parse_schema.params.len(), 1);
336 assert_eq!(parse_schema.params[0].name, "text");
337 assert!(parse_schema.params[0].required);
338 assert_eq!(parse_schema.return_type.as_deref(), Some("Result<HashMap>"));
339
340 let stringify_schema = module.get_schema("stringify").unwrap();
341 assert_eq!(stringify_schema.params.len(), 1);
342 assert!(stringify_schema.params[0].required);
343
344 let is_valid_schema = module.get_schema("is_valid").unwrap();
345 assert_eq!(is_valid_schema.params.len(), 1);
346 assert_eq!(is_valid_schema.return_type.as_deref(), Some("bool"));
347 }
348}