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