refactor(tvix/eval): use Clap for arg+env parsing

Refactor the environment variable and argument parsing for the tvix repl
to use Clap instead of doing things ad-hoc, and thread through options
obtained from environment variables via explicit arguments rather than
obtaining them from the environment as they're needed. This makes adding
more flags more sustainable, and also makes the binary fully
self-documenting, including supported env vars, via `--help`.

Change-Id: Ib1f6a0cd20056e8c9196760ff755fa5729667760
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6653
Autosubmit: grfn <grfn@gws.fyi>
Tested-by: BuildkiteCI
Reviewed-by: tazjin <tazjin@tvl.su>
This commit is contained in:
Griffin Smith 2022-09-18 15:59:59 -04:00 committed by clbot
parent 6f70f32513
commit e720545e5b
8 changed files with 168 additions and 29 deletions

108
tvix/eval/Cargo.lock generated
View file

@ -74,10 +74,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"bitflags",
"textwrap",
"textwrap 0.11.0",
"unicode-width",
]
[[package]]
name = "clap"
version = "3.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"once_cell",
"strsim",
"termcolor",
"textwrap 0.15.1",
]
[[package]]
name = "clap_derive"
version = "3.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2 1.0.43",
"quote 1.0.21",
"syn 1.0.99",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "clipboard-win"
version = "4.4.2"
@ -120,7 +159,7 @@ checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
dependencies = [
"atty",
"cast",
"clap",
"clap 2.34.0",
"criterion-plot",
"csv",
"itertools",
@ -370,6 +409,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -379,6 +424,16 @@ dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -517,6 +572,12 @@ version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "os_str_bytes"
version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
[[package]]
name = "output_vt100"
version = "0.1.3"
@ -578,6 +639,30 @@ dependencies = [
"yansi",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2 1.0.43",
"quote 1.0.21",
"syn 1.0.99",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2 1.0.43",
"quote 1.0.21",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "0.4.30"
@ -942,6 +1027,12 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "structmeta"
version = "0.1.5"
@ -1068,6 +1159,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
[[package]]
name = "thiserror"
version = "1.0.35"
@ -1102,6 +1199,7 @@ dependencies = [
name = "tvix-eval"
version = "0.1.0"
dependencies = [
"clap 3.2.22",
"codemap",
"codemap-diagnostic",
"criterion",
@ -1150,6 +1248,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.3.2"

View file

@ -23,6 +23,7 @@ codemap = "0.1.3"
codemap-diagnostic = "0.1.1"
proptest = { version = "1.0.0", default_features = false, features = ["std", "alloc", "break-dead-code", "tempfile"], optional = true }
test-strategy = { version = "0.2.1", optional = true }
clap = { version = "3.2.22", optional = true, features = ["derive", "env"] }
# rnix has not been released in a while (as of 2022-09-18), we will
# use it from git.
@ -45,7 +46,7 @@ default = [ "repl", "arbitrary" ]
nix_tests = []
# Enables building the binary (tvix-eval REPL)
repl = [ "dep:rustyline" ]
repl = [ "dep:rustyline", "dep:clap" ]
# Enables Arbitrary impls for internal types (required to run tests)
arbitrary = [ "proptest", "test-strategy" ]

View file

@ -3,12 +3,20 @@ use itertools::Itertools;
use tvix_eval::interpret;
fn eval_literals(c: &mut Criterion) {
c.bench_function("int", |b| b.iter(|| black_box(interpret("42", None))));
c.bench_function("int", |b| {
b.iter(|| black_box(interpret("42", None, Default::default())))
});
}
fn eval_merge_attrs(c: &mut Criterion) {
c.bench_function("merge small attrs", |b| {
b.iter(|| black_box(interpret("{ a = 1; b = 2; } // { c = 3; }", None)))
b.iter(|| {
black_box(interpret(
"{ a = 1; b = 2; } // { c = 3; }",
None,
Default::default(),
))
})
});
c.bench_function("merge large attrs with small attrs", |b| {
@ -17,7 +25,7 @@ fn eval_merge_attrs(c: &mut Criterion) {
(0..10000).map(|n| format!("a{n} = {n};")).join(" ")
);
let expr = format!("{large_attrs} // {{ c = 3; }}");
b.iter(move || black_box(interpret(&expr, None)))
b.iter(move || black_box(interpret(&expr, None, Default::default())))
});
}

View file

@ -7,7 +7,24 @@ use crate::{
value::Value,
};
pub fn interpret(code: &str, location: Option<PathBuf>) -> EvalResult<Value> {
/// Runtime options for the Tvix interpreter
#[derive(Debug, Clone, Copy, 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,
}
pub fn interpret(code: &str, location: Option<PathBuf>, options: Options) -> EvalResult<Value> {
let mut codemap = codemap::CodeMap::new();
let file = codemap.add_file(
location
@ -37,11 +54,11 @@ pub fn interpret(code: &str, location: Option<PathBuf>) -> EvalResult<Value> {
.expr()
.expect("expression should exist if no errors occured");
if std::env::var("TVIX_DISPLAY_AST").is_ok() {
if options.display_ast {
println!("{:?}", root_expr);
}
let result = if std::env::var("TVIX_DUMP_BYTECODE").is_ok() {
let result = if options.dump_bytecode {
crate::compiler::compile(
root_expr,
location,
@ -76,7 +93,7 @@ pub fn interpret(code: &str, location: Option<PathBuf>) -> EvalResult<Value> {
return Err(err.clone());
}
if std::env::var("TVIX_TRACE_RUNTIME").is_ok() {
if options.trace_runtime {
crate::vm::run_lambda(&mut TracingObserver::new(std::io::stderr()), result.lambda)
} else {
crate::vm::run_lambda(&mut NoOpObserver::default(), result.lambda)

View file

@ -21,6 +21,6 @@ mod tests;
pub use crate::builtins::global_builtins;
pub use crate::compiler::compile;
pub use crate::errors::EvalResult;
pub use crate::eval::interpret;
pub use crate::eval::{interpret, Options};
pub use crate::value::Value;
pub use crate::vm::run_lambda;

View file

@ -1,30 +1,35 @@
use std::{
env, fs,
fs,
path::{Path, PathBuf},
process,
};
use clap::Parser;
use rustyline::{error::ReadlineError, Editor};
fn main() {
let mut args = env::args();
if args.len() > 2 {
println!("Usage: tvix-eval [script]");
process::exit(1);
}
#[derive(Parser)]
struct Args {
/// Path to a script to evaluate
script: Option<PathBuf>,
if let Some(file) = args.nth(1) {
run_file(&file);
#[clap(flatten)]
eval_options: tvix_eval::Options,
}
fn main() {
let args = Args::parse();
if let Some(file) = &args.script {
run_file(file, args.eval_options)
} else {
run_prompt();
run_prompt(args.eval_options)
}
}
fn run_file(file: &str) {
fn run_file(file: &Path, eval_options: tvix_eval::Options) {
let contents = fs::read_to_string(file).expect("failed to read the input file");
let path = Path::new(file).to_owned();
match tvix_eval::interpret(&contents, Some(path)) {
match tvix_eval::interpret(&contents, Some(path), eval_options) {
Ok(result) => println!("=> {} :: {}", result, result.type_of()),
Err(err) => eprintln!("{}", err),
}
@ -38,7 +43,7 @@ fn state_dir() -> Option<PathBuf> {
path
}
fn run_prompt() {
fn run_prompt(eval_options: tvix_eval::Options) {
let mut rl = Editor::<()>::new().expect("should be able to launch rustyline");
let history_path = match state_dir() {
@ -63,7 +68,7 @@ fn run_prompt() {
}
rl.add_history_entry(&line);
match tvix_eval::interpret(&line, None) {
match tvix_eval::interpret(&line, None, eval_options) {
Ok(result) => {
println!("=> {} :: {}", result, result.type_of());
}

View file

@ -12,7 +12,8 @@ fn eval_okay_test(code_path: &str) {
let code = std::fs::read_to_string(code_path).expect("should be able to read test code");
let exp = std::fs::read_to_string(exp_path).expect("should be able to read test expectation");
let result = interpret(&code, None).expect("evaluation of eval-okay test should succeed");
let result = interpret(&code, None, Default::default())
.expect("evaluation of eval-okay test should succeed");
let result_str = format!("{}", result);
assert_eq!(
@ -28,7 +29,8 @@ fn eval_okay_test(code_path: &str) {
fn identity(code_path: &str) {
let code = std::fs::read_to_string(code_path).expect("should be able to read test code");
let result = interpret(&code, None).expect("evaluation of identity test should succeed");
let result = interpret(&code, None, Default::default())
.expect("evaluation of identity test should succeed");
let result_str = format!("{}", result);
assert_eq!(

View file

@ -40,7 +40,9 @@ fn nix_eval(expr: &str) -> String {
#[track_caller]
fn compare_eval(expr: &str) {
let nix_result = nix_eval(expr);
let tvix_result = tvix_eval::interpret(expr, None).unwrap().to_string();
let tvix_result = tvix_eval::interpret(expr, None, Default::default())
.unwrap()
.to_string();
assert_eq!(nix_result.trim(), tvix_result);
}