Skip to main content

shape_value/
extraction.rs

1//! Centralized extraction helpers for ValueWord values.
2//!
3//! These `require_*` functions provide a single source of truth for extracting
4//! typed values from ValueWord, returning descriptive VMError on type mismatch.
5//! They replace scattered ad-hoc patterns like:
6//!
7//! ```ignore
8//! let s = nb.as_str()
9//!     .ok_or_else(|| VMError::TypeError { expected: "string", got: "other" })?;
10//! ```
11
12use crate::context::VMError;
13use crate::datatable::DataTable;
14use crate::slot::ValueSlot;
15use crate::value::Upvalue;
16use crate::value_word::{ArrayView, ValueWord};
17use std::sync::Arc;
18
19/// Extract a string reference from a ValueWord, or return a type error.
20#[inline]
21pub fn require_string(nb: &ValueWord) -> Result<&str, VMError> {
22    nb.as_str()
23        .ok_or_else(|| VMError::type_mismatch("string", nb.type_name()))
24}
25
26/// Extract an Arc<String> reference from a ValueWord, or return a type error.
27#[inline]
28pub fn require_arc_string(nb: &ValueWord) -> Result<&Arc<String>, VMError> {
29    nb.as_arc_string()
30        .ok_or_else(|| VMError::type_mismatch("string", nb.type_name()))
31}
32
33/// Extract an f64 from a ValueWord, or return a type error.
34///
35/// This only extracts inline f64 values; it does not coerce integers.
36#[inline]
37pub fn require_f64(nb: &ValueWord) -> Result<f64, VMError> {
38    nb.as_f64()
39        .ok_or_else(|| VMError::type_mismatch("number", nb.type_name()))
40}
41
42/// Extract a number (f64 or i64 coerced to f64) from a ValueWord, or return a type error.
43#[inline]
44pub fn require_number(nb: &ValueWord) -> Result<f64, VMError> {
45    nb.as_number_coerce()
46        .ok_or_else(|| VMError::type_mismatch("number", nb.type_name()))
47}
48
49/// Extract an i64 from a ValueWord, or return a type error.
50///
51/// This only extracts inline i48 values; it does not coerce floats.
52#[inline]
53pub fn require_int(nb: &ValueWord) -> Result<i64, VMError> {
54    nb.as_i64()
55        .ok_or_else(|| VMError::type_mismatch("int", nb.type_name()))
56}
57
58/// Extract a bool from a ValueWord, or return a type error.
59#[inline]
60pub fn require_bool(nb: &ValueWord) -> Result<bool, VMError> {
61    nb.as_bool()
62        .ok_or_else(|| VMError::type_mismatch("bool", nb.type_name()))
63}
64
65/// Extract an array view from a ValueWord, or return a type error.
66///
67/// Handles all array variants: Generic, Int, Float, and Bool.
68#[inline]
69pub fn require_array(nb: &ValueWord) -> Result<ArrayView<'_>, VMError> {
70    nb.as_any_array()
71        .ok_or_else(|| VMError::type_mismatch("array", nb.type_name()))
72}
73
74/// Extract a DataTable reference from a ValueWord, or return a type error.
75#[inline]
76pub fn require_datatable(nb: &ValueWord) -> Result<&Arc<DataTable>, VMError> {
77    nb.as_datatable()
78        .ok_or_else(|| VMError::type_mismatch("datatable", nb.type_name()))
79}
80
81/// Extract TypedObject fields (schema_id, slots, heap_mask) from a ValueWord, or return a type error.
82#[inline]
83pub fn require_typed_object(nb: &ValueWord) -> Result<(u64, &[ValueSlot], u64), VMError> {
84    nb.as_typed_object()
85        .ok_or_else(|| VMError::type_mismatch("object", nb.type_name()))
86}
87
88/// Extract Closure fields (function_id, upvalues) from a ValueWord, or return a type error.
89#[inline]
90pub fn require_closure(nb: &ValueWord) -> Result<(u16, &[Upvalue]), VMError> {
91    nb.as_closure()
92        .ok_or_else(|| VMError::type_mismatch("closure", nb.type_name()))
93}
94
95/// Convert a ValueWord to a display string.
96///
97/// Uses the Display impl which dispatches through HeapValue.
98#[inline]
99pub fn nb_to_display_string(nb: &ValueWord) -> String {
100    format!("{}", nb)
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use std::sync::Arc;
107
108    #[test]
109    fn test_require_string_ok() {
110        let nb = ValueWord::from_string(Arc::new("hello".to_string()));
111        assert_eq!(require_string(&nb).unwrap(), "hello");
112    }
113
114    #[test]
115    fn test_require_string_err() {
116        let nb = ValueWord::from_f64(42.0);
117        let err = require_string(&nb).unwrap_err();
118        match err {
119            VMError::TypeError { expected, got } => {
120                assert_eq!(expected, "string");
121                assert_eq!(got, "number");
122            }
123            other => panic!("expected TypeError, got {:?}", other),
124        }
125    }
126
127    #[test]
128    fn test_require_number_coerce() {
129        // f64 works
130        let nb = ValueWord::from_f64(3.14);
131        assert_eq!(require_number(&nb).unwrap(), 3.14);
132
133        // i64 is coerced to f64
134        let nb = ValueWord::from_i64(42);
135        assert_eq!(require_number(&nb).unwrap(), 42.0);
136
137        // string fails
138        let nb = ValueWord::from_string(Arc::new("x".to_string()));
139        assert!(require_number(&nb).is_err());
140    }
141
142    #[test]
143    fn test_require_int() {
144        let nb = ValueWord::from_i64(99);
145        assert_eq!(require_int(&nb).unwrap(), 99);
146
147        let nb = ValueWord::from_f64(1.5);
148        assert!(require_int(&nb).is_err());
149    }
150
151    #[test]
152    fn test_require_bool() {
153        let nb = ValueWord::from_bool(true);
154        assert_eq!(require_bool(&nb).unwrap(), true);
155
156        let nb = ValueWord::from_i64(1);
157        assert!(require_bool(&nb).is_err());
158    }
159
160    #[test]
161    fn test_require_array() {
162        let nb = ValueWord::from_array(Arc::new(vec![ValueWord::from_i64(1)]));
163        assert_eq!(require_array(&nb).unwrap().len(), 1);
164
165        let nb = ValueWord::from_f64(1.0);
166        assert!(require_array(&nb).is_err());
167    }
168
169    #[test]
170    fn test_require_typed_object() {
171        let nb = ValueWord::from_f64(1.0);
172        let err = require_typed_object(&nb).unwrap_err();
173        match err {
174            VMError::TypeError { expected, got } => {
175                assert_eq!(expected, "object");
176                assert_eq!(got, "number");
177            }
178            other => panic!("expected TypeError, got {:?}", other),
179        }
180    }
181
182    #[test]
183    fn test_nb_to_display_string() {
184        assert_eq!(nb_to_display_string(&ValueWord::from_f64(3.14)), "3.14");
185        assert_eq!(nb_to_display_string(&ValueWord::from_i64(42)), "42");
186        assert_eq!(nb_to_display_string(&ValueWord::from_bool(true)), "true");
187        assert_eq!(nb_to_display_string(&ValueWord::none()), "none");
188        let s = ValueWord::from_string(Arc::new("hello".to_string()));
189        assert_eq!(nb_to_display_string(&s), "hello");
190    }
191}