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}