diff options
-rw-r--r-- | tvix/eval/src/builtins/impure.rs | 84 | ||||
-rw-r--r-- | tvix/eval/src/builtins/mod.rs | 2 | ||||
-rw-r--r-- | tvix/eval/src/errors.rs | 52 | ||||
-rw-r--r-- | tvix/eval/src/eval.rs | 13 |
4 files changed, 143 insertions, 8 deletions
diff --git a/tvix/eval/src/builtins/impure.rs b/tvix/eval/src/builtins/impure.rs index 7073deaaa7ac..675bdd50950e 100644 --- a/tvix/eval/src/builtins/impure.rs +++ b/tvix/eval/src/builtins/impure.rs @@ -1,19 +1,23 @@ use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, + rc::Rc, time::{SystemTime, UNIX_EPOCH}, }; use crate::{ - value::{Builtin, NixString}, - Value, + errors::ErrorKind, + observer::NoOpObserver, + value::{Builtin, NixString, Thunk}, + vm::VM, + SourceCode, Value, }; fn impure_builtins() -> Vec<Builtin> { vec![] } -/// Return all impure builtins, that is all builtins which may perform I/O outside of the VM and so -/// cannot be used in all contexts (e.g. WASM). +/// Return all impure builtins, that is all builtins which may perform I/O +/// outside of the VM and so cannot be used in all contexts (e.g. WASM). pub(super) fn builtins() -> BTreeMap<NixString, Value> { let mut map: BTreeMap<NixString, Value> = impure_builtins() .into_iter() @@ -34,3 +38,73 @@ pub(super) fn builtins() -> BTreeMap<NixString, Value> { map } + +/// Constructs and inserts the `import` builtin. This builtin is special in that +/// it needs to capture the [crate::SourceCode] structure to correctly track +/// source code locations while invoking a compiler. +// TODO: need to be able to pass through a CompilationObserver, too. +pub fn builtins_import(source: SourceCode) -> Builtin { + Builtin::new( + "import", + &[true], + move |mut args: Vec<Value>, _: &mut VM| { + let path = match args.pop().unwrap() { + Value::Path(path) => path, + Value::String(_) => { + return Err(ErrorKind::NotImplemented("importing from string-paths")) + } + other => { + return Err(ErrorKind::TypeError { + expected: "path or string", + actual: other.type_of(), + }) + } + }; + + let contents = + std::fs::read_to_string(&path).map_err(|err| ErrorKind::ReadFileError { + path: path.clone(), + error: Rc::new(err), + })?; + + let parsed = rnix::ast::Root::parse(&contents); + let errors = parsed.errors(); + + if !errors.is_empty() { + return Err(ErrorKind::ImportParseError { + path, + errors: errors.to_vec(), + }); + } + + let file = source.add_file(path.to_string_lossy().to_string(), contents); + + let result = crate::compile( + &parsed.tree().expr().unwrap(), + Some(path.clone()), + file, + HashMap::new(), // TODO: pass through globals + &mut NoOpObserver::default(), + ) + .map_err(|err| ErrorKind::ImportCompilerError { + path: path.clone(), + errors: vec![err], + })?; + + if !result.errors.is_empty() { + return Err(ErrorKind::ImportCompilerError { + path, + errors: result.errors, + }); + } + + // TODO: deal with runtime *warnings* (most likely through an + // emit_warning function on the VM that might return it together with + // the result) + + // Compilation succeeded, we can construct a thunk from whatever it spat + // out and return that. + Ok(Value::Thunk(Thunk::new(result.lambda))) + }, + ) +} diff --git a/tvix/eval/src/builtins/mod.rs b/tvix/eval/src/builtins/mod.rs index ad3ab807e3d1..b0aab352d23b 100644 --- a/tvix/eval/src/builtins/mod.rs +++ b/tvix/eval/src/builtins/mod.rs @@ -19,7 +19,7 @@ use crate::{arithmetic_op, cmp_op}; use self::versions::{VersionPart, VersionPartsIter}; #[cfg(feature = "impure")] -mod impure; +pub mod impure; pub mod versions; /// Coerce a Nix Value to a plain path, e.g. in order to access the file it diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs index 75fac9c926cf..b37469617160 100644 --- a/tvix/eval/src/errors.rs +++ b/tvix/eval/src/errors.rs @@ -1,5 +1,6 @@ use crate::value::CoercionKind; use std::path::PathBuf; +use std::rc::Rc; use std::{fmt::Display, num::ParseIntError}; use codemap::Span; @@ -99,6 +100,24 @@ pub enum ErrorKind { /// literal attribute sets. UnmergeableValue, + /// Tvix failed to read a file from disk for some reason. + ReadFileError { + path: PathBuf, + error: Rc<std::io::Error>, + }, + + /// Parse errors occured while importing a file. + ImportParseError { + path: PathBuf, + errors: Vec<rnix::parser::ParseError>, + }, + + /// Compilation errors occured while importing a file. + ImportCompilerError { + path: PathBuf, + errors: Vec<Error>, + }, + /// Tvix internal warning for features triggered by users that are /// not actually implemented yet, and without which eval can not /// proceed. @@ -271,6 +290,34 @@ to a missing value in the attribute set(s) included via `with`."#, .into() } + ErrorKind::ReadFileError { path, error } => { + format!( + "failed to read file '{}': {}", + path.to_string_lossy(), + error + ) + } + + ErrorKind::ImportParseError { errors, path } => { + format!( + "{} parse errors occured while importing '{}'", + errors.len(), + path.to_string_lossy() + ) + } + + ErrorKind::ImportCompilerError { errors, path } => { + // TODO: chain display of these errors, though this is + // probably not the right place for that (should + // branch into a more elaborate diagnostic() call + // below). + format!( + "{} errors occured while importing '{}'", + errors.len(), + path.to_string_lossy() + ) + } + ErrorKind::NotImplemented(feature) => { format!("feature not yet implemented in Tvix: {}", feature) } @@ -305,6 +352,11 @@ to a missing value in the attribute set(s) included via `with`."#, ErrorKind::TailEmptyList { .. } => "E023", ErrorKind::UnmergeableInherit { .. } => "E024", ErrorKind::UnmergeableValue => "E025", + ErrorKind::ReadFileError { .. } => "E026", + ErrorKind::ImportParseError { .. } => "E027", + ErrorKind::ImportCompilerError { .. } => "E028", + + // Placeholder error while Tvix is under construction. ErrorKind::NotImplemented(_) => "E999", // TODO: thunk force errors should yield a chained diff --git a/tvix/eval/src/eval.rs b/tvix/eval/src/eval.rs index 7b7d3983d4ec..aed4292282b9 100644 --- a/tvix/eval/src/eval.rs +++ b/tvix/eval/src/eval.rs @@ -58,12 +58,21 @@ pub fn interpret(code: &str, location: Option<PathBuf>, options: Options) -> Eva println!("{:?}", root_expr); } + // TODO: encapsulate this import weirdness in builtins + let mut builtins = global_builtins(); + + #[cfg(feature = "impure")] + builtins.insert( + "import", + Value::Builtin(crate::builtins::impure::builtins_import(source.clone())), + ); + let result = if options.dump_bytecode { crate::compiler::compile( &root_expr, location, file.clone(), - global_builtins(), + builtins, &mut DisassemblingObserver::new(source.clone(), std::io::stderr()), ) } else { @@ -71,7 +80,7 @@ pub fn interpret(code: &str, location: Option<PathBuf>, options: Options) -> Eva &root_expr, location, file.clone(), - global_builtins(), + builtins, &mut NoOpObserver::default(), ) }?; |