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