Skip to main content

shape_vm/executor/
printing.rs

1//! VM-native value formatting
2//!
3//! This module provides native formatting for ValueWord values without converting to
4//! runtime values. It respects TypeSchema for TypedObjects with their field names.
5//!
6//! The primary entry point is `format_nb()` which formats ValueWord values directly
7//! using NanTag/HeapValue dispatch.
8
9use 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
15/// Formatter for ValueWord values
16///
17/// Uses TypeSchemaRegistry to format TypedObjects with their field names.
18pub struct ValueFormatter<'a> {
19    /// Type schema registry for TypedObject field resolution
20    schema_registry: &'a TypeSchemaRegistry,
21}
22
23/// Backward-compat alias used by test code.
24#[cfg(test)]
25pub type VMValueFormatter<'a> = ValueFormatter<'a>;
26
27impl<'a> ValueFormatter<'a> {
28    /// Create a new formatter
29    pub fn new(schema_registry: &'a TypeSchemaRegistry) -> Self {
30        Self { schema_registry }
31    }
32
33    /// Format a ValueWord to string (test-only, delegates to ValueWord path)
34    #[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    /// Format a ValueWord value to string — primary entry point.
41    ///
42    /// Uses NanTag/HeapValue dispatch for inline types (f64, i48, bool, None,
43    /// Unit) and heap types (String, Array, TypedObject, Decimal, etc.).
44    pub fn format_nb(&self, value: &ValueWord) -> String {
45        self.format_nb_with_depth(value, 0)
46    }
47
48    /// Format a ValueWord value with depth tracking.
49    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        // Fast path: inline types (no heap access needed)
55        match value.tag() {
56            NanTag::F64 => {
57                if let Some(n) = value.as_f64() {
58                    return format_number(n);
59                }
60                // Shouldn't happen, but fallback
61                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        // Heap path: dispatch on HeapValue variant
89        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!("Vec<int>[{}]", 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!("Vec<number>[{}]", 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!("Vec<bool>[{}]", elems.join(", "))
268            }
269            Some(HeapValue::I8Array(a)) => {
270                let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
271                format!("Vec<i8>[{}]", elems.join(", "))
272            }
273            Some(HeapValue::I16Array(a)) => {
274                let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
275                format!("Vec<i16>[{}]", elems.join(", "))
276            }
277            Some(HeapValue::I32Array(a)) => {
278                let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
279                format!("Vec<i32>[{}]", elems.join(", "))
280            }
281            Some(HeapValue::U8Array(a)) => {
282                let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
283                format!("Vec<u8>[{}]", elems.join(", "))
284            }
285            Some(HeapValue::U16Array(a)) => {
286                let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
287                format!("Vec<u16>[{}]", elems.join(", "))
288            }
289            Some(HeapValue::U32Array(a)) => {
290                let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
291                format!("Vec<u32>[{}]", elems.join(", "))
292            }
293            Some(HeapValue::U64Array(a)) => {
294                let elems: Vec<String> = a.data.iter().map(|v| v.to_string()).collect();
295                format!("Vec<u64>[{}]", 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!("Vec<f32>[{}]", 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    /// Format an array of ValueWord values
347    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    /// Format a TypedObject using its schema
356    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        // Check if this is an enum type - format specially
391        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        // Default format: { field1: val1, field2: val2, ... }
396        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    /// Format an enum value using its variant info
420    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        // Read variant ID from slot 0
429        if slots.is_empty() {
430            return format!("{}::?", enum_name);
431        }
432
433        let variant_id = slots[0].as_i64() as u16;
434
435        // Look up variant by ID
436        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        // Unit variant (no payload)
442        if variant.payload_fields == 0 {
443            return format!("{}::{}", enum_name, variant.name);
444        }
445
446        // Variant with payload - read payload values from slots 1+
447        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            // Single payload - use parentheses style
465            format!("{}::{}({})", enum_name, variant.name, payload_values[0])
466        } else {
467            // Multiple payloads - use tuple style with variant name
468            format!(
469                "{}::{}({})",
470                enum_name,
471                variant.name,
472                payload_values.join(", ")
473            )
474        }
475    }
476
477    /// Format a TypedObject slot value directly from its ValueSlot.
478    /// Heap slots are converted via `as_heap_nb()` and formatted with `format_nb_with_depth`.
479    /// Non-heap slots are dispatched by field type to read the correct representation.
480    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            // Heap slot: read as HeapValue, wrap in ValueWord, format directly
494            let nb = slot.as_heap_nb();
495            self.format_nb_with_depth(&nb, depth)
496        } else {
497            // Non-heap: dispatch on field type to read raw bits correctly
498            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                // Any other non-heap type: reconstruct via as_value_word to
503                // preserve inline NanTag variants for correct Display formatting
504                _ => {
505                    let vw = slot.as_value_word(false);
506                    self.format_nb_with_depth(&vw, depth)
507                }
508            }
509        }
510    }
511}
512
513/// Format a number, removing unnecessary decimal places
514fn 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        // Integer-like numbers: show without decimal
525        format!("{}", n as i64)
526    } else {
527        // Use default formatting
528        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        // TypedObject fields come from schema order
588        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)); // 3.14 exactly
621        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    // ===== ValueWord format_nb tests =====
635
636    #[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        // Verify that format_nb produces the same output as format for common types
760        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}