use clap::{Parser, Subcommand};
use miette::IntoDiagnostic as _;
use crate::commands;
use crate::session::{self, VersioningFlags};
const DEFAULT_STACK_SIZE: usize = 64 * 1024 * 1024;
#[derive(Parser)]
#[command(name = "cljrs", version, about, long_about = None)]
pub struct Cli {
#[arg(
long,
global = true,
value_name = "MB",
help = "Set thread stack size (default 64MB)"
)]
stack_size_mb: Option<usize>,
#[arg(long, global = true, help = "Enable debug logging")]
debug: bool,
#[arg(long, global = true, help = "Enable trace logging (implies --debug)")]
trace: bool,
#[arg(long, global = true)]
verify_commit_signatures: bool,
#[arg(long, global = true)]
enforce_native_versions: bool,
#[arg(
long,
global = true,
value_name = "N",
help = "JIT invocation threshold (0 to disable, default 1000)"
)]
jit_threshold: Option<u32>,
#[arg(
long,
global = true,
value_name = "N",
help = "Background IR lowering threshold (0 to disable, default 50)"
)]
ir_threshold: Option<u32>,
#[arg(short = 'X', global = true, value_name = "LEVEL:FEATURES")]
x_flags: Vec<String>,
#[arg(
long = "gc-stats",
global = true,
value_name = "FILE",
num_args = 0..=1,
default_missing_value = "",
)]
gc_stats: Option<String>,
#[arg(
long = "jit-stats",
global = true,
value_name = "FILE",
num_args = 0..=1,
default_missing_value = "",
)]
jit_stats: Option<String>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
Run(commands::run::Args),
Repl(commands::repl::Args),
Compile(commands::compile::Args),
Eval(commands::eval::Args),
Ir {
#[command(subcommand)]
command: commands::ir::IrCommands,
},
Test(commands::test::Args),
Deps {
#[command(subcommand)]
command: commands::deps::DepsCommands,
},
BuildNative(commands::build_native::Args),
Lsp,
Nrepl(commands::nrepl::Args),
}
fn init_tracing(cli: &Cli) -> miette::Result<()> {
let default_level = if cli.trace {
tracing::Level::TRACE
} else if cli.debug {
tracing::Level::DEBUG
} else {
tracing::Level::INFO
};
let mut filter =
cljrs_runtime::logging::apply_rust_log(cljrs_runtime::logging::base_filter(default_level));
for flag in &cli.x_flags {
filter = cljrs_runtime::logging::apply_x_flag(filter, flag)
.map_err(|e| miette::miette!("invalid -X flag: {e}"))?;
}
cljrs_runtime::logging::init(filter);
Ok(())
}
pub fn main() -> miette::Result<()> {
miette::set_hook(Box::new(|_| {
Box::new(
miette::MietteHandlerOpts::new()
.terminal_links(true)
.build(),
)
}))
.into_diagnostic()?;
let cli = Cli::parse();
init_tracing(&cli)?;
let stack_size = cli
.stack_size_mb
.map(|mb| mb * 1024 * 1024)
.unwrap_or(DEFAULT_STACK_SIZE);
let builder = std::thread::Builder::new()
.name("cljrs-main".into())
.stack_size(stack_size);
let handle = builder
.spawn(move || {
#[cfg(feature = "async")]
{
session::with_async_driver(|| run(cli))
}
#[cfg(not(feature = "async"))]
run(cli)
})
.into_diagnostic()?;
let result: miette::Result<i32> = handle.join().unwrap_or_else(|e| {
eprintln!("cljrs: thread panicked: {e:?}");
std::process::exit(1);
});
match result {
Ok(0) => Ok(()),
Ok(code) => std::process::exit(code),
Err(e) => Err(e),
}
}
fn run(cli: Cli) -> miette::Result<i32> {
let _mutator = cljrs_gc::register_mutator();
session::configure_jit(cli.jit_threshold);
match cli.ir_threshold {
Some(0) => cljrs_eval::set_ir_threshold(u32::MAX),
Some(t) => cljrs_eval::set_ir_threshold(t),
None => {}
}
let gc_stats_target = cli.gc_stats.clone();
let jit_stats_target = cli.jit_stats.clone();
let versioning = VersioningFlags {
verify_commit_signatures: cli.verify_commit_signatures,
enforce_native_versions: cli.enforce_native_versions,
};
let supports_gc_stats = matches!(
&cli.command,
Commands::Run(..) | Commands::Eval(..) | Commands::Test(..),
);
let result = run_command(cli.command, versioning);
if supports_gc_stats
&& let Some(target) = gc_stats_target.as_deref()
&& let Err(e) = write_gc_stats(target)
{
eprintln!("cljrs: failed to write GC stats: {e}");
}
if supports_gc_stats
&& let Some(target) = jit_stats_target.as_deref()
&& let Err(e) = write_jit_stats(target)
{
eprintln!("cljrs: failed to write JIT stats: {e}");
}
result
}
fn run_command(command: Commands, versioning: VersioningFlags) -> miette::Result<i32> {
match command {
Commands::Run(args) => commands::run::run(args, versioning),
Commands::Repl(args) => commands::repl::run(args, versioning),
Commands::Compile(args) => commands::compile::run(args, versioning),
Commands::Eval(args) => commands::eval::run(args, versioning),
Commands::Ir { command } => commands::ir::run(command),
Commands::Test(args) => commands::test::run(args, versioning),
Commands::Deps { command } => commands::deps::run(command),
Commands::BuildNative(args) => commands::build_native::run(args),
Commands::Lsp => commands::lsp::run(),
Commands::Nrepl(args) => commands::nrepl::run(args, versioning),
}
}
fn write_jit_stats(target: &str) -> std::io::Result<()> {
let snapshot = cljrs_compiler::rt_abi::jit_stats::snapshot();
if target.is_empty() {
println!("{snapshot}");
Ok(())
} else {
std::fs::write(target, snapshot)
}
}
fn write_gc_stats(target: &str) -> std::io::Result<()> {
let snapshot = cljrs_gc::GC_STATS.snapshot();
if target.is_empty() {
println!("{snapshot}");
Ok(())
} else {
std::fs::write(target, format!("{snapshot}\n"))
}
}