//! rustnim — Rust -> Nim transpiler. //! //! Usage: rustnim [-o ] mod fmt; mod lower; mod ty; use std::path::PathBuf; use std::process::ExitCode; fn main() -> ExitCode { match run() { Ok(()) => ExitCode::SUCCESS, Err(e) => { eprintln!("rustnim: {e}"); // Exiting non-zero on any failure is load-bearing: the entire // point of this project is that a transpiler must never report // success for work it did not do. ExitCode::FAILURE } } } fn run() -> Result<(), String> { let mut args = std::env::args_os().skip(1); let mut inputs: Vec = Vec::new(); let mut output: Option = None; let mut features: Vec = Vec::new(); while let Some(a) = args.next() { match a.to_string_lossy().as_ref() { "-o" | "--output" => { output = Some( args.next() .ok_or("`-o` needs a path")? .into(), ); } "--cfg" => { let v = args.next().ok_or("`--cfg` needs an argument")?; let v = v.to_string_lossy().into_owned(); let f = v .strip_prefix("feature=") .ok_or("only `--cfg feature=` is supported")?; features.push(f.trim_matches('"').to_string()); } "-h" | "--help" => { println!("usage: rustnim ... [--cfg feature=]... [-o ]"); println!(); println!("Several inputs are concatenated into one Nim module, in the"); println!("order given. That is how a multi-file crate is handled: Nim"); println!("has no equivalent of Rust's per-file `mod`, so the items are"); println!("flattened. Names must not collide across the files."); return Ok(()); } s if s.starts_with('-') => return Err(format!("unknown flag `{s}`")), _ => inputs.push(a.into()), } } if inputs.is_empty() { return Err("no input file; usage: rustnim ... [-o ]".into()); } // Several files become one Nim module: Rust's `mod` has no Nim analogue // inside a single output file, so the items are flattened in argument // order. A name collision between two files is a Nim compile error, which // is a loud failure rather than a silently shadowed definition. let mut items = Vec::new(); for input in &inputs { let src = std::fs::read_to_string(input) .map_err(|e| format!("cannot read {}: {e}", input.display()))?; let parsed: syn::File = syn::parse_file(&src) .map_err(|e| format!("{}: parse error: {e}", input.display()))?; items.extend(parsed.items); } let file = syn::File { shebang: None, frontmatter: None, attrs: Vec::new(), items }; let mut lowerer = lower::Lowerer::new(); lowerer.features = features; let nim = lowerer.lower_file(&file)?; match output { Some(p) => std::fs::write(&p, nim) .map_err(|e| format!("cannot write {}: {e}", p.display()))?, None => print!("{nim}"), } Ok(()) }