use std::ffi::OsString;
use std::process::ExitCode;
use std::sync::Arc;
use sim_run_core::Bootloader;
const COMMANDS_HELP: &str = "\
The SIM runtime command line.
Commands:
sim repl Start a read-eval-print loop (Lisp).
sim webui Serve the browser Web UI.
sim mcp Serve an MCP server over stdio.
sim <expression> Evaluate a payload through the boot codec.
";
fn main() -> ExitCode {
let args = normalize_aliases(std::env::args_os().collect());
if is_help_request(&args) {
print!("{COMMANDS_HELP}");
}
if is_version_request(&args) {
println!("sim {}", env!("CARGO_PKG_VERSION"));
return ExitCode::SUCCESS;
}
let bootloader = if verb_is(&args, "repl") {
configure_repl_bootloader(Bootloader::standard())
} else {
sim_web_shell::configure_web_bootloader(sim_lib_mcp::configure_mcp_bootloader(
Bootloader::standard().with_context(install_eval_stack),
))
};
match bootloader.run(args) {
Ok(0) => ExitCode::SUCCESS,
Ok(code) => ExitCode::from(code as u8),
Err(err) => {
eprintln!("sim: {err}");
ExitCode::from(2)
}
}
}
fn install_eval_stack(cx: &mut sim::kernel::Cx) {
cx.set_eval_policy(Arc::new(sim::kernel::EagerPolicy));
sim::runtime::install_core_runtime(cx);
sim::numbers_prelude::NumbersPreludeLib::new()
.install_all(cx)
.expect("numbers prelude installs");
}
fn configure_repl_bootloader(loader: Bootloader) -> Bootloader {
loader
.with_context(install_eval_stack)
.host_lib("codec/lisp", || {
Box::new(
sim::codec_lisp::LispCodecLib::new(sim::kernel::CodecId(1))
.expect("repl lisp boot codec builds"),
)
})
.host_verb(
"repl",
"lib/repl",
|| Box::new(sim_lib_repl::ReplLib::new()),
)
}
fn verb_is(args: &[OsString], want: &str) -> bool {
args.iter()
.skip(1)
.find(|arg| !arg.to_string_lossy().starts_with('-'))
.is_some_and(|arg| arg == want)
}
fn is_help_request(args: &[OsString]) -> bool {
let has_verb = args
.iter()
.skip(1)
.any(|arg| !arg.to_string_lossy().starts_with('-'));
if has_verb {
return false;
}
args.iter().skip(1).any(|arg| {
let arg = arg.to_string_lossy();
arg == "--help" || arg == "-h"
})
}
fn is_version_request(args: &[OsString]) -> bool {
match args
.iter()
.skip(1)
.find(|arg| !arg.to_string_lossy().starts_with('-'))
{
Some(verb) => verb == "version",
None => args.iter().skip(1).any(|arg| {
let arg = arg.to_string_lossy();
arg == "--version" || arg == "-V"
}),
}
}
fn normalize_aliases(mut args: Vec<OsString>) -> Vec<OsString> {
let Some(i) = args
.iter()
.enumerate()
.skip(1)
.find(|(_, arg)| !arg.to_string_lossy().starts_with('-'))
.map(|(i, _)| i)
else {
return args;
};
match args[i].to_string_lossy().as_ref() {
"mcp" if !has_codec(&args) => splice_codec(&mut args, i, "mcp"),
"serve" if args.get(i + 1).is_some_and(|v| v == "mcp") => {
args.remove(i + 1);
args[i] = OsString::from("mcp");
if !has_codec(&args) {
splice_codec(&mut args, i, "mcp");
}
}
"webui" => {
args[i] = OsString::from("serve");
if !has_codec(&args) {
splice_codec(&mut args, i, "lisp");
}
}
"serve" if !has_codec(&args) => splice_codec(&mut args, i, "lisp"),
_ => {}
}
args
}
fn splice_codec(args: &mut Vec<OsString>, at: usize, codec: &str) {
args.splice(at..at, [OsString::from("--codec"), OsString::from(codec)]);
}
fn has_codec(args: &[OsString]) -> bool {
args.iter().any(|arg| {
let arg = arg.to_string_lossy();
arg == "--codec" || arg.starts_with("--codec=")
})
}