about summary refs log tree commit diff
path: root/tvix/cli/src
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2023-01-05T09·56+0300
committertazjin <tazjin@tvl.su>2023-01-06T12·23+0000
commit0e421a16c584db34742b8db644b80c9c9a5faf02 (patch)
tree910f358554af46aa7554ed9c996d54c31f9f3fbf /tvix/cli/src
parent5926a05f4663bd11cef64598d1604b61fb60bede (diff)
feat(tvix/cli): add `--compile-only` "linter" functionality r/5600
With this, tvix/cli can be run on files and only produce compiler
errors and warnings.

Change-Id: I5dd9229fc065647787daafd17d7c1540579a1d98
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7764
Reviewed-by: flokli <flokli@flokli.de>
Tested-by: BuildkiteCI
Diffstat (limited to 'tvix/cli/src')
-rw-r--r--tvix/cli/src/main.rs53
1 files changed, 52 insertions, 1 deletions
diff --git a/tvix/cli/src/main.rs b/tvix/cli/src/main.rs
index 6b9f4abe861b..eec4d8bbb2ac 100644
--- a/tvix/cli/src/main.rs
+++ b/tvix/cli/src/main.rs
@@ -27,6 +27,11 @@ struct Args {
     #[clap(long, env = "TVIX_TRACE_RUNTIME")]
     trace_runtime: bool,
 
+    /// Only compile, but do not execute code. This will make Tvix act
+    /// sort of like a linter.
+    #[clap(long)]
+    compile_only: bool,
+
     /// A colon-separated list of directories to use to resolve `<...>`-style paths
     #[clap(long, short = 'I', env = "NIX_PATH")]
     nix_search_path: Option<String>,
@@ -86,6 +91,42 @@ fn interpret(code: &str, path: Option<PathBuf>, args: &Args, explain: bool) -> b
     result.errors.is_empty()
 }
 
+/// 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 {
+    let mut eval = tvix_eval::Evaluation::new_impure(code, path);
+    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!");
+    }
+
+    let result = eval.compile_only();
+
+    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()
+}
+
 fn main() {
     let args = Args::parse();
 
@@ -106,7 +147,13 @@ fn run_file(mut path: PathBuf, args: &Args) {
     }
     let contents = fs::read_to_string(&path).expect("failed to read the input file");
 
-    if !interpret(&contents, Some(path), args, false) {
+    let success = if args.compile_only {
+        lint(&contents, Some(path), args)
+    } else {
+        interpret(&contents, Some(path), args, false)
+    };
+
+    if !success {
         std::process::exit(1);
     }
 }
@@ -130,6 +177,10 @@ fn state_dir() -> Option<PathBuf> {
 fn run_prompt(args: &Args) {
     let mut rl = Editor::<()>::new().expect("should be able to launch rustyline");
 
+    if args.compile_only {
+        eprintln!("warning: `--compile-only` has no effect on REPL usage!");
+    }
+
     let history_path = match state_dir() {
         // Attempt to set up these paths, but do not hard fail if it
         // doesn't work.