d9e2bec953
The tvix-eval project is independent from any *uses* of the evaluator, such as the tvix-repl. This functionality has been split out into a separate "tvix-cli" crate. Note that this doesn't have to mean that this CLI crate is the "final" CLI crate for tvix, the point of this is not "getting the CLI structure right" but rather "getting the evaluator structure right". This reshuffling is part of restructuring the way that functionality like store communication is injected into language evaluation. Note that at this commit the new CLI crate is not at feature-parity. Change-Id: Id0af03dc8e07ef09a9f882a89612ad555eca8f93 Reviewed-on: https://cl.tvl.fyi/c/depot/+/7541 Autosubmit: tazjin <tazjin@tvl.su> Reviewed-by: grfn <grfn@gws.fyi> Tested-by: BuildkiteCI
198 lines
6.2 KiB
Rust
198 lines
6.2 KiB
Rust
//! `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;
|
|
mod errors;
|
|
pub mod observer;
|
|
mod opcode;
|
|
mod pretty_ast;
|
|
mod source;
|
|
mod spans;
|
|
mod systems;
|
|
mod upvalues;
|
|
mod value;
|
|
mod vm;
|
|
mod warnings;
|
|
|
|
mod nix_search_path;
|
|
#[cfg(test)]
|
|
mod properties;
|
|
#[cfg(test)]
|
|
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::{Error, ErrorKind, EvalResult};
|
|
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.
|
|
pub mod internal {
|
|
pub use crate::value::{Builtin, BuiltinArgument};
|
|
pub use crate::vm::VM;
|
|
}
|
|
|
|
// 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(crate) fn unwrap_or_clone_rc<T: Clone>(rc: Rc<T>) -> 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<PathBuf>,
|
|
|
|
/// Source code map used for error reporting.
|
|
source_map: SourceCode,
|
|
|
|
/// Top-level file reference for this code inside the source map.
|
|
file: Arc<codemap::File>,
|
|
|
|
/// Root expression of the Nix code after parsing.
|
|
expr: Option<rnix::ast::Expr>,
|
|
}
|
|
|
|
/// 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<Value>,
|
|
|
|
/// Errors that occured during evaluation (if any).
|
|
pub errors: Vec<Error>,
|
|
|
|
/// Warnings that occured during evaluation. Warnings are not critical, but
|
|
/// should be addressed either to modernise code or improve performance.
|
|
pub warnings: Vec<EvalWarning>,
|
|
}
|
|
|
|
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<PathBuf>) -> 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
|
|
}
|
|
}
|