This commit implements (lazy) resolution of `<...>` paths via either the NIX_PATH environment variable, or the -I command-line flag - both handled via EvalOptions. As a result, EvalOptions can no longer derive Copy, meaning we have to clone it at each line of the repl - this is probably not a huge deal as repl performance is not exactly an inner loop and we're not cloning very much. Internally, this works by creating a thunk which pushes a constant containing the string inside the brackets to the stack, then a new opcode to resolve that path via the `NixPath`. To get that opcode to work, we now have to pass in the NixPath when constructing the VM. This (intentionally) leaves out proper implementation of path resolution via `findFile` (cppnix just calls whatever identifier called findFile is in scope!!!) as that's widely considered a bit of a misfeature, but if we do decide to implement that down the road it likely wouldn't be more than a few extra ops within the thunk introduced here. Change-Id: Ibc979b7e425b65cbe88599940520239a4a10cee2 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6918 Autosubmit: grfn <grfn@gws.fyi> Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI
137 lines
3.8 KiB
Rust
137 lines
3.8 KiB
Rust
use std::{cell::RefCell, path::PathBuf, rc::Rc};
|
|
|
|
use crate::{
|
|
builtins::global_builtins,
|
|
errors::{Error, ErrorKind, EvalResult},
|
|
nix_path::NixPath,
|
|
observer::{DisassemblingObserver, NoOpObserver, TracingObserver},
|
|
value::Value,
|
|
SourceCode,
|
|
};
|
|
|
|
/// Runtime options for the Tvix interpreter
|
|
#[derive(Debug, Clone, Default)]
|
|
#[cfg_attr(feature = "repl", derive(clap::Parser))]
|
|
pub struct Options {
|
|
/// Dump the raw AST to stdout before interpreting
|
|
#[cfg_attr(feature = "repl", clap(long, env = "TVIX_DISPLAY_AST"))]
|
|
display_ast: bool,
|
|
|
|
/// Dump the bytecode to stdout before evaluating
|
|
#[cfg_attr(feature = "repl", clap(long, env = "TVIX_DUMP_BYTECODE"))]
|
|
dump_bytecode: bool,
|
|
|
|
/// Trace the runtime of the VM
|
|
#[cfg_attr(feature = "repl", clap(long, env = "TVIX_TRACE_RUNTIME"))]
|
|
trace_runtime: bool,
|
|
|
|
/// A colon-separated list of directories to use to resolve `<...>`-style paths
|
|
#[cfg_attr(feature = "repl", clap(long, short = 'I', env = "NIX_PATH"))]
|
|
nix_path: Option<NixPath>,
|
|
}
|
|
|
|
pub fn interpret(code: &str, location: Option<PathBuf>, options: Options) -> EvalResult<Value> {
|
|
let source = SourceCode::new();
|
|
let file = source.add_file(
|
|
location
|
|
.as_ref()
|
|
.map(|p| p.to_string_lossy().to_string())
|
|
.unwrap_or_else(|| "[tvix-repl]".into()),
|
|
code.into(),
|
|
);
|
|
|
|
let parsed = rnix::ast::Root::parse(code);
|
|
let errors = parsed.errors();
|
|
|
|
if !errors.is_empty() {
|
|
let err = Error {
|
|
kind: ErrorKind::ParseErrors(errors.to_vec()),
|
|
span: file.span,
|
|
};
|
|
err.fancy_format_stderr(&source);
|
|
return Err(err);
|
|
}
|
|
|
|
// If we've reached this point, there are no errors.
|
|
let root_expr = parsed
|
|
.tree()
|
|
.expr()
|
|
.expect("expression should exist if no errors occured");
|
|
|
|
if options.display_ast {
|
|
println!("{:?}", root_expr);
|
|
}
|
|
|
|
// TODO: encapsulate this import weirdness in builtins
|
|
|
|
let builtins = Rc::new(RefCell::new(global_builtins()));
|
|
|
|
#[cfg(feature = "impure")]
|
|
{
|
|
// We need to insert import into the builtins, but the
|
|
// builtins passed to import must have import *in it*.
|
|
let import = Value::Builtin(crate::builtins::impure::builtins_import(
|
|
builtins.clone(),
|
|
source.clone(),
|
|
));
|
|
|
|
builtins.borrow_mut().insert("import", import);
|
|
// TODO: also add it into the inner builtins set
|
|
};
|
|
|
|
let result = if options.dump_bytecode {
|
|
crate::compiler::compile(
|
|
&root_expr,
|
|
location,
|
|
file.clone(),
|
|
builtins,
|
|
&mut DisassemblingObserver::new(source.clone(), std::io::stderr()),
|
|
)
|
|
} else {
|
|
crate::compiler::compile(
|
|
&root_expr,
|
|
location,
|
|
file.clone(),
|
|
builtins,
|
|
&mut NoOpObserver::default(),
|
|
)
|
|
}?;
|
|
|
|
for warning in result.warnings {
|
|
warning.fancy_format_stderr(&source);
|
|
}
|
|
|
|
for error in &result.errors {
|
|
error.fancy_format_stderr(&source);
|
|
}
|
|
|
|
if let Some(err) = result.errors.last() {
|
|
return Err(err.clone());
|
|
}
|
|
|
|
let result = if options.trace_runtime {
|
|
crate::vm::run_lambda(
|
|
options.nix_path.unwrap_or_default(),
|
|
&mut TracingObserver::new(std::io::stderr()),
|
|
result.lambda,
|
|
)
|
|
} else {
|
|
crate::vm::run_lambda(
|
|
options.nix_path.unwrap_or_default(),
|
|
&mut NoOpObserver::default(),
|
|
result.lambda,
|
|
)
|
|
};
|
|
|
|
if let Err(err) = &result {
|
|
err.fancy_format_stderr(&source);
|
|
}
|
|
|
|
result.map(|r| {
|
|
for warning in r.warnings {
|
|
warning.fancy_format_stderr(&source);
|
|
}
|
|
|
|
r.value
|
|
})
|
|
}
|