1use shape_runtime::type_schema::TypeSchemaRegistry;
10use shape_runtime::type_schema::field_types::FieldType;
11use shape_runtime::type_system::annotation_to_string;
12use shape_value::heap_value::HeapValue;
13use shape_value::{NanTag, ValueWord};
14
15pub struct ValueFormatter<'a> {
19 schema_registry: &'a TypeSchemaRegistry,
21}
22
23#[cfg(test)]
25pub type VMValueFormatter<'a> = ValueFormatter<'a>;
26
27impl<'a> ValueFormatter<'a> {
28 pub fn new(schema_registry: &'a TypeSchemaRegistry) -> Self {
30 Self { schema_registry }
31 }
32
33 #[cfg(test)]
35 pub fn format(&self, value: &ValueWord) -> String {
36 let nb = value.clone();
37 self.format_nb_with_depth(&nb, 0)
38 }
39
40 pub fn format_nb(&self, value: &ValueWord) -> String {
45 self.format_nb_with_depth(value, 0)
46 }
47
48 fn format_nb_with_depth(&self, value: &ValueWord, depth: usize) -> String {
50 if depth > 50 {
51 return "[max depth reached]".to_string();
52 }
53
54 match value.tag() {
56 NanTag::F64 => {
57 if let Some(n) = value.as_f64() {
58 return format_number(n);
59 }
60 return "NaN".to_string();
62 }
63 NanTag::I48 => {
64 if let Some(i) = value.as_i64() {
65 return i.to_string();
66 }
67 return "0".to_string();
68 }
69 NanTag::Bool => {
70 if let Some(b) = value.as_bool() {
71 return b.to_string();
72 }
73 return "false".to_string();
74 }
75 NanTag::None => return "None".to_string(),
76 NanTag::Unit => return "()".to_string(),
77 NanTag::Function => {
78 if let Some(id) = value.as_function() {
79 return format!("[Function:{}]", id);
80 }
81 return "[Function]".to_string();
82 }
83 NanTag::ModuleFunction => return "[ModuleFunction]".to_string(),
84 NanTag::Ref => return "&ref".to_string(),
85 NanTag::Heap => {}
86 }
87
88 match value.as_heap_ref() {
90 Some(HeapValue::String(s)) => s.as_ref().clone(),
91 Some(HeapValue::Array(arr)) => self.format_nanboxed_array(arr.as_ref(), depth),
92 Some(HeapValue::TypedObject {
93 schema_id,
94 slots,
95 heap_mask,
96 }) => self.format_typed_object(*schema_id as u32, slots, *heap_mask, depth),
97 Some(HeapValue::Decimal(d)) => format!("{}D", d),
98 Some(HeapValue::BigInt(i)) => i.to_string(),
99 Some(HeapValue::Closure { function_id, .. }) => format!("[Closure:{}]", function_id),
100 Some(HeapValue::HostClosure(_)) => "<HostClosure>".to_string(),
101 Some(HeapValue::DataTable(dt)) => format!("{}", dt),
102 Some(HeapValue::TypedTable { table, .. }) => format!("{}", table),
103 Some(HeapValue::RowView { table, row_idx, .. }) => {
104 format!("[Row {} of {} rows]", row_idx, table.row_count())
105 }
106 Some(HeapValue::ColumnRef { table, col_id, .. }) => {
107 let col = table.inner().column(*col_id as usize);
108 let dtype = col.data_type();
109 let type_str = match dtype {
110 arrow_schema::DataType::Float64 => "f64",
111 arrow_schema::DataType::Int64 => "i64",
112 arrow_schema::DataType::Boolean => "bool",
113 arrow_schema::DataType::Utf8 | arrow_schema::DataType::LargeUtf8 => "string",
114 _ => "unknown",
115 };
116 let name = table
117 .column_names()
118 .get(*col_id as usize)
119 .cloned()
120 .unwrap_or_else(|| format!("col_{}", col_id));
121 format!("Column<{}>({}, {} rows)", type_str, name, col.len())
122 }
123 Some(HeapValue::IndexedTable {
124 table, index_col, ..
125 }) => {
126 let col_name = table
127 .column_names()
128 .get(*index_col as usize)
129 .cloned()
130 .unwrap_or_else(|| format!("col_{}", index_col));
131 format!(
132 "IndexedTable({} rows, index: {})",
133 table.row_count(),
134 col_name
135 )
136 }
137 Some(HeapValue::Range {
138 start,
139 end,
140 inclusive,
141 }) => {
142 let start_str = start
143 .as_ref()
144 .map(|s| self.format_nb_with_depth(s, depth + 1))
145 .unwrap_or_default();
146 let end_str = end
147 .as_ref()
148 .map(|e| self.format_nb_with_depth(e, depth + 1))
149 .unwrap_or_default();
150 let op = if *inclusive { "..=" } else { ".." };
151 format!("{}{}{}", start_str, op, end_str)
152 }
153 Some(HeapValue::Enum(e)) => format!("{:?}", e),
154 Some(HeapValue::Some(inner)) => {
155 format!("Some({})", self.format_nb_with_depth(inner, depth + 1))
156 }
157 Some(HeapValue::Ok(inner)) => {
158 format!("Ok({})", self.format_nb_with_depth(inner, depth + 1))
159 }
160 Some(HeapValue::Err(inner)) => {
161 format!("Err({})", self.format_nb_with_depth(inner, depth + 1))
162 }
163 Some(HeapValue::Future(id)) => format!("[Future:{}]", id),
164 Some(HeapValue::TaskGroup { kind, task_ids }) => {
165 let kind_str = match kind {
166 0 => "All",
167 1 => "Race",
168 2 => "Any",
169 3 => "Settle",
170 _ => "Unknown",
171 };
172 format!("[TaskGroup:{}({})]", kind_str, task_ids.len())
173 }
174 Some(HeapValue::TraitObject { value, .. }) => {
175 self.format_nb_with_depth(value, depth + 1)
176 }
177 Some(HeapValue::ExprProxy(col)) => format!("<ExprProxy:{}>", col),
178 Some(HeapValue::FilterExpr(node)) => format!("<FilterExpr:{:?}>", node),
179 Some(HeapValue::Time(t)) => t.to_rfc3339(),
180 Some(HeapValue::Duration(duration)) => format!("{}{:?}", duration.value, duration.unit),
181 Some(HeapValue::TimeSpan(ts)) => format!("{:?}", ts),
182 Some(HeapValue::Timeframe(tf)) => format!("{:?}", tf),
183 Some(HeapValue::TimeReference(value)) => format!("{:?}", value),
184 Some(HeapValue::DateTimeExpr(value)) => format!("{:?}", value),
185 Some(HeapValue::DataDateTimeRef(value)) => format!("{:?}", value),
186 Some(HeapValue::TypeAnnotation(value)) => annotation_to_string(value),
187 Some(HeapValue::TypeAnnotatedValue { value, .. }) => {
188 self.format_nb_with_depth(value, depth + 1)
189 }
190 Some(HeapValue::PrintResult(p)) => p.rendered.clone(),
191 Some(HeapValue::SimulationCall(_)) => "[SimulationCall]".to_string(),
192 Some(HeapValue::FunctionRef { .. }) => "[FunctionRef]".to_string(),
193 Some(HeapValue::DataReference(_)) => "[DataReference]".to_string(),
194 Some(HeapValue::NativeScalar(v)) => v.to_string(),
195 Some(HeapValue::NativeView(v)) => format!(
196 "<{}:{}@0x{:x}>",
197 if v.mutable { "cmut" } else { "cview" },
198 v.layout.name,
199 v.ptr
200 ),
201 Some(HeapValue::HashMap(d)) => {
202 let mut parts = Vec::new();
203 for (k, v) in d.keys.iter().zip(d.values.iter()) {
204 parts.push(format!(
205 "{}: {}",
206 self.format_nb_with_depth(k, depth + 1),
207 self.format_nb_with_depth(v, depth + 1)
208 ));
209 }
210 format!("HashMap{{{}}}", parts.join(", "))
211 }
212 Some(HeapValue::Set(d)) => {
213 let parts: Vec<String> = d
214 .items
215 .iter()
216 .map(|v| self.format_nb_with_depth(v, depth + 1))
217 .collect();
218 format!("Set{{{}}}", parts.join(", "))
219 }
220 Some(HeapValue::Deque(d)) => {
221 let parts: Vec<String> = d
222 .items
223 .iter()
224 .map(|v| self.format_nb_with_depth(v, depth + 1))
225 .collect();
226 format!("Deque[{}]", parts.join(", "))
227 }
228 Some(HeapValue::PriorityQueue(d)) => {
229 let parts: Vec<String> = d
230 .items
231 .iter()
232 .map(|v| self.format_nb_with_depth(v, depth + 1))
233 .collect();
234 format!("PriorityQueue[{}]", parts.join(", "))
235 }
236 Some(HeapValue::Content(node)) => format!("{}", node),
237 Some(HeapValue::Instant(t)) => format!("<instant:{:?}>", t.elapsed()),
238 Some(HeapValue::IoHandle(data)) => {
239 let status = if data.is_open() { "open" } else { "closed" };
240 format!("<io_handle:{}:{}>", data.path, status)
241 }
242 Some(HeapValue::SharedCell(arc)) => {
243 self.format_nb_with_depth(&arc.read().unwrap(), depth)
244 }
245 Some(HeapValue::IntArray(a)) => {
246 let elems: Vec<String> = a.iter().map(|v| v.to_string()).collect();
247 format!("[{}]", elems.join(", "))
248 }
249 Some(HeapValue::FloatArray(a)) => {
250 let elems: Vec<String> = a
251 .iter()
252 .map(|v| {
253 if *v == v.trunc() && v.abs() < 1e15 {
254 format!("{}", *v as i64)
255 } else {
256 format!("{}", v)
257 }
258 })
259 .collect();
260 format!("[{}]", elems.join(", "))
261 }
262 Some(HeapValue::BoolArray(a)) => {
263 let elems: Vec<String> = a
264 .iter()
265 .map(|v| if *v != 0 { "true" } else { "false" }.to_string())
266 .collect();
267 format!("[{}]", elems.join(", "))
268 }
269 Some(HeapValue::I8Array(a)) => {
270 let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
271 format!("[{}]", elems.join(", "))
272 }
273 Some(HeapValue::I16Array(a)) => {
274 let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
275 format!("[{}]", elems.join(", "))
276 }
277 Some(HeapValue::I32Array(a)) => {
278 let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
279 format!("[{}]", elems.join(", "))
280 }
281 Some(HeapValue::U8Array(a)) => {
282 let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
283 format!("[{}]", elems.join(", "))
284 }
285 Some(HeapValue::U16Array(a)) => {
286 let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
287 format!("[{}]", elems.join(", "))
288 }
289 Some(HeapValue::U32Array(a)) => {
290 let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
291 format!("[{}]", elems.join(", "))
292 }
293 Some(HeapValue::U64Array(a)) => {
294 let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
295 format!("[{}]", elems.join(", "))
296 }
297 Some(HeapValue::F32Array(a)) => {
298 let elems: Vec<String> = a
299 .data
300 .iter()
301 .map(|v| {
302 if *v == v.trunc() && v.abs() < 1e15 {
303 format!("{}", *v as i64)
304 } else {
305 format!("{}", v)
306 }
307 })
308 .collect();
309 format!("[{}]", elems.join(", "))
310 }
311 Some(HeapValue::Matrix(m)) => {
312 format!("<Mat<number>:{}x{}>", m.rows, m.cols)
313 }
314 Some(HeapValue::Iterator(it)) => {
315 format!("<iterator:pos={}>", it.position)
316 }
317 Some(HeapValue::Generator(g)) => {
318 format!("<generator:state={}>", g.state)
319 }
320 Some(HeapValue::Mutex(_)) => "<mutex>".to_string(),
321 Some(HeapValue::Atomic(a)) => {
322 format!(
323 "<atomic:{}>",
324 a.inner.load(std::sync::atomic::Ordering::Relaxed)
325 )
326 }
327 Some(HeapValue::Lazy(l)) => {
328 let initialized = l.value.lock().map(|g| g.is_some()).unwrap_or(false);
329 if initialized {
330 "<lazy:initialized>".to_string()
331 } else {
332 "<lazy:pending>".to_string()
333 }
334 }
335 Some(HeapValue::Channel(c)) => {
336 if c.is_sender() {
337 "<channel:sender>".to_string()
338 } else {
339 "<channel:receiver>".to_string()
340 }
341 }
342 None => format!("<unknown:{}>", value.type_name()),
343 }
344 }
345
346 fn format_nanboxed_array(&self, arr: &[ValueWord], depth: usize) -> String {
348 let elements: Vec<String> = arr
349 .iter()
350 .map(|nb| self.format_nb_with_depth(nb, depth + 1))
351 .collect();
352 format!("[{}]", elements.join(", "))
353 }
354
355 fn format_typed_object(
357 &self,
358 schema_id: u32,
359 slots: &[shape_value::ValueSlot],
360 heap_mask: u64,
361 depth: usize,
362 ) -> String {
363 let schema_ref = self.schema_registry.get_by_id(schema_id);
364 let schema = if let Some(s) = schema_ref {
365 s
366 } else {
367 let nb = ValueWord::from_heap_value(HeapValue::TypedObject {
368 schema_id: schema_id as u64,
369 slots: slots.to_vec().into_boxed_slice(),
370 heap_mask,
371 });
372 if let Some(map) = shape_runtime::type_schema::typed_object_to_hashmap_nb(&nb) {
373 let mut fields: Vec<(String, String)> = map
374 .into_iter()
375 .map(|(k, v)| (k, self.format_nb_with_depth(&v, depth + 1)))
376 .collect();
377 fields.sort_by(|a, b| a.0.cmp(&b.0));
378 return format!(
379 "{{ {} }}",
380 fields
381 .into_iter()
382 .map(|(k, v)| format!("{}: {}", k, v))
383 .collect::<Vec<_>>()
384 .join(", ")
385 );
386 }
387 return format!("[TypedObject:{}]", schema_id);
388 };
389
390 if let Some(enum_info) = schema.get_enum_info() {
392 return self.format_enum(&schema.name, enum_info, slots, heap_mask, depth);
393 }
394
395 let mut fields = Vec::new();
397
398 for field in &schema.fields {
399 let field_index = field.index as usize;
400 if field_index < slots.len() {
401 let formatted = self.format_slot_value(
402 slots,
403 heap_mask,
404 field_index,
405 &field.field_type,
406 depth + 1,
407 );
408 fields.push(format!("{}: {}", field.name, formatted));
409 }
410 }
411
412 if fields.is_empty() {
413 "{}".to_string()
414 } else {
415 format!("{{ {} }}", fields.join(", "))
416 }
417 }
418
419 fn format_enum(
421 &self,
422 enum_name: &str,
423 enum_info: &shape_runtime::type_schema::EnumInfo,
424 slots: &[shape_value::ValueSlot],
425 heap_mask: u64,
426 depth: usize,
427 ) -> String {
428 if slots.is_empty() {
430 return format!("{}::?", enum_name);
431 }
432
433 let variant_id = slots[0].as_i64() as u16;
434
435 let variant = match enum_info.variant_by_id(variant_id) {
437 Some(v) => v,
438 None => return format!("{}::?[{}]", enum_name, variant_id),
439 };
440
441 if variant.payload_fields == 0 {
443 return format!("{}::{}", enum_name, variant.name);
444 }
445
446 let mut payload_values = Vec::new();
448 for i in 0..variant.payload_fields {
449 let slot_idx = 1 + i as usize;
450 if slot_idx < slots.len() {
451 payload_values.push(self.format_slot_value(
452 slots,
453 heap_mask,
454 slot_idx,
455 &FieldType::Any,
456 depth + 1,
457 ));
458 }
459 }
460
461 if payload_values.is_empty() {
462 format!("{}::{}", enum_name, variant.name)
463 } else if payload_values.len() == 1 {
464 format!("{}::{}({})", enum_name, variant.name, payload_values[0])
466 } else {
467 format!(
469 "{}::{}({})",
470 enum_name,
471 variant.name,
472 payload_values.join(", ")
473 )
474 }
475 }
476
477 fn format_slot_value(
481 &self,
482 slots: &[shape_value::ValueSlot],
483 heap_mask: u64,
484 index: usize,
485 field_type: &FieldType,
486 depth: usize,
487 ) -> String {
488 if index >= slots.len() {
489 return "None".to_string();
490 }
491 let slot = &slots[index];
492 if heap_mask & (1u64 << index) != 0 {
493 let nb = slot.as_heap_nb();
495 self.format_nb_with_depth(&nb, depth)
496 } else {
497 match field_type {
499 FieldType::I64 | FieldType::Timestamp => slot.as_i64().to_string(),
500 FieldType::Bool => slot.as_bool().to_string(),
501 FieldType::F64 | FieldType::Decimal => format_number(slot.as_f64()),
502 _ => {
505 let vw = slot.as_value_word(false);
506 self.format_nb_with_depth(&vw, depth)
507 }
508 }
509 }
510 }
511}
512
513fn format_number(n: f64) -> String {
515 if n.is_nan() {
516 "NaN".to_string()
517 } else if n.is_infinite() {
518 if n.is_sign_positive() {
519 "Infinity".to_string()
520 } else {
521 "-Infinity".to_string()
522 }
523 } else if n.fract() == 0.0 && n.abs() < 1e15 {
524 format!("{}", n as i64)
526 } else {
527 n.to_string()
529 }
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535 use std::sync::Arc;
536
537 fn create_test_registry() -> TypeSchemaRegistry {
538 TypeSchemaRegistry::new()
539 }
540
541 fn predeclared_object(fields: &[(&str, ValueWord)]) -> ValueWord {
542 let field_names: Vec<String> = fields.iter().map(|(name, _)| (*name).to_string()).collect();
543 let _ = shape_runtime::type_schema::register_predeclared_any_schema(&field_names);
544 shape_runtime::type_schema::typed_object_from_pairs(fields)
545 }
546
547 #[test]
548 fn test_format_primitives() {
549 let schema_reg = create_test_registry();
550 let formatter = VMValueFormatter::new(&schema_reg);
551
552 assert_eq!(formatter.format(&ValueWord::from_f64(42.0)), "42");
553 assert_eq!(formatter.format(&ValueWord::from_f64(3.14)), "3.14");
554 assert_eq!(
555 formatter.format(&ValueWord::from_string(Arc::new("hello".to_string()))),
556 "hello"
557 );
558 assert_eq!(formatter.format(&ValueWord::from_bool(true)), "true");
559 assert_eq!(formatter.format(&ValueWord::none()), "None");
560 assert_eq!(formatter.format(&ValueWord::unit()), "()");
561 }
562
563 #[test]
564 fn test_format_array() {
565 let schema_reg = create_test_registry();
566 let formatter = VMValueFormatter::new(&schema_reg);
567
568 let arr = ValueWord::from_array(Arc::new(vec![
569 ValueWord::from_f64(1.0),
570 ValueWord::from_f64(2.0),
571 ValueWord::from_f64(3.0),
572 ]));
573 assert_eq!(formatter.format(&arr), "[1, 2, 3]");
574 }
575
576 #[test]
577 fn test_format_object() {
578 let schema_reg = create_test_registry();
579 let formatter = VMValueFormatter::new(&schema_reg);
580
581 let value = predeclared_object(&[
582 ("x", ValueWord::from_f64(1.0)),
583 ("y", ValueWord::from_f64(2.0)),
584 ]);
585
586 let formatted = formatter.format(&value);
587 assert!(formatted.contains("x: 1"));
589 assert!(formatted.contains("y: 2"));
590 }
591
592 #[test]
593 fn test_format_number_integers() {
594 assert_eq!(format_number(42.0), "42");
595 assert_eq!(format_number(-100.0), "-100");
596 assert_eq!(format_number(0.0), "0");
597 }
598
599 #[test]
600 fn test_format_number_decimals() {
601 assert_eq!(format_number(3.14), "3.14");
602 assert_eq!(format_number(-2.5), "-2.5");
603 }
604
605 #[test]
606 fn test_format_number_special() {
607 assert_eq!(format_number(f64::NAN), "NaN");
608 assert_eq!(format_number(f64::INFINITY), "Infinity");
609 assert_eq!(format_number(f64::NEG_INFINITY), "-Infinity");
610 }
611
612 #[test]
613 fn test_format_decimal() {
614 let schema_reg = create_test_registry();
615 let formatter = VMValueFormatter::new(&schema_reg);
616
617 let d = ValueWord::from_decimal(rust_decimal::Decimal::from(42));
618 assert_eq!(formatter.format(&d), "42D");
619
620 let d2 = ValueWord::from_decimal(rust_decimal::Decimal::new(314, 2)); assert_eq!(formatter.format(&d2), "3.14D");
622 }
623
624 #[test]
625 fn test_format_int() {
626 let schema_reg = create_test_registry();
627 let formatter = VMValueFormatter::new(&schema_reg);
628
629 assert_eq!(formatter.format(&ValueWord::from_i64(42)), "42");
630 assert_eq!(formatter.format(&ValueWord::from_i64(-100)), "-100");
631 assert_eq!(formatter.format(&ValueWord::from_i64(0)), "0");
632 }
633
634 #[test]
637 fn test_format_nb_primitives() {
638 let schema_reg = create_test_registry();
639 let formatter = VMValueFormatter::new(&schema_reg);
640
641 assert_eq!(formatter.format_nb(&ValueWord::from_f64(42.0)), "42");
642 assert_eq!(formatter.format_nb(&ValueWord::from_f64(3.14)), "3.14");
643 assert_eq!(
644 formatter.format_nb(&ValueWord::from_string(Arc::new("hello".to_string()))),
645 "hello"
646 );
647 assert_eq!(formatter.format_nb(&ValueWord::from_bool(true)), "true");
648 assert_eq!(formatter.format_nb(&ValueWord::from_bool(false)), "false");
649 assert_eq!(formatter.format_nb(&ValueWord::none()), "None");
650 assert_eq!(formatter.format_nb(&ValueWord::unit()), "()");
651 }
652
653 #[test]
654 fn test_format_nb_integers() {
655 let schema_reg = create_test_registry();
656 let formatter = VMValueFormatter::new(&schema_reg);
657
658 assert_eq!(formatter.format_nb(&ValueWord::from_i64(42)), "42");
659 assert_eq!(formatter.format_nb(&ValueWord::from_i64(-100)), "-100");
660 assert_eq!(formatter.format_nb(&ValueWord::from_i64(0)), "0");
661 }
662
663 #[test]
664 fn test_format_nb_decimal() {
665 let schema_reg = create_test_registry();
666 let formatter = VMValueFormatter::new(&schema_reg);
667
668 assert_eq!(
669 formatter.format_nb(&ValueWord::from_decimal(rust_decimal::Decimal::from(42))),
670 "42D"
671 );
672 assert_eq!(
673 formatter.format_nb(&ValueWord::from_decimal(rust_decimal::Decimal::new(314, 2))),
674 "3.14D"
675 );
676 }
677
678 #[test]
679 fn test_format_nb_array() {
680 let schema_reg = create_test_registry();
681 let formatter = VMValueFormatter::new(&schema_reg);
682
683 let arr = ValueWord::from_array(Arc::new(vec![
684 ValueWord::from_f64(1.0),
685 ValueWord::from_f64(2.0),
686 ValueWord::from_f64(3.0),
687 ]));
688 assert_eq!(formatter.format_nb(&arr), "[1, 2, 3]");
689 }
690
691 #[test]
692 fn test_format_nb_mixed_array() {
693 let schema_reg = create_test_registry();
694 let formatter = VMValueFormatter::new(&schema_reg);
695
696 let arr = ValueWord::from_array(Arc::new(vec![
697 ValueWord::from_i64(1),
698 ValueWord::from_string(Arc::new("two".to_string())),
699 ValueWord::from_bool(true),
700 ]));
701 assert_eq!(formatter.format_nb(&arr), "[1, two, true]");
702 }
703
704 #[test]
705 fn test_format_nb_object() {
706 let schema_reg = create_test_registry();
707 let formatter = VMValueFormatter::new(&schema_reg);
708
709 let value = predeclared_object(&[
710 ("x", ValueWord::from_f64(1.0)),
711 ("y", ValueWord::from_f64(2.0)),
712 ]);
713 let nb = value;
714
715 let formatted = formatter.format_nb(&nb);
716 assert!(formatted.contains("x: 1"));
717 assert!(formatted.contains("y: 2"));
718 }
719
720 #[test]
721 fn test_format_nb_special_numbers() {
722 let schema_reg = create_test_registry();
723 let formatter = VMValueFormatter::new(&schema_reg);
724
725 assert_eq!(formatter.format_nb(&ValueWord::from_f64(f64::NAN)), "NaN");
726 assert_eq!(
727 formatter.format_nb(&ValueWord::from_f64(f64::INFINITY)),
728 "Infinity"
729 );
730 assert_eq!(
731 formatter.format_nb(&ValueWord::from_f64(f64::NEG_INFINITY)),
732 "-Infinity"
733 );
734 }
735
736 #[test]
737 fn test_format_nb_result_types() {
738 let schema_reg = create_test_registry();
739 let formatter = VMValueFormatter::new(&schema_reg);
740
741 assert_eq!(
742 formatter.format_nb(&ValueWord::from_ok(ValueWord::from_i64(42))),
743 "Ok(42)"
744 );
745 assert_eq!(
746 formatter.format_nb(&ValueWord::from_err(ValueWord::from_string(Arc::new(
747 "fail".to_string()
748 )))),
749 "Err(fail)"
750 );
751 assert_eq!(
752 formatter.format_nb(&ValueWord::from_some(ValueWord::from_f64(3.14))),
753 "Some(3.14)"
754 );
755 }
756
757 #[test]
758 fn test_format_nb_consistency_with_vmvalue() {
759 let schema_reg = create_test_registry();
761 let formatter = VMValueFormatter::new(&schema_reg);
762
763 let test_cases: Vec<(ValueWord, ValueWord)> = vec![
764 (ValueWord::from_f64(42.0), ValueWord::from_f64(42.0)),
765 (ValueWord::from_f64(3.14), ValueWord::from_f64(3.14)),
766 (ValueWord::from_i64(99), ValueWord::from_i64(99)),
767 (ValueWord::from_bool(true), ValueWord::from_bool(true)),
768 (ValueWord::none(), ValueWord::none()),
769 (ValueWord::unit(), ValueWord::unit()),
770 (
771 ValueWord::from_string(Arc::new("test".to_string())),
772 ValueWord::from_string(Arc::new("test".to_string())),
773 ),
774 ];
775
776 for (vmval, nb) in &test_cases {
777 assert_eq!(
778 formatter.format(vmval),
779 formatter.format_nb(nb),
780 "Mismatch for ValueWord: {:?}",
781 vmval
782 );
783 }
784 }
785}