use std::env;
use std::process;
#[path = "../bench/mod.rs"]
mod bench;
#[path = "../fuzz/mod.rs"]
mod fuzz;
use neyuki::*;
#[cfg(feature = "mimalloc")]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
const MAIN_STACK_SIZE: usize = 256 * 1024 * 1024;
fn main() {
let worker = std::thread::Builder::new()
.name("main".to_string())
.stack_size(MAIN_STACK_SIZE)
.spawn(run_cli)
.unwrap_or_else(|err| {
eprintln!("failed to start the main thread: {}", err);
process::exit(1);
});
if worker.join().is_err() {
process::exit(101);
}
}
const USAGE: &str = "Usage: neyuki <command> [arguments]
Commands:
run <file> [args...] Run a .nyk script or .nykb bytecode file
<file> [args...] Same as run
fmt [--check] <path> Format .nyk source files
lint [--json] [--stdin] <file>
Report errors and warnings without running
lsp Serve the Language Server Protocol on stdio
compile <file> [out] Compile to .nykb bytecode
build [project] Build a standalone app executable
test Run every script in tests/
repl Start an interactive session
pkg <command> Manage packages
dap Serve the Debug Adapter Protocol on stdio
disasm <file> Print the bytecode the compiler emits
dump-ir <file> Print the IR before and after optimization
assemble <file> [out] Assemble bytecode from its text form
bench Run the built-in benchmarks
fuzz [target] [--seconds N]
Run the fuzzers' regression cases, or fuzz a
target (file, tree, vm, libs, memory, all)
help, version";
fn run_cli() {
match app::find_own_bundle() {
Ok(Some(bundle)) => return run_app(bundle),
Ok(None) => {}
Err(err) => {
app::report_error(&format!("error: {}", err));
process::exit(1);
}
}
let mut args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("{}", USAGE);
process::exit(1);
}
if args[1].ends_with(".nyk") || args[1].ends_with(".nykb") {
args.insert(1, "run".to_string());
}
let action = &args[1];
if matches!(action.as_str(), "help" | "--help" | "-h") {
println!("{}", USAGE);
} else if matches!(action.as_str(), "version" | "--version" | "-V") {
println!("neyuki {}", env!("CARGO_PKG_VERSION"));
} else if action == "fmt" {
if let Err(err) = fmt::run_cli(&args[2..]) {
eprintln!("{}", err);
process::exit(1);
}
} else if action == "lint" {
if let Err(err) = lint::run_cli(&args[2..]) {
eprintln!("{}", err);
process::exit(1);
}
} else if action == "lsp" {
if let Err(err) = lsp::run_lsp_server() {
eprintln!("lsp error: {}", err);
process::exit(1);
}
} else if action == "compile" {
let Some(path) = args.get(2) else {
eprintln!("Usage: {} compile <path> [output]", args[0]);
process::exit(1);
};
match compiler::compile_file_to_bytecode(path) {
Ok(bytecode) => {
let out_path = args.get(3).cloned().unwrap_or_else(|| format!("{}b", path));
if let Err(err) = std::fs::write(&out_path, &bytecode) {
eprintln!("failed to write bytecode to {}: {}", out_path, err);
process::exit(1);
}
println!(
"compiled {} -> {} ({} bytes)",
path,
out_path,
bytecode.len()
);
}
Err(err) => {
if err.starts_with("error[") || err.starts_with("runtime error") {
eprint!("{}", err);
if !err.ends_with('\n') {
eprintln!();
}
} else {
eprintln!("{}: {}", path, err);
}
process::exit(1);
}
}
} else if action == "build" {
if let Err(err) = app::build::run_cli(&args[2..]) {
if err.starts_with("error[") {
eprint!("{}", err);
} else {
eprintln!("build error: {}", err);
}
process::exit(1);
}
} else if action == "run" {
let Some(path) = args.get(2) else {
eprintln!("Usage: {} run <path> [args...]", args[0]);
process::exit(1);
};
vm::libs::os::set_script_args(args[3..].to_vec());
if let Err(err) = vm::run_file(path) {
if err.starts_with("error[") || err.starts_with("runtime error") {
eprint!("{}", err);
if !err.ends_with('\n') {
eprintln!();
}
} else {
eprintln!("error: {}", err);
}
process::exit(1);
}
} else if action == "dump-ir" {
let Some(path) = args.get(2) else {
eprintln!("Usage: {} dump-ir <path>", args[0]);
process::exit(1);
};
match compiler::compile_file(path) {
Ok(stmts) => print!("{}", compiler::dump_ir(&stmts)),
Err(err) => {
eprintln!("{}: {}", path, err);
process::exit(1);
}
}
} else if action == "disasm" {
let Some(path) = args.get(2) else {
eprintln!("Usage: {} disasm <path>", args[0]);
process::exit(1);
};
let compiled = std::fs::read_to_string(path)
.map_err(|err| format!("failed to read {}: {}", path, err))
.and_then(|source| {
compiler::compile_program(&source, path, compiler::Options::UNCHECKED)
});
match compiled {
Ok(proto) => print!("{}", bytecode::disasm::disassemble_proto(&proto, 0)),
Err(err) => {
eprintln!("{}: {}", path, err);
process::exit(1);
}
}
} else if action == "assemble" {
let Some(path) = args.get(2) else {
eprintln!("Usage: {} assemble <assembly_file> [output]", args[0]);
process::exit(1);
};
let text = match std::fs::read_to_string(path) {
Ok(t) => t,
Err(e) => {
eprintln!("failed to read {}: {}", path, e);
process::exit(1);
}
};
let proto = match bytecode::parse_assembly(&text) {
Ok(p) => p,
Err(e) => {
eprintln!("assembly parse error: {}", e);
process::exit(1);
}
};
let out_path = args
.get(3)
.cloned()
.unwrap_or_else(|| format!("{}.bin", path));
let serialized = bytecode::serialize(&proto);
if let Err(e) = std::fs::write(&out_path, &serialized) {
eprintln!("failed to write {}: {}", out_path, e);
process::exit(1);
}
println!(
"assembled {} -> {} ({} bytes)",
path,
out_path,
serialized.len()
);
} else if action == "test" {
tests::run_all_tests();
} else if action == "fuzz" {
if let Err(err) = fuzz::driver::run_cli(&args[2..]) {
eprintln!("{}", err);
process::exit(1);
}
} else if action == "bench" {
bench::run_all_benchmarks();
} else if action == "repl" {
if let Err(err) = repl::run_repl() {
eprintln!("repl error: {}", err);
process::exit(1);
}
} else if action == "dap" {
if let Err(err) = dap::run_dap_session() {
eprintln!("dap error: {}", err);
process::exit(1);
}
} else if action == "pkg" {
if let Err(err) = pkg::run_pkg_cli(&args[2..]) {
eprintln!("pkg error: {}", err);
process::exit(1);
}
} else {
eprintln!(
"Unknown command: {}
{}",
action, USAGE
);
process::exit(1);
}
}
fn run_app(bundle: app::Bundle) {
app::install(Some(bundle));
vm::libs::os::set_script_args(env::args().skip(1).collect());
if let Err(err) = app::run_installed() {
let message = if err.starts_with("error[") || err.starts_with("runtime error") {
err.trim_end().to_string()
} else {
format!("error: {}", err)
};
app::report_error(&message);
process::exit(1);
}
}