about summary refs log tree commit diff
path: root/tvix/eval/src
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2023-01-05T09·43+0300
committertazjin <tazjin@tvl.su>2023-01-06T12·23+0000
commit5926a05f4663bd11cef64598d1604b61fb60bede (patch)
tree4b47e9b769a44a9b3278ec5f2a36ca4c453c6342 /tvix/eval/src
parent27fecee1bca2ef92a46912b5787d62d86ce02c45 (diff)
feat(tvix/eval): add Evaluation::compile_only method r/5599
This would make it possible to implement something like a linter based
on the tvix-eval compiler warnings.

Change-Id: I1feb4e7c4a44be7d1204b0a962ab522fd32b93c6
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7763
Tested-by: BuildkiteCI
Reviewed-by: flokli <flokli@flokli.de>
Diffstat (limited to 'tvix/eval/src')
-rw-r--r--tvix/eval/src/compiler/mod.rs4
-rw-r--r--tvix/eval/src/lib.rs142
2 files changed, 99 insertions, 47 deletions
diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs
index 4a44f95691c0..efebd2277094 100644
--- a/tvix/eval/src/compiler/mod.rs
+++ b/tvix/eval/src/compiler/mod.rs
@@ -47,6 +47,8 @@ pub struct CompilationOutput {
 
     // This field must outlive the rc::Weak reference which breaks
     // the builtins -> import -> builtins reference cycle.
+    //
+    // TODO: ensure through compiler
     pub globals: Rc<GlobalsMap>,
 }
 
@@ -82,7 +84,7 @@ pub type Global = Rc<dyn Fn(&mut Compiler, Span)>;
 
 /// The map of globally available functions that should implicitly
 /// be resolvable in the global scope.
-type GlobalsMap = HashMap<&'static str, Rc<dyn Fn(&mut Compiler, Span)>>;
+pub(crate) type GlobalsMap = HashMap<&'static str, Rc<dyn Fn(&mut Compiler, Span)>>;
 
 /// 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 8980814fc163..f623180f1f39 100644
--- a/tvix/eval/src/lib.rs
+++ b/tvix/eval/src/lib.rs
@@ -37,18 +37,23 @@ mod test_utils;
 mod tests;
 
 use std::path::PathBuf;
+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};
+pub use crate::compiler::{compile, prepare_globals, CompilationOutput};
 pub use crate::errors::{Error, ErrorKind, EvalResult};
 pub use crate::io::{DummyIO, EvalIO, FileType};
-use crate::observer::{CompilerObserver, RuntimeObserver};
 pub use crate::pretty_ast::pretty_print_expr;
 pub use crate::source::SourceCode;
 pub use crate::value::{Builtin, BuiltinArgument, NixAttrs, NixList, NixString, Value};
-pub use crate::vm::{run_lambda, VM};
+pub use crate::vm::VM;
 pub use crate::warnings::{EvalWarning, WarningKind};
 
 #[cfg(feature = "impure")]
@@ -172,56 +177,54 @@ impl<'code, 'co, 'ro> Evaluation<'code, 'co, 'ro> {
         self.source_map.clone()
     }
 
-    /// Evaluate the provided source code.
-    pub fn evaluate(mut self) -> EvaluationResult {
+    /// Only compile the provided source code. This does not *run* the
+    /// code, it only provides analysis (errors and warnings) of the
+    /// compiler.
+    pub fn compile_only(mut self) -> EvaluationResult {
         let mut result = EvaluationResult::default();
-        let parsed = rnix::ast::Root::parse(self.code);
-        let parse_errors = parsed.errors();
-
-        if !parse_errors.is_empty() {
-            result.errors.push(Error {
-                kind: ErrorKind::ParseErrors(parse_errors.to_vec()),
-                span: self.file.span,
-            });
-            return result;
-        }
+        let source = self.source_map();
+
+        let mut noop_observer = observer::NoOpObserver::default();
+        let compiler_observer = self.compiler_observer.take().unwrap_or(&mut noop_observer);
 
-        // At this point we know that the code is free of parse errors and we
-        // can continue to compile it.
-        //
-        // The root expression is persisted in self in case the caller wants
-        // access to the parsed expression.
-        result.expr = parsed.tree().expr();
+        parse_compile_internal(
+            &mut result,
+            self.code,
+            self.file.clone(),
+            self.location,
+            source,
+            self.builtins,
+            self.enable_import,
+            compiler_observer,
+        );
 
+        result
+    }
+
+    /// Evaluate the provided source code.
+    pub fn evaluate(mut self) -> EvaluationResult {
+        let mut result = EvaluationResult::default();
         let source = self.source_map();
-        let builtins = crate::compiler::prepare_globals(self.builtins, source, self.enable_import);
 
         let mut noop_observer = observer::NoOpObserver::default();
         let compiler_observer = self.compiler_observer.take().unwrap_or(&mut noop_observer);
 
-        let compiler_result = match compiler::compile(
-            result.expr.as_ref().unwrap(),
-            self.location.take(),
+        let (lambda, _globals) = match parse_compile_internal(
+            &mut result,
+            self.code,
             self.file.clone(),
-            builtins,
+            self.location,
+            source,
+            self.builtins,
+            self.enable_import,
             compiler_observer,
         ) {
-            Ok(result) => result,
-            Err(err) => {
-                result.errors.push(err);
-                return result;
-            }
+            None => return result,
+            Some(cr) => cr,
         };
 
-        result.warnings = compiler_result.warnings;
-
-        if !compiler_result.errors.is_empty() {
-            result.errors = compiler_result.errors;
-            return result;
-        }
-
-        // If there were no errors during compilation, the resulting bytecode is
-        // safe to execute.
+        // If bytecode was returned, there were no errors and the
+        // code is safe to execute.
 
         let nix_path = self
             .nix_path
@@ -239,12 +242,7 @@ impl<'code, 'co, 'ro> Evaluation<'code, 'co, 'ro> {
             .unwrap_or_default();
 
         let runtime_observer = self.runtime_observer.take().unwrap_or(&mut noop_observer);
-        let vm_result = run_lambda(
-            nix_path,
-            self.io_handle,
-            runtime_observer,
-            compiler_result.lambda,
-        );
+        let vm_result = run_lambda(nix_path, self.io_handle, runtime_observer, lambda);
 
         match vm_result {
             Ok(mut runtime_result) => {
@@ -259,3 +257,55 @@ impl<'code, 'co, 'ro> Evaluation<'code, 'co, 'ro> {
         result
     }
 }
+
+/// Internal helper function for common parsing & compilation logic
+/// between the public functions.
+fn parse_compile_internal(
+    result: &mut EvaluationResult,
+    code: &str,
+    file: Arc<codemap::File>,
+    location: Option<PathBuf>,
+    source: SourceCode,
+    builtins: Vec<(&'static str, Value)>,
+    enable_import: bool,
+    compiler_observer: &mut dyn CompilerObserver,
+) -> Option<(Rc<Lambda>, Rc<GlobalsMap>)> {
+    let parsed = rnix::ast::Root::parse(code);
+    let parse_errors = parsed.errors();
+
+    if !parse_errors.is_empty() {
+        result.errors.push(Error {
+            kind: ErrorKind::ParseErrors(parse_errors.to_vec()),
+            span: file.span,
+        });
+        return None;
+    }
+
+    // At this point we know that the code is free of parse errors and
+    // we can continue to compile it. The expression is persisted in
+    // the result, in case the caller needs it for something.
+    result.expr = parsed.tree().expr();
+
+    let builtins = crate::compiler::prepare_globals(builtins, source, enable_import);
+
+    let compiler_result = match compiler::compile(
+        result.expr.as_ref().unwrap(),
+        location,
+        file.clone(),
+        builtins,
+        compiler_observer,
+    ) {
+        Ok(result) => result,
+        Err(err) => {
+            result.errors.push(err);
+            return None;
+        }
+    };
+
+    result.warnings = compiler_result.warnings;
+    result.errors.extend(compiler_result.errors);
+
+    // Return the lambda (for execution) and the globals map (to
+    // ensure the invariant that the globals outlive the runtime).
+    Some((compiler_result.lambda, compiler_result.globals))
+}