152 lines
4.3 KiB
Rust
152 lines
4.3 KiB
Rust
use std::path::PathBuf;
|
|
use std::process::exit;
|
|
|
|
use anyhow::Result;
|
|
use clap::{Args, Parser, Subcommand};
|
|
use fix::Evaluator;
|
|
use fix_error::Source;
|
|
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<String>,
|
|
#[clap(short, long)]
|
|
file: Option<PathBuf>,
|
|
}
|
|
|
|
fn run_compile(eval: &mut Evaluator, 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 eval.compile_bytecode(src) {
|
|
Ok(ip) => {
|
|
if !silent {
|
|
println!("{}", eval.disassemble_colored(ip));
|
|
}
|
|
}
|
|
Err(err) => {
|
|
eprintln!("{:?}", miette::Report::new(*err));
|
|
exit(1);
|
|
}
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
fn run_eval(eval: &mut Evaluator, 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 eval.eval_deep(src) {
|
|
Ok(value) => {
|
|
println!("{}", value.display_compat());
|
|
}
|
|
Err(err) => {
|
|
eprintln!("{:?}", miette::Report::new(*err));
|
|
exit(1);
|
|
}
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
fn run_repl(eval: &mut Evaluator) -> 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("fix-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 eval.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 eval.eval_repl(src, &scope) {
|
|
Ok(value) => println!("{value}"),
|
|
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
|
}
|
|
}
|
|
} else {
|
|
let src = Source::new_repl(line)?;
|
|
match eval.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 eval = Evaluator::new();
|
|
|
|
match cli.command {
|
|
Command::Compile { source, silent } => run_compile(&mut eval, source, silent),
|
|
Command::Eval { source } => run_eval(&mut eval, source),
|
|
Command::Repl => run_repl(&mut eval),
|
|
}
|
|
}
|