use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
use clap::Parser;
use miette::{IntoDiagnostic, WrapErr};
use xqasm::assemble_source;
use xqvm::Program;
use xqvm::{JsonTracer, RegVal, TextTracer, Vm};
#[derive(Clone, Debug, clap::ValueEnum)]
enum TraceFormat {
Text,
Json,
}
#[derive(Debug, Parser)]
pub(crate) struct Args {
#[arg(long)]
text: bool,
#[arg(long, value_delimiter = ',')]
calldata: Vec<i64>,
#[arg(long, default_value = "16")]
outputs: usize,
#[arg(long, default_value = "10000000")]
step_limit: u64,
#[arg(long)]
trace: bool,
#[arg(long, default_value = "text", requires = "trace")]
trace_format: TraceFormat,
#[arg(long, requires = "trace")]
trace_file: Option<PathBuf>,
file: PathBuf,
}
pub(crate) fn exec(args: Args) -> miette::Result<()> {
let source = std::fs::read(&args.file)
.into_diagnostic()
.wrap_err_with(|| format!("failed to read '{}'", args.file.display()))?;
let program: Program = if args.text {
let text = String::from_utf8(source)
.into_diagnostic()
.wrap_err("assembly file is not valid UTF-8")?;
assemble_source(&text)
.map_err(|e| miette::miette!("{e}"))
.wrap_err("assembly failed")?
} else {
Program::decode(&source)
.into_diagnostic()
.wrap_err("failed to decode program")?
};
let calldata: Vec<RegVal> = args.calldata.into_iter().map(RegVal::Int).collect();
let mut vm = Vm::new();
let _ = vm.set_calldata(calldata).set_output_slots(args.outputs);
if args.step_limit > 0 {
let _ = vm.set_step_limit(args.step_limit);
}
let file_name = args.file.to_string_lossy();
if args.trace {
let writer: Box<dyn Write> = match &args.trace_file {
Some(path) => {
let file = File::create(path)
.into_diagnostic()
.wrap_err_with(|| format!("failed to create '{}'", path.display()))?;
Box::new(BufWriter::new(file))
}
None => Box::new(BufWriter::new(std::io::stderr())),
};
match args.trace_format {
TraceFormat::Text => {
let mut tracer = TextTracer::new(writer);
vm.run_trace(&mut tracer, &program)
.map_err(|e| e.into_diagnostic(&program, &file_name))?;
}
TraceFormat::Json => {
let mut tracer = JsonTracer::new(writer);
vm.run_trace(&mut tracer, &program)
.map_err(|e| e.into_diagnostic(&program, &file_name))?;
}
}
} else {
vm.run(&program)
.map_err(|e| e.into_diagnostic(&program, &file_name))?;
}
print_results(&vm);
Ok(())
}
fn print_results(vm: &Vm) {
let outputs = vm.outputs();
let has_outputs = outputs.iter().any(|v| !matches!(v, RegVal::Unset));
if has_outputs {
println!("outputs:");
for (i, v) in outputs.iter().enumerate() {
if !matches!(v, RegVal::Unset) {
println!(" [{i}] = {v:?}");
}
}
}
let stack = vm.stack();
if !stack.is_empty() {
println!("stack (bottom to top):");
for v in stack {
println!(" {v}");
}
}
}