1use crate::format::{IrError, SlotType};
2use crate::parser::{IrModule, SlotTable};
3
4#[derive(Debug, Clone)]
6pub enum SlotValue {
7 Null,
8 Text(String),
9 Bool(bool),
10 Number(f64),
11 Array(Vec<SlotValue>),
12 Object(Vec<(String, SlotValue)>),
13}
14
15static NULL_SLOT: SlotValue = SlotValue::Null;
17
18impl SlotValue {
19 pub fn to_text(&self) -> String {
21 match self {
22 SlotValue::Null => String::new(),
23 SlotValue::Text(s) => s.clone(),
24 SlotValue::Bool(true) => "true".to_string(),
25 SlotValue::Bool(false) => "false".to_string(),
26 SlotValue::Number(n) => {
27 if n.fract() == 0.0 && n.is_finite() {
28 format!("{}", *n as i64)
29 } else {
30 let s = format!("{}", n);
32 s
33 }
34 }
35 SlotValue::Array(_) => "[Array]".to_string(),
36 SlotValue::Object(_) => "[Object]".to_string(),
37 }
38 }
39
40 pub fn as_bool(&self) -> bool {
42 match self {
43 SlotValue::Null => false,
44 SlotValue::Bool(b) => *b,
45 SlotValue::Text(s) => !s.is_empty(),
46 SlotValue::Number(n) => *n != 0.0,
47 SlotValue::Array(a) => !a.is_empty(),
48 SlotValue::Object(_) => true,
49 }
50 }
51
52 pub fn get_property(&self, name: &str) -> SlotValue {
54 match self {
55 SlotValue::Object(pairs) => {
56 for (k, v) in pairs {
57 if k == name {
58 return v.clone();
59 }
60 }
61 SlotValue::Null
62 }
63 _ => SlotValue::Null,
64 }
65 }
66
67 pub fn as_array(&self) -> Option<&[SlotValue]> {
69 match self {
70 SlotValue::Array(a) => Some(a),
71 _ => None,
72 }
73 }
74
75 pub fn as_text_ref(&self) -> &str {
78 match self {
79 SlotValue::Text(s) => s.as_str(),
80 _ => "",
81 }
82 }
83
84 pub fn to_json(&self) -> serde_json::Value {
86 match self {
87 SlotValue::Null => serde_json::Value::Null,
88 SlotValue::Text(s) => serde_json::Value::String(s.clone()),
89 SlotValue::Bool(b) => serde_json::Value::Bool(*b),
90 SlotValue::Number(n) => {
91 if n.fract() == 0.0 && n.is_finite() {
92 serde_json::Value::Number(serde_json::Number::from(*n as i64))
93 } else {
94 serde_json::Number::from_f64(*n)
95 .map(serde_json::Value::Number)
96 .unwrap_or(serde_json::Value::Null)
97 }
98 }
99 SlotValue::Array(items) => {
100 serde_json::Value::Array(items.iter().map(|v| v.to_json()).collect())
101 }
102 SlotValue::Object(pairs) => {
103 let map: serde_json::Map<String, serde_json::Value> = pairs
104 .iter()
105 .map(|(k, v)| (k.clone(), v.to_json()))
106 .collect();
107 serde_json::Value::Object(map)
108 }
109 }
110 }
111}
112
113#[derive(Debug, Clone)]
118pub struct SlotData {
119 slots: Vec<SlotValue>,
120}
121
122impl SlotData {
123 pub fn new(capacity: usize) -> Self {
125 let mut slots = Vec::with_capacity(capacity);
126 slots.resize_with(capacity, || SlotValue::Null);
127 Self { slots }
128 }
129
130 pub fn set(&mut self, slot_id: u16, value: SlotValue) {
132 let idx = slot_id as usize;
133 if idx < self.slots.len() {
134 self.slots[idx] = value;
135 }
136 }
137
138 pub fn get(&self, slot_id: u16) -> &SlotValue {
140 let idx = slot_id as usize;
141 self.slots.get(idx).unwrap_or(&NULL_SLOT)
142 }
143
144 pub fn get_text(&self, slot_id: u16) -> Option<&str> {
146 match self.get(slot_id) {
147 SlotValue::Text(s) => Some(s.as_str()),
148 _ => None,
149 }
150 }
151
152 pub fn from_json(json_str: &str, module: &IrModule) -> Result<Self, IrError> {
163 let parsed: serde_json::Value =
164 serde_json::from_str(json_str).map_err(|e| IrError::JsonParseError(e.to_string()))?;
165
166 let obj = match parsed {
167 serde_json::Value::Object(map) => map,
168 _ => return Err(IrError::JsonParseError("expected JSON object".to_string())),
169 };
170
171 let mut name_to_slot: std::collections::HashMap<String, u16> =
173 std::collections::HashMap::new();
174 for entry in module.slots.entries() {
175 if let Ok(name) = module.strings.get(entry.name_str_idx) {
176 name_to_slot.insert(name.to_string(), entry.slot_id);
177 }
178 }
179
180 let mut data = Self::new_from_defaults(&module.slots);
182
183 for (key, value) in &obj {
185 if let Some(&slot_id) = name_to_slot.get(key) {
186 data.set(slot_id, json_to_slot_value(value));
187 }
188 }
190
191 Ok(data)
192 }
193
194 pub fn new_from_defaults(table: &SlotTable) -> Self {
198 let entries = table.entries();
199 let capacity = entries
200 .iter()
201 .map(|e| e.slot_id as usize + 1)
202 .max()
203 .unwrap_or(0);
204 let mut slots = Vec::with_capacity(capacity);
205 slots.resize_with(capacity, || SlotValue::Null);
206
207 for entry in entries {
208 let idx = entry.slot_id as usize;
209 if idx >= slots.len() {
210 continue;
211 }
212 if entry.default_bytes.is_empty() {
213 continue;
214 }
215
216 let default_str = std::str::from_utf8(&entry.default_bytes).unwrap_or("");
217 let value = match entry.type_hint {
218 SlotType::Bool => match default_str {
219 "true" => SlotValue::Bool(true),
220 "false" => SlotValue::Bool(false),
221 _ => SlotValue::Null,
222 },
223 SlotType::Text => SlotValue::Text(default_str.to_string()),
224 SlotType::Number => default_str
225 .parse::<f64>()
226 .map(SlotValue::Number)
227 .unwrap_or(SlotValue::Null),
228 SlotType::Array => {
229 if default_str == "[]" {
230 SlotValue::Array(vec![])
231 } else {
232 SlotValue::Null
233 }
234 }
235 SlotType::Object => SlotValue::Null,
236 };
237 slots[idx] = value;
238 }
239
240 Self { slots }
241 }
242}
243
244pub fn json_to_slot_value(value: &serde_json::Value) -> SlotValue {
254 match value {
255 serde_json::Value::Null => SlotValue::Null,
256 serde_json::Value::String(s) => SlotValue::Text(s.clone()),
257 serde_json::Value::Bool(b) => SlotValue::Bool(*b),
258 serde_json::Value::Number(n) => SlotValue::Number(n.as_f64().unwrap_or(0.0)),
259 serde_json::Value::Array(arr) => {
260 SlotValue::Array(arr.iter().map(json_to_slot_value).collect())
261 }
262 serde_json::Value::Object(map) => SlotValue::Object(
263 map.iter()
264 .map(|(k, v)| (k.clone(), json_to_slot_value(v)))
265 .collect(),
266 ),
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use crate::parser::{IrModule, SlotTable};
274
275 fn build_slot_table_bytes(entries: &[(u16, u32, u8, u8, &[u8])]) -> Vec<u8> {
278 let mut buf = Vec::new();
279 buf.extend_from_slice(&(entries.len() as u16).to_le_bytes());
280 for &(slot_id, name_str_idx, type_hint, source, default_bytes) in entries {
281 buf.extend_from_slice(&slot_id.to_le_bytes());
282 buf.extend_from_slice(&name_str_idx.to_le_bytes());
283 buf.push(type_hint);
284 buf.push(source);
285 buf.extend_from_slice(&(default_bytes.len() as u16).to_le_bytes());
286 buf.extend_from_slice(default_bytes);
287 }
288 buf
289 }
290
291 #[test]
292 fn slot_value_text_to_text() {
293 assert_eq!(SlotValue::Text("hello".to_string()).to_text(), "hello");
294 }
295
296 #[test]
297 fn slot_value_bool_to_text() {
298 assert_eq!(SlotValue::Bool(true).to_text(), "true");
299 assert_eq!(SlotValue::Bool(false).to_text(), "false");
300 }
301
302 #[test]
303 fn slot_value_number_integer() {
304 assert_eq!(SlotValue::Number(42.0).to_text(), "42");
305 }
306
307 #[test]
308 fn slot_value_number_float() {
309 assert_eq!(SlotValue::Number(3.15).to_text(), "3.15");
310 }
311
312 #[test]
313 fn slot_value_null_to_text() {
314 assert_eq!(SlotValue::Null.to_text(), "");
315 }
316
317 #[test]
318 fn slot_value_as_bool_truthy() {
319 assert!(SlotValue::Text("x".to_string()).as_bool());
320 assert!(SlotValue::Number(1.0).as_bool());
321 assert!(SlotValue::Bool(true).as_bool());
322 assert!(SlotValue::Array(vec![SlotValue::Null]).as_bool());
323 assert!(SlotValue::Object(vec![]).as_bool());
324 }
325
326 #[test]
327 fn slot_value_as_bool_falsy() {
328 assert!(!SlotValue::Null.as_bool());
329 assert!(!SlotValue::Text("".to_string()).as_bool());
330 assert!(!SlotValue::Number(0.0).as_bool());
331 assert!(!SlotValue::Bool(false).as_bool());
332 assert!(!SlotValue::Array(vec![]).as_bool());
333 }
334
335 #[test]
336 fn slot_value_as_array() {
337 let arr = SlotValue::Array(vec![SlotValue::Text("a".to_string())]);
338 assert!(arr.as_array().is_some());
339 assert_eq!(arr.as_array().unwrap().len(), 1);
340
341 assert!(SlotValue::Null.as_array().is_none());
342 assert!(SlotValue::Text("x".to_string()).as_array().is_none());
343 assert!(SlotValue::Number(1.0).as_array().is_none());
344 assert!(SlotValue::Bool(true).as_array().is_none());
345 assert!(SlotValue::Object(vec![]).as_array().is_none());
346 }
347
348 #[test]
349 fn slot_data_set_and_get() {
350 let mut data = SlotData::new(4);
351 data.set(0, SlotValue::Text("title".to_string()));
352 data.set(2, SlotValue::Number(99.0));
353
354 assert_eq!(data.get(0).to_text(), "title");
355 assert_eq!(data.get(2).to_text(), "99");
356 assert_eq!(data.get(1).to_text(), "");
358 }
359
360 #[test]
361 fn slot_data_out_of_bounds() {
362 let data = SlotData::new(2);
363 assert_eq!(data.get(5).to_text(), "");
365 assert!(!data.get(100).as_bool());
366 }
367
368 #[test]
369 fn slot_data_get_text() {
370 let mut data = SlotData::new(3);
371 data.set(0, SlotValue::Text("hello".to_string()));
372 data.set(1, SlotValue::Number(42.0));
373
374 assert_eq!(data.get_text(0), Some("hello"));
375 assert_eq!(data.get_text(1), None); assert_eq!(data.get_text(2), None); }
378
379 #[test]
382 fn slot_data_new_from_defaults_basic() {
383 let bytes = build_slot_table_bytes(&[
388 (0, 0, 0x04, 0x00, b"[]"), (1, 1, 0x02, 0x01, b"false"), (2, 2, 0x01, 0x01, b"hello"), ]);
392 let table = SlotTable::parse(&bytes).unwrap();
393 let data = SlotData::new_from_defaults(&table);
394
395 assert!(matches!(data.get(0), SlotValue::Array(v) if v.is_empty()));
397 assert!(matches!(data.get(1), SlotValue::Bool(false)));
399 assert_eq!(data.get_text(2), Some("hello"));
401 }
402
403 #[test]
404 fn slot_data_new_from_defaults_empty_table() {
405 let bytes = build_slot_table_bytes(&[]);
407 let table = SlotTable::parse(&bytes).unwrap();
408 let data = SlotData::new_from_defaults(&table);
409
410 assert!(matches!(data.get(0), SlotValue::Null));
412 }
413
414 #[test]
415 fn slot_data_new_from_defaults_no_default_bytes() {
416 let bytes = build_slot_table_bytes(&[
418 (0, 0, 0x01, 0x00, b""), (1, 1, 0x03, 0x01, b""), ]);
421 let table = SlotTable::parse(&bytes).unwrap();
422 let data = SlotData::new_from_defaults(&table);
423
424 assert!(matches!(data.get(0), SlotValue::Null));
425 assert!(matches!(data.get(1), SlotValue::Null));
426 }
427
428 #[test]
429 fn slot_data_new_from_defaults_number() {
430 let bytes = build_slot_table_bytes(&[
432 (0, 0, 0x03, 0x00, b"42"), (1, 1, 0x03, 0x01, b"3.15"), (2, 2, 0x03, 0x00, b"nope"), ]);
436 let table = SlotTable::parse(&bytes).unwrap();
437 let data = SlotData::new_from_defaults(&table);
438
439 assert!(matches!(data.get(0), SlotValue::Number(n) if (*n - 42.0).abs() < f64::EPSILON));
440 assert!(matches!(data.get(1), SlotValue::Number(n) if (*n - 3.15).abs() < f64::EPSILON));
441 assert!(matches!(data.get(2), SlotValue::Null));
443 }
444
445 #[test]
446 fn slot_data_new_from_defaults_bool_true() {
447 let bytes = build_slot_table_bytes(&[
448 (0, 0, 0x02, 0x00, b"true"), ]);
450 let table = SlotTable::parse(&bytes).unwrap();
451 let data = SlotData::new_from_defaults(&table);
452
453 assert!(matches!(data.get(0), SlotValue::Bool(true)));
454 }
455
456 #[test]
457 fn slot_data_new_from_defaults_object_ignored() {
458 let bytes = build_slot_table_bytes(&[
460 (0, 0, 0x05, 0x00, b"{}"), ]);
462 let table = SlotTable::parse(&bytes).unwrap();
463 let data = SlotData::new_from_defaults(&table);
464
465 assert!(matches!(data.get(0), SlotValue::Null));
466 }
467
468 use crate::parser::test_helpers::{build_minimal_ir, encode_text};
471
472 fn build_module_for_json(
474 strings: &[&str],
475 slot_decls: &[(u16, u32, u8, u8, &[u8])],
476 ) -> IrModule {
477 let opcodes = encode_text(0);
479 let data = build_minimal_ir(strings, slot_decls, &opcodes, &[]);
480 IrModule::parse(&data).unwrap()
481 }
482
483 #[test]
484 fn from_json_basic() {
485 let module = build_module_for_json(
489 &["title", "count"],
490 &[
491 (0, 0, 0x01, 0x00, &[]), (1, 1, 0x03, 0x00, &[]), ],
494 );
495
496 let data = SlotData::from_json(r#"{"title": "Hello", "count": 42}"#, &module).unwrap();
497 assert_eq!(data.get(0).to_text(), "Hello");
498 assert_eq!(data.get(1).to_text(), "42");
499 }
500
501 #[test]
502 fn from_json_missing_key_uses_default() {
503 let module = build_module_for_json(
505 &["greeting"],
506 &[
507 (0, 0, 0x01, 0x00, b"hi"), ],
509 );
510
511 let data = SlotData::from_json(r#"{}"#, &module).unwrap();
513 assert_eq!(data.get(0).to_text(), "hi");
514 }
515
516 #[test]
517 fn from_json_null_value() {
518 let module = build_module_for_json(&["name"], &[(0, 0, 0x01, 0x00, &[])]);
519
520 let data = SlotData::from_json(r#"{"name": null}"#, &module).unwrap();
521 assert!(matches!(data.get(0), SlotValue::Null));
522 }
523
524 #[test]
525 fn from_json_bool_values() {
526 let module = build_module_for_json(
527 &["active", "hidden"],
528 &[
529 (0, 0, 0x02, 0x00, &[]), (1, 1, 0x02, 0x00, &[]), ],
532 );
533
534 let data = SlotData::from_json(r#"{"active": true, "hidden": false}"#, &module).unwrap();
535 assert!(matches!(data.get(0), SlotValue::Bool(true)));
536 assert!(matches!(data.get(1), SlotValue::Bool(false)));
537 }
538
539 #[test]
540 fn from_json_array() {
541 let module = build_module_for_json(
542 &["items"],
543 &[(0, 0, 0x04, 0x00, &[])], );
545
546 let data = SlotData::from_json(r#"{"items": ["a", "b", 3]}"#, &module).unwrap();
547 if let SlotValue::Array(arr) = data.get(0) {
548 assert_eq!(arr.len(), 3);
549 assert_eq!(arr[0].to_text(), "a");
550 assert_eq!(arr[1].to_text(), "b");
551 assert_eq!(arr[2].to_text(), "3");
552 } else {
553 panic!("expected Array, got {:?}", data.get(0));
554 }
555 }
556
557 #[test]
558 fn from_json_nested_object() {
559 let module = build_module_for_json(
560 &["config"],
561 &[(0, 0, 0x05, 0x00, &[])], );
563
564 let data = SlotData::from_json(r#"{"config": {"key": "value", "n": 7}}"#, &module).unwrap();
565 if let SlotValue::Object(pairs) = data.get(0) {
566 assert_eq!(pairs.len(), 2);
567 assert_eq!(pairs[0].0, "key");
568 assert_eq!(pairs[0].1.to_text(), "value");
569 assert_eq!(pairs[1].0, "n");
570 assert_eq!(pairs[1].1.to_text(), "7");
571 } else {
572 panic!("expected Object, got {:?}", data.get(0));
573 }
574 }
575
576 #[test]
577 fn from_json_unknown_key_ignored() {
578 let module = build_module_for_json(&["title"], &[(0, 0, 0x01, 0x00, &[])]);
579
580 let data =
582 SlotData::from_json(r#"{"title": "Hi", "extra_key": "ignored"}"#, &module).unwrap();
583 assert_eq!(data.get(0).to_text(), "Hi");
584 }
585
586 #[test]
587 fn from_json_invalid_json() {
588 let module = build_module_for_json(&["x"], &[(0, 0, 0x01, 0x00, &[])]);
589
590 let result = SlotData::from_json(r#"not valid json"#, &module);
591 assert!(result.is_err());
592 match result.unwrap_err() {
593 crate::format::IrError::JsonParseError(_) => {} other => panic!("expected JsonParseError, got {other:?}"),
595 }
596 }
597}