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