diff --git a/tvix/eval/src/lib.rs b/tvix/eval/src/lib.rs index 3e160fa92..43a085108 100644 --- a/tvix/eval/src/lib.rs +++ b/tvix/eval/src/lib.rs @@ -1,3 +1,17 @@ +//! `tvix-eval` implements the evaluation of the Nix programming language in +//! Tvix. +//! +//! It is designed to allow users to use Nix as a versatile language for +//! different use-cases. +//! +//! This module exports the high-level functions and types needed for evaluating +//! Nix code and interacting with the language's data structures. +//! +//! Nix has several language features that make use of impurities (such as +//! reading from the NIX_PATH environment variable, or interacting with files). +//! These features are optional and the API of this crate exposes functionality +//! for controlling how they work. + mod builtins; mod chunk; mod compiler; @@ -22,17 +36,20 @@ mod test_utils; #[cfg(test)] mod tests; +use std::path::PathBuf; use std::rc::Rc; +use std::sync::Arc; // Re-export the public interface used by other crates. pub use crate::builtins::global_builtins; pub use crate::compiler::{compile, prepare_globals}; -pub use crate::errors::{ErrorKind, EvalResult}; +pub use crate::errors::{Error, ErrorKind, EvalResult}; pub use crate::eval::{interpret, Options}; pub use crate::pretty_ast::pretty_print_expr; pub use crate::source::SourceCode; pub use crate::value::Value; pub use crate::vm::run_lambda; +pub use crate::warnings::EvalWarning; /// Internal-only parts of `tvix-eval`, exported for use in macros, but not part of the public /// interface of the crate. @@ -43,6 +60,141 @@ pub mod internal { // TODO: use Rc::unwrap_or_clone once it is stabilised. // https://doc.rust-lang.org/std/rc/struct.Rc.html#method.unwrap_or_clone -pub fn unwrap_or_clone_rc(rc: Rc) -> T { +pub(crate) fn unwrap_or_clone_rc(rc: Rc) -> T { Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()) } + +/// An `Evaluation` represents how a piece of Nix code is evaluated. It can be +/// instantiated and configured directly, or it can be accessed through the +/// various simplified helper methods available below. +#[derive(Clone)] +pub struct Evaluation<'a> { + /// The Nix source code to be evaluated. + code: &'a str, + + /// 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. + location: Option, + + /// Source code map used for error reporting. + source_map: SourceCode, + + /// Top-level file reference for this code inside the source map. + file: Arc, + + /// Root expression of the Nix code after parsing. + expr: Option, +} + +/// Result of evaluating a piece of Nix code. If evaluation succeeded, a value +/// will be present (and potentially some warnings!). If evaluation failed, +/// errors will be present. +#[derive(Debug, Default)] +pub struct EvaluationResult { + /// Nix value that the code evaluated to. + pub value: Option, + + /// Errors that occured during evaluation (if any). + pub errors: Vec, + + /// Warnings that occured during evaluation. Warnings are not critical, but + /// should be addressed either to modernise code or improve performance. + pub warnings: Vec, +} + +impl<'a> Evaluation<'a> { + /// Initialise an `Evaluation` for the given Nix source code snippet, and + /// an optional code location. + /// reporting the location of errors in the code. + pub fn new(code: &'a str, location: Option) -> Self { + let source_map = SourceCode::new(); + + let location_str = location + .as_ref() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_else(|| "[code]".into()); + + let file = source_map.add_file(location_str, code.into()); + + Evaluation { + code, + location, + source_map, + file, + expr: None, + } + } + + /// 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() + } + + /// Evaluate the provided source code. + pub fn evaluate(&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; + } + + // 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. + self.expr = parsed.tree().expr(); + + let builtins = + crate::compiler::prepare_globals(Box::new(global_builtins(self.source_map()))); + + let compiler_result = match compiler::compile( + self.expr.as_ref().unwrap(), + self.location.take(), + self.file.clone(), + builtins, + &mut observer::NoOpObserver::default(), // TODO: compilation observer + ) { + Ok(result) => result, + Err(err) => { + result.errors.push(err); + return result; + } + }; + + 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. + let vm_result = run_lambda( + Default::default(), // TODO: add nix search path to `Evaluation` + &mut observer::NoOpObserver::default(), // TODO: runtime observer + compiler_result.lambda, + ); + + match vm_result { + Ok(mut runtime_result) => { + result.warnings.append(&mut runtime_result.warnings); + result.value = Some(runtime_result.value); + } + Err(err) => { + result.errors.push(err); + } + } + + result + } +}