nandi/rustnimpublic Fork 0
99b837678a29fedd20bb68a5117a6621d8d178b4
Commits
Clone
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 · on 99b837678a29fedd20bb68a5117a6621d8d178b4 · nandithebull · 4h ago
main.rs · 105 lines · 3.8 KBRust Blame HistoryRaw
  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
 96
 97
 98
 99
100
101
102
103
104
105
//! 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.
    // The first input is the crate root; each later one is a module named by
    // its file stem. That is what keeps `lower::decode` and `mixed::decode`
    // apart once everything is flattened into a single Nim module.
    let mut files = Vec::new();
    for (i, input) in inputs.iter().enumerate() {
        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()))?;
        let module = if i == 0 {
            String::new()
        } else {
            input
                .file_stem()
                .map(|s| s.to_string_lossy().into_owned())
                .unwrap_or_default()
        };
        files.push((module, parsed));
    }

    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(&files)?;

    match output {
        Some(p) => std::fs::write(&p, nim)
            .map_err(|e| format!("cannot write {}: {e}", p.display()))?,
        None => print!("{nim}"),
    }
    Ok(())
}