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 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//! - Similar to [ReScript], methods are considered syntactic sugar for functions,
43//!   with the method receiver considered the first function argument. For example, `(1, 2).map(sin)`
44//!   is equivalent to `map((1, 2), sin)`. To specify namespaced functions, you can specify
45//!   a method name in a `{}` block: `(1, 2).{Array.map}(sin)`. There's no magic here; the method name
46//!   expression is *just* another expression. Thus, something like `x.{curried(2)}(y, z)` is a valid
47//!   method call equivalent to `curried(2)(x, y, z)`.
48//! - No type checks are performed before evaluation.
49//! - Type annotations and type casts are completely ignored.
50//!   This means that the interpreter may execute  code that is incorrect with annotations
51//!   (e.g., assignment of a tuple to a variable which is annotated to have a numeric type).
52//!
53//! ## Value comparisons
54//!
55//! Equality comparisons (`==`, `!=`) are defined on all types of values.
56//!
57//! - For bool values, the comparisons work as expected.
58//! - For functions, the equality is determined by the pointer (2 functions are equal
59//!   iff they alias each other).
60//! - `OpaqueRef`s either use the [`PartialEq`] impl of the underlying type or
61//!   the pointer equality, depending on how the reference was created; see [`OpaqueRef`] docs
62//!   for more details.
63//! - Equality for primitive values is determined by the [`Arithmetic`].
64//! - Tuples are equal if they contain the same number of elements and elements are pairwise
65//!   equal.
66//! - Different types of values are always non-equal.
67//!
68//! Order comparisons (`>`, `<`, `>=`, `<=`) are defined for primitive values only and use
69//! [`OrdArithmetic`].
70//!
71//! ## Tuples
72//!
73//! - Tuples are created using a [`Tuple` expression], e.g., `(x, 1, 5)`.
74//! - Indexing for tuples is performed via [`FieldAccess`] with a numeric field name: `xs.0`.
75//!   Thus, the index is always a "compile-time" constant. An error is raised if the index
76//!   is out of bounds or the receiver is not a tuple.
77//! - Tuples can be destructured using a [`Destructure`] LHS of an assignment, e.g.,
78//!   `(x, y, ...) = (1, 2, 3, 4)`. An error will be raised if the destructured value is
79//!   not a tuple, or has an incompatible length.
80//!
81//! ## Objects
82//!
83//! - Objects can be created using [object expressions], which are similar to ones in JavaScript.
84//!   For example, `#{ x: 1, y: (2, 3) }` will create an object with two fields:
85//!   `x` equal to 1 and `y` equal to `(2, 3)`. Similar to Rust / modern JavaScript, shortcut
86//!   field initialization is available: `#{ x, y }` will take vars `x` and `y` from the context.
87//! - Object fields can be accessed via [`FieldAccess`] with a field name that is a valid
88//!   variable name. No other values have such fields. An error will be raised if the object
89//!   does not have the specified field.
90//! - Objects can be destructured using an [`ObjectDestructure`] LHS of an assignment, e.g.,
91//!   `{ x, y } = obj`. An error will be raised if the destructured value is not an object
92//!   or does not have the specified fields. Destructuring is not exhaustive; i.e.,
93//!   the destructured object may have extra fields.
94//! - Functional fields are permitted. Similar to Rust, to call a function field, it must
95//!   be enclosed in parentheses: `(obj.run)(arg0, arg1)` or braces: `{obj.run}(arg0, arg1)`.
96//!
97//! # Crate features
98//!
99//! ## `std`
100//!
101//! *(On by default)*
102//!
103//! Enables support of types from `std`, such as the `Error` trait, and propagates to dependencies.
104//! Importantly, `std` is necessary for floating-point arithmetics.
105//!
106//! ## `hashbrown`
107//!
108//! *(Off by default)*
109//!
110//! Imports hash maps and sets from the [eponymous crate][`hashbrown`] instead of using ones
111//! from the Rust std library. This feature is necessary if the `std` feature is disabled.
112//!
113//! ## `complex`
114//!
115//! *(Off by default)*
116//!
117//! Implements [`Number`] for floating-point complex numbers from the [`num-complex`] crate
118//! (i.e., `Complex32` and `Complex64`). Enables complex number parsing in `arithmetic-parser`.
119//!
120//! ## `bigint`
121//!
122//! *(Off by default)*
123//!
124//! Implements `Number` and a couple of other helpers for big integers from the [`num-bigint`] crate
125//! (i.e., `BigInt` and `BigUint`). Enables big integer parsing in `arithmetic-parser`.
126//!
127//! [`Arithmetic`]: arith::Arithmetic
128//! [`OrdArithmetic`]: arith::OrdArithmetic
129//! [`arithmetic-parser`]: https://crates.io/crates/arithmetic-parser
130//! [`num-complex`]: https://crates.io/crates/num-complex
131//! [`num-bigint`]: https://crates.io/crates/num-bigint
132//! [`Tuple` expression]: arithmetic_parser::Expr::Tuple
133//! [`Destructure`]: arithmetic_parser::Destructure
134//! [`ObjectDestructure`]: arithmetic_parser::ObjectDestructure
135//! [`FieldAccess`]: arithmetic_parser::Expr::FieldAccess
136//! [object expressions]: arithmetic_parser::Expr::Object
137//! [`hashbrown`]: https://crates.io/crates/hashbrown
138//! [ReScript]: https://rescript-lang.org/
139//!
140//! # Examples
141//!
142//! ```
143//! use arithmetic_parser::grammars::{F32Grammar, Parse, Untyped};
144//! use arithmetic_eval::{
145//!     env::{Assertions, Comparisons, Environment, Prelude}, ExecutableModule, Value,
146//! };
147//!
148//! # fn main() -> anyhow::Result<()> {
149//! let program = "
150//!     // The interpreter supports all parser features, including
151//!     // function definitions, tuples and blocks.
152//!     order = |x, y| (min(x, y), max(x, y));
153//!     assert_eq(order(0.5, -1), (-1, 0.5));
154//!     (_, M) = order(3^2, { x = 3; x + 5 });
155//!     M
156//! ";
157//! let program = Untyped::<F32Grammar>::parse_statements(program)?;
158//! // To execute statements, we first compile them into a module.
159//! let module = ExecutableModule::new("test", &program)?;
160//!
161//! // Then, we construct an environment to run the module.
162//! let mut env = Environment::new();
163//! // Add some native functions to the environment.
164//! env.extend(Prelude::iter());
165//! env.extend(Assertions::iter());
166//! env.extend(Comparisons::iter());
167//!
168//! // Then, the module can be run.
169//! assert_eq!(module.with_env(&env)?.run()?, Value::Prim(9.0));
170//! # Ok(())
171//! # }
172//! ```
173//!
174//! Using objects:
175//!
176//! ```
177//! # use arithmetic_parser::grammars::{F32Grammar, Parse, Untyped};
178//! # use arithmetic_eval::{env::{Assertions, Environment, Prelude}, ExecutableModule, Value};
179//! # fn main() -> anyhow::Result<()> {
180//! let program = "
181//!     minmax = |...xs| xs.fold(#{ min: INF, max: -INF }, |acc, x| #{
182//!          min: if(x < acc.min, x, acc.min),
183//!          max: if(x > acc.max, x, acc.max),
184//!     });
185//!     assert_eq(minmax(3, 7, 2, 4).min, 2);
186//!     assert_eq(minmax(5, -4, 6, 9, 1), #{ min: -4, max: 9 });
187//! ";
188//! let program = Untyped::<F32Grammar>::parse_statements(program)?;
189//! let module = ExecutableModule::new("minmax", &program)?;
190//!
191//! let mut env = Environment::new();
192//! env.extend(Prelude::iter().chain(Assertions::iter()));
193//! env.insert("INF", Value::Prim(f32::INFINITY));
194//! module.with_env(&env)?.run()?;
195//! # Ok(())
196//! # }
197//! ```
198//!
199//! More complex examples are available in the `examples` directory of the crate.
200
201#![cfg_attr(not(feature = "std"), no_std)]
202#![cfg_attr(docsrs, feature(doc_cfg))]
203#![doc(html_root_url = "https://docs.rs/arithmetic-eval/0.4.0-beta.1")]
204#![warn(missing_docs, missing_debug_implementations)]
205#![warn(clippy::all, clippy::pedantic)]
206#![allow(
207    clippy::missing_errors_doc,
208    clippy::must_use_candidate,
209    clippy::module_name_repetitions
210)]
211
212// Polyfill for `alloc` types.
213mod alloc {
214    #[cfg(not(feature = "std"))]
215    extern crate alloc as std;
216
217    pub(crate) use std::{
218        borrow::ToOwned,
219        boxed::Box,
220        format,
221        string::{String, ToString},
222        sync::Arc,
223        vec,
224        vec::Vec,
225    };
226
227    #[cfg(all(not(feature = "std"), not(feature = "hashbrown")))]
228    compile_error!(
229        "One of `std` or `hashbrown` features must be enabled in order \
230         to get a hash map implementation"
231    );
232
233    #[cfg(not(feature = "hashbrown"))]
234    pub(crate) use std::collections::{hash_map, HashMap, HashSet};
235
236    #[cfg(feature = "hashbrown")]
237    pub(crate) use hashbrown::{hash_map, HashMap, HashSet};
238}
239
240pub use self::{
241    env::Environment,
242    error::{Error, ErrorKind, EvalResult},
243    exec::ExecutableModule,
244    values::{
245        CallContext, Function, InterpretedFn, NativeFn, Object, OpaqueRef, SpannedValue, Tuple,
246        Value, ValueType,
247    },
248};
249
250pub mod arith;
251mod compiler;
252pub mod env;
253pub mod error;
254pub mod exec;
255pub mod fns;
256mod values;
257
258/// Marker trait for possible literals.
259///
260/// This trait is somewhat of a crutch, necessary to ensure that [function wrappers] can accept
261/// number arguments and distinguish them from other types (booleans, vectors, tuples, etc.).
262///
263/// [function wrappers]: fns::FnWrapper
264pub trait Number: Clone + 'static {}
265
266impl Number for i8 {}
267impl Number for u8 {}
268impl Number for i16 {}
269impl Number for u16 {}
270impl Number for i32 {}
271impl Number for u32 {}
272impl Number for i64 {}
273impl Number for u64 {}
274impl Number for i128 {}
275impl Number for u128 {}
276
277impl Number for f32 {}
278impl Number for f64 {}
279
280#[cfg(feature = "num-complex")]
281impl Number for num_complex::Complex32 {}
282#[cfg(feature = "num-complex")]
283impl Number for num_complex::Complex64 {}
284
285#[cfg(feature = "num-bigint")]
286impl Number for num_bigint::BigInt {}
287#[cfg(feature = "num-bigint")]
288impl Number for num_bigint::BigUint {}