arithmetic_eval/
lib.rs

1//! Simple interpreter for ASTs produced by [`arithmetic-parser`].
2//!
3//! # How it works
4//!
5//! 1. A `Block` of statements is *compiled* into an [`ExecutableModule`]. Internally,
6//!   compilation processes the AST of the block and transforms it into a non-recusrive form.
7//!   An [`ExecutableModule`] may require *imports* (such as [`NativeFn`]s or constant [`Value`]s),
8//!   which can be taken from a [`VariableMap`] (e.g., an [`Environment`]).
9//! 2. [`ExecutableModule`] can then be executed, for the return value and/or for the
10//!   changes at the top-level variable scope. There are two major variables influencing
11//!   the execution outcome. An [arithmetic](crate::arith) is used to define arithmetic ops
12//!   (`+`, unary and binary `-`, `*`, `/`, `^`) and comparisons (`==`, `!=`, `>`, `<`, `>=`, `<=`).
13//!   Imports may be redefined at this stage as well.
14//!
15//! # Type system
16//!
17//! [`Value`]s have 5 major types:
18//!
19//! - **Primitive values** corresponding to literals in the parsed `Block`
20//! - **Boolean values**
21//! - **Functions,** which are further subdivided into native functions (defined in the Rust code)
22//!   and interpreted ones (defined within a module)
23//! - [**Tuples / arrays**](#tuples).
24//! - [**Objects**](#objects).
25//!
26//! Besides these types, there is an auxiliary one: [`OpaqueRef`], which represents a
27//! reference-counted native value, which can be returned from native functions or provided to
28//! them as an arg, but is otherwise opaque from the point of view of the interpreted code
29//! (cf. `anyref` in WASM).
30//!
31//! # Semantics
32//!
33//! - All variables are immutable. Re-declaring a var shadows the previous declaration.
34//! - Functions are first-class (in fact, a function is just a variant of the [`Value`] enum).
35//! - Functions can capture variables (including other functions). All captures are by value.
36//! - Arithmetic operations are defined on primitive values, tuples and objects. Ops on primitives are defined
37//!   via an [`Arithmetic`]. With tuples and objects, operations are performed per element / field.
38//!   Binary operations require tuples of the same size / objects of the same shape,
39//!   or a tuple / object and a primitive value.
40//!   As an example, `(1, 2) + 3` and `#{ x: 2, y: 3 } / #{ x: 4, y: 5 }` are valid,
41//!   but `(1, 2) * (3, 4, 5)` isn't.
42//! - Methods are considered syntactic sugar for functions, with the method receiver considered
43//!   the first function argument. For example, `(1, 2).map(sin)` is equivalent to
44//!   `map((1, 2), sin)`.
45//! - No type checks are performed before evaluation.
46//! - Type annotations and type casts are completely ignored.
47//!   This means that the interpreter may execute  code that is incorrect with annotations
48//!   (e.g., assignment of a tuple to a variable which is annotated to have a numeric type).
49//!
50//! ## Value comparisons
51//!
52//! Equality comparisons (`==`, `!=`) are defined on all types of values.
53//!
54//! - For bool values, the comparisons work as expected.
55//! - For functions, the equality is determined by the pointer (2 functions are equal
56//!   iff they alias each other).
57//! - `OpaqueRef`s either use the [`PartialEq`] impl of the underlying type or
58//!   the pointer equality, depending on how the reference was created; see [`OpaqueRef`] docs
59//!   for more details.
60//! - Equality for primitive values is determined by the [`Arithmetic`].
61//! - Tuples are equal if they contain the same number of elements and elements are pairwise
62//!   equal.
63//! - Different types of values are always non-equal.
64//!
65//! Order comparisons (`>`, `<`, `>=`, `<=`) are defined for primitive values only and use
66//! [`OrdArithmetic`].
67//!
68//! ## Tuples
69//!
70//! - Tuples are created using a [`Tuple` expression], e.g., `(x, 1, 5)`.
71//! - Indexing for tuples is performed via [`FieldAccess`] with a numeric field name: `xs.0`.
72//!   Thus, the index is always a "compile-time" constant. An error is raised if the index
73//!   is out of bounds or the receiver is not a tuple.
74//! - Tuples can be destructured using a [`Destructure`] LHS of an assignment, e.g.,
75//!   `(x, y, ...) = (1, 2, 3, 4)`. An error will be raised if the destructured value is
76//!   not a tuple, or has an incompatible length.
77//!
78//! ## Objects
79//!
80//! - Objects can be created using [object expressions], which are similar to ones in JavaScript.
81//!   For example, `#{ x: 1, y: (2, 3) }` will create an object with two fields:
82//!   `x` equal to 1 and `y` equal to `(2, 3)`. Similar to Rust / modern JavaScript, shortcut
83//!   field initialization is available: `#{ x, y }` will take vars `x` and `y` from the context.
84//! - Object fields can be accessed via [`FieldAccess`] with a field name that is a valid
85//!   variable name. No other values have such fields. An error will be raised if the object
86//!   does not have the specified field.
87//! - Objects can be destructured using an [`ObjectDestructure`] LHS of an assignment, e.g.,
88//!   `{ x, y } = obj`. An error will be raised if the destructured value is not an object
89//!   or does not have the specified fields. Destructuring is not exhaustive; i.e.,
90//!   the destructured object may have extra fields.
91//! - Functional fields are permitted. Similar to Rust, to call a function field, it must
92//!   be enclosed in parentheses: `(obj.run)(arg0, arg1)`.
93//!
94//! # Crate features
95//!
96//! - `std`. Enables support of types from `std`, such as the `Error` trait, and propagates
97//!   to dependencies. Importantly, `std` is necessary for floating-point arithmetics.
98//! - `complex`. Implements [`Number`] for floating-point complex numbers from
99//!   the [`num-complex`] crate (i.e., `Complex32` and `Complex64`). Enables complex number parsing
100//!   in `arithmetic-parser`.
101//! - `bigint`. Implements `Number` and a couple of other helpers for big integers
102//!   from the [`num-bigint`] crate (i.e., `BigInt` and `BigUint`). Enables big integer parsing
103//!   in `arithmetic-parser`.
104//!
105//! [`Arithmetic`]: crate::arith::Arithmetic
106//! [`OrdArithmetic`]: crate::arith::OrdArithmetic
107//! [`arithmetic-parser`]: https://crates.io/crates/arithmetic-parser
108//! [`num-complex`]: https://crates.io/crates/num-complex
109//! [`num-bigint`]: https://crates.io/crates/num-bigint
110//! [`Tuple` expression]: arithmetic_parser::Expr::Tuple
111//! [`Destructure`]: arithmetic_parser::Destructure
112//! [`ObjectDestructure`]: arithmetic_parser::ObjectDestructure
113//! [`FieldAccess`]: arithmetic_parser::Expr::FieldAccess
114//! [object expressions]: arithmetic_parser::Expr::Object
115//!
116//! # Examples
117//!
118//! ```
119//! use arithmetic_parser::grammars::{F32Grammar, Parse, Untyped};
120//! use arithmetic_eval::{
121//!     Assertions, Comparisons, Environment, Prelude, Value, VariableMap,
122//! };
123//!
124//! # fn main() -> anyhow::Result<()> {
125//! let program = r#"
126//!     // The interpreter supports all parser features, including
127//!     // function definitions, tuples and blocks.
128//!     order = |x, y| (min(x, y), max(x, y));
129//!     assert_eq(order(0.5, -1), (-1, 0.5));
130//!     (_, M) = order(3^2, { x = 3; x + 5 });
131//!     M
132//! "#;
133//! let program = Untyped::<F32Grammar>::parse_statements(program)?;
134//!
135//! let mut env = Environment::new();
136//! // Add some native functions to the environment.
137//! env.extend(Prelude.iter());
138//! env.extend(Assertions.iter());
139//! env.extend(Comparisons.iter());
140//!
141//! // To execute statements, we first compile them into a module.
142//! let module = env.compile_module("test", &program)?;
143//! // Then, the module can be run.
144//! assert_eq!(module.run()?, Value::Prim(9.0));
145//! # Ok(())
146//! # }
147//! ```
148//!
149//! Using objects:
150//!
151//! ```
152//! # use arithmetic_parser::grammars::{F32Grammar, Parse, Untyped};
153//! # use arithmetic_eval::{Assertions, Environment, Prelude, Value, VariableMap};
154//! # fn main() -> anyhow::Result<()> {
155//! let program = r#"
156//!     minmax = |xs| xs.fold(#{ min: INF, max: -INF }, |acc, x| #{
157//!          min: if(x < acc.min, x, acc.min),
158//!          max: if(x > acc.max, x, acc.max),
159//!     });
160//!     assert_eq((3, 7, 2, 4).minmax().min, 2);
161//!     assert_eq((5, -4, 6, 9, 1).minmax(), #{ min: -4, max: 9 });
162//! "#;
163//! let program = Untyped::<F32Grammar>::parse_statements(program)?;
164//!
165//! let mut env = Environment::new();
166//! env.extend(Prelude.iter());
167//! env.extend(Assertions.iter());
168//! let module = env.compile_module("minmax", &program)?;
169//! module.run()?;
170//! # Ok(())
171//! # }
172//! ```
173//!
174//! More complex examples are available in the `examples` directory of the crate.
175
176#![cfg_attr(not(feature = "std"), no_std)]
177#![cfg_attr(docsrs, feature(doc_cfg))]
178#![doc(html_root_url = "https://docs.rs/arithmetic-eval/0.3.0")]
179#![warn(missing_docs, missing_debug_implementations)]
180#![warn(clippy::all, clippy::pedantic)]
181#![allow(
182    clippy::missing_errors_doc,
183    clippy::must_use_candidate,
184    clippy::module_name_repetitions
185)]
186
187// Polyfill for `alloc` types.
188mod alloc {
189    #[cfg(not(feature = "std"))]
190    extern crate alloc;
191
192    #[cfg(not(feature = "std"))]
193    pub use alloc::{
194        borrow::ToOwned,
195        boxed::Box,
196        format,
197        rc::Rc,
198        string::{String, ToString},
199        vec,
200        vec::Vec,
201    };
202
203    #[cfg(feature = "std")]
204    pub use std::{
205        borrow::ToOwned,
206        boxed::Box,
207        format,
208        rc::Rc,
209        string::{String, ToString},
210        vec,
211        vec::Vec,
212    };
213}
214
215pub use self::{
216    compiler::CompilerExt,
217    error::{Error, ErrorKind, EvalResult},
218    executable::{
219        ExecutableModule, ExecutableModuleBuilder, IndexedId, ModuleId, ModuleImports, WildcardId,
220        WithArithmetic,
221    },
222    values::{
223        Assertions, CallContext, Comparisons, Environment, Function, InterpretedFn, NativeFn,
224        OpaqueRef, Prelude, SpannedValue, Value, ValueType, VariableMap,
225    },
226};
227
228pub mod arith;
229mod compiler;
230pub mod error;
231mod executable;
232pub mod fns;
233mod values;
234
235/// Marker trait for possible literals.
236///
237/// This trait is somewhat of a crutch, necessary to ensure that [function wrappers] can accept
238/// number arguments and distinguish them from other types (booleans, vectors, tuples, etc.).
239///
240/// [function wrappers]: crate::fns::FnWrapper
241pub trait Number: Clone + 'static {}
242
243impl Number for i8 {}
244impl Number for u8 {}
245impl Number for i16 {}
246impl Number for u16 {}
247impl Number for i32 {}
248impl Number for u32 {}
249impl Number for i64 {}
250impl Number for u64 {}
251impl Number for i128 {}
252impl Number for u128 {}
253
254impl Number for f32 {}
255impl Number for f64 {}
256
257#[cfg(feature = "num-complex")]
258impl Number for num_complex::Complex32 {}
259#[cfg(feature = "num-complex")]
260impl Number for num_complex::Complex64 {}
261
262#[cfg(feature = "num-bigint")]
263impl Number for num_bigint::BigInt {}
264#[cfg(feature = "num-bigint")]
265impl Number for num_bigint::BigUint {}