about summary refs log tree commit diff
path: root/tvix/eval
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/eval')
-rw-r--r--tvix/eval/src/compiler/mod.rs2
-rw-r--r--tvix/eval/src/lib.rs151
2 files changed, 115 insertions, 38 deletions
diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs
index 1ec47599ff9c..3a25052aabb2 100644
--- a/tvix/eval/src/compiler/mod.rs
+++ b/tvix/eval/src/compiler/mod.rs
@@ -117,7 +117,7 @@ impl TrackedFormal {
 
 /// The map of globally available functions and other values that
 /// should implicitly be resolvable in the global scope.
-pub(crate) type GlobalsMap = HashMap<&'static str, Value>;
+pub type GlobalsMap = HashMap<&'static str, Value>;
 
 /// Set of builtins that (if they exist) should be made available in
 /// the global scope, meaning that they can be accessed not just
diff --git a/tvix/eval/src/lib.rs b/tvix/eval/src/lib.rs
index 00dc7918d41f..a53a2a02d140 100644
--- a/tvix/eval/src/lib.rs
+++ b/tvix/eval/src/lib.rs
@@ -42,13 +42,12 @@ use std::rc::Rc;
 use std::str::FromStr;
 use std::sync::Arc;
 
-use crate::compiler::GlobalsMap;
 use crate::observer::{CompilerObserver, RuntimeObserver};
 use crate::value::Lambda;
 use crate::vm::run_lambda;
 
 // Re-export the public interface used by other crates.
-pub use crate::compiler::{compile, prepare_globals, CompilationOutput};
+pub use crate::compiler::{compile, prepare_globals, CompilationOutput, GlobalsMap};
 pub use crate::errors::{AddContext, CatchableErrorKind, Error, ErrorKind, EvalResult};
 pub use crate::io::{DummyIO, EvalIO, FileType};
 pub use crate::pretty_ast::pretty_print_expr;
@@ -64,6 +63,16 @@ pub use crate::value::{Builtin, CoercionKind, NixAttrs, NixList, NixString, Valu
 #[cfg(feature = "impure")]
 pub use crate::io::StdIO;
 
+struct BuilderBuiltins {
+    builtins: Vec<(&'static str, Value)>,
+    src_builtins: Vec<(&'static str, &'static str)>,
+}
+
+enum BuilderGlobals {
+    Builtins(BuilderBuiltins),
+    Globals(Rc<GlobalsMap>),
+}
+
 /// Builder for building an [`Evaluation`].
 ///
 /// Construct an [`EvaluationBuilder`] by calling one of:
@@ -75,9 +84,8 @@ pub use crate::io::StdIO;
 /// Then configure the fields by calling the various methods on [`EvaluationBuilder`], and finally
 /// call [`build`](Self::build) to construct an [`Evaluation`]
 pub struct EvaluationBuilder<'co, 'ro, 'env, IO> {
-    source_map: SourceCode,
-    builtins: Vec<(&'static str, Value)>,
-    src_builtins: Vec<(&'static str, &'static str)>,
+    source_map: Option<SourceCode>,
+    globals: BuilderGlobals,
     env: Option<&'env HashMap<SmolStr, Value>>,
     io_handle: IO,
     enable_import: bool,
@@ -98,21 +106,31 @@ where
     /// - Adds a `"storeDir"` builtin containing the store directory of the configured IO handle
     /// - Sets up globals based on the configured builtins
     /// - Copies all other configured fields to the [`Evaluation`]
-    pub fn build(mut self) -> Evaluation<'co, 'ro, 'env, IO> {
-        // Insert a storeDir builtin *iff* a store directory is present.
-        if let Some(store_dir) = self.io_handle.as_ref().store_dir() {
-            self.builtins.push(("storeDir", store_dir.into()));
-        }
+    pub fn build(self) -> Evaluation<'co, 'ro, 'env, IO> {
+        let source_map = self.source_map.unwrap_or_default();
+
+        let globals = match self.globals {
+            BuilderGlobals::Globals(globals) => globals,
+            BuilderGlobals::Builtins(BuilderBuiltins {
+                mut builtins,
+                src_builtins,
+            }) => {
+                // Insert a storeDir builtin *iff* a store directory is present.
+                if let Some(store_dir) = self.io_handle.as_ref().store_dir() {
+                    builtins.push(("storeDir", store_dir.into()));
+                }
 
-        let globals = crate::compiler::prepare_globals(
-            self.builtins,
-            self.src_builtins,
-            self.source_map.clone(),
-            self.enable_import,
-        );
+                crate::compiler::prepare_globals(
+                    builtins,
+                    src_builtins,
+                    source_map.clone(),
+                    self.enable_import,
+                )
+            }
+        };
 
         Evaluation {
-            source_map: self.source_map,
+            source_map,
             globals,
             env: self.env,
             io_handle: self.io_handle,
@@ -132,11 +150,13 @@ impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO> {
         builtins.extend(builtins::placeholders()); // these are temporary
 
         Self {
-            source_map: SourceCode::default(),
+            source_map: None,
             enable_import: false,
             io_handle,
-            builtins,
-            src_builtins: vec![],
+            globals: BuilderGlobals::Builtins(BuilderBuiltins {
+                builtins,
+                src_builtins: vec![],
+            }),
             env: None,
             strict: false,
             nix_path: None,
@@ -149,8 +169,7 @@ impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO> {
         EvaluationBuilder {
             io_handle,
             source_map: self.source_map,
-            builtins: self.builtins,
-            src_builtins: self.src_builtins,
+            globals: self.globals,
             env: self.env,
             enable_import: self.enable_import,
             strict: self.strict,
@@ -175,14 +194,64 @@ impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO> {
         self.with_enable_import(true)
     }
 
+    fn builtins_mut(&mut self) -> &mut BuilderBuiltins {
+        match &mut self.globals {
+            BuilderGlobals::Builtins(builtins) => builtins,
+            BuilderGlobals::Globals(_) => {
+                panic!("Cannot modify builtins on an EvaluationBuilder with globals configured")
+            }
+        }
+    }
+
+    /// Add additional builtins (represented as tuples of name and [`Value`]) to this evaluation
+    /// builder.
+    ///
+    /// # Panics
+    ///
+    /// Panics if this evaluation builder has had globals set via [`with_globals`]
     pub fn add_builtins<I>(mut self, builtins: I) -> Self
     where
         I: IntoIterator<Item = (&'static str, Value)>,
     {
-        self.builtins.extend(builtins);
+        self.builtins_mut().builtins.extend(builtins);
+        self
+    }
+
+    /// Add additional builtins that are implemented in Nix source code (represented as tuples of
+    /// name and nix source) to this evaluation builder.
+    ///
+    /// # Panics
+    ///
+    /// Panics if this evaluation builder has had globals set via [`with_globals`]
+    pub fn add_src_builtin(mut self, name: &'static str, src: &'static str) -> Self {
+        self.builtins_mut().src_builtins.push((name, src));
         self
     }
 
+    /// Set the globals for this evaluation builder to a previously-constructed globals map.
+    /// Intended to allow sharing globals across multiple evaluations (eg for the REPL).
+    ///
+    /// Discards any builtins previously configured via [`add_builtins`] and [`add_src_builtins`].
+    /// If either of those methods is called on the evaluation builder after this one, they will
+    /// panic.
+    pub fn with_globals(self, globals: Rc<GlobalsMap>) -> Self {
+        Self {
+            globals: BuilderGlobals::Globals(globals),
+            ..self
+        }
+    }
+
+    pub fn with_source_map(self, source_map: SourceCode) -> Self {
+        debug_assert!(
+            self.source_map.is_none(),
+            "Cannot set the source_map on an EvaluationBuilder twice"
+        );
+        Self {
+            source_map: Some(source_map),
+            ..self
+        }
+    }
+
     pub fn with_strict(self, strict: bool) -> Self {
         Self { strict, ..self }
     }
@@ -191,11 +260,6 @@ impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO> {
         self.with_strict(true)
     }
 
-    pub fn add_src_builtin(mut self, name: &'static str, src: &'static str) -> Self {
-        self.src_builtins.push((name, src));
-        self
-    }
-
     pub fn nix_path(self, nix_path: Option<String>) -> Self {
         Self { nix_path, ..self }
     }
@@ -234,8 +298,8 @@ impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO> {
 }
 
 impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO> {
-    pub fn source_map(&self) -> &SourceCode {
-        &self.source_map
+    pub fn source_map(&mut self) -> &SourceCode {
+        self.source_map.get_or_insert_with(SourceCode::default)
     }
 }
 
@@ -255,7 +319,9 @@ impl<'co, 'ro, 'env> EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>> {
     pub fn enable_impure(mut self, io: Option<Box<dyn EvalIO>>) -> Self {
         self.io_handle = io.unwrap_or_else(|| Box::new(StdIO) as Box<dyn EvalIO>);
         self.enable_import = true;
-        self.builtins.extend(builtins::impure_builtins());
+        self.builtins_mut()
+            .builtins
+            .extend(builtins::impure_builtins());
 
         // Make `NIX_PATH` resolutions work by default, unless the
         // user already overrode this with something else.
@@ -332,9 +398,26 @@ pub struct EvaluationResult {
 }
 
 impl<'co, 'ro, 'env, IO> Evaluation<'co, 'ro, 'env, IO> {
+    /// Make a new [builder][] for configuring an evaluation
+    ///
+    /// [builder]: EvaluationBuilder
     pub fn builder(io_handle: IO) -> EvaluationBuilder<'co, 'ro, 'env, IO> {
         EvaluationBuilder::new(io_handle)
     }
+
+    /// Clone the reference to the map of Nix globals for this evaluation. If [`Value`]s are shared
+    /// across subsequent [`Evaluation`]s, it is important that those evaluations all have the same
+    /// underlying globals map.
+    pub fn globals(&self) -> Rc<GlobalsMap> {
+        self.globals.clone()
+    }
+
+    /// Clone the reference to the contained source code map. This is used after an evaluation for
+    /// pretty error printing. Also, if [`Value`]s are shared across subsequent [`Evaluation`]s, it
+    /// is important that those evaluations all have the same underlying source code map.
+    pub fn source_map(&self) -> SourceCode {
+        self.source_map.clone()
+    }
 }
 
 impl<'co, 'ro, 'env> Evaluation<'co, 'ro, 'env, Box<dyn EvalIO>> {
@@ -352,12 +435,6 @@ impl<'co, 'ro, 'env, IO> Evaluation<'co, 'ro, 'env, IO>
 where
     IO: AsRef<dyn EvalIO> + 'static,
 {
-    /// Clone the reference to the contained source code map. This is used after
-    /// an evaluation for pretty error printing.
-    pub fn source_map(&self) -> SourceCode {
-        self.source_map.clone()
-    }
-
     /// Only compile the provided source code, at an optional location of the
     /// source code (i.e. path to the file it was read from; used for error
     /// reporting, and for resolving relative paths in impure functions)