Skip to main content

wolfram_library_link/
data_store.rs

1use std::{
2    ffi::{CStr, CString},
3    fmt,
4    marker::PhantomData,
5    os::raw::c_char,
6};
7
8use once_cell::sync::OnceCell;
9use static_assertions::assert_not_impl_any;
10
11use crate::{
12    rtl,
13    sys::{self, mcomplex, mint, mreal},
14    FromArg, Image, NumericArray,
15};
16
17/// Storage for heterogenous expression-like data.
18///
19/// `DataStore` can be used to pass expression-like structures via *LibraryLink* functions.
20///
21/// `DataStore` can be used as an argument or return type in a *LibraryLink* function
22/// exposed via [`#[export]`][crate::export].
23///
24/// Use [`DataStore::nodes()`] to get an iterator over the [`DataStoreNode`]s contained
25/// in this `DataStore`.
26///
27/// # Example
28///
29/// The following `DataStore` expression:
30///
31/// ```wolfram
32/// Developer`DataStore[1, "hello", False]
33/// ```
34///
35/// can be created using the Rust code:
36///
37/// ```no_run
38/// use wolfram_library_link::DataStore;
39///
40/// let mut data = DataStore::new();
41///
42/// data.add_i64(1);
43/// data.add_str("hello");
44/// data.add_bool(false);
45/// ```
46// TODO: Provide better Debug formatting for this type.
47#[derive(Debug)]
48#[derive(ref_cast::RefCast)]
49#[repr(transparent)]
50pub struct DataStore(sys::DataStore);
51
52assert_not_impl_any!(DataStore: Copy);
53
54/// Element borrowed from the linked list of nodes that make up a [`DataStore`].
55///
56/// # Lifetime `'store`
57///
58/// A `DataStoreNode` is borrowed from a [`DataStore`], and cannot outlive the `DataStore`
59/// it was borrowed from:
60///
61/// ```compile_fail
62/// # use wolfram_library_link::DataStore;
63/// let mut store = DataStore::new();
64/// store.add_named_i64("value", 5);
65///
66/// let node = store.first_node().unwrap();
67///
68/// drop(store);
69///
70/// // Error: `node` cannot outlive `store`.
71/// println!("node name: {:?}", node.name());
72/// ```
73pub struct DataStoreNode<'store> {
74    raw: sys::DataStoreNode,
75    marker: PhantomData<&'store DataStore>,
76    /// Private.
77    ///
78    /// This field is a cache that allows us to return a `&MArgument` reference that has
79    /// the same lifetime as the `self` node, which is ultimately needed as the lifetime
80    /// for references stored in [`DataStoreNodeValue`].
81    ///
82    /// We'd prefer to be able to directly return a reference to the "inner" field stored
83    /// on the `sys::DataStoreNode` opaque type, but we're not able to access that field
84    /// directly. Instead, we just cache the value of that field (accessed using
85    /// [`rtl::DataStoreNode_getData`]). Caching the value is valid because `DataStoreNode`
86    /// values are immutable.
87    data: OnceCell<sys::MArgument>,
88}
89
90/// Value of a [`DataStoreNode`].
91///
92/// Instances of this type are returned by [`DataStoreNode::value()`].
93///
94/// [`DataStoreNode`]s can contain any value that can be stored in an
95/// [`MArgument`][sys::MArgument].
96///
97// TODO: Rename this to `ArgValue`, as this is based on `MArgument`?
98#[allow(missing_docs)]
99pub enum DataStoreNodeValue<'node> {
100    Boolean(bool),
101    Integer(mint),
102    Real(mreal),
103    Complex(mcomplex),
104    Str(&'node str),
105    NumericArray(&'node NumericArray),
106    Image(&'node Image),
107    DataStore(&'node DataStore),
108}
109
110/// Iterator over the [`DataStoreNode`]s stored in a [`DataStore`].
111///
112/// Instances of this type are returned by [`DataStore::nodes()`].
113///
114/// # Example
115///
116/// ```no_run
117/// # use wolfram_library_link::DataStore;
118/// let mut store = DataStore::new();
119///
120/// store.add_i64(5);
121/// store.add_named_bool("condition", true);
122/// store.add_str("Hello, World!");
123///
124/// for node in store.nodes() {
125///     println!("node: {:?}", node);
126/// }
127/// ```
128///
129/// prints:
130///
131/// ```text
132/// node: DataStoreNode { name: None, value: 5 }
133/// node: DataStoreNode { name: Some("condition"), value: true }
134/// node: DataStoreNode { name: None, value: "Hello, World!" }
135/// ```
136pub struct Nodes<'s> {
137    node: Option<DataStoreNode<'s>>,
138}
139
140//======================================
141// Impls
142//======================================
143
144impl DataStore {
145    /// Create an empty [`DataStore`].
146    ///
147    /// *LibraryLink C Function:* [`createDataStore`][rtl::createDataStore].
148    pub fn new() -> Self {
149        let ds: sys::DataStore = unsafe { rtl::createDataStore() };
150
151        if ds.is_null() {
152            panic!("sys::DataStore is NULL");
153        }
154
155        DataStore(ds)
156    }
157
158    /// Returns the number of elements in this data store.
159    ///
160    /// *LibraryLink C Function:* [`DataStore_getLength`][rtl::DataStore_getLength].
161    pub fn len(&self) -> usize {
162        let DataStore(ds) = *self;
163
164        let len: i64 = unsafe { rtl::DataStore_getLength(ds) };
165
166        usize::try_from(len).expect("DataStore i64 length overflows usize")
167    }
168
169    /// Construct a `DataStore` from a raw [`wolfram_library_link_sys::DataStore`] pointer.
170    pub unsafe fn from_raw(raw: sys::DataStore) -> Self {
171        DataStore(raw)
172    }
173
174    /// Convert this `DataStore` into a raw [`wolfram_library_link_sys::DataStore`] pointer.
175    pub fn into_raw(self) -> sys::DataStore {
176        let DataStore(ds) = self;
177
178        // Don't run Drop on `self`; ownership of this value is being given to the caller.
179        std::mem::forget(self);
180
181        ds
182    }
183
184    //==================================
185    // Unnamed data
186    //==================================
187
188    /// Add a `bool` value to this `DataStore`.
189    ///
190    /// *LibraryLink C Function:* [`DataStore_addBoolean`][rtl::DataStore_addBoolean].
191    pub fn add_bool(&mut self, value: bool) {
192        let DataStore(ds) = *self;
193
194        unsafe { rtl::DataStore_addBoolean(ds, sys::mbool::from(value)) }
195    }
196
197    /// Add an `i64` value to this `DataStore`.
198    ///
199    /// *LibraryLink C Function:* [`DataStore_addInteger`][rtl::DataStore_addBoolean].
200    pub fn add_i64(&mut self, value: i64) {
201        let DataStore(ds) = *self;
202
203        unsafe { rtl::DataStore_addInteger(ds, value) }
204    }
205
206    /// Add an `f64` value to this `DataStore`.
207    ///
208    /// *LibraryLink C Function:* [`DataStore_addReal`][rtl::DataStore_addReal].
209    pub fn add_f64(&mut self, value: f64) {
210        let DataStore(ds) = *self;
211
212        unsafe { rtl::DataStore_addReal(ds, value) }
213    }
214
215    /// Add an [`mcomplex`][sys::mcomplex] value to this `DataStore`.
216    ///
217    /// *LibraryLink C Function:* [`DataStore_addComplex`][rtl::DataStore_addComplex].
218    pub fn add_complex_f64(&mut self, value: sys::mcomplex) {
219        let DataStore(ds) = *self;
220
221        unsafe { rtl::DataStore_addComplex(ds, value) }
222    }
223
224    /// Add a [`str`] value to this `DataStore`.
225    ///
226    /// See also: [`DataStore::add_c_str()`].
227    ///
228    /// *LibraryLink C Function:* [`DataStore_addString`][rtl::DataStore_addString].
229    pub fn add_str(&mut self, value: &str) {
230        let DataStore(ds) = *self;
231
232        let value = CString::new(value).expect("could not convert &str to CString");
233
234        unsafe { rtl::DataStore_addString(ds, value.as_ptr() as *mut c_char) }
235    }
236
237    /// Add a [`CStr`] value to this `DataStore`.
238    ///
239    /// See also: [`DataStore::add_str()`].
240    ///
241    /// *LibraryLink C Function:* [`DataStore_addString`][rtl::DataStore_addString].
242    pub fn add_c_str(&mut self, value: &CStr) {
243        let DataStore(ds) = *self;
244
245        unsafe { rtl::DataStore_addString(ds, value.as_ptr() as *mut c_char) }
246    }
247
248    /// Add a `DataStore` value to this `DataStore`.
249    ///
250    /// *LibraryLink C Function:* [`DataStore_addDataStore`][rtl::DataStore_addDataStore].
251    ///
252    /// # Example
253    ///
254    /// The `DataStore` value constructed by the following code:
255    ///
256    /// ```no_run
257    /// use wolfram_library_link::DataStore;
258    ///
259    /// let mut inner = DataStore::new();
260    /// inner.add_i64(0);
261    /// let mut outer = DataStore::new();
262    /// outer.add_data_store(inner);
263    /// ```
264    ///
265    /// will have this representation when passed via LibraryLink into Wolfram Language:
266    ///
267    /// ```wolfram
268    /// Developer`DataStore[Developer`DataStore[0]]
269    /// ```
270    pub fn add_data_store(&mut self, ds: DataStore) {
271        let DataStore(this_ds) = *self;
272        // Use into_raw() to avoid running Drop on `ds`.
273        let other_ds = ds.into_raw();
274
275        unsafe { rtl::DataStore_addDataStore(this_ds, other_ds) }
276    }
277
278    /// Add a [`NumericArray`] value to this `DataStore`.
279    ///
280    /// *LibraryLink C Function:* [`DataStore_addMNumericArray`][rtl::DataStore_addMNumericArray].
281    ///
282    /// # Example
283    ///
284    /// ```no_run
285    /// use wolfram_library_link::{DataStore, NumericArray};
286    ///
287    /// let array: NumericArray<i64> = NumericArray::from_slice(&[1, 2, 3]);
288    ///
289    /// let mut store = DataStore::new();
290    ///
291    /// // Erase the NumericArray data type.
292    /// store.add_numeric_array(array.into_generic());
293    /// ```
294    ///
295    /// See also: [`NumericArray::into_generic()`].
296    pub fn add_numeric_array(&mut self, array: NumericArray) {
297        let DataStore(ds) = *self;
298        let array = unsafe { array.into_raw() };
299
300        unsafe { rtl::DataStore_addMNumericArray(ds, array) }
301    }
302
303    //==================================
304    // Named data
305    //==================================
306
307    /// Add a `bool` value to this `DataStore`.
308    ///
309    /// *LibraryLink C Function:* [`DataStore_addNamedBoolean`][rtl::DataStore_addNamedBoolean].
310    pub fn add_named_bool(&mut self, name: &str, value: bool) {
311        let DataStore(ds) = *self;
312
313        let name = CString::new(name).expect("could not convert &str to CString");
314
315        unsafe {
316            rtl::DataStore_addNamedBoolean(
317                ds,
318                name.as_ptr() as *mut c_char,
319                sys::mbool::from(value),
320            )
321        }
322    }
323
324    /// Add an `i64` value to this `DataStore`.
325    ///
326    /// *LibraryLink C Function:* [`DataStore_addNamedInteger`][rtl::DataStore_addNamedBoolean].
327    pub fn add_named_i64(&mut self, name: &str, value: i64) {
328        let DataStore(ds) = *self;
329
330        let name = CString::new(name).expect("could not convert &str to CString");
331
332        unsafe { rtl::DataStore_addNamedInteger(ds, name.as_ptr() as *mut c_char, value) }
333    }
334
335    /// Add an `f64` value to this `DataStore`.
336    ///
337    /// *LibraryLink C Function:* [`DataStore_addNamedReal`][rtl::DataStore_addNamedReal].
338    pub fn add_named_f64(&mut self, name: &str, value: f64) {
339        let DataStore(ds) = *self;
340
341        let name = CString::new(name).expect("could not convert &str to CString");
342
343        unsafe { rtl::DataStore_addNamedReal(ds, name.as_ptr() as *mut c_char, value) }
344    }
345
346    /// Add an [`mcomplex`][sys::mcomplex] value to this `DataStore`.
347    ///
348    /// *LibraryLink C Function:* [`DataStore_addNamedComplex`][rtl::DataStore_addNamedComplex].
349    pub fn add_named_complex_f64(&mut self, name: &str, value: sys::mcomplex) {
350        let DataStore(ds) = *self;
351
352        let name = CString::new(name).expect("could not convert &str to CString");
353
354        unsafe { rtl::DataStore_addNamedComplex(ds, name.as_ptr() as *mut c_char, value) }
355    }
356
357    /// Add a [`str`] value to this `DataStore`.
358    ///
359    /// See also: [`DataStore::add_c_str()`].
360    ///
361    /// *LibraryLink C Function:* [`DataStore_addNamedString`][rtl::DataStore_addNamedString].
362    pub fn add_named_str(&mut self, name: &str, value: &str) {
363        let DataStore(ds) = *self;
364
365        let name = CString::new(name).expect("could not convert &str to CString");
366        let value = CString::new(value).expect("could not convert &str to CString");
367
368        unsafe {
369            rtl::DataStore_addNamedString(
370                ds,
371                name.as_ptr() as *mut c_char,
372                value.as_ptr() as *mut c_char,
373            )
374        }
375    }
376
377    /// Add a [`CStr`] value to this `DataStore`.
378    ///
379    /// See also: [`DataStore::add_str()`].
380    ///
381    /// *LibraryLink C Function:* [`DataStore_addNamedString`][rtl::DataStore_addNamedString].
382    pub fn add_named_c_str(&mut self, name: &str, value: &CStr) {
383        let DataStore(ds) = *self;
384
385        let name = CString::new(name).expect("could not convert &str to CString");
386
387        unsafe {
388            rtl::DataStore_addNamedString(
389                ds,
390                name.as_ptr() as *mut c_char,
391                value.as_ptr() as *mut c_char,
392            )
393        }
394    }
395
396    /// Add a `DataStore` value to this `DataStore`.
397    ///
398    /// *LibraryLink C Function:* [`DataStore_addNamedDataStore`][rtl::DataStore_addNamedDataStore].
399    ///
400    /// # Example
401    ///
402    /// The `DataStore` value constructed by the following code:
403    ///
404    /// ```no_run
405    /// use wolfram_library_link::DataStore;
406    ///
407    /// let mut inner = DataStore::new();
408    /// inner.add_i64(0);
409    /// let mut outer = DataStore::new();
410    /// outer.add_named_data_store("inner", inner);
411    /// ```
412    ///
413    /// will have this representation when passed via LibraryLink into Wolfram Language:
414    ///
415    /// ```wolfram
416    /// Developer`DataStore["inner" -> Developer`DataStore[0]]
417    /// ```
418    pub fn add_named_data_store(&mut self, name: &str, ds: DataStore) {
419        let DataStore(this_ds) = *self;
420        // Use into_raw() to avoid running Drop on `ds`.
421        let other_ds = ds.into_raw();
422
423        let name = CString::new(name).expect("could not convert &str to CString");
424
425        unsafe {
426            rtl::DataStore_addNamedDataStore(
427                this_ds,
428                name.as_ptr() as *mut c_char,
429                other_ds,
430            )
431        }
432    }
433
434    /// Add a [`NumericArray`] value to this `DataStore`.
435    ///
436    /// See also [`DataStore::add_numeric_array()`].
437    ///
438    /// *LibraryLink C Function:* [`DataStore_addNamedMNumericArray`][rtl::DataStore_addNamedMNumericArray].
439    pub fn add_named_numeric_array(&mut self, name: &str, array: NumericArray) {
440        let DataStore(ds) = *self;
441        let array = unsafe { array.into_raw() };
442
443        let name = CString::new(name).expect("could not convert &str to CString");
444
445        unsafe {
446            rtl::DataStore_addNamedMNumericArray(ds, name.as_ptr() as *mut c_char, array)
447        }
448    }
449
450    /// Returns an iterator over the [`DataStoreNode`]s of this `DataStore`.
451    ///
452    /// A [`DataStore`] is made up of a linked list of [`DataStoreNode`]s. The [`Nodes`]
453    /// iterator will repeatedly call the [`DataStoreNode::next_node()`] method to iterate
454    /// over the nodes.
455    pub fn nodes<'s>(&'s self) -> Nodes<'s> {
456        Nodes {
457            node: self.first_node(),
458        }
459    }
460
461    /// Get the first [`DataStoreNode`] of this `DataStore`.
462    pub fn first_node<'s>(&'s self) -> Option<DataStoreNode<'s>> {
463        let DataStore(raw) = *self;
464
465        let node = unsafe { rtl::DataStore_getFirstNode(raw) };
466
467        if node.is_null() {
468            return None;
469        }
470
471        Some(DataStoreNode {
472            raw: node,
473            marker: PhantomData,
474            data: OnceCell::new(),
475        })
476    }
477
478    // Note: No `last_node()` method is provided to wrap the DataStoreNode_getLastNode()
479    //       function, because it would have no purpose, since there is no way to get the
480    //       previous node.
481    // pub fn last_node(&self) -> DataStoreNode { ... }
482}
483
484//--------------
485// DataStoreNode
486//--------------
487
488impl<'store> DataStoreNode<'store> {
489    /// Get the name associated with this node, if any.
490    ///
491    /// *LibraryLink C Function:* [`DataStoreNode_getName`][rtl::DataStoreNode_getName].
492    pub fn name(&self) -> Option<String> {
493        // TODO: Do we need to free this string, or does getName() just return a
494        //       borrwed reference?
495        let mut raw_c_str: *mut c_char = std::ptr::null_mut();
496
497        let err_code: sys::errcode_t =
498            unsafe { rtl::DataStoreNode_getName(self.raw, &mut raw_c_str) };
499
500        if err_code != 0 || raw_c_str.is_null() {
501            return None;
502        }
503
504        let c_str = unsafe { CStr::from_ptr(raw_c_str) };
505
506        let str: &str = c_str.to_str().ok()?;
507
508        Some(str.to_owned())
509    }
510
511    /// Get the value stored in this `DataStoreNode`.
512    ///
513    /// This is a safe wrapper around [`DataStoreNode::data_raw()`].
514    pub fn value<'node>(&'node self) -> DataStoreNodeValue<'node> {
515        use DataStoreNodeValue as V;
516
517        let data_raw: &'node sys::MArgument = unsafe { self.data_raw() };
518
519        unsafe {
520            match self.data_type_raw() as u32 {
521                sys::MType_Undef => panic!("unexpected DataStoreNode Undef data type"),
522                sys::MType_Boolean => V::Boolean(bool::from_arg(data_raw)),
523                sys::MType_Integer => V::Integer(mint::from_arg(data_raw)),
524                sys::MType_Real => V::Real(mreal::from_arg(data_raw)),
525                sys::MType_Complex => V::Complex(mcomplex::from_arg(data_raw)),
526                sys::MType_UTF8String => V::Str(<&str>::from_arg(data_raw)),
527                sys::MType_Tensor => {
528                    unimplemented!("unhandled DataStoreNode Tensor data type")
529                },
530                sys::MType_SparseArray => {
531                    unimplemented!("unhandled DataStoreNode SparseArray data type")
532                },
533                sys::MType_NumericArray => {
534                    V::NumericArray(<&NumericArray>::from_arg(data_raw))
535                },
536                sys::MType_Image => V::Image(<&Image>::from_arg(data_raw)),
537                sys::MType_DataStore => V::DataStore(<&DataStore>::from_arg(data_raw)),
538                type_ => {
539                    panic!("unexpected DataStoreNode::data_type_raw() value: {}", type_)
540                },
541            }
542        }
543    }
544
545    /// Get the next node in this linked list of `DataStoreNode`'s.
546    ///
547    /// *LibraryLink C Function:* [`DataStoreNode_getNextNode`][rtl::DataStoreNode_getNextNode].
548    pub fn next_node(&self) -> Option<DataStoreNode<'store>> {
549        let raw_next: sys::DataStoreNode =
550            unsafe { rtl::DataStoreNode_getNextNode(self.raw) };
551
552        if raw_next.is_null() {
553            return None;
554        }
555
556        Some(DataStoreNode {
557            raw: raw_next,
558            marker: PhantomData,
559            data: OnceCell::new(),
560        })
561    }
562
563    /// *LibraryLink C Function:* [`DataStoreNode_getDataType`][rtl::DataStoreNode_getDataType].
564    pub fn data_type_raw(&self) -> sys::type_t {
565        unsafe { rtl::DataStoreNode_getDataType(self.raw) }
566    }
567
568    /// *LibraryLink C Function:* [`DataStoreNode_getData`][rtl::DataStoreNode_getData].
569    pub unsafe fn data_raw(&self) -> &sys::MArgument {
570        match self.try_data_raw() {
571            Ok(value) => value,
572            Err(code) => panic!(
573                "DataStoreNode::data_raw: failed to get data (error code: {})",
574                code
575            ),
576        }
577    }
578
579    /// *LibraryLink C Function:* [`DataStoreNode_getData`][rtl::DataStoreNode_getData].
580    pub unsafe fn try_data_raw<'node>(
581        &'node self,
582    ) -> Result<&'node sys::MArgument, sys::errcode_t> {
583        self.data
584            .get_or_try_init(|| -> Result<sys::MArgument, sys::errcode_t> {
585                let mut arg: sys::MArgument = sys::MArgument {
586                    integer: std::ptr::null_mut(),
587                };
588
589                let err_code: sys::errcode_t =
590                    rtl::DataStoreNode_getData(self.raw, &mut arg);
591
592                if err_code != 0 {
593                    return Err(err_code);
594                }
595
596                Ok(arg)
597            })
598    }
599}
600
601//---------------
602// Nodes iterator
603//---------------
604
605impl<'store> Iterator for Nodes<'store> {
606    type Item = DataStoreNode<'store>;
607
608    fn next(&mut self) -> Option<Self::Item> {
609        let Nodes { node } = self;
610
611        let curr = node.take()?;
612
613        *node = curr.next_node();
614
615        Some(curr)
616    }
617}
618
619//======================================
620// Clone and Drop Impls
621//======================================
622
623impl Clone for DataStore {
624    fn clone(&self) -> DataStore {
625        let DataStore(ds) = *self;
626
627        let duplicate = unsafe { rtl::copyDataStore(ds) };
628
629        DataStore(duplicate)
630    }
631}
632
633impl Drop for DataStore {
634    fn drop(&mut self) {
635        let DataStore(ds) = *self;
636        let ds: sys::DataStore = ds;
637
638        unsafe { rtl::deleteDataStore(ds) }
639    }
640}
641
642//======================================
643// Formatting Impls
644//======================================
645
646impl<'store> fmt::Debug for DataStoreNode<'store> {
647    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
648        f.debug_struct("DataStoreNode")
649            .field("name", &self.name())
650            .field("value", &self.value())
651            // TODO: Add an enum to wrap the raw data type and use that here instead.
652            // .field("data_type_raw", &self.data_type_raw())
653            .finish()
654    }
655}
656
657impl<'node> fmt::Debug for DataStoreNodeValue<'node> {
658    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
659        use DataStoreNodeValue as V;
660
661        match self {
662            V::Boolean(val) => val.fmt(f),
663            V::Integer(val) => val.fmt(f),
664            V::Real(val) => val.fmt(f),
665            V::Complex(val) => val.fmt(f),
666            V::Str(val) => val.fmt(f),
667            V::NumericArray(val) => val.fmt(f),
668            V::Image(val) => val.fmt(f),
669            V::DataStore(val) => val.fmt(f),
670        }
671    }
672}