use std::ffi::{OsStr, OsString};
use clap::{FromArgMatches, Parser, Subcommand};
use miden_assembly::diagnostics::Report;
#[cfg(feature = "tracing-forest")]
use tracing_forest::ForestLayer;
#[cfg(not(feature = "tracing-forest"))]
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::{EnvFilter, prelude::*};
mod cli;
#[derive(Parser, Debug)]
#[command(
name = "miden-vm",
about = "The Miden virtual machine",
version,
rename_all = "kebab-case"
)]
#[command(multicall(true))]
pub struct MidenVmCli {
#[command(subcommand)]
behavior: Behavior,
}
impl TryFrom<MidenVmCli> for Cli {
type Error = Report;
fn try_from(value: MidenVmCli) -> Result<Self, Self::Error> {
match value.behavior {
Behavior::MidenVm { cli } => Ok(cli),
Behavior::External(args) => {
let used_alias = args.first();
let is_known_alias = used_alias
.map(|command_name| command_name.as_os_str() == OsStr::new("miden vm"))
.unwrap_or(false);
if !is_known_alias {
let error_message = used_alias
.map(|command_name| {
format!(
"Called the CLI with an unknown alias '{}'",
command_name.to_string_lossy()
)
})
.unwrap_or(String::from("Called the CLI under an empty alias"));
return Err(Report::msg(error_message));
}
Ok(Cli::parse_from(args).set_external())
},
}
}
}
#[derive(Debug, Subcommand)]
#[command(rename_all = "kebab-case")]
enum Behavior {
MidenVm {
#[command(flatten)]
cli: Cli,
},
#[command(external_subcommand)]
External(Vec<OsString>),
}
#[derive(Parser, Debug)]
#[command(name = "miden-vm")]
pub struct Cli {
#[command(subcommand)]
action: Actions,
#[arg(skip)]
external: bool,
}
#[derive(Debug, Parser)]
pub enum Actions {
Compile(cli::CompileCmd),
Bundle(cli::BundleCmd),
Prove(cli::ProveCmd),
Run(cli::RunCmd),
Verify(cli::VerifyCmd),
}
impl Cli {
pub fn execute(&self) -> Result<(), Report> {
match &self.action {
Actions::Compile(compile) => compile.execute(),
Actions::Bundle(compile) => compile.execute(),
Actions::Prove(prove) => prove.execute(),
Actions::Run(run) => run.execute(),
Actions::Verify(verify) => verify.execute(),
}
}
fn set_external(mut self) -> Self {
self.external = true;
self
}
}
pub fn main() -> Result<(), Report> {
let cli = <MidenVmCli as clap::CommandFactory>::command();
let matches = cli.get_matches();
let parsed = MidenVmCli::from_arg_matches(&matches).unwrap_or_else(|err| err.exit());
let cli: Cli = parsed.try_into()?;
initialize_diagnostics();
if std::env::var("MIDEN_LOG").is_err() {
unsafe { std::env::set_var("MIDEN_LOG", "warn") };
}
let registry =
tracing_subscriber::registry::Registry::default().with(EnvFilter::from_env("MIDEN_LOG"));
#[cfg(feature = "tracing-forest")]
registry.with(ForestLayer::default()).init();
#[cfg(not(feature = "tracing-forest"))]
{
let format = tracing_subscriber::fmt::layer()
.with_level(false)
.with_target(false)
.with_thread_names(false)
.with_span_events(FmtSpan::CLOSE)
.with_ansi(false)
.compact();
registry.with(format).init();
}
cli.execute()
}
fn initialize_diagnostics() {
use miden_assembly::diagnostics::reporting::{self, ReportHandlerOpts};
#[cfg(feature = "std")]
{
let result = reporting::set_hook(Box::new(|_| Box::new(ReportHandlerOpts::new().build())));
if result.is_ok() {
reporting::set_panic_hook();
}
}
#[cfg(not(feature = "std"))]
{
let _ = reporting::set_hook(Box::new(|_| Box::new(ReportHandlerOpts::new().build())));
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_multicall() {
let miden_vm_command = MidenVmCli::try_parse_from(["miden-vm", "run", "my_file.masm"])
.expect("failed to parse commands");
assert!(matches!(
miden_vm_command.behavior,
Behavior::MidenVm { cli: Cli { external: false, .. } }
));
let cli: Cli = miden_vm_command.try_into().expect("Failed to turn MidenVmCli into Cli.");
assert!(matches!(cli, Cli { external: false, .. }));
let external = MidenVmCli::try_parse_from(["miden vm", "run", "my_file.masm"])
.expect("failed to parse commands");
assert!(matches!(external.behavior, Behavior::External(_)));
let cli: Cli = external.try_into().expect("Failed to turn MidenVmCli into Cli.");
assert!(matches!(cli, Cli { external: true, .. }));
}
}