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