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}