arithmetic_eval/exec/
mod.rs

1//! [`ExecutableModule`] and related types.
2
3use core::fmt;
4
5use arithmetic_parser::{grammars::Grammar, Block};
6
7pub use self::module_id::{IndexedId, ModuleId, WildcardId};
8pub(crate) use self::{
9    command::{Atom, Command, CompiledExpr, FieldName, LocatedAtom},
10    registers::{Executable, ExecutableFn, Operations, Registers},
11};
12pub use crate::compiler::CompilerExt;
13use crate::{
14    alloc::Arc,
15    compiler::{Captures, Compiler},
16    env::Environment,
17    error::{Backtrace, Error, ErrorKind, ErrorWithBacktrace},
18    Value,
19};
20
21mod command;
22mod module_id;
23mod registers;
24
25/// Executable module together with its imports.
26///
27/// An `ExecutableModule` is a result of compiling a `Block` of statements. A module can *import*
28/// [`Value`]s, such as [commonly used functions](crate::fns). Importing is performed
29/// when building the module.
30///
31/// After the module is created, it can be associated with an environment via [`Self::with_env()`]
32/// and [`run`](WithEnvironment::run()).
33/// If the last statement of the block is an expression (that is, not terminated with a `;`),
34/// it is the result of the execution; otherwise, the result is [`Value::void()`].
35///
36/// In some cases (e.g., when building a REPL) it is useful to get not only the outcome
37/// of the module execution, but the intermediate results as well. Use [`Self::with_mutable_env()`]
38/// for such cases.
39///
40/// `ExecutableModule`s are generic with respect to the primitive value type, just like [`Value`].
41///
42/// # Examples
43///
44/// ## Basic usage
45///
46/// ```
47/// use arithmetic_parser::grammars::{F32Grammar, Parse, Untyped};
48/// use arithmetic_eval::{env, fns, Environment, ExecutableModule, Value};
49/// # use std::collections::HashSet;
50///
51/// # fn main() -> anyhow::Result<()> {
52/// let module = Untyped::<F32Grammar>::parse_statements(
53///     "xs.fold(-INFINITY, max)",
54/// )?;
55/// let module = ExecutableModule::new("test", &module)?;
56///
57/// let mut env = Environment::new();
58/// env.insert("INFINITY", Value::Prim(f32::INFINITY))
59///     .insert("xs", Value::void())
60///     .extend(env::Prelude::iter().chain(env::Comparisons::iter()));
61///
62/// // With the original imports, the returned value is `-INFINITY`.
63/// assert_eq!(module.with_env(&env)?.run()?, Value::Prim(f32::NEG_INFINITY));
64///
65/// // Imports can be changed. Let's check that `xs` is indeed an import.
66/// assert!(module.is_import("xs"));
67/// // It's possible to iterate over imports, too.
68/// let imports = module.import_names().collect::<HashSet<_>>();
69/// assert!(imports.is_superset(&HashSet::from_iter(vec!["max", "fold"])));
70/// # drop(imports); // necessary to please the borrow checker
71///
72/// // Change the `xs` import and run the module again.
73/// let array = [1.0, -3.0, 2.0, 0.5].iter().copied()
74///     .map(Value::Prim)
75///     .collect();
76/// env.insert("xs", Value::Tuple(array));
77/// assert_eq!(module.with_env(&env)?.run()?, Value::Prim(2.0));
78/// # Ok(())
79/// # }
80/// ```
81///
82/// ## Reusing a module
83///
84/// The same module can be run with multiple imports:
85///
86/// ```
87/// # use arithmetic_parser::grammars::{F32Grammar, Parse, Untyped};
88/// # use arithmetic_eval::{Environment, ExecutableModule, Value};
89/// # fn main() -> anyhow::Result<()> {
90/// let block = Untyped::<F32Grammar>::parse_statements("x + y")?;
91/// let module = ExecutableModule::new("test", &block)?;
92///
93/// let mut env = Environment::new();
94/// env.insert("x", Value::Prim(3.0)).insert("y", Value::Prim(5.0));
95/// assert_eq!(module.with_env(&env)?.run()?, Value::Prim(8.0));
96///
97/// env.insert("x", Value::Prim(-1.0));
98/// assert_eq!(module.with_env(&env)?.run()?, Value::Prim(4.0));
99/// # Ok(())
100/// # }
101/// ```
102///
103/// ## Behavior on errors
104///
105/// [`Self::with_mutable_env()`] modifies the environment even if an error occurs during execution:
106///
107/// ```
108/// # use arithmetic_parser::grammars::{F32Grammar, Parse, Untyped};
109/// # use arithmetic_eval::{env::Assertions, Environment, ExecutableModule, Value};
110/// # fn main() -> anyhow::Result<()> {
111/// let module = Untyped::<F32Grammar>::parse_statements("x = 5; assert_eq(x, 4);")?;
112/// let module = ExecutableModule::new("test", &module)?;
113///
114/// let mut env = Environment::new();
115/// env.extend(Assertions::iter());
116/// assert!(module.with_mutable_env(&mut env)?.run().is_err());
117/// assert_eq!(env["x"], Value::Prim(5.0));
118/// # Ok(())
119/// # }
120/// ```
121#[derive(Debug, Clone)]
122pub struct ExecutableModule<T> {
123    inner: Executable<T>,
124    captures: Captures,
125}
126
127#[cfg(test)]
128static_assertions::assert_impl_all!(ExecutableModule<f32>: Send, Sync);
129
130impl<T: Clone + fmt::Debug> ExecutableModule<T> {
131    /// Creates a new module.
132    pub fn new<G, Id>(id: Id, block: &Block<'_, G>) -> Result<Self, Error>
133    where
134        Id: ModuleId,
135        G: Grammar<Lit = T>,
136    {
137        Compiler::compile_module(id, block)
138    }
139}
140
141impl<T> ExecutableModule<T> {
142    pub(crate) fn from_parts(inner: Executable<T>, captures: Captures) -> Self {
143        Self { inner, captures }
144    }
145
146    /// Gets the identifier of this module.
147    pub fn id(&self) -> &Arc<dyn ModuleId> {
148        self.inner.id()
149    }
150
151    /// Returns a shared reference to imports of this module.
152    pub fn import_names(&self) -> impl Iterator<Item = &str> + '_ {
153        self.captures.iter().map(|(name, _)| name)
154    }
155
156    /// Checks if the specified variable is an import.
157    pub fn is_import(&self, name: &str) -> bool {
158        self.captures.contains(name)
159    }
160
161    /// Combines this module with the specified [`Environment`]. The environment must contain
162    /// all module imports; otherwise, an error will be raised.
163    ///
164    /// # Errors
165    ///
166    /// Returns an error if the environment does not contain all variables imported by this module.
167    pub fn with_env<'s>(
168        &'s self,
169        env: &'s Environment<T>,
170    ) -> Result<WithEnvironment<'s, T>, Error> {
171        self.check_imports(env)?;
172        Ok(WithEnvironment {
173            module: self,
174            env: Reference::Shared(env),
175        })
176    }
177
178    fn check_imports(&self, env: &Environment<T>) -> Result<(), Error> {
179        for (name, span) in self.captures.iter() {
180            if !env.contains(name) {
181                let err = ErrorKind::Undefined(name.into());
182                return Err(Error::new(self.inner.id().clone(), span, err));
183            }
184        }
185        Ok(())
186    }
187
188    /// Analogue of [`Self::with_env()`] that modifies the provided [`Environment`]
189    /// when the module is [run](WithEnvironment::run()).
190    ///
191    /// # Errors
192    ///
193    /// Returns an error if the environment does not contain all variables imported by this module.
194    pub fn with_mutable_env<'s>(
195        &'s self,
196        env: &'s mut Environment<T>,
197    ) -> Result<WithEnvironment<'s, T>, Error> {
198        self.check_imports(env)?;
199        Ok(WithEnvironment {
200            module: self,
201            env: Reference::Mutable(env),
202        })
203    }
204}
205
206impl<T: 'static + Clone> ExecutableModule<T> {
207    fn run_with_registers(
208        &self,
209        registers: &mut Registers<T>,
210        operations: Operations<'_, T>,
211    ) -> Result<Value<T>, ErrorWithBacktrace> {
212        let mut backtrace = Backtrace::default();
213        registers
214            .execute(&self.inner, operations, Some(&mut backtrace))
215            .map_err(|err| ErrorWithBacktrace::new(err, backtrace))
216    }
217}
218
219#[derive(Debug)]
220enum Reference<'a, T> {
221    Shared(&'a T),
222    Mutable(&'a mut T),
223}
224
225impl<T> AsRef<T> for Reference<'_, T> {
226    fn as_ref(&self) -> &T {
227        match self {
228            Self::Shared(shared) => shared,
229            Self::Mutable(mutable) => mutable,
230        }
231    }
232}
233
234/// Container for an [`ExecutableModule`] together with an [`Environment`].
235#[derive(Debug)]
236pub struct WithEnvironment<'env, T> {
237    module: &'env ExecutableModule<T>,
238    env: Reference<'env, Environment<T>>,
239}
240
241impl<T: 'static + Clone> WithEnvironment<'_, T> {
242    /// Runs the module in the previously provided [`Environment`].
243    ///
244    /// If a mutable reference was provided to the environment, the environment is modified
245    /// to reflect top-level assignments in the module (both new and reassigned variables).
246    /// If an error occurs, the assignments are performed up until the error (i.e., the environment
247    /// is **not** rolled back on error).
248    ///
249    /// # Errors
250    ///
251    /// Returns an error if module execution fails.
252    pub fn run(self) -> Result<Value<T>, ErrorWithBacktrace> {
253        let mut registers = Registers::from(&self.module.captures);
254        registers.update_from_env(self.env.as_ref());
255        let result = self
256            .module
257            .run_with_registers(&mut registers, self.env.as_ref().operations());
258
259        if let Reference::Mutable(env) = self.env {
260            registers.update_env(env);
261        }
262        result
263    }
264}