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