Skip to main content

nickel_lang_core/eval/
mod.rs

1//! Evaluation of a Nickel term.
2//!
3//! Implementation of the Nickel abstract machine. Note that this machine is not currently
4//! formalized somewhere and is just a convenient name to designate the current implementation.
5//!
6//! # The Nickel Abstract Machine
7//!
8//! The abstract machine is a stack machine composed of the following elements:
9//!
10//! - The term being currently evaluated
11//! - The main stack, storing arguments, cache indices and pending computations (continuations)
12//! - A pair of [environment][Environment], mapping identifiers to [closures][Closure]:
13//!     - The initial environment contains builtin functions accessible from anywhere, is immutable
14//!       and alive during the whole evaluation
15//!     - The local environment contains the variables in scope of the current term and is subject
16//!       to garbage collection (currently reference counting based)
17//! - A [callstack], mainly for error reporting purpose
18//!
19//! Depending on the shape of the current term, the following actions are performed:
20//!
21//! ## Core calculus
22//!
23//! - **variable (`id`)**: the term bound to `id` in the environment is fetched, and an update index is
24//!   pushed on the stack to indicate that once this term has been evaluated, the cached value for
25//!   this variable must be updated
26//! - **application `func arg`**: a closure containing the argument and the current environment is pushed on
27//!   the stack and the VM proceed with the evaluation of `func`
28//! - **`let id = term in body`**: `term` is bound to `id` in the environment and the VM proceeds
29//!   with the evaluation of `body`
30//! - **`fun id => body`**: tries to pop an argument from the stack. If there is one, we bind it to
31//!   `id` in the environment and proceed with the body of the function. Otherwise, we are done:
32//!   the end result is a (unapplied) function
33//! - **Index on stack**: if the evaluation of the current term is done, and there is one (or
34//!   several) index on the stack, this means we have to perform an evaluation cache update.
35//!   Consecutive indices are popped from the stack and are updated to point to the current evaluated
36//!   term.
37//! - **Import**: import must have been resolved before the evaluation starts. An unresolved import
38//!   causes an [`crate::error::EvalErrorKind::InternalError`]. A resolved import, identified by a
39//!   `FileId`, is retrieved from the import resolver and evaluation proceeds.
40//!
41//! ## Operators
42//!
43//! Operators are strict by definition. To evaluate say `exp1 + exp2`, the following steps have to
44//! be performed:
45//!
46//! - `exp1` needs to be evaluated. The result must be saved somewhere, together with the resulting
47//!   environment
48//! - same thing for `exp2`
49//! - Finally, the implementation of `+` can proceed with the computation
50//!
51//! We detail the case of binary operators, as the case of unary ones is similar and simpler.
52//!
53//! - **Op(op, first, second)**: pushes an `OpFirst` element on the stack, which saves the operator
54//!   `op`, the second argument `second` and the current environment, and proceed with the evaluation
55//!   of `first`
56//! - **OpFirst on stack**: if the evaluation of the current term is done and there is an `OpFirst`
57//!   marker on the stack, then:
58//!     1. Extract the saved operator, the second argument and the environment `env2` from the
59//!        marker
60//!     2. Push an `OpSecond` marker, saving the operator and the evaluated form of the first
61//!        argument with its environment
62//!     3. Proceed with the evaluation of the second argument in environment `env2`
63//! - **OpSecond on stack**: once the second term is evaluated, we can get back the operator and the
64//!   first term evaluated, and forward all both arguments evaluated and their respective
65//!   environment to the specific implementation of the operator (located in [operation], or in
66//!   [merge] for `merge`).
67//!
68//! # Garbage collection
69//!
70//! Currently the machine relies on Rust's reference counting to manage memory. Precisely, the
71//! environment stores `Rc<RefCell<Closure>>` objects, which are reference-counted pointers to a
72//! mutable memory cell. This means that we do not deep copy everything everywhere, but this is
73//! probably suboptimal for a functional language and is unable to collect cyclic data, which may
74//! appear inside recursive records. A dedicated garbage collector is probably something to
75//! consider at some point.
76use crate::{
77    cache::{CacheHub as ImportCaches, ImportResolver},
78    closurize::{Closurize, closurize_rec_record},
79    environment::Environment as GenericEnvironment,
80    error::{
81        Error, EvalCtxt, EvalError, EvalErrorData, EvalErrorKind, PointedExportErrorData, Reporter,
82        warning::Warning,
83    },
84    files::{FileId, Files},
85    identifier::{Ident, LocIdent},
86    metrics::{increment, measure_runtime},
87    position::{PosIdx, PosTable},
88    program::FieldPath,
89    term::{
90        BinaryOp, BindingType, FunData, Import, RecordOpKind, RuntimeContract, StrChunk, Term,
91        UnaryOp, make as mk_term,
92        record::{Field, RecordData},
93        string::NickelString,
94    },
95    transform::gen_pending_contracts,
96    typ::UnboundTypeVariableError,
97};
98
99use std::{
100    io::Write,
101    mem::ManuallyDrop,
102    ops::{Deref, DerefMut},
103};
104
105pub mod cache;
106pub mod callstack;
107pub mod contract_eq;
108pub mod fixpoint;
109pub mod merge;
110pub mod operation;
111pub mod stack;
112pub mod value;
113
114use callstack::*;
115use stack::{
116    Op1ContItem, Op2FirstContItem, OpNContItem, PrimopAppInfo, SealedCont, Stack, StrAccItem,
117};
118use value::{Container, EnumVariantData, NickelValue, ValueContent, ValueContentRef};
119
120use self::cache::{Cache, CacheIndex};
121
122impl AsRef<Vec<StackElem>> for CallStack {
123    fn as_ref(&self) -> &Vec<StackElem> {
124        &self.0
125    }
126}
127
128/// The context of the Nickel virtual machine. The context stores external state that might need to
129/// outlive one VM instance. The virtual machine typically borrows the context mutably.
130pub struct VmContext<R: ImportResolver, C: Cache> {
131    /// The interface used to resolve and fetch imports.
132    pub import_resolver: R,
133    /// The stream for writing trace output.
134    pub trace: Box<dyn Write>,
135    /// A collector for warnings. Currently we only collect warnings and not errors; errors
136    /// terminate evaluation (or typechecking, or whatever) immediately, and so they just
137    /// get early-returned in a `Result`.
138    pub reporter: Box<dyn Reporter<(Warning, Files)>>,
139    /// The evaluation cache.
140    pub cache: C,
141    /// The position table, mapping position indices to spans.
142    pub pos_table: PosTable,
143}
144
145impl<R: ImportResolver, C: Cache> VmContext<R, C> {
146    /// Creates a new VM context from a resolver, a trace sink and a reporter. Creates a new empty
147    /// position table and eval cache.
148    pub fn new(
149        import_resolver: R,
150        trace: impl Write + 'static,
151        reporter: impl Reporter<(Warning, Files)> + 'static,
152    ) -> Self {
153        Self::new_with_pos_table(import_resolver, PosTable::new(), trace, reporter)
154    }
155
156    /// Creates a new VM context from a resolver, a position table, a trace sink and a reporter.
157    /// Creates a new empty eval cache.
158    pub fn new_with_pos_table(
159        import_resolver: R,
160        pos_table: PosTable,
161        trace: impl Write + 'static,
162        reporter: impl Reporter<(Warning, Files)> + 'static,
163    ) -> Self {
164        VmContext {
165            import_resolver,
166            trace: Box::new(trace),
167            reporter: Box::new(reporter),
168            cache: C::new(),
169            pos_table,
170        }
171    }
172}
173
174/// Many functions in [self] and submodules returns an error of type [EvalErrorKind]. However, this
175/// error is large, so we box it to avoid performance issues related to large error variant (and to
176/// satisfy clippy).
177pub type ErrorKind = Box<EvalErrorKind>;
178
179impl From<UnboundTypeVariableError> for ErrorKind {
180    fn from(err: UnboundTypeVariableError) -> Self {
181        Box::new(err.into())
182    }
183}
184
185impl From<PointedExportErrorData> for ErrorKind {
186    fn from(err: PointedExportErrorData) -> Self {
187        Box::new(err.into())
188    }
189}
190
191impl<C: Cache> VmContext<ImportCaches, C> {
192    /// Prepares the underlying program for evaluation (load the stdlib, typecheck, transform,
193    /// etc.).
194    pub fn prepare_eval(&mut self, main_id: FileId) -> Result<NickelValue, Error> {
195        self.prepare_eval_impl(main_id, true)
196    }
197
198    /// Same as [Self::prepare_eval], but skip typechecking.
199    pub fn prepare_eval_only(&mut self, main_id: FileId) -> Result<NickelValue, Error> {
200        self.prepare_eval_impl(main_id, false)
201    }
202
203    fn prepare_eval_impl(
204        &mut self,
205        main_id: FileId,
206        typecheck: bool,
207    ) -> Result<NickelValue, Error> {
208        measure_runtime!(
209            "runtime:prepare_stdlib",
210            self.import_resolver.prepare_stdlib(&mut self.pos_table)?
211        );
212
213        measure_runtime!(
214            "runtime:prepare_main",
215            if typecheck {
216                self.import_resolver.prepare(&mut self.pos_table, main_id)
217            } else {
218                self.import_resolver
219                    .prepare_eval_only(&mut self.pos_table, main_id)
220            }
221        )?;
222
223        // Unwrap: closurization only fails if the input wasn't parsed, and we just
224        // parsed it.
225        self.import_resolver
226            .closurize(&mut self.cache, main_id)
227            .unwrap();
228
229        Ok(self.import_resolver.get(main_id).unwrap())
230    }
231}
232
233/// The Nickel virtual machine.
234///
235/// # Drop
236///
237/// The virtual machine implements [Drop]. The stack is unwinded by default upon dropping, which
238/// amounts to calling [VirtualMachine::reset()], and avoids leaving thunks in the blackholed state
239/// on abort. If you don't need unwinding and don't want to pay for it (though it doesn't cost
240/// anything for successful executions), see [NoUnwindVirtualMachine].
241pub struct VirtualMachine<'ctxt, R: ImportResolver, C: Cache> {
242    context: &'ctxt mut VmContext<R, C>,
243    /// The main stack, storing arguments, cache indices and pending computations.
244    stack: Stack<C>,
245    /// The call stack, for error reporting.
246    call_stack: CallStack,
247    /// The initial environment containing stdlib and builtin functions accessible from anywhere
248    initial_env: Environment,
249}
250
251impl<'ctxt, R: ImportResolver, C: Cache> Drop for VirtualMachine<'ctxt, R, C> {
252    fn drop(&mut self) {
253        self.reset();
254    }
255}
256
257impl<'ctxt, R: ImportResolver, C: Cache> VirtualMachine<'ctxt, R, C> {
258    /// Creates a new VM with an empty initial environment. See [Self::new] for initialization of
259    /// the initial environment when `R` is instantiated to [crate::cache::CacheHub].
260    pub fn new_empty_env(context: &'ctxt mut VmContext<R, C>) -> Self {
261        VirtualMachine {
262            context,
263            call_stack: Default::default(),
264            stack: Stack::new(),
265            initial_env: Environment::new(),
266        }
267    }
268
269    pub fn warn(&mut self, warning: Warning) {
270        self.context
271            .reporter
272            .report((warning, self.context.import_resolver.files().clone()));
273    }
274
275    /// Reset the state of the machine (stacks, eval mode and state of cached elements) to prepare
276    /// for another evaluation round.
277    pub fn reset(&mut self) {
278        self.call_stack.0.clear();
279        self.stack.unwind(&mut self.context.cache);
280    }
281
282    pub fn import_resolver(&self) -> &R {
283        &self.context.import_resolver
284    }
285
286    pub fn import_resolver_mut(&mut self) -> &mut R {
287        &mut self.context.import_resolver
288    }
289
290    /// Evaluate a Nickel expression. Wrapper around [VirtualMachine::eval_closure] that starts
291    /// from an empty local environment and drops the final environment.
292    pub fn eval(&mut self, value: NickelValue) -> Result<NickelValue, EvalError> {
293        self.eval_closure(value.into()).map(|closure| closure.value)
294    }
295
296    /// Fully evaluate a Nickel term: the result is not a WHNF but to a value with all variables
297    /// substituted.
298    pub fn eval_full(&mut self, value: NickelValue) -> Result<NickelValue, EvalError> {
299        self.eval_full_closure(value.into())
300            .map(|result| result.value)
301    }
302
303    /// Same as [Self::eval_full], but takes a closure as an argument instead of a term.
304    pub fn eval_full_closure(&mut self, closure: Closure) -> Result<Closure, EvalError> {
305        self.eval_deep_closure_impl(closure, false)
306            .map(|result| Closure {
307                value: subst(
308                    &self.context.pos_table,
309                    &self.context.cache,
310                    result.value,
311                    &self.initial_env,
312                    &result.env,
313                ),
314                env: result.env,
315            })
316    }
317
318    /// Like [Self::eval_full], but skips evaluating record fields marked `not_exported`.
319    pub fn eval_full_for_export(&mut self, value: NickelValue) -> Result<NickelValue, EvalError> {
320        self.eval_deep_closure_impl(value.into(), true)
321            .map(|result| {
322                subst(
323                    &self.context.pos_table,
324                    &self.context.cache,
325                    result.value,
326                    &self.initial_env,
327                    &result.env,
328                )
329            })
330    }
331
332    /// Same as [Self::eval_full_for_export], but takes a closure as an argument instead of a term.
333    pub fn eval_full_for_export_closure(
334        &mut self,
335        closure: Closure,
336    ) -> Result<NickelValue, EvalError> {
337        self.eval_deep_closure_impl(closure, true).map(|result| {
338            subst(
339                &self.context.pos_table,
340                &self.context.cache,
341                result.value,
342                &self.initial_env,
343                &result.env,
344            )
345        })
346    }
347
348    /// Fully evaluates a Nickel term like `eval_full`, but does not substitute all variables.
349    pub fn eval_deep(&mut self, value: NickelValue) -> Result<NickelValue, EvalError> {
350        self.eval_deep_closure_impl(value.into(), false)
351            .map(|result| result.value)
352    }
353
354    /// Same as [Self::eval_deep], but take a closure as an argument instead of a term.
355    pub fn eval_deep_closure(&mut self, closure: Closure) -> Result<NickelValue, EvalError> {
356        self.eval_deep_closure_impl(closure, false)
357            .map(|result| result.value)
358    }
359
360    /// Use a specific initial environment for evaluation. Usually, [VmContext::prepare_eval] is
361    /// populating the initial environment. But in some cases, such as testing or benchmarks, we
362    /// might want to use a different one.
363    ///
364    /// Return the new virtual machine with the updated initial environment.
365    pub fn with_initial_env(mut self, env: Environment) -> Self {
366        self.initial_env = env;
367        self
368    }
369
370    fn eval_deep_closure_impl(
371        &mut self,
372        mut closure: Closure,
373        for_export: bool,
374    ) -> Result<Closure, EvalError> {
375        closure.value = mk_term::op1(
376            UnaryOp::Force {
377                ignore_not_exported: for_export,
378            },
379            closure.value,
380        );
381
382        self.eval_closure(closure)
383    }
384
385    /// Take a term and a field path, and evaluate until the corresponding field can be extracted.
386    /// Return the resulting field in its final environment.
387    ///
388    /// Note that this method doesn't evaluate the content of the field itself. Calling it with an
389    /// empty path simply returns the original expression unevaluated in an empty environment. If
390    /// you need to evaluate the value later, don't forget to apply pending contracts stored in the
391    /// field.
392    ///
393    /// For example, extracting `foo.bar.baz` on a term `exp` will evaluate `exp` to a record and
394    /// try to extract the field `foo`. If anything goes wrong (the result isn't a record or the
395    /// field `bar` doesn't exist), a proper error is raised. Otherwise,
396    /// [Self::extract_field_closure] applies the same recipe recursively and evaluate the content
397    /// of the `foo` field extracted from `exp` to a record, tries to extract `bar`, and so on.
398    pub fn extract_field_closure(
399        &mut self,
400        closure: Closure,
401        path: &FieldPath,
402    ) -> Result<(Field, Environment), EvalError> {
403        self.extract_field_impl(closure, path, false)
404    }
405
406    /// Same as [Self::extract_field_closure], but also requires that the field value is defined
407    /// and returns the value directly.
408    ///
409    /// This method also applies potential pending contracts to the value.
410    ///
411    /// In theory, extracting the value could be done manually after calling to
412    /// [Self::extract_field_closure], instead of needing a separate method.
413    ///
414    /// However, once [Self::extract_field_closure] returns, most contextual information required
415    /// to raise a proper error if the field is missing (e.g. positions) has been lost. So, if the
416    /// returned field's value is `None`, we would have a hard time reporting a good error message.
417    /// On the other hand, [Self::extract_field_value_closure] raises the error earlier, when the
418    /// context is still available.
419    pub fn extract_field_value_closure(
420        &mut self,
421        closure: Closure,
422        path: &FieldPath,
423    ) -> Result<Closure, EvalError> {
424        let (field, env) = self.extract_field_impl(closure, path, true)?;
425
426        // unwrap(): by definition, extract_field_impl(_, _, true) ensure that
427        // `field.value.is_some()`
428        let value = field.value.unwrap();
429        let pos_idx = value.pos_idx();
430
431        let value_with_ctr =
432            RuntimeContract::apply_all(value, field.pending_contracts.iter().cloned(), pos_idx);
433
434        let Closure { value, env } = self.eval_closure(Closure {
435            value: value_with_ctr,
436            env,
437        })?;
438
439        Ok(Closure { value, env })
440    }
441
442    fn extract_field_impl(
443        &mut self,
444        closure: Closure,
445        path: &FieldPath,
446        require_defined: bool,
447    ) -> Result<(Field, Environment), EvalError> {
448        let mut prev_pos_idx = closure.value.pos_idx();
449
450        let mut field: Field = closure.value.into();
451        let mut path = path.0.iter().peekable();
452        let mut env = closure.env;
453
454        let Some(mut prev_id) = path.peek().cloned() else {
455            return Ok((field, env));
456        };
457
458        for id in path {
459            let Some(current_value) = field.value else {
460                return self.throw_with_ctxt(EvalErrorKind::MissingFieldDef {
461                    id: *prev_id,
462                    metadata: field.metadata.into_inner(),
463                    pos_record: prev_pos_idx,
464                    pos_access: PosIdx::NONE,
465                });
466            };
467
468            // We evaluate the fields' value, either to handle the next ident of the
469            // path, or to show the value if we are treating the last ident of the path
470
471            prev_pos_idx = current_value.pos_idx();
472
473            let curr_value_with_ctr =
474                RuntimeContract::apply_all(current_value, field.pending_contracts, prev_pos_idx);
475
476            let current_evaled = self.eval_closure(Closure {
477                value: curr_value_with_ctr,
478                env,
479            })?;
480
481            env = current_evaled.env;
482
483            match current_evaled.value.content_ref() {
484                ValueContentRef::Record(container) => {
485                    let Some(next_field) = container.get(*id).cloned() else {
486                        let pos_op = self.context.pos_table.push(id.pos);
487
488                        return self.throw_with_ctxt(EvalErrorKind::FieldMissing {
489                            id: *id,
490                            field_names: container.field_names(RecordOpKind::IgnoreEmptyOpt),
491                            operator: "extract_field".to_owned(),
492                            pos_record: prev_pos_idx,
493                            // TODO: we need to push back the position in the table, which isn't
494                            // too bad, but is a bit useless. Maybe we should have a runtime
495                            // version of identifiers with `PosIdx` instead of `TermPos`?
496                            pos_op,
497                        });
498                    };
499
500                    field = next_field;
501                }
502                _ => {
503                    //unwrap(): if we enter this pattern branch, `field.value` must be `Some(_)`
504                    return self.throw_with_ctxt(EvalErrorKind::QueryNonRecord {
505                        pos: prev_pos_idx,
506                        id: *id,
507                        value: current_evaled.value,
508                    });
509                }
510            }
511
512            prev_id = id;
513        }
514
515        if field.value.is_none() && require_defined {
516            return self.throw_with_ctxt(EvalErrorKind::MissingFieldDef {
517                id: *prev_id,
518                metadata: field.metadata.into_inner(),
519                pos_record: prev_pos_idx,
520                pos_access: PosIdx::NONE,
521            });
522        }
523
524        Ok((field, env))
525    }
526
527    pub fn query(&mut self, v: NickelValue, path: &FieldPath) -> Result<Field, EvalError> {
528        self.query_closure(v.into(), path)
529    }
530
531    /// Generates an error context from this virtual machine. This consumes the callstack, which
532    /// doesn't have to be preserved after an error. However, we have to preserve the position
533    /// table, since the VM could be reset and used for another evaluation round. The latter is
534    /// copied.
535    fn eval_ctxt(&mut self) -> EvalCtxt {
536        EvalCtxt {
537            call_stack: std::mem::take(&mut self.call_stack),
538            pos_table: self.context.pos_table.clone(),
539        }
540    }
541
542    /// Wraps [Self::err_with_ctxt] in the `Err` variant.
543    fn throw_with_ctxt<T>(&mut self, error: EvalErrorKind) -> Result<T, EvalError> {
544        Err(self.err_with_ctxt(error))
545    }
546
547    /// Wraps an evaluation error [crate::error::EvalError] with the current evaluation context
548    /// ([Self::eval_ctxt]) to make a [crate::error::EvalError].
549    fn err_with_ctxt(&mut self, error: EvalErrorKind) -> EvalError {
550        Box::new(EvalErrorData {
551            error,
552            ctxt: self.eval_ctxt(),
553        })
554    }
555
556    /// Same as [VirtualMachine::query], but starts from a closure instead of a term in an empty
557    /// environment.
558    pub fn query_closure(
559        &mut self,
560        closure: Closure,
561        path: &FieldPath,
562    ) -> Result<Field, EvalError> {
563        // extract_field does almost what we want, but for querying, we evaluate the content of the
564        // field as well in order to print a potential default value.
565        let (mut field, env) = self.extract_field_closure(closure, path)?;
566
567        if let Some(value) = field.value.take() {
568            let pos_idx = value.pos_idx();
569
570            let value_with_ctr =
571                RuntimeContract::apply_all(value, field.pending_contracts.iter().cloned(), pos_idx);
572
573            field.value = Some(
574                self.eval_closure(Closure {
575                    value: value_with_ctr,
576                    env,
577                })?
578                .value,
579            );
580        }
581
582        Ok(field)
583    }
584
585    fn enter_cache_index(
586        &mut self,
587        var: Option<LocIdent>,
588        mut idx: CacheIndex,
589        pos_idx: PosIdx,
590        env: Environment,
591    ) -> Result<Closure, ErrorKind> {
592        // idx may be a 1-counted RC, so we make sure we drop any reference to it from `env`, which
593        // is going to be discarded anyway
594        std::mem::drop(env);
595
596        match self.context.cache.get_update_index(&mut idx) {
597            Ok(Some(idx_upd)) => self.stack.push_update_index(idx_upd),
598            Ok(None) => {}
599            Err(_blackholed_error) => {
600                return Err(Box::new(EvalErrorKind::InfiniteRecursion(
601                    self.call_stack.clone(),
602                    pos_idx,
603                )));
604            }
605        }
606
607        if let Some(var) = var {
608            self.call_stack.enter_var(var, pos_idx);
609        }
610
611        // If we are fetching a recursive field from the environment that doesn't have
612        // a definition, we complete the error with the additional information of where
613        // it was accessed:
614        let closure = self.context.cache.get(idx);
615
616        let value = match closure.value.content_ref() {
617            ValueContentRef::Term(Term::RuntimeError(data))
618                if matches!(&**data, EvalErrorKind::MissingFieldDef { .. }) =>
619            {
620                let EvalErrorKind::MissingFieldDef {
621                    id,
622                    metadata,
623                    pos_record,
624                    pos_access: PosIdx::NONE,
625                } = (**data).clone()
626                else {
627                    unreachable!();
628                };
629
630                NickelValue::term(
631                    Term::RuntimeError(Box::new(EvalErrorKind::MissingFieldDef {
632                        id,
633                        metadata,
634                        pos_record,
635                        pos_access: pos_idx,
636                    })),
637                    pos_idx,
638                )
639            }
640            _ => closure.value,
641        };
642
643        Ok(Closure { value, ..closure })
644    }
645
646    /// Fetches a closure from the local or the initial environment, or fails with
647    /// [crate::error::EvalError::UnboundIdentifier] with either the variable position, or the
648    /// provided fallback position if the former isn't defined.
649    fn get_var(
650        &self,
651        id: LocIdent,
652        env: &Environment,
653        pos_idx: PosIdx,
654    ) -> Result<CacheIndex, ErrorKind> {
655        get_var(&self.context.pos_table, id, &self.initial_env, env, pos_idx)
656    }
657
658    /// The main loop of evaluation.
659    ///
660    /// Implement the evaluation loop of the core language. The specific implementations of
661    /// primitive operations is delegated to the modules [operation] and [merge].
662    ///
663    /// # Arguments
664    ///
665    /// - `clos`: the closure to evaluate
666    /// - `initial_env`: the initial environment containing the stdlib items.
667    ///   Accessible from anywhere in the program.
668    ///
669    /// # Return
670    ///
671    /// Either:
672    ///  - an evaluation error
673    ///  - the evaluated term with its final environment
674    pub fn eval_closure(&mut self, closure: Closure) -> Result<Closure, EvalError> {
675        self.eval_closure_impl(closure)
676            .map_err(|err| self.err_with_ctxt(*err))
677    }
678
679    /// Actual implementation of [Self::eval_closure]. We use this indirection mostly to use the
680    /// `?` operator on [crate::error::EvalError], and only add the missing context to make it
681    /// an [crate:error::EvalError] once at the end.
682    fn eval_closure_impl(&mut self, mut closure: Closure) -> Result<Closure, ErrorKind> {
683        #[cfg(feature = "metrics")]
684        let start_time = std::time::Instant::now();
685
686        // See https://github.com/rust-lang/rust-clippy/issues/15987.
687        #[allow(clippy::let_and_return)]
688        let result = loop {
689            let Closure { value, mut env } = closure;
690            let pos_idx = value.pos_idx();
691            let has_cont_on_stack = self.stack.is_top_idx() || self.stack.is_top_cont();
692
693            closure = match value.content_ref() {
694                ValueContentRef::Thunk(_) => {
695                    // unwrap(): we know that `value` is a thunk in this branch
696                    self.enter_cache_index(None, value.try_into_thunk().unwrap(), pos_idx, env)?
697                }
698                ValueContentRef::Term(Term::Sealed(data)) => {
699                    let closure = Closure {
700                        value: NickelValue::term(Term::Sealed(data.clone()), pos_idx),
701                        env: env.clone(),
702                    };
703
704                    // Update at the original index (the index which holds the result of the op) in
705                    // both cases, even if we continue with a seq.
706                    //
707                    // We do this because we are on a `Sealed` term which is in weak head normal
708                    // form, and if we don't, we will be unwrapping a `Sealed` term and assigning
709                    // the "unsealed" value to the result of the `Seq` operation. See also:
710                    // https://github.com/tweag/nickel/issues/123
711                    update_at_indices(&mut self.context.cache, &mut self.stack, &closure);
712
713                    // We have to peek the stack to see what operation is coming next and decide
714                    // what to do.
715                    //
716                    // - If it's `unseal`, then we proceed with its evaluation, as `unseal` legitly
717                    //   operates on sealed terms.
718                    // - `seq` is the only primitive operation allowed to see through a sealed
719                    //   term. Indeed, `seq`-ing doesn't violate parametricity, `seq`-ing shouldn't
720                    //   be observable (that is, adding seq shouldn't change the semantics and
721                    //   suddenly make a program blame), and it's useful in practice to seq sealed
722                    //   terms such as in the implementation of `std.fold_left` to ensure we don't
723                    //   accumulate thunks in memory.
724                    // - If it's anything else, we raise an error right away because the
725                    //   corresponding polymorphic contract has been violated: a function tried to
726                    //   use a polymorphic sealed value.
727                    match self.stack.peek_sealed_cont() {
728                        SealedCont::Unseal => self.continue_op(closure)?,
729                        SealedCont::Seq => {
730                            // evaluate / `Seq` the inner value.
731                            Closure {
732                                value: data.inner.clone(),
733                                env,
734                            }
735                        }
736                        SealedCont::Other => {
737                            // This operation should not be allowed to evaluate a sealed term
738                            break Err(Box::new(EvalErrorKind::BlameError {
739                                evaluated_arg: data.label.get_evaluated_arg(&self.context.cache),
740                                label: (*data.label).clone(),
741                            }));
742                        }
743                    }
744                }
745                ValueContentRef::Term(Term::Var(id)) => {
746                    let idx = self.get_var(*id, &env, pos_idx)?;
747                    self.enter_cache_index(Some(*id), idx, pos_idx, env)?
748                }
749                ValueContentRef::Term(Term::App(data)) => {
750                    self.call_stack.enter_app(&self.context.pos_table, pos_idx);
751
752                    self.stack.push_arg(
753                        Closure {
754                            value: data.arg.clone(),
755                            env: env.clone(),
756                        },
757                        pos_idx,
758                    );
759                    Closure {
760                        value: data.head.clone(),
761                        env,
762                    }
763                }
764                ValueContentRef::Term(Term::Let(data)) => {
765                    let mut indices = Vec::new();
766                    let init_env = env.clone();
767
768                    for (x, bound) in &data.bindings {
769                        let bound_closure = Closure {
770                            value: bound.clone(),
771                            env: init_env.clone(),
772                        };
773
774                        let idx = self
775                            .context
776                            .cache
777                            .add(bound_closure, data.attrs.binding_type.clone());
778
779                        // Patch the environment with the (x <- closure) binding
780                        if data.attrs.rec {
781                            indices.push(idx.clone());
782                        }
783
784                        env.insert(x.ident(), idx);
785                    }
786
787                    for idx in indices {
788                        self.context.cache.patch(idx, |cl| cl.env = env.clone());
789                    }
790
791                    Closure {
792                        value: data.body.clone(),
793                        env,
794                    }
795                }
796                ValueContentRef::Term(Term::Op1(data)) => {
797                    self.stack.push_op1_cont(Op1ContItem {
798                        op: data.op.clone(),
799                        orig_pos_arg: data.arg.pos_idx(),
800                        app_info: PrimopAppInfo {
801                            call_stack_size: self.call_stack.len(),
802                            pos_idx,
803                        },
804                    });
805
806                    Closure {
807                        value: data.arg.clone(),
808                        env,
809                    }
810                }
811                ValueContentRef::Term(Term::Op2(data)) => {
812                    self.stack.push_op2_first_cont(Op2FirstContItem {
813                        op: data.op.clone(),
814                        app_info: PrimopAppInfo {
815                            call_stack_size: self.call_stack.len(),
816                            pos_idx,
817                        },
818                        arg2: Closure {
819                            value: data.arg2.clone(),
820                            env: env.clone(),
821                        },
822                        orig_pos_arg1: data.arg1.pos_idx(),
823                    });
824
825                    Closure {
826                        value: data.arg1.clone(),
827                        env,
828                    }
829                }
830                ValueContentRef::Term(Term::OpN(data)) => {
831                    // Arguments are passed as a stack to the operation continuation, so we reverse
832                    // the original list.
833                    let mut args_iter = data.args.iter();
834                    let fst_arg = args_iter.next().ok_or_else(|| {
835                        EvalErrorKind::NotEnoughArgs(data.op.arity(), data.op.to_string(), pos_idx)
836                    })?;
837
838                    let pending: Vec<Closure> = args_iter
839                        .rev()
840                        .map(|value| Closure {
841                            value: value.clone(),
842                            env: env.clone(),
843                        })
844                        .collect();
845
846                    self.stack.push_opn_cont(OpNContItem {
847                        op: data.op.clone(),
848                        app_info: PrimopAppInfo {
849                            call_stack_size: self.call_stack.len(),
850                            pos_idx,
851                        },
852                        evaluated: Vec::with_capacity(pending.len() + 1),
853                        pending,
854                        current_pos_idx: fst_arg.pos_idx(),
855                    });
856
857                    Closure {
858                        value: fst_arg.clone(),
859                        env,
860                    }
861                }
862                ValueContentRef::Term(Term::StrChunks(chunks)) => {
863                    let mut chunks_iter = chunks.iter().cloned();
864                    match chunks_iter.next_back() {
865                        None => NickelValue::string(NickelString::new(), pos_idx).into(),
866                        Some(chunk) => {
867                            let (arg, indent) = match chunk {
868                                StrChunk::Literal(s) => (NickelValue::string_posless(s), 0),
869                                StrChunk::Expr(e, indent) => (e, indent),
870                            };
871
872                            self.stack.push_str_chunks(chunks_iter);
873                            self.stack.push_str_acc(StrAccItem {
874                                acc: String::new(),
875                                env: env.clone(),
876                                // unwrap(): we don't expect an indentation of `u32::MAX` lines...
877                                curr_indent: indent.try_into().unwrap(),
878                                curr_pos: arg.pos_idx(),
879                            });
880
881                            // TODO: we should set up the stack properly, instead of allocating an
882                            // `op1` term here.
883                            Closure {
884                                value: NickelValue::term(
885                                    Term::op1(UnaryOp::ChunksConcat, arg),
886                                    pos_idx,
887                                ),
888                                env,
889                            }
890                        }
891                    }
892                }
893                ValueContentRef::Term(Term::Closurize(value)) => {
894                    match value.content_ref() {
895                        ValueContentRef::Array(Container::Empty)
896                        | ValueContentRef::Record(Container::Empty) => value.clone(),
897                        ValueContentRef::Array(Container::Alloc(array_data)) => {
898                            // This *should* make it unnecessary to call closurize in [operation].
899                            // See the comment on the `BinaryOp::ArrayConcat` match arm.
900                            let array = array_data
901                                .array
902                                .iter()
903                                .cloned()
904                                .map(|t| t.closurize(&mut self.context.cache, env.clone()))
905                                .collect();
906
907                            let pending_contracts = array_data
908                                .pending_contracts
909                                .iter()
910                                .cloned()
911                                .map(|ctr| {
912                                    RuntimeContract::new(
913                                        ctr.contract
914                                            .closurize(&mut self.context.cache, env.clone()),
915                                        ctr.label,
916                                    )
917                                })
918                                .collect();
919
920                            NickelValue::array(array, pending_contracts, pos_idx)
921                        }
922                        ValueContentRef::Record(Container::Alloc(data)) => NickelValue::record(
923                            // unwrap(): we treated the empty record case already
924                            data.clone().closurize(&mut self.context.cache, env),
925                            pos_idx,
926                        ),
927                        ValueContentRef::EnumVariant(data) => {
928                            let EnumVariantData { tag, arg } = data.clone();
929                            let arg = arg.map(|arg| arg.closurize(&mut self.context.cache, env));
930                            NickelValue::enum_variant(tag, arg, pos_idx)
931                        }
932                        _ => {
933                            // This case is a red flag (it should be unreachable), but
934                            // isn't a blocker per se, so we only fail in debug mode.
935                            debug_assert!(false, "trying to closurize a non-container value");
936                            value.clone()
937                        }
938                    }
939                    // We can use an empty environment for a freshly closurized value
940                    .into()
941                }
942                ValueContentRef::Term(Term::RecRecord(data)) => {
943                    // We start by closurizing the fields, which might not be if the record is
944                    // coming out of the parser.
945
946                    // We must avoid re-closurizing a recursive record that is already closurized
947                    // (coming from `merge`, for example), as the current representation is broken
948                    // if we add a new indirection. This should ideally be encoded in the Rust
949                    // type, once we have a different representation for runtime evaluation,
950                    // instead of relying on invariants. But for now, we have to live with it.
951                    let (mut static_part, dyn_fields) = if !data.closurized {
952                        let includes_as_terms: Result<Vec<_>, _> = data
953                            .includes
954                            .iter()
955                            .map(|incl| -> Result<_, ErrorKind> {
956                                let field = Field {
957                                    value: Some(
958                                        NickelValue::from(self.get_var(
959                                            incl.ident,
960                                            &env,
961                                            PosIdx::NONE,
962                                        )?)
963                                        .with_pos(&mut self.context.pos_table, incl.ident.pos),
964                                    ),
965                                    metadata: incl.metadata.clone().into(),
966                                    pending_contracts: Vec::new(),
967                                };
968
969                                Ok((
970                                    incl.ident,
971                                    gen_pending_contracts::with_pending_contracts(
972                                        &mut self.context.pos_table,
973                                        field,
974                                    )?,
975                                ))
976                            })
977                            .collect();
978
979                        // We assume that the parser doesn't allow conflicts between field
980                        // definitions and includes (the same field is defined in both). This
981                        // restriction might be lifted in the future (we would probably merge the
982                        // included field and the other definition pieces), but for now it's
983                        // simpler this way.
984                        let mut record = data.record.clone();
985                        record.fields.extend(includes_as_terms?);
986
987                        closurize_rec_record(
988                            &mut self.context.cache,
989                            record,
990                            data.dyn_fields.clone(),
991                            data.deps.clone(),
992                            env,
993                        )
994                    } else {
995                        // In a record that has been already closurized, we expect include
996                        // expressions to be evaluated away.
997                        debug_assert!(data.includes.is_empty());
998                        (data.record.clone(), data.dyn_fields.clone())
999                    };
1000
1001                    let rec_env = fixpoint::rec_env(
1002                        &mut self.context.cache,
1003                        static_part.fields.iter(),
1004                        pos_idx,
1005                    );
1006
1007                    for rt in static_part.fields.values_mut() {
1008                        fixpoint::patch_field(&mut self.context.cache, rt, &rec_env);
1009                    }
1010
1011                    // Transform the static part `{stat1 = val1, ..., statn = valn}` and the
1012                    // dynamic part `{exp1 = dyn_val1, ..., expm = dyn_valm}` to a sequence of
1013                    // extensions
1014                    //
1015                    // ```
1016                    // %record/insert% exp1
1017                    //   (...
1018                    //     (%record/insert% expn {stat1 = val1, ..., statn = valn} dyn_valn)
1019                    //   ...)
1020                    //   dyn_val1
1021                    //
1022                    // ```
1023                    //
1024                    // The `dyn_val` are given access to the recursive environment, but the
1025                    // recursive environment only contains the static fields, and not the dynamic
1026                    // fields.
1027                    let extended = dyn_fields.into_iter().fold(
1028                        NickelValue::record(static_part, pos_idx),
1029                        |acc, (name_as_term, mut field)| {
1030                            let pos_dyn_field = field
1031                                .value
1032                                .as_ref()
1033                                .map(NickelValue::pos_idx)
1034                                .unwrap_or_else(|| name_as_term.pos_idx());
1035
1036                            fixpoint::patch_field(&mut self.context.cache, &mut field, &rec_env);
1037
1038                            let ext_kind = field.extension_kind();
1039                            let Field {
1040                                metadata,
1041                                value,
1042                                pending_contracts,
1043                            } = field;
1044
1045                            let extend = mk_term::op2(
1046                                BinaryOp::RecordInsert {
1047                                    metadata,
1048                                    pending_contracts,
1049                                    ext_kind,
1050                                    op_kind: RecordOpKind::ConsiderAllFields,
1051                                },
1052                                name_as_term,
1053                                acc,
1054                            );
1055
1056                            match value {
1057                                Some(value) => NickelValue::term(
1058                                    Term::app(extend, value),
1059                                    pos_dyn_field.to_inherited(),
1060                                ),
1061                                None => extend,
1062                            }
1063                        },
1064                    );
1065
1066                    extended.with_pos_idx(pos_idx).into()
1067                }
1068                ValueContentRef::Term(Term::ResolvedImport(id)) => {
1069                    increment!(format!("import:{id:?}"));
1070
1071                    if let Some(val) = self.context.import_resolver.get(*id) {
1072                        val.into()
1073                    } else {
1074                        break Err(Box::new(EvalErrorKind::InternalError(
1075                            format!("Resolved import not found ({id:?})"),
1076                            pos_idx,
1077                        )));
1078                    }
1079                }
1080                ValueContentRef::Term(Term::Import(Import::Path { path, .. })) => {
1081                    break Err(Box::new(EvalErrorKind::InternalError(
1082                        format!("Unresolved import ({})", path.to_string_lossy()),
1083                        pos_idx,
1084                    )));
1085                }
1086                ValueContentRef::Term(Term::Import(Import::Package { id })) => {
1087                    return Err(Box::new(EvalErrorKind::InternalError(
1088                        format!("Unresolved package import ({id})"),
1089                        pos_idx,
1090                    )));
1091                }
1092                ValueContentRef::Term(Term::ParseError(parse_error)) => {
1093                    break Err(Box::new(EvalErrorKind::ParseError((**parse_error).clone())));
1094                }
1095                ValueContentRef::Term(Term::RuntimeError(error)) => {
1096                    break Err(error.clone());
1097                }
1098                // For now, we simply erase annotations at runtime. They aren't accessible anyway
1099                // (as opposed to field metadata) and don't change the operational semantics, as
1100                // long as we generate the corresponding contract application when consuming it.
1101                //
1102                // The situation could change if we want to implement optimizations such as
1103                // avoiding repeated contract application. Annotations could then be a good way of
1104                // remembering which contracts have been applied to a value.
1105                ValueContentRef::Term(Term::Annotated(data)) => {
1106                    increment!("contract:free-standing(annotated)");
1107
1108                    // We apply the contract coming from the static type annotation separately as
1109                    // it is optimized.
1110                    let static_contract = data.annot.static_contract(&mut self.context.pos_table);
1111                    let contracts = data.annot.pending_contracts(&mut self.context.pos_table)?;
1112                    let pos_inner = data.inner.pos_idx();
1113                    let inner = data.inner.clone();
1114
1115                    let inner_with_static = if let Some(static_ctr) = static_contract {
1116                        static_ctr?.apply(inner, pos_inner)
1117                    } else {
1118                        inner
1119                    };
1120
1121                    let inner_with_ctr =
1122                        RuntimeContract::apply_all(inner_with_static, contracts, pos_inner);
1123
1124                    Closure {
1125                        value: inner_with_ctr,
1126                        env,
1127                    }
1128                }
1129                // Function call if there's no continuation on the stack (otherwise, the function
1130                // is just an argument to a primop or to put in the eval cache)
1131                ValueContentRef::Term(Term::Fun(FunData { arg, body })) if !has_cont_on_stack => {
1132                    if let Some((idx, pos_app)) = self.stack.pop_arg_as_idx(&mut self.context.cache)
1133                    {
1134                        self.call_stack.enter_fun(&self.context.pos_table, pos_app);
1135                        env.insert(arg.ident(), idx);
1136                        Closure {
1137                            value: body.clone(),
1138                            env,
1139                        }
1140                    } else {
1141                        break Ok(Closure { value, env });
1142                    }
1143                }
1144                // At this point, we've evaluated the current term to a weak head normal form.
1145                _ => {
1146                    let evaluated = Closure { value, env };
1147
1148                    // If there is a cache index update frame on the stack, we proceed with the
1149                    // update of the corresponding cached value.
1150                    if self.stack.is_top_idx() {
1151                        update_at_indices(&mut self.context.cache, &mut self.stack, &evaluated);
1152                        evaluated
1153                    }
1154                    // If there is a primitive operator continuation on the stack, we proceed with
1155                    // the continuation.
1156                    else if self.stack.is_top_cont() {
1157                        self.continue_op(evaluated)?
1158                    }
1159                    // Otherwise, if the stack is non-empty, this is an ill-formed application (we
1160                    // are supposed to evaluate an application, but the left hand side isn't a
1161                    // function)
1162                    else if let Some((arg, pos_app)) = self.stack.pop_arg(&self.context.cache) {
1163                        break Err(Box::new(EvalErrorKind::NotAFunc(
1164                            evaluated.value,
1165                            arg.value,
1166                            pos_app,
1167                        )));
1168                    }
1169                    // Finally, if the stack is empty, it's all good: it just means we are done
1170                    // evaluating.
1171                    else {
1172                        break Ok(evaluated);
1173                    }
1174                }
1175            };
1176        };
1177
1178        increment!("runtime:eval", start_time.elapsed().as_millis() as u64);
1179
1180        result
1181    }
1182
1183    /// Evaluate a term, but attempt to continue on errors.
1184    ///
1185    /// This differs from `VirtualMachine::eval_full` in three ways:
1186    /// - We try to accumulate errors instead of bailing out. When recursing into record
1187    ///   fields and array elements, we keep evaluating subsequent elements even if one
1188    ///   fails.
1189    /// - We only return the accumulated errors; we don't return the eval'ed term.
1190    /// - We support a recursion limit, to limit the number of times we recurse into
1191    ///   arrays or records.
1192    pub fn eval_permissive(
1193        &mut self,
1194        value: NickelValue,
1195        recursion_limit: usize,
1196    ) -> Vec<EvalError> {
1197        fn inner<R: ImportResolver, C: Cache>(
1198            this: &mut VirtualMachine<R, C>,
1199            acc: &mut Vec<EvalError>,
1200            value: NickelValue,
1201            recursion_limit: usize,
1202        ) {
1203            if recursion_limit == 0 {
1204                return;
1205            }
1206
1207            let pos_idx = value.pos_idx();
1208            match this.eval(value) {
1209                Err(e) => {
1210                    acc.push(e);
1211                    this.reset();
1212                }
1213                Ok(val) => match val.content_ref() {
1214                    ValueContentRef::Array(Container::Alloc(data)) => {
1215                        for elt in data.array.iter() {
1216                            // After eval_closure, all the array elements  are
1217                            // closurized already, so we don't need to do any tracking
1218                            // of the env.
1219                            let value_with_ctr = RuntimeContract::apply_all(
1220                                elt.clone(),
1221                                data.pending_contracts.iter().cloned(),
1222                                elt.pos_idx(),
1223                            );
1224                            inner(this, acc, value_with_ctr, recursion_limit.saturating_sub(1));
1225                        }
1226                    }
1227                    ValueContentRef::Record(Container::Alloc(data)) => {
1228                        for (id, field) in &data.fields {
1229                            if let Some(v) = &field.value {
1230                                let value_with_ctr = RuntimeContract::apply_all(
1231                                    v.clone(),
1232                                    field.pending_contracts.iter().cloned(),
1233                                    v.pos_idx(),
1234                                );
1235                                inner(this, acc, value_with_ctr, recursion_limit.saturating_sub(1));
1236                            } else {
1237                                acc.push(this.err_with_ctxt(EvalErrorKind::MissingFieldDef {
1238                                    id: *id,
1239                                    metadata: field.metadata.clone_inner(),
1240                                    pos_record: pos_idx,
1241                                    pos_access: PosIdx::NONE,
1242                                }));
1243                            }
1244                        }
1245                    }
1246                    ValueContentRef::EnumVariant(EnumVariantData { arg: Some(arg), .. }) => {
1247                        inner(this, acc, arg.clone(), recursion_limit.saturating_sub(1));
1248                    }
1249                    _ => {}
1250                },
1251            }
1252        }
1253
1254        let mut ret = Vec::new();
1255        inner(self, &mut ret, value, recursion_limit);
1256        ret
1257    }
1258}
1259
1260impl<'ctxt, C: Cache> VirtualMachine<'ctxt, ImportCaches, C> {
1261    /// Creates a new VM, and automatically fills the initial environment with
1262    /// `context.import_resolver.mk_initial_env()`.
1263    pub fn new(context: &'ctxt mut VmContext<ImportCaches, C>) -> Self {
1264        let initial_env = context.import_resolver.mk_eval_env(&mut context.cache);
1265
1266        VirtualMachine {
1267            context,
1268            call_stack: Default::default(),
1269            stack: Stack::new(),
1270            initial_env,
1271        }
1272    }
1273}
1274
1275/// An wrapper around [VirtualMachine] which doesn't unwind the VM stack upon destruction.
1276///
1277/// Unwinding ensures all thunks are properly cleaned from their previous state if the VM abort,
1278/// which makes it possible to run subsequent evaluations (whether in the same or another VM
1279/// instance). Since VM are light instances that are built on the spot, the default behavior is to
1280/// unwind upon dropping, to avoid bad surprises (and unwinding is virtually free if the evaluation
1281/// succeeds, since the stack is then empty).
1282///
1283/// However, it could happen that in some workflows, one wishes to avoid the cost of unwinding. In
1284/// that case, use this wrapper instead of [VirtualMachine].
1285pub struct NoUnwindVirtualMachine<'ctxt, R: ImportResolver, C: Cache>(
1286    ManuallyDrop<VirtualMachine<'ctxt, R, C>>,
1287);
1288
1289impl<'ctxt, R: ImportResolver, C: Cache> NoUnwindVirtualMachine<'ctxt, R, C> {
1290    pub fn new(vm: VirtualMachine<'ctxt, R, C>) -> Self {
1291        Self(ManuallyDrop::new(vm))
1292    }
1293}
1294
1295impl<'ctxt, R: ImportResolver, C: Cache> Deref for NoUnwindVirtualMachine<'ctxt, R, C> {
1296    type Target = VirtualMachine<'ctxt, R, C>;
1297
1298    fn deref(&self) -> &Self::Target {
1299        self.0.deref()
1300    }
1301}
1302
1303impl<'ctxt, R: ImportResolver, C: Cache> DerefMut for NoUnwindVirtualMachine<'ctxt, R, C> {
1304    fn deref_mut(&mut self) -> &mut Self::Target {
1305        self.0.deref_mut()
1306    }
1307}
1308
1309/// A closure, which is a value packed with its environment.
1310#[derive(PartialEq, Clone)]
1311pub struct Closure {
1312    pub value: NickelValue,
1313    pub env: Environment,
1314}
1315
1316impl std::fmt::Debug for Closure {
1317    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1318        f.debug_struct("Closure")
1319            .field("body", &self.value)
1320            .finish_non_exhaustive()
1321    }
1322}
1323
1324impl From<NickelValue> for Closure {
1325    fn from(value: NickelValue) -> Self {
1326        Closure {
1327            value,
1328            env: Environment::new(),
1329        }
1330    }
1331}
1332
1333#[allow(type_alias_bounds)] // TODO: Look into this warning.
1334pub type Environment = GenericEnvironment<Ident, CacheIndex>;
1335
1336/// Raised when trying to build an environment from a term which is not a record.
1337#[derive(Clone, Debug)]
1338pub enum EnvBuildError {
1339    NotARecord(NickelValue),
1340}
1341
1342/// Add the bindings of a record to an environment. Ignore the fields defined by interpolation as
1343/// well, fields without definition and `include` expressions.
1344pub fn env_add_record<C: Cache>(
1345    cache: &mut C,
1346    env: &mut Environment,
1347    closure: Closure,
1348) -> Result<(), EnvBuildError> {
1349    if closure.value.is_inline_empty_record() {
1350        return Ok(());
1351    }
1352
1353    // We unwrap potential `Closurize` sprinkled in unevaluated terms, which is typically the case
1354    // for the stdlib.
1355    let value = if let Some(Term::Closurize(inner)) = closure.value.as_term() {
1356        inner
1357    } else {
1358        &closure.value
1359    };
1360
1361    let record = match value.content_ref() {
1362        ValueContentRef::Record(Container::Empty) => return Ok(()),
1363        ValueContentRef::Record(Container::Alloc(record)) => record,
1364        ValueContentRef::Term(Term::RecRecord(data)) => &data.record,
1365        _ => return Err(EnvBuildError::NotARecord(closure.value)),
1366    };
1367
1368    let ext = record.fields.iter().filter_map(|(id, field)| {
1369        field.value.as_ref().map(|value| {
1370            (
1371                id.ident(),
1372                cache.add(
1373                    Closure {
1374                        value: value.clone(),
1375                        env: closure.env.clone(),
1376                    },
1377                    BindingType::Normal,
1378                ),
1379            )
1380        })
1381    });
1382
1383    env.extend(ext);
1384    Ok(())
1385}
1386
1387/// Bind a closure in an environment.
1388pub fn env_add<C: Cache>(
1389    cache: &mut C,
1390    env: &mut Environment,
1391    id: LocIdent,
1392    value: NickelValue,
1393    local_env: Environment,
1394) {
1395    let closure = Closure {
1396        value,
1397        env: local_env,
1398    };
1399    env.insert(id.ident(), cache.add(closure, BindingType::Normal));
1400}
1401
1402/// Pop and update all the indices on the top of the stack with the given closure.
1403fn update_at_indices<C: Cache>(cache: &mut C, stack: &mut Stack<C>, closure: &Closure) {
1404    while let Some(idx) = stack.pop_update_index() {
1405        cache.update(closure.clone(), idx);
1406    }
1407}
1408
1409/// Free-standing implementation of [VirtualMachine::get_var].
1410fn get_var(
1411    pos_table: &PosTable,
1412    id: LocIdent,
1413    initial_env: &Environment,
1414    env: &Environment,
1415    pos_idx: PosIdx,
1416) -> Result<CacheIndex, ErrorKind> {
1417    env.get(&id.ident())
1418        .or_else(|| initial_env.get(&id.ident()))
1419        .cloned()
1420        .ok_or(Box::new(EvalErrorKind::UnboundIdentifier(
1421            id,
1422            id.pos.or(pos_table.get(pos_idx)),
1423        )))
1424}
1425
1426/// Recursively substitute each variable occurrence of a term for its value in the environment.
1427pub fn subst<C: Cache>(
1428    pos_table: &PosTable,
1429    cache: &C,
1430    value: NickelValue,
1431    initial_env: &Environment,
1432    env: &Environment,
1433) -> NickelValue {
1434    use value::lens::TermContent;
1435
1436    let pos_idx = value.pos_idx();
1437
1438    match value.content() {
1439        lens @ (ValueContent::Null(_)
1440        | ValueContent::Bool(_)
1441        | ValueContent::Number(_)
1442        | ValueContent::String(_)
1443        | ValueContent::CustomContract(_)
1444        | ValueContent::Label(_)
1445        // We could recurse in types, because types can contain terms which would then be subject
1446        // to substitution. Not recursing should be fine, though, because a type in term position
1447        // turns into a contract, and we don't substitute inside contracts either currently.
1448        | ValueContent::Type(_)
1449        | ValueContent::ForeignId(_)
1450        | ValueContent::SealingKey(_)) => lens.restore(),
1451        ValueContent::Thunk(lens) => {
1452            let closure = cache.get(lens.take());
1453            subst(pos_table, cache, closure.value, initial_env, &closure.env)
1454        }
1455        ValueContent::Record(lens) => {
1456            let Container::Alloc(record) = lens.take() else {
1457                return NickelValue::empty_record().with_pos_idx(pos_idx);
1458            };
1459
1460            let record = record.map_defined_values(|_, value| subst(pos_table, cache, value, initial_env, env));
1461
1462            //unwrap(): we didn't change the size of the record, so whether it was inline or not,
1463            //its position index should be of the according type
1464            NickelValue::record(record, pos_idx)
1465        }
1466        ValueContent::Array(lens) => {
1467            let Container::Alloc(array_data) = lens.take() else {
1468                return NickelValue::empty_array().with_pos_idx(pos_idx);
1469            };
1470
1471            let array = array_data.array.into_iter()
1472                .map(|t| subst(pos_table, cache, t, initial_env, env))
1473                .collect();
1474
1475            NickelValue::array(array, array_data.pending_contracts, pos_idx)
1476        }
1477        ValueContent::EnumVariant(lens) => {
1478            let EnumVariantData { tag, arg } = lens.take();
1479            let arg = arg.map(|arg| subst(pos_table, cache, arg, initial_env, env));
1480
1481            NickelValue::enum_variant(tag, arg, pos_idx)
1482        }
1483        ValueContent::Term(content) => {
1484            match content {
1485                TermContent::Var(lens) => {
1486                    let id = lens.take();
1487
1488                    get_var(pos_table, id, env, initial_env, PosIdx::NONE)
1489                    .map(|idx| {
1490                        let closure = cache.get(idx);
1491                        subst(pos_table, cache, closure.value, initial_env, &closure.env)
1492                    })
1493                    .unwrap_or_else(|_| NickelValue::term(Term::Var(id), pos_idx))
1494                }
1495                lens @ (TermContent::ParseError(_) | TermContent::RuntimeError(_)
1496                // Do not substitute under lambdas: mutually recursive function could cause an infinite
1497                // loop. Although avoidable, this requires some care and is not currently needed.
1498                | TermContent::Fun(..)
1499                | TermContent::Import(_)
1500                | TermContent::ResolvedImport(_)) => lens.restore(),
1501                TermContent::Let(lens) => {
1502                    let mut data = lens.take();
1503
1504                    for (_key, val) in &mut data.bindings {
1505                        let prev = std::mem::take(val);
1506                        *val = subst(pos_table, cache, prev, initial_env, env);
1507                    }
1508                    data.body = subst(pos_table, cache, data.body, initial_env, env);
1509
1510                    NickelValue::term(Term::Let(data), pos_idx)
1511                }
1512                TermContent::App(lens) => {
1513                    let mut data = lens.take();
1514                    data.head = subst(pos_table, cache, data.head, initial_env, env);
1515                    data.arg = subst(pos_table, cache, data.arg, initial_env, env);
1516
1517                    NickelValue::term(Term::App(data), pos_idx)
1518                }
1519                TermContent::Op1(lens) => {
1520                    let mut data = lens.take();
1521                    data.arg = subst(pos_table, cache, data.arg, initial_env, env);
1522
1523                    NickelValue::term(Term::Op1(data), pos_idx)
1524                }
1525                TermContent::Op2(lens) => {
1526                    let mut data = lens.take();
1527                    data.arg1 = subst(pos_table, cache, data.arg1, initial_env, env);
1528                    data.arg2 = subst(pos_table, cache, data.arg2, initial_env, env);
1529
1530                    NickelValue::term(Term::Op2(data), pos_idx)
1531                }
1532                TermContent::OpN(lens) => {
1533                    let mut data = lens.take();
1534                    data.args = data.args
1535                        .into_iter()
1536                        .map(|t| subst(pos_table, cache, t, initial_env, env))
1537                        .collect();
1538
1539                    NickelValue::term(Term::OpN(data), pos_idx)
1540                }
1541                TermContent::Sealed(lens) => {
1542                    let mut data = lens.take();
1543                    data.inner = subst(pos_table, cache, data.inner, initial_env, env);
1544                    NickelValue::term(Term::Sealed(data), pos_idx)
1545                }
1546                // Currently, we downright ignore `include` expressions. However, one could argue that
1547                // substituting `foo` for `bar` in `{include foo}` should result in `{foo = bar}`.
1548                TermContent::RecRecord(lens) => {
1549                    let mut data = lens.take();
1550
1551                    data.record = data.record
1552                        .map_defined_values(|_, value| subst(pos_table, cache, value, initial_env, env));
1553
1554                    data.dyn_fields = data.dyn_fields
1555                        .into_iter()
1556                        .map(|(id_t, field)| {
1557                            (
1558                                subst(pos_table, cache, id_t, initial_env, env),
1559                                field.map_value(|v| subst(pos_table, cache, v, initial_env, env)),
1560                            )
1561                        })
1562                        .collect();
1563
1564                    NickelValue::term(Term::RecRecord(data), pos_idx)
1565                }
1566                TermContent::StrChunks(lens) => {
1567                    let chunks = lens.take()
1568                        .into_iter()
1569                        .map(|chunk| match chunk {
1570                            chunk @ StrChunk::Literal(_) => chunk,
1571                            StrChunk::Expr(t, indent) => StrChunk::Expr(
1572                                subst(pos_table, cache, t, initial_env, env),
1573                                indent,
1574                            ),
1575                        })
1576                        .collect();
1577
1578                    NickelValue::term(Term::StrChunks(chunks), pos_idx)
1579                }
1580                TermContent::Annotated(lens) => {
1581                    let mut data = lens.take();
1582                    data.inner = subst(pos_table, cache, data.inner, initial_env, env);
1583                    // Currently, there is no interest in replacing variables inside contracts, thus we
1584                    // limit the work of `subst`.
1585                    NickelValue::term(Term::Annotated(data), pos_idx)
1586                }
1587                TermContent::Closurize(lens) => {
1588                    NickelValue::term(Term::Closurize(subst(pos_table, cache, lens.take(), initial_env, env)), pos_idx)
1589                }
1590            }
1591        }
1592    }
1593}
1594
1595#[cfg(test)]
1596mod tests;