1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
//! rustnim — Rust -> Nim transpiler.
//!
//! Usage: rustnim <input.rs> [-o <output.nim>]
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<PathBuf> = Vec::new();
let mut output: Option<PathBuf> = None;
let mut features: Vec<String> = 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=<name>` is supported")?;
features.push(f.trim_matches('"').to_string());
}
"-h" | "--help" => {
println!("usage: rustnim <input.rs>... [--cfg feature=<name>]... [-o <output.nim>]");
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 <input.rs>... [-o <output.nim>]".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;
// A `mod x;` is satisfied when x.rs is one of the inputs.
lowerer.modules = inputs
.iter()
.filter_map(|p| p.file_stem().map(|s| s.to_string_lossy().into_owned()))
.collect();
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(())
}
|