From d101151fc58c9ec8bbc6e39c6c29ebfc7f538473 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 9 Dec 2022 00:15:45 +0300 Subject: feat(tvix/eval): begin introducing new public API A step towards something more like how I imagine the future public API for tvix-eval. Please note that this is definitely not the final version yet, but it's better than the previous API that either exposed a side-effecting blackbox, or a very low-level "interface". The basic idea is that an evaluation of some Nix code is requested by a caller with various parameters, but not all callers are interested in all of these parameters. There are also some bits of information that are returned from an evaluation that are not necessarily relevant to all callers. To support this somewhat ergonomically, the API is built around an `Evaluation` struct that is configured by the caller with the various parameters and then "executed". Change-Id: I71826f3897126898adc2873d31c44d3eaf5c2be0 Reviewed-on: https://cl.tvl.fyi/c/depot/+/7542 Reviewed-by: grfn Autosubmit: tazjin Tested-by: BuildkiteCI --- tvix/eval/src/lib.rs | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 2 deletions(-) (limited to 'tvix/eval/src') diff --git a/tvix/eval/src/lib.rs b/tvix/eval/src/lib.rs index 3e160fa92cf6..43a085108258 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 + } +} -- cgit 1.4.1