2022-12-08 22:15:45 +01:00
|
|
|
//! `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.
|
|
|
|
|
2022-08-24 15:03:17 +02:00
|
|
|
mod builtins;
|
2022-08-25 17:00:05 +02:00
|
|
|
mod chunk;
|
|
|
|
mod compiler;
|
|
|
|
mod errors;
|
2022-12-12 15:19:27 +01:00
|
|
|
mod io;
|
2022-09-04 17:43:28 +02:00
|
|
|
pub mod observer;
|
2022-08-25 17:00:05 +02:00
|
|
|
mod opcode;
|
2022-10-13 14:23:45 +02:00
|
|
|
mod pretty_ast;
|
2022-10-04 16:05:34 +02:00
|
|
|
mod source;
|
2022-10-06 13:33:09 +02:00
|
|
|
mod spans;
|
2022-10-12 11:26:40 +02:00
|
|
|
mod systems;
|
2022-08-29 17:07:58 +02:00
|
|
|
mod upvalues;
|
2022-08-25 17:00:05 +02:00
|
|
|
mod value;
|
|
|
|
mod vm;
|
2022-08-12 16:07:32 +02:00
|
|
|
mod warnings;
|
2022-08-25 17:00:05 +02:00
|
|
|
|
2022-10-10 20:43:51 +02:00
|
|
|
mod nix_search_path;
|
2022-09-17 19:50:58 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
mod properties;
|
2022-08-25 17:00:05 +02:00
|
|
|
#[cfg(test)]
|
2022-09-18 18:38:53 +02:00
|
|
|
mod test_utils;
|
|
|
|
#[cfg(test)]
|
2022-08-25 17:00:05 +02:00
|
|
|
mod tests;
|
|
|
|
|
2022-12-08 22:15:45 +01:00
|
|
|
use std::path::PathBuf;
|
2022-11-05 12:57:33 +01:00
|
|
|
use std::rc::Rc;
|
2022-12-09 10:47:54 +01:00
|
|
|
use std::str::FromStr;
|
2022-12-08 22:15:45 +01:00
|
|
|
use std::sync::Arc;
|
2022-11-05 12:57:33 +01:00
|
|
|
|
2022-09-04 17:43:28 +02:00
|
|
|
// Re-export the public interface used by other crates.
|
|
|
|
pub use crate::builtins::global_builtins;
|
2022-10-26 14:16:04 +02:00
|
|
|
pub use crate::compiler::{compile, prepare_globals};
|
2022-12-08 22:15:45 +01:00
|
|
|
pub use crate::errors::{Error, ErrorKind, EvalResult};
|
2022-12-12 15:38:28 +01:00
|
|
|
pub use crate::io::{DummyIO, EvalIO, StdIO};
|
2022-12-09 10:58:58 +01:00
|
|
|
use crate::observer::{CompilerObserver, RuntimeObserver};
|
2022-10-13 14:23:45 +02:00
|
|
|
pub use crate::pretty_ast::pretty_print_expr;
|
2022-10-04 16:05:34 +02:00
|
|
|
pub use crate::source::SourceCode;
|
2022-11-06 16:07:46 +01:00
|
|
|
pub use crate::value::Value;
|
|
|
|
pub use crate::vm::run_lambda;
|
2022-12-09 10:47:54 +01:00
|
|
|
pub use crate::warnings::{EvalWarning, WarningKind};
|
2022-11-06 16:07:46 +01:00
|
|
|
|
|
|
|
/// Internal-only parts of `tvix-eval`, exported for use in macros, but not part of the public
|
|
|
|
/// interface of the crate.
|
|
|
|
pub mod internal {
|
2022-11-06 16:28:34 +01:00
|
|
|
pub use crate::value::{Builtin, BuiltinArgument};
|
2022-11-06 16:07:46 +01:00
|
|
|
pub use crate::vm::VM;
|
|
|
|
}
|
2022-11-05 12:57:33 +01:00
|
|
|
|
|
|
|
// TODO: use Rc::unwrap_or_clone once it is stabilised.
|
|
|
|
// https://doc.rust-lang.org/std/rc/struct.Rc.html#method.unwrap_or_clone
|
2022-12-08 22:15:45 +01:00
|
|
|
pub(crate) fn unwrap_or_clone_rc<T: Clone>(rc: Rc<T>) -> T {
|
2022-11-05 12:57:33 +01:00
|
|
|
Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone())
|
|
|
|
}
|
2022-12-08 22:15:45 +01:00
|
|
|
|
|
|
|
/// 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.
|
2022-12-09 10:47:54 +01:00
|
|
|
///
|
|
|
|
/// Public fields are intended to be set by the caller. Setting all
|
|
|
|
/// fields is optional.
|
2022-12-09 10:58:58 +01:00
|
|
|
pub struct Evaluation<'code, 'co, 'ro> {
|
2022-12-08 22:15:45 +01:00
|
|
|
/// The Nix source code to be evaluated.
|
2022-12-09 10:58:58 +01:00
|
|
|
code: &'code str,
|
2022-12-08 22:15:45 +01:00
|
|
|
|
|
|
|
/// 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>,
|
|
|
|
|
2022-12-12 15:38:28 +01:00
|
|
|
/// Implementation of file-IO to use during evaluation, e.g. for
|
|
|
|
/// impure builtins.
|
|
|
|
///
|
|
|
|
/// Defaults to [`DummyIO`] if not set explicitly.
|
|
|
|
pub io_handle: Box<dyn EvalIO>,
|
|
|
|
|
2022-12-09 10:47:54 +01:00
|
|
|
/// (optional) Nix search path, e.g. the value of `NIX_PATH` used
|
|
|
|
/// for resolving items on the search path (such as `<nixpkgs>`).
|
|
|
|
pub nix_path: Option<String>,
|
2022-12-09 10:58:58 +01:00
|
|
|
|
|
|
|
/// (optional) compiler observer for reporting on compilation
|
|
|
|
/// details, like the emitted bytecode.
|
|
|
|
pub compiler_observer: Option<&'co mut dyn CompilerObserver>,
|
|
|
|
|
|
|
|
/// (optional) runtime observer, for reporting on execution steps
|
|
|
|
/// of Nix code.
|
|
|
|
pub runtime_observer: Option<&'ro mut dyn RuntimeObserver>,
|
2022-12-08 22:15:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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>,
|
2022-12-09 11:16:01 +01:00
|
|
|
|
|
|
|
/// AST node that was parsed from the code (on success only).
|
|
|
|
pub expr: Option<rnix::ast::Expr>,
|
2022-12-08 22:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-12-09 10:58:58 +01:00
|
|
|
impl<'code, 'co, 'ro> Evaluation<'code, 'co, 'ro> {
|
2022-12-08 22:15:45 +01:00
|
|
|
/// Initialise an `Evaluation` for the given Nix source code snippet, and
|
|
|
|
/// an optional code location.
|
|
|
|
/// reporting the location of errors in the code.
|
2022-12-09 10:58:58 +01:00
|
|
|
pub fn new(code: &'code str, location: Option<PathBuf>) -> Self {
|
2022-12-08 22:15:45 +01:00
|
|
|
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,
|
2022-12-12 15:38:28 +01:00
|
|
|
io_handle: Box::new(DummyIO {}),
|
2022-12-09 10:47:54 +01:00
|
|
|
nix_path: None,
|
2022-12-09 10:58:58 +01:00
|
|
|
compiler_observer: None,
|
|
|
|
runtime_observer: None,
|
2022-12-08 22:15:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.
|
2022-12-09 11:16:01 +01:00
|
|
|
pub fn evaluate(mut self) -> EvaluationResult {
|
2022-12-08 22:15:45 +01:00
|
|
|
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.
|
2022-12-09 11:16:01 +01:00
|
|
|
result.expr = parsed.tree().expr();
|
2022-12-08 22:15:45 +01:00
|
|
|
|
|
|
|
let builtins =
|
|
|
|
crate::compiler::prepare_globals(Box::new(global_builtins(self.source_map())));
|
|
|
|
|
2022-12-09 10:58:58 +01:00
|
|
|
let mut noop_observer = observer::NoOpObserver::default();
|
|
|
|
let compiler_observer = self.compiler_observer.take().unwrap_or(&mut noop_observer);
|
|
|
|
|
2022-12-08 22:15:45 +01:00
|
|
|
let compiler_result = match compiler::compile(
|
2022-12-09 11:16:01 +01:00
|
|
|
result.expr.as_ref().unwrap(),
|
2022-12-08 22:15:45 +01:00
|
|
|
self.location.take(),
|
|
|
|
self.file.clone(),
|
|
|
|
builtins,
|
2022-12-09 10:58:58 +01:00
|
|
|
compiler_observer,
|
2022-12-08 22:15:45 +01:00
|
|
|
) {
|
|
|
|
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.
|
2022-12-09 10:47:54 +01:00
|
|
|
|
|
|
|
let nix_path = self
|
|
|
|
.nix_path
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|s| match nix_search_path::NixSearchPath::from_str(s) {
|
|
|
|
Ok(path) => Some(path),
|
|
|
|
Err(err) => {
|
|
|
|
result.warnings.push(EvalWarning {
|
|
|
|
kind: WarningKind::InvalidNixPath(err.to_string()),
|
|
|
|
span: self.file.span,
|
|
|
|
});
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|| Default::default());
|
|
|
|
|
2022-12-09 10:58:58 +01:00
|
|
|
let runtime_observer = self.runtime_observer.take().unwrap_or(&mut noop_observer);
|
2022-12-12 15:38:28 +01:00
|
|
|
let vm_result = run_lambda(
|
|
|
|
nix_path,
|
|
|
|
self.io_handle,
|
|
|
|
runtime_observer,
|
|
|
|
compiler_result.lambda,
|
|
|
|
);
|
2022-12-08 22:15:45 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|