about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGriffin Smith <root@gws.fyi>2022-09-18T19·59-0400
committerclbot <clbot@tvl.fyi>2022-09-18T22·08+0000
commite720545e5b32683a3cdb135b6004a06304e025aa (patch)
tree33414930f82266722a53a184604a694448bd6a2b
parent6f70f325138b48f2c9b03a2103371663cb210d7c (diff)
refactor(tvix/eval): use Clap for arg+env parsing r/4911
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>
-rw-r--r--tvix/eval/Cargo.lock108
-rw-r--r--tvix/eval/Cargo.toml3
-rw-r--r--tvix/eval/benches/eval.rs14
-rw-r--r--tvix/eval/src/eval.rs25
-rw-r--r--tvix/eval/src/lib.rs2
-rw-r--r--tvix/eval/src/main.rs33
-rw-r--r--tvix/eval/src/tests/mod.rs6
-rw-r--r--tvix/eval/tests/nix_oracle.rs4
8 files changed, 167 insertions, 28 deletions
diff --git a/tvix/eval/Cargo.lock b/tvix/eval/Cargo.lock
index 2804e7f1298f..71c6525b0a41 100644
--- a/tvix/eval/Cargo.lock
+++ b/tvix/eval/Cargo.lock
@@ -74,11 +74,50 @@ 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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -120,7 +159,7 @@ checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
 dependencies = [
  "atty",
  "cast",
- "clap",
+ "clap 2.34.0",
  "criterion-plot",
  "csv",
  "itertools",
@@ -371,6 +410,12 @@ 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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -380,6 +425,16 @@ dependencies = [
 ]
 
 [[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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -518,6 +573,12 @@ 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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -579,6 +640,30 @@ dependencies = [
 ]
 
 [[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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -943,6 +1028,12 @@ 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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1069,6 +1160,12 @@ dependencies = [
 ]
 
 [[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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1102,6 +1199,7 @@ dependencies = [
 name = "tvix-eval"
 version = "0.1.0"
 dependencies = [
+ "clap 3.2.22",
  "codemap",
  "codemap-diagnostic",
  "criterion",
@@ -1151,6 +1249,12 @@ 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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/tvix/eval/Cargo.toml b/tvix/eval/Cargo.toml
index 1c56f3171645..bf34159656aa 100644
--- a/tvix/eval/Cargo.toml
+++ b/tvix/eval/Cargo.toml
@@ -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" ]
diff --git a/tvix/eval/benches/eval.rs b/tvix/eval/benches/eval.rs
index 9be381e0f803..3e4da75936d0 100644
--- a/tvix/eval/benches/eval.rs
+++ b/tvix/eval/benches/eval.rs
@@ -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())))
     });
 }
 
diff --git a/tvix/eval/src/eval.rs b/tvix/eval/src/eval.rs
index 6510ef0afb2b..21591802a443 100644
--- a/tvix/eval/src/eval.rs
+++ b/tvix/eval/src/eval.rs
@@ -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)
diff --git a/tvix/eval/src/lib.rs b/tvix/eval/src/lib.rs
index 1c3fea3d5b9f..d7255af14e68 100644
--- a/tvix/eval/src/lib.rs
+++ b/tvix/eval/src/lib.rs
@@ -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;
diff --git a/tvix/eval/src/main.rs b/tvix/eval/src/main.rs
index 177d4f2ec8e6..351554c2d538 100644
--- a/tvix/eval/src/main.rs
+++ b/tvix/eval/src/main.rs
@@ -1,30 +1,35 @@
 use std::{
-    env, fs,
+    fs,
     path::{Path, PathBuf},
-    process,
 };
 
+use clap::Parser;
 use rustyline::{error::ReadlineError, Editor};
 
+#[derive(Parser)]
+struct Args {
+    /// Path to a script to evaluate
+    script: Option<PathBuf>,
+
+    #[clap(flatten)]
+    eval_options: tvix_eval::Options,
+}
+
 fn main() {
-    let mut args = env::args();
-    if args.len() > 2 {
-        println!("Usage: tvix-eval [script]");
-        process::exit(1);
-    }
+    let args = Args::parse();
 
-    if let Some(file) = args.nth(1) {
-        run_file(&file);
+    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());
                     }
diff --git a/tvix/eval/src/tests/mod.rs b/tvix/eval/src/tests/mod.rs
index ec9ee0d1c9ea..49ca35973e56 100644
--- a/tvix/eval/src/tests/mod.rs
+++ b/tvix/eval/src/tests/mod.rs
@@ -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!(
diff --git a/tvix/eval/tests/nix_oracle.rs b/tvix/eval/tests/nix_oracle.rs
index 1091d6adf0ee..61f2be674e27 100644
--- a/tvix/eval/tests/nix_oracle.rs
+++ b/tvix/eval/tests/nix_oracle.rs
@@ -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);
 }