use std::path::PathBuf; use std::process::exit; use anyhow::Result; use clap::{Args, Parser, Subcommand}; use fix::error::Source; use fix::runtime::Runtime; use hashbrown::HashSet; use rustyline::DefaultEditor; use rustyline::error::ReadlineError; #[derive(Parser)] #[command(name = "fix", about = "Nix expression evaluator")] struct Cli { #[command(subcommand)] command: Command, } #[derive(Subcommand)] enum Command { Compile { #[clap(flatten)] source: ExprSource, #[arg(long)] silent: bool, }, Eval { #[clap(flatten)] source: ExprSource, }, Repl, } #[derive(Args)] #[group(required = true, multiple = false)] struct ExprSource { #[clap(short, long)] expr: Option, #[clap(short, long)] file: Option, } fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<()> { let src = if let Some(expr) = src.expr { Source::new_eval(expr)? } else if let Some(file) = src.file { Source::new_file(file)? } else { unreachable!() }; match runtime.compile_bytecode(src) { Ok(ip) => { if !silent { println!("{}", runtime.disassemble_colored(ip)); } } Err(err) => { eprintln!("{:?}", miette::Report::new(*err)); exit(1); } }; Ok(()) } fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> { let src = if let Some(expr) = src.expr { Source::new_eval(expr)? } else if let Some(file) = src.file { Source::new_file(file)? } else { unreachable!() }; match runtime.eval_deep(src) { Ok(value) => { println!("{}", value.display_compat()); } Err(err) => { eprintln!("{:?}", miette::Report::new(*err)); exit(1); } }; Ok(()) } fn run_repl(runtime: &mut Runtime) -> Result<()> { let mut rl = DefaultEditor::new()?; let mut scope = HashSet::new(); const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$"); loop { let readline = rl.readline("nix-js-repl> "); match readline { Ok(line) => { if line.trim().is_empty() { continue; } let _ = rl.add_history_entry(line.as_str()); if let Some([Some(_), Some(ident), Some(rest)]) = RE.exec(&line) { if let Some(expr) = rest.strip_prefix('=') { let expr = expr.trim_start(); if expr.is_empty() { eprintln!("Error: missing expression after '='"); continue; } match runtime.add_binding(ident, expr, &mut scope) { Ok(value) => println!("{} = {}", ident, value), Err(err) => eprintln!("{:?}", miette::Report::new(*err)), } } else { let src = Source::new_repl(line)?; match runtime.eval_repl(src, &scope) { Ok(value) => println!("{value}"), Err(err) => eprintln!("{:?}", miette::Report::new(*err)), } } } else { let src = Source::new_repl(line)?; match runtime.eval_repl(src, &scope) { Ok(value) => println!("{value}"), Err(err) => eprintln!("{:?}", miette::Report::new(*err)), } } } Err(ReadlineError::Interrupted) => { println!(); } Err(ReadlineError::Eof) => { println!("CTRL-D"); break; } Err(err) => { eprintln!("Error: {err:?}"); break; } } } Ok(()) } fn main() -> Result<()> { fix::logging::init_logging(); let cli = Cli::parse(); let mut runtime = Runtime::new()?; match cli.command { Command::Compile { source, silent } => run_compile(&mut runtime, source, silent), Command::Eval { source } => run_eval(&mut runtime, source), Command::Repl => run_repl(&mut runtime), } }