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}