Skip to main content

wolfram_expr/
lib.rs

1//! Efficient and ergonomic representation of Wolfram expressions in Rust.
2
3#![allow(clippy::let_and_return)]
4#![warn(missing_docs)]
5
6mod array_buf;
7mod association;
8mod bignum;
9mod byte_array;
10mod complex;
11mod conversion;
12mod macros;
13mod numeric_array;
14mod packed_array;
15mod ptr_cmp;
16mod wl;
17mod wxf;
18mod wxf_impls;
19
20pub mod symbol;
21
22#[cfg(test)]
23mod tests;
24
25mod test_readme {
26    //! Ensure that doc tests in the README.md file get run.
27    #![doc(hidden)]
28    #![doc = include_str!("../README.md")]
29}
30
31use std::fmt;
32use std::mem;
33use std::sync::Arc;
34
35#[doc(inline)]
36pub use self::symbol::Symbol;
37
38pub use self::array_buf::{ArrayBuf, ArrayElement, NumericArrayRead};
39pub use self::association::{Association, RuleEntry};
40pub use self::bignum::{BigInteger, BigReal};
41pub use self::byte_array::ByteArray;
42pub use self::complex::{Complex32, Complex64};
43pub use self::numeric_array::NumericArray;
44pub use self::packed_array::PackedArray;
45pub use self::wxf::{ExpressionEnum, HeaderEnum, NumericArrayEnum, PackedArrayEnum};
46
47#[cfg(feature = "unstable_parse")]
48pub use self::ptr_cmp::ExprRefCmp;
49
50/// Wolfram Language expression.
51///
52/// # Example
53///
54/// Construct the expression `{1, 2, 3}`:
55///
56/// ```
57/// use wolfram_expr::{Expr, Symbol};
58///
59/// let expr = Expr::normal(Symbol::new("System`List"), vec![
60///     Expr::from(1),
61///     Expr::from(2),
62///     Expr::from(3)
63/// ]);
64/// ```
65///
66/// # Reference counting
67///
68/// Internally, `Expr` is an atomically reference-counted [`ExprKind`]. This makes cloning
69/// an expression computationally inexpensive.
70#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
71pub struct Expr {
72    inner: Arc<ExprKind>,
73}
74
75// Assert that Expr has the same size and alignment as a usize / pointer.
76const _: () = assert!(mem::size_of::<Expr>() == mem::size_of::<usize>());
77const _: () = assert!(mem::size_of::<Expr>() == mem::size_of::<*const ()>());
78const _: () = assert!(mem::align_of::<Expr>() == mem::align_of::<usize>());
79const _: () = assert!(mem::align_of::<Expr>() == mem::align_of::<*const ()>());
80
81impl Expr {
82    /// Construct a new expression from an [`ExprKind`].
83    pub fn new(kind: ExprKind) -> Expr {
84        Expr {
85            inner: Arc::new(kind),
86        }
87    }
88
89    /// Consume `self` and return an owned [`ExprKind`].
90    ///
91    /// If the reference count of `self` is equal to 1 this function will *not* perform
92    /// a clone of the stored `ExprKind`, making this operation very cheap in that case.
93    // Silence the clippy warning about this method. While this method technically doesn't
94    // follow the Rust style convention of using `into` to prefix methods which take
95    // `self` by move, I think using `to` is more appropriate given the expected
96    // performance characteristics of this method. `into` implies that the method is
97    // always returning data already owned by this type, and as such should be a very
98    // cheap operation. This method can make no such guarantee; if the reference count is
99    // 1, then performance is very good, but if the reference count is >1, a deeper clone
100    // must be done.
101    #[allow(clippy::wrong_self_convention)]
102    pub fn to_kind(self) -> ExprKind {
103        match Arc::try_unwrap(self.inner) {
104            Ok(kind) => kind,
105            Err(self_) => (*self_).clone(),
106        }
107    }
108
109    /// Get the [`ExprKind`] representing this expression.
110    pub fn kind(&self) -> &ExprKind {
111        &self.inner
112    }
113
114    /// Get mutable access to the [`ExprKind`] that represents this expression.
115    ///
116    /// If the reference count of the underlying shared pointer is not equal to 1, this
117    /// will clone the [`ExprKind`] to make it unique.
118    pub fn kind_mut(&mut self) -> &mut ExprKind {
119        Arc::make_mut(&mut self.inner)
120    }
121
122    /// Retrieve the reference count of this expression.
123    pub fn ref_count(&self) -> usize {
124        Arc::strong_count(&self.inner)
125    }
126
127    /// Construct a new normal expression from the head and elements.
128    pub fn normal<H: Into<Expr>>(head: H, contents: Vec<Expr>) -> Expr {
129        let head = head.into();
130        // let contents = contents.into();
131        Expr {
132            inner: Arc::new(ExprKind::Normal(Normal { head, contents })),
133        }
134    }
135
136    // TODO: Should Expr's be cached? Especially Symbol exprs? Would certainly save
137    //       a lot of allocations.
138    /// Construct a new expression from a [`Symbol`].
139    pub fn symbol<S: Into<Symbol>>(s: S) -> Expr {
140        let s = s.into();
141        Expr {
142            inner: Arc::new(ExprKind::Symbol(s)),
143        }
144    }
145
146    /// Construct a new expression from a [`Number`].
147    ///
148    /// # Migration
149    ///
150    /// ```
151    /// # use wolfram_expr::{Expr, ExprKind, F64};
152    /// // Expr::number(Number::Integer(42))
153    /// let _int = Expr::from(42_i64);
154    ///
155    /// // Expr::number(Number::real(3.14))
156    /// let _real = Expr::from(3.14_f64);  // or Expr::real(3.14)
157    ///
158    /// // Expr::number(Number::Real(f))  — when you already have an F64
159    /// let f = F64::new(3.14).unwrap();
160    /// let _real = Expr::new(ExprKind::Real(f));
161    /// ```
162    #[deprecated(
163        since = "0.6.0-alpha.3",
164        note = "use `Expr::from(i64)` or `Expr::from(f64)` instead"
165    )]
166    #[allow(deprecated)]
167    pub fn number(num: Number) -> Expr {
168        match num {
169            Number::Integer(i) => Expr::from(i),
170            Number::Real(r) => Expr::new(ExprKind::Real(r)),
171        }
172    }
173
174    /// Construct a new expression from a [`String`].
175    pub fn string<S: Into<String>>(s: S) -> Expr {
176        Expr {
177            inner: Arc::new(ExprKind::String(s.into())),
178        }
179    }
180
181    /// Construct an expression from a floating-point number.
182    ///
183    /// ```
184    /// # use wolfram_expr::Expr;
185    /// let expr = Expr::real(3.14159);
186    /// ```
187    ///
188    /// # Panics
189    ///
190    /// This function will panic if `real` is NaN.
191    pub fn real(real: f64) -> Expr {
192        let r = ordered_float::NotNan::new(real)
193            .unwrap_or_else(|_| panic!("Expr::real: got NaN"));
194        Expr::new(ExprKind::Real(r))
195    }
196
197    /// Returns the outer-most symbol "tag" used in this expression.
198    ///
199    /// To illustrate:
200    ///
201    /// Expression   | Tag
202    /// -------------|----
203    /// `5`          | `None`
204    /// `"hello"`    | `None`
205    /// `foo`        | `foo`
206    /// `f[1, 2, 3]` | `f`
207    /// `g[x][y]`    | `g`
208    //
209    // TODO: _[x] probably should return None, even though technically
210    //       Blank[][x] has the tag Blank.
211    // TODO: The above TODO is probably wrong -- tag() shouldn't have any language
212    //       semantics built in to it.
213    pub fn tag(&self) -> Option<Symbol> {
214        match *self.inner {
215            ExprKind::Normal(ref normal) => normal.head.tag(),
216            ExprKind::Symbol(ref sym) => Some(sym.clone()),
217            // Atomic variants (no symbolic head): Integer, Real, String, ByteArray,
218            // Association, NumericArray, PackedArray, BigInteger, BigReal.
219            _ => None,
220        }
221    }
222
223    /// If this represents a [`Normal`] expression, return its head. Otherwise, return
224    /// `None`.
225    pub fn normal_head(&self) -> Option<Expr> {
226        match *self.inner {
227            ExprKind::Normal(ref normal) => Some(normal.head.clone()),
228            _ => None,
229        }
230    }
231
232    /// Attempt to get the element at `index` of a `Normal` expression.
233    ///
234    /// Return `None` if this is not a `Normal` expression, or the given index is out of
235    /// bounds.
236    ///
237    /// `index` is 0-based. The 0th index is the first element, not the head.
238    ///
239    /// This function does not panic.
240    pub fn normal_part(&self, index_0: usize) -> Option<&Expr> {
241        match self.kind() {
242            ExprKind::Normal(ref normal) => normal.contents.get(index_0),
243            _ => None,
244        }
245    }
246
247    /// Returns `true` if `self` is a `Normal` expr with the head `sym`.
248    pub fn has_normal_head(&self, sym: &Symbol) -> bool {
249        match *self.kind() {
250            ExprKind::Normal(ref normal) => normal.has_head(sym),
251            _ => false,
252        }
253    }
254
255    //==================================
256    // Common values
257    //==================================
258
259    /// [`Null`](https://reference.wolfram.com/language/ref/Null.html) <sub>WL</sub>.
260    pub fn null() -> Expr {
261        crate::expr!(System::Null)
262    }
263
264    //==================================
265    // Convenience creation functions
266    //==================================
267
268    /// Construct a new `Rule[_, _]` expression from the left-hand side and right-hand
269    /// side.
270    ///
271    /// # Example
272    ///
273    /// Construct the expression `FontSize -> 16`:
274    ///
275    /// ```
276    /// use wolfram_expr::{Expr, Symbol};
277    ///
278    /// let option = Expr::rule(Symbol::new("System`FontSize"), Expr::from(16));
279    /// ```
280    pub fn rule<LHS: Into<Expr>>(lhs: LHS, rhs: Expr) -> Expr {
281        let lhs = lhs.into();
282        crate::expr!(System::Rule[lhs, rhs])
283    }
284    /// Construct a new `RuleDelayed[_, _]` expression from the left-hand side and right-hand
285    /// side.
286    ///
287    /// # Example
288    ///
289    /// Construct the expression `x :> RandomReal[]`:
290    ///
291    /// ```
292    /// use wolfram_expr::{Expr, Symbol};
293    ///
294    /// let delayed = Expr::rule_delayed(
295    ///     Symbol::new("Global`x"),
296    ///     Expr::normal(Symbol::new("System`RandomReal"), vec![])
297    /// );
298    /// ```
299    pub fn rule_delayed<LHS: Into<Expr>>(lhs: LHS, rhs: Expr) -> Expr {
300        let lhs = lhs.into();
301        crate::expr!(System::RuleDelayed[lhs, rhs])
302    }
303
304    /// Construct a new `List[...]`(`{...}`) expression from it's elements.
305    ///
306    /// # Example
307    ///
308    /// Construct the expression `{1, 2, 3}`:
309    ///
310    /// ```
311    /// use wolfram_expr::Expr;
312    ///
313    /// let list = Expr::list(vec![Expr::from(1), Expr::from(2), Expr::from(3)]);
314    /// ```
315    pub fn list(elements: Vec<Expr>) -> Expr {
316        // `..elements` splices the Vec; the in-place `collect` reuses its
317        // allocation (no realloc), so this is as cheap as the direct form.
318        crate::expr!(System::List[..elements])
319    }
320}
321
322/// Wolfram Language expression variants.
323///
324/// Marked `#[non_exhaustive]` so that future variant additions (for new WXF wire types,
325/// etc.) are non-breaking. Downstream `match` expressions over `ExprKind` from outside
326/// this crate must include a `_ => …` arm.
327#[non_exhaustive]
328#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
329pub enum ExprKind<E = Expr> {
330    /// A machine (64-bit) integer.
331    Integer(i64),
332    /// A machine (64-bit) real, guaranteed non-NaN.
333    Real(F64),
334    /// A string.
335    String(String),
336    /// A symbol such as `` System`Plus ``.
337    Symbol(Symbol),
338    /// A normal expression `head[args…]` — see [`Normal`].
339    Normal(Normal<E>),
340    // WXF-derived variants:
341    /// A `ByteArray` — a flat buffer of bytes.
342    ByteArray(ByteArray),
343    /// An `Association` of key/value rules.
344    Association(Association),
345    /// A `NumericArray` — a packed array of fixed-width numbers.
346    NumericArray(NumericArray),
347    /// A `PackedArray` — a packed rectangular array of machine numbers.
348    PackedArray(PackedArray),
349    /// An arbitrary-precision integer.
350    BigInteger(BigInteger),
351    /// An arbitrary-precision real.
352    BigReal(BigReal),
353}
354
355/// Wolfram Language "normal" expression: `f[...]`.
356///
357/// A *normal* expression is any expression that consists of a head and zero or
358/// more arguments.
359#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
360pub struct Normal<E = Expr> {
361    /// The head of this normal expression.
362    head: E,
363
364    /// The elements of this normal expression.
365    ///
366    /// If `head` conceptually represents a function, these are the arguments that are
367    /// being applied to `head`.
368    contents: Vec<E>,
369}
370
371/// Subset of [`ExprKind`] that covers number-type expression values.
372///
373/// # Migration
374///
375/// Match on [`ExprKind`] directly — no intermediate `Number` needed:
376///
377/// ```
378/// # use wolfram_expr::{Expr, ExprKind, expr};
379/// let expr = expr!(42);
380///
381/// // Before:
382/// // if let Some(n) = expr.try_as_number() {
383/// //     match n { Number::Integer(i) => …, Number::Real(r) => … }
384/// // }
385///
386/// // After:
387/// match expr.kind() {
388///     ExprKind::Integer(i) => println!("integer: {i}"),
389///     ExprKind::Real(r)    => println!("real: {}", r.into_inner()),
390///     _ => {} // non-numeric expression
391/// }
392/// ```
393///
394/// To *construct* a number expression use [`Expr::from`] or [`Expr::real`] — see
395/// [`Expr::number`] for the full mapping.
396#[deprecated(
397    since = "0.6.0-alpha.3",
398    note = "match on `ExprKind::Integer` / `ExprKind::Real` directly"
399)]
400#[allow(missing_docs)]
401#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
402pub enum Number {
403    Integer(i64),
404    Real(F64),
405}
406
407/// 64-bit floating-point real number. Not NaN.
408pub type F64 = ordered_float::NotNan<f64>;
409/// 32-bit floating-point real number. Not NaN.
410pub type F32 = ordered_float::NotNan<f32>;
411
412//=======================================
413// Type Impl's
414//=======================================
415
416impl Normal {
417    /// Construct a new normal expression from the head and elements.
418    pub fn new<E: Into<Expr>>(head: E, contents: Vec<Expr>) -> Self {
419        Normal {
420            head: head.into(),
421            contents,
422        }
423    }
424
425    /// The head of this normal expression.
426    pub fn head(&self) -> &Expr {
427        &self.head
428    }
429
430    /// The elements of this normal expression.
431    ///
432    /// If `head` conceptually represents a function, these are the arguments that are
433    /// being applied to `head`.
434    pub fn elements(&self) -> &[Expr] {
435        &self.contents
436    }
437
438    /// The elements of this normal expression.
439    ///
440    /// Use [`Normal::elements()`] to get a reference to this value.
441    pub fn into_elements(self) -> Vec<Expr> {
442        self.contents
443    }
444
445    /// Returns `true` if the head of this expression is `sym`.
446    pub fn has_head(&self, sym: &Symbol) -> bool {
447        self.head == *sym
448    }
449}
450
451#[allow(deprecated)]
452impl Number {
453    /// Construct a `Number::Real` from an `f64`.
454    ///
455    /// # Migration
456    ///
457    /// ```
458    /// # use wolfram_expr::Expr;
459    /// // Before: Expr::number(Number::real(3.14))
460    /// let _real = Expr::from(3.14_f64);  // or Expr::real(3.14)
461    /// ```
462    ///
463    /// # Panics
464    ///
465    /// Panics if `r` is NaN.
466    #[deprecated(
467        since = "0.6.0-alpha.3",
468        note = "use `Expr::from(f64)` or `Expr::real(f64)` instead"
469    )]
470    pub fn real(r: f64) -> Self {
471        let ExprKind::Real(f) = Expr::real(r).to_kind() else {
472            unreachable!()
473        };
474        Number::Real(f)
475    }
476}
477
478//=======================================
479// Display & Debug impl/s
480//=======================================
481
482impl fmt::Debug for Expr {
483    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
484        let Expr { inner } = self;
485        write!(f, "{:?}", inner)
486    }
487}
488
489//======================================
490// Comparision trait impls
491//======================================
492
493impl PartialEq<Symbol> for Expr {
494    fn eq(&self, other: &Symbol) -> bool {
495        match self.kind() {
496            ExprKind::Symbol(self_sym) => self_sym == other,
497            _ => false,
498        }
499    }
500}