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}