Add a README
Points at DESIGN.md for the reasoning; covers build, usage, what maps, what is rejected, and how the differential tests are run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b66da6b parent: b0ccd80 added
README.md +79 -0 | new file mode 100644 | ||
| @@ -0,0 +1,79 @@ | ||
| 1 | +# rustnim | |
| 2 | + | |
| 3 | +A Rust → Nim transpiler. It parses Rust with `syn` and emits Nim source that | |
| 4 | +behaves the same way — same stdout, same exit status — when both are built and | |
| 5 | +run. | |
| 6 | + | |
| 7 | +The rule the whole thing is organised around: **never approximate a semantic | |
| 8 | +you cannot represent.** If a construct doesn't map, `rustnim` says so and exits | |
| 9 | +non-zero. It never writes a file it can't stand behind. | |
| 10 | + | |
| 11 | +That rule comes from a real failure. We tried an existing transpiler that | |
| 12 | +advertises Rust support on the `base16ct` crate; it emitted an empty file and | |
| 13 | +exited 0. The write-up is in [`findings/`](findings/). | |
| 14 | + | |
| 15 | +## Build and run | |
| 16 | + | |
| 17 | +```bash | |
| 18 | +cargo build --release | |
| 19 | +./target/release/rustnim input.rs -o out.nim | |
| 20 | +nim c out.nim | |
| 21 | +``` | |
| 22 | + | |
| 23 | +Several inputs are concatenated into one Nim module, in the order given — Nim | |
| 24 | +has no per-file `mod`, so items are flattened and names must not collide: | |
| 25 | + | |
| 26 | +```bash | |
| 27 | +rustnim src/lib.rs src/error.rs --cfg feature=alloc -o crate.nim | |
| 28 | +``` | |
| 29 | + | |
| 30 | +## What works | |
| 31 | + | |
| 32 | +Functions and `impl` methods, structs, enums (C-like and data-carrying), | |
| 33 | +`Option`/`Result` with `?`, `let`/`let mut`, the integer and float operators at | |
| 34 | +exact widths, `as` casts, `if`/`while`/`loop`/`for`, `match` including binding | |
| 35 | +patterns, `Vec`/slices/arrays, type aliases, function-typed parameters | |
| 36 | +(`impl Fn(A) -> B`), multi-file input, `#[cfg]`, and `println!`/`format!` with | |
| 37 | +`{}`, `{:?}`, `{:x}`, `{:b}`, positional and inline-named arguments, and | |
| 38 | +zero/space padding. | |
| 39 | + | |
| 40 | +Rejected with a reason, rather than guessed at: `i128`/`u128`, traits and trait | |
| 41 | +impls, generics, closures, iterator adaptors, float→int casts, and any | |
| 42 | +standard-library method that isn't mapped. | |
| 43 | + | |
| 44 | +## Tests | |
| 45 | + | |
| 46 | +```bash | |
| 47 | +cargo test | |
| 48 | +``` | |
| 49 | + | |
| 50 | +The tests are differential, with rustc as the oracle. Each case in | |
| 51 | +`tests/cases/` is compiled by rustc and run, transpiled and compiled by Nim and | |
| 52 | +run, and the two stdouts and exit statuses must match. Building anything less | |
| 53 | +than that — checking the Nim output "looks right", say — is how you end up | |
| 54 | +shipping the bug in `findings/`. `rustnim` exiting 0 with a missing or empty | |
| 55 | +output file is a hard failure, and so is an empty corpus. | |
| 56 | + | |
| 57 | +rustc is invoked without `-O` so debug-profile overflow checks are on, which is | |
| 58 | +the matching pair for `nim c`'s defaults. | |
| 59 | + | |
| 60 | +Nim is found at `.nim-toolchain/bin/nim` in the repo root or any parent, or via | |
| 61 | +`RUSTNIM_NIM`. Run a single case with: | |
| 62 | + | |
| 63 | +```bash | |
| 64 | +RUSTNIM_CASE=005 cargo test --test differential -- --nocapture | |
| 65 | +``` | |
| 66 | + | |
| 67 | +## Layout | |
| 68 | + | |
| 69 | +| file | role | | |
| 70 | +|---|---| | |
| 71 | +| `src/ty.rs` | Rust type → Nim type, exact widths, explicit rejections | | |
| 72 | +| `src/lower.rs` | items, statements, expressions → Nim | | |
| 73 | +| `src/fmt.rs` | `println!`/`format!` format strings | | |
| 74 | +| `src/prelude.nim` | `Option`/`Result`, panic, `Display`/`Debug` runtime | | |
| 75 | +| `tests/differential.rs` | the runner above | | |
| 76 | + | |
| 77 | +[`DESIGN.md`](DESIGN.md) has the reasoning: how each construct is mapped, what | |
| 78 | +was settled by running both compilers rather than by reading docs, and what's | |
| 79 | +still open. | |
| new file mode 100644 | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | +# rustnim | ||
| 2 | + | ||
| 3 | +A Rust → Nim transpiler. It parses Rust with `syn` and emits Nim source that | ||
| 4 | +behaves the same way — same stdout, same exit status — when both are built and | ||
| 5 | +run. | ||
| 6 | + | ||
| 7 | +The rule the whole thing is organised around: **never approximate a semantic | ||
| 8 | +you cannot represent.** If a construct doesn't map, `rustnim` says so and exits | ||
| 9 | +non-zero. It never writes a file it can't stand behind. | ||
| 10 | + | ||
| 11 | +That rule comes from a real failure. We tried an existing transpiler that | ||
| 12 | +advertises Rust support on the `base16ct` crate; it emitted an empty file and | ||
| 13 | +exited 0. The write-up is in [`findings/`](findings/). | ||
| 14 | + | ||
| 15 | +## Build and run | ||
| 16 | + | ||
| 17 | +```bash | ||
| 18 | +cargo build --release | ||
| 19 | +./target/release/rustnim input.rs -o out.nim | ||
| 20 | +nim c out.nim | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +Several inputs are concatenated into one Nim module, in the order given — Nim | ||
| 24 | +has no per-file `mod`, so items are flattened and names must not collide: | ||
| 25 | + | ||
| 26 | +```bash | ||
| 27 | +rustnim src/lib.rs src/error.rs --cfg feature=alloc -o crate.nim | ||
| 28 | +``` | ||
| 29 | + | ||
| 30 | +## What works | ||
| 31 | + | ||
| 32 | +Functions and `impl` methods, structs, enums (C-like and data-carrying), | ||
| 33 | +`Option`/`Result` with `?`, `let`/`let mut`, the integer and float operators at | ||
| 34 | +exact widths, `as` casts, `if`/`while`/`loop`/`for`, `match` including binding | ||
| 35 | +patterns, `Vec`/slices/arrays, type aliases, function-typed parameters | ||
| 36 | +(`impl Fn(A) -> B`), multi-file input, `#[cfg]`, and `println!`/`format!` with | ||
| 37 | +`{}`, `{:?}`, `{:x}`, `{:b}`, positional and inline-named arguments, and | ||
| 38 | +zero/space padding. | ||
| 39 | + | ||
| 40 | +Rejected with a reason, rather than guessed at: `i128`/`u128`, traits and trait | ||
| 41 | +impls, generics, closures, iterator adaptors, float→int casts, and any | ||
| 42 | +standard-library method that isn't mapped. | ||
| 43 | + | ||
| 44 | +## Tests | ||
| 45 | + | ||
| 46 | +```bash | ||
| 47 | +cargo test | ||
| 48 | +``` | ||
| 49 | + | ||
| 50 | +The tests are differential, with rustc as the oracle. Each case in | ||
| 51 | +`tests/cases/` is compiled by rustc and run, transpiled and compiled by Nim and | ||
| 52 | +run, and the two stdouts and exit statuses must match. Building anything less | ||
| 53 | +than that — checking the Nim output "looks right", say — is how you end up | ||
| 54 | +shipping the bug in `findings/`. `rustnim` exiting 0 with a missing or empty | ||
| 55 | +output file is a hard failure, and so is an empty corpus. | ||
| 56 | + | ||
| 57 | +rustc is invoked without `-O` so debug-profile overflow checks are on, which is | ||
| 58 | +the matching pair for `nim c`'s defaults. | ||
| 59 | + | ||
| 60 | +Nim is found at `.nim-toolchain/bin/nim` in the repo root or any parent, or via | ||
| 61 | +`RUSTNIM_NIM`. Run a single case with: | ||
| 62 | + | ||
| 63 | +```bash | ||
| 64 | +RUSTNIM_CASE=005 cargo test --test differential -- --nocapture | ||
| 65 | +``` | ||
| 66 | + | ||
| 67 | +## Layout | ||
| 68 | + | ||
| 69 | +| file | role | | ||
| 70 | +|---|---| | ||
| 71 | +| `src/ty.rs` | Rust type → Nim type, exact widths, explicit rejections | | ||
| 72 | +| `src/lower.rs` | items, statements, expressions → Nim | | ||
| 73 | +| `src/fmt.rs` | `println!`/`format!` format strings | | ||
| 74 | +| `src/prelude.nim` | `Option`/`Result`, panic, `Display`/`Debug` runtime | | ||
| 75 | +| `tests/differential.rs` | the runner above | | ||
| 76 | + | ||
| 77 | +[`DESIGN.md`](DESIGN.md) has the reasoning: how each construct is mapped, what | ||
| 78 | +was settled by running both compilers rather than by reading docs, and what's | ||
| 79 | +still open. | ||