2023-01-14 13:45:22 +01:00
|
|
|
use std::cell::RefCell;
|
|
|
|
use std::rc::Rc;
|
2022-10-08 20:22:26 +02:00
|
|
|
use std::{fs, path::PathBuf};
|
2023-11-03 12:34:37 +01:00
|
|
|
use tvix_glue::known_paths::KnownPaths;
|
2023-11-03 15:40:32 +01:00
|
|
|
use tvix_glue::{builtins::add_derivation_builtins, configure_nix_path};
|
2022-08-11 23:27:02 +02:00
|
|
|
|
2022-09-18 21:59:59 +02:00
|
|
|
use clap::Parser;
|
2022-08-11 23:27:02 +02:00
|
|
|
use rustyline::{error::ReadlineError, Editor};
|
2022-12-09 11:49:36 +01:00
|
|
|
use tvix_eval::observer::{DisassemblingObserver, TracingObserver};
|
2023-06-22 17:57:50 +02:00
|
|
|
use tvix_eval::Value;
|
2023-11-03 12:34:37 +01:00
|
|
|
use tvix_glue::tvix_store_io::TvixStoreIO;
|
2022-08-04 15:43:51 +02:00
|
|
|
|
2022-09-18 21:59:59 +02:00
|
|
|
#[derive(Parser)]
|
|
|
|
struct Args {
|
|
|
|
/// Path to a script to evaluate
|
|
|
|
script: Option<PathBuf>,
|
|
|
|
|
2022-10-11 01:41:42 +02:00
|
|
|
#[clap(long, short = 'E')]
|
|
|
|
expr: Option<String>,
|
2022-12-09 11:29:08 +01:00
|
|
|
|
2022-12-09 11:49:36 +01:00
|
|
|
/// Dump the raw AST to stdout before interpreting
|
|
|
|
#[clap(long, env = "TVIX_DISPLAY_AST")]
|
|
|
|
display_ast: bool,
|
|
|
|
|
|
|
|
/// Dump the bytecode to stdout before evaluating
|
|
|
|
#[clap(long, env = "TVIX_DUMP_BYTECODE")]
|
|
|
|
dump_bytecode: bool,
|
|
|
|
|
|
|
|
/// Trace the runtime of the VM
|
|
|
|
#[clap(long, env = "TVIX_TRACE_RUNTIME")]
|
|
|
|
trace_runtime: bool,
|
|
|
|
|
2023-01-05 10:56:19 +01:00
|
|
|
/// Only compile, but do not execute code. This will make Tvix act
|
|
|
|
/// sort of like a linter.
|
|
|
|
#[clap(long)]
|
|
|
|
compile_only: bool,
|
|
|
|
|
2023-05-25 10:50:13 +02:00
|
|
|
/// Don't print warnings.
|
|
|
|
#[clap(long)]
|
|
|
|
no_warnings: bool,
|
|
|
|
|
2022-12-09 11:33:06 +01:00
|
|
|
/// A colon-separated list of directories to use to resolve `<...>`-style paths
|
|
|
|
#[clap(long, short = 'I', env = "NIX_PATH")]
|
|
|
|
nix_search_path: Option<String>,
|
|
|
|
|
2022-12-09 11:29:08 +01:00
|
|
|
/// Print "raw" (unquoted) output.
|
|
|
|
#[clap(long)]
|
|
|
|
raw: bool,
|
2023-03-17 22:15:03 +01:00
|
|
|
|
|
|
|
/// Strictly evaluate values, traversing them and forcing e.g.
|
|
|
|
/// elements of lists and attribute sets before printing the
|
|
|
|
/// return value.
|
|
|
|
#[clap(long)]
|
|
|
|
strict: bool,
|
2023-12-31 13:28:51 +01:00
|
|
|
|
|
|
|
#[arg(long, env, default_value = "memory://")]
|
|
|
|
blob_service_addr: String,
|
|
|
|
|
|
|
|
#[arg(long, env, default_value = "memory://")]
|
|
|
|
directory_service_addr: String,
|
|
|
|
|
|
|
|
#[arg(long, env, default_value = "memory://")]
|
|
|
|
path_info_service_addr: String,
|
|
|
|
}
|
|
|
|
|
2022-12-08 22:19:22 +01:00
|
|
|
/// Interprets the given code snippet, printing out warnings, errors
|
|
|
|
/// and the result itself. The return value indicates whether
|
|
|
|
/// evaluation succeeded.
|
2022-12-19 10:58:39 +01:00
|
|
|
fn interpret(code: &str, path: Option<PathBuf>, args: &Args, explain: bool) -> bool {
|
2023-12-30 21:36:48 +01:00
|
|
|
let mut eval = tvix_eval::Evaluation::new_impure();
|
2023-03-17 22:15:03 +01:00
|
|
|
eval.strict = args.strict;
|
2023-05-14 20:21:27 +02:00
|
|
|
|
2023-12-31 13:28:51 +01:00
|
|
|
let tokio_runtime = tokio::runtime::Runtime::new().expect("failed to setup tokio runtime");
|
|
|
|
|
|
|
|
let (blob_service, directory_service, path_info_service) = tokio_runtime
|
|
|
|
.block_on({
|
|
|
|
let blob_service_addr = args.blob_service_addr.clone();
|
|
|
|
let directory_service_addr = args.directory_service_addr.clone();
|
|
|
|
let path_info_service_addr = args.path_info_service_addr.clone();
|
|
|
|
async move {
|
2023-12-31 15:33:26 +01:00
|
|
|
tvix_store::utils::construct_services(
|
2023-12-31 13:28:51 +01:00
|
|
|
blob_service_addr,
|
|
|
|
directory_service_addr,
|
|
|
|
path_info_service_addr,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.expect("unable to setup {blob|directory|pathinfo}service before interpreter setup");
|
2023-05-14 20:21:27 +02:00
|
|
|
|
2023-11-03 12:34:37 +01:00
|
|
|
let known_paths: Rc<RefCell<KnownPaths>> = Default::default();
|
|
|
|
add_derivation_builtins(&mut eval, known_paths.clone());
|
2023-11-03 13:03:19 +01:00
|
|
|
configure_nix_path(&mut eval, &args.nix_search_path);
|
2023-12-26 02:03:05 +01:00
|
|
|
eval.io_handle = Box::new(tvix_glue::tvix_io::TvixIO::new(TvixStoreIO::new(
|
|
|
|
blob_service,
|
|
|
|
directory_service,
|
2024-01-05 16:03:13 +01:00
|
|
|
path_info_service,
|
2023-12-26 02:03:05 +01:00
|
|
|
tokio_runtime.handle().clone(),
|
|
|
|
)));
|
2023-03-04 01:43:59 +01:00
|
|
|
|
2022-12-09 11:16:01 +01:00
|
|
|
let source_map = eval.source_map();
|
2022-12-09 11:49:36 +01:00
|
|
|
let result = {
|
|
|
|
let mut compiler_observer =
|
|
|
|
DisassemblingObserver::new(source_map.clone(), std::io::stderr());
|
|
|
|
if args.dump_bytecode {
|
|
|
|
eval.compiler_observer = Some(&mut compiler_observer);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut runtime_observer = TracingObserver::new(std::io::stderr());
|
|
|
|
if args.trace_runtime {
|
|
|
|
eval.runtime_observer = Some(&mut runtime_observer);
|
|
|
|
}
|
|
|
|
|
2023-12-30 21:36:48 +01:00
|
|
|
eval.evaluate(code, path)
|
2022-12-09 11:49:36 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if args.display_ast {
|
|
|
|
if let Some(ref expr) = result.expr {
|
|
|
|
eprintln!("AST: {}", tvix_eval::pretty_print_expr(expr));
|
|
|
|
}
|
|
|
|
}
|
2022-12-08 22:19:22 +01:00
|
|
|
|
|
|
|
for error in &result.errors {
|
|
|
|
error.fancy_format_stderr(&source_map);
|
|
|
|
}
|
|
|
|
|
2023-05-25 10:50:13 +02:00
|
|
|
if !args.no_warnings {
|
|
|
|
for warning in &result.warnings {
|
|
|
|
warning.fancy_format_stderr(&source_map);
|
|
|
|
}
|
2022-12-08 22:19:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(value) = result.value.as_ref() {
|
2022-12-19 10:58:39 +01:00
|
|
|
if explain {
|
|
|
|
println!("=> {}", value.explain());
|
|
|
|
} else {
|
|
|
|
println_result(value, args.raw);
|
|
|
|
}
|
2022-12-08 22:19:22 +01:00
|
|
|
}
|
2022-10-11 01:41:42 +02:00
|
|
|
|
2022-12-08 22:19:22 +01:00
|
|
|
// inform the caller about any errors
|
|
|
|
result.errors.is_empty()
|
2022-09-18 21:59:59 +02:00
|
|
|
}
|
|
|
|
|
2023-01-05 10:56:19 +01:00
|
|
|
/// Interpret the given code snippet, but only run the Tvix compiler
|
|
|
|
/// on it and return errors and warnings.
|
|
|
|
fn lint(code: &str, path: Option<PathBuf>, args: &Args) -> bool {
|
2023-12-30 21:36:48 +01:00
|
|
|
let mut eval = tvix_eval::Evaluation::new_impure();
|
2023-03-17 22:15:03 +01:00
|
|
|
eval.strict = args.strict;
|
|
|
|
|
2023-01-05 10:56:19 +01:00
|
|
|
let source_map = eval.source_map();
|
|
|
|
|
|
|
|
let mut compiler_observer = DisassemblingObserver::new(source_map.clone(), std::io::stderr());
|
|
|
|
|
|
|
|
if args.dump_bytecode {
|
|
|
|
eval.compiler_observer = Some(&mut compiler_observer);
|
|
|
|
}
|
|
|
|
|
|
|
|
if args.trace_runtime {
|
|
|
|
eprintln!("warning: --trace-runtime has no effect with --compile-only!");
|
|
|
|
}
|
|
|
|
|
2023-12-30 21:36:48 +01:00
|
|
|
let result = eval.compile_only(code, path);
|
2023-01-05 10:56:19 +01:00
|
|
|
|
|
|
|
if args.display_ast {
|
|
|
|
if let Some(ref expr) = result.expr {
|
|
|
|
eprintln!("AST: {}", tvix_eval::pretty_print_expr(expr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for error in &result.errors {
|
|
|
|
error.fancy_format_stderr(&source_map);
|
|
|
|
}
|
|
|
|
|
|
|
|
for warning in &result.warnings {
|
|
|
|
warning.fancy_format_stderr(&source_map);
|
|
|
|
}
|
|
|
|
|
|
|
|
// inform the caller about any errors
|
|
|
|
result.errors.is_empty()
|
|
|
|
}
|
|
|
|
|
2022-08-04 15:29:38 +02:00
|
|
|
fn main() {
|
2022-09-18 21:59:59 +02:00
|
|
|
let args = Args::parse();
|
2022-08-04 15:43:51 +02:00
|
|
|
|
2022-12-09 11:29:08 +01:00
|
|
|
if let Some(file) = &args.script {
|
|
|
|
run_file(file.clone(), &args)
|
|
|
|
} else if let Some(expr) = &args.expr {
|
2022-12-19 10:58:39 +01:00
|
|
|
if !interpret(expr, None, &args, false) {
|
2022-12-08 22:19:22 +01:00
|
|
|
std::process::exit(1);
|
2022-10-11 01:41:42 +02:00
|
|
|
}
|
2022-08-04 15:43:51 +02:00
|
|
|
} else {
|
2022-12-09 11:29:08 +01:00
|
|
|
run_prompt(&args)
|
2022-08-04 15:43:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-09 11:29:08 +01:00
|
|
|
fn run_file(mut path: PathBuf, args: &Args) {
|
2022-10-08 20:22:26 +02:00
|
|
|
if path.is_dir() {
|
|
|
|
path.push("default.nix");
|
|
|
|
}
|
|
|
|
let contents = fs::read_to_string(&path).expect("failed to read the input file");
|
2022-12-08 22:19:22 +01:00
|
|
|
|
2023-01-05 10:56:19 +01:00
|
|
|
let success = if args.compile_only {
|
|
|
|
lint(&contents, Some(path), args)
|
|
|
|
} else {
|
|
|
|
interpret(&contents, Some(path), args, false)
|
|
|
|
};
|
|
|
|
|
|
|
|
if !success {
|
2022-12-08 22:19:22 +01:00
|
|
|
std::process::exit(1);
|
2022-08-11 23:27:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-25 23:47:39 +01:00
|
|
|
fn println_result(result: &Value, raw: bool) {
|
|
|
|
if raw {
|
2023-12-29 19:18:08 +01:00
|
|
|
println!("{}", result.to_contextful_str().unwrap().as_str())
|
2022-11-25 23:47:39 +01:00
|
|
|
} else {
|
|
|
|
println!("=> {} :: {}", result, result.type_of())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-11 23:27:02 +02:00
|
|
|
fn state_dir() -> Option<PathBuf> {
|
|
|
|
let mut path = dirs::data_dir();
|
2022-08-14 01:51:09 +02:00
|
|
|
if let Some(p) = path.as_mut() {
|
|
|
|
p.push("tvix")
|
|
|
|
}
|
2022-08-11 23:27:02 +02:00
|
|
|
path
|
2022-08-04 15:43:51 +02:00
|
|
|
}
|
|
|
|
|
2022-12-09 11:29:08 +01:00
|
|
|
fn run_prompt(args: &Args) {
|
2022-08-11 23:27:02 +02:00
|
|
|
let mut rl = Editor::<()>::new().expect("should be able to launch rustyline");
|
|
|
|
|
2023-01-05 10:56:19 +01:00
|
|
|
if args.compile_only {
|
|
|
|
eprintln!("warning: `--compile-only` has no effect on REPL usage!");
|
|
|
|
}
|
|
|
|
|
2022-08-11 23:27:02 +02:00
|
|
|
let history_path = match state_dir() {
|
2022-08-14 19:08:15 +02:00
|
|
|
// Attempt to set up these paths, but do not hard fail if it
|
|
|
|
// doesn't work.
|
2022-08-11 23:27:02 +02:00
|
|
|
Some(mut path) => {
|
2022-08-26 18:41:56 +02:00
|
|
|
let _ = std::fs::create_dir_all(&path);
|
2022-08-11 23:27:02 +02:00
|
|
|
path.push("history.txt");
|
2022-08-26 18:41:56 +02:00
|
|
|
let _ = rl.load_history(&path);
|
2022-08-11 23:27:02 +02:00
|
|
|
Some(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
None => None,
|
|
|
|
};
|
2022-08-04 15:43:51 +02:00
|
|
|
|
|
|
|
loop {
|
2022-08-11 23:27:02 +02:00
|
|
|
let readline = rl.readline("tvix-repl> ");
|
|
|
|
match readline {
|
|
|
|
Ok(line) => {
|
|
|
|
if line.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-09-05 04:12:36 +02:00
|
|
|
rl.add_history_entry(&line);
|
2022-12-19 10:58:39 +01:00
|
|
|
|
|
|
|
if let Some(without_prefix) = line.strip_prefix(":d ") {
|
|
|
|
interpret(without_prefix, None, args, true);
|
|
|
|
} else {
|
|
|
|
interpret(&line, None, args, false);
|
|
|
|
}
|
2022-08-11 23:27:02 +02:00
|
|
|
}
|
|
|
|
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break,
|
|
|
|
|
|
|
|
Err(err) => {
|
|
|
|
eprintln!("error: {}", err);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-08-04 15:43:51 +02:00
|
|
|
}
|
|
|
|
|
2022-08-11 23:27:02 +02:00
|
|
|
if let Some(path) = history_path {
|
|
|
|
rl.save_history(&path).unwrap();
|
2022-08-04 15:43:51 +02:00
|
|
|
}
|
2022-08-04 15:29:38 +02:00
|
|
|
}
|