nandi/rustnimpublic Fork 0
1a218c2a103c5c2dec0bdbf9b197a7265bfa8e8b
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.

DESIGN.md · 115 lines · 4.7 KBmarkdown Blame HistoryRaw
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 8h ago1# rustnim — a Rust → Nim transpiler
2
3## Status
4
5**Early scaffold.** `src/ty.rs` (type mapping) is written. `src/main.rs` is
6still cargo's default hello-world. Nothing transpiles yet.
7
8## Why this exists
9
10We tried [tarekwasfy01/Code-Transpiler](https://github.com/tarekwasfy01/Code-Transpiler),
11which advertises `rust` as a source language, on the `base16ct` crate. It emits
12empty files and exits 0. The full investigation is in [`findings/`](findings/)
13and is published at
14https://rickub.com/nandi/code-transpiler-rust-frontend-findings
15
16The decisive finding, and the reason this is a new project rather than a patch:
17its Universal AST cannot represent Rust. `defaultSemanticTypeContract()` in
18`internal/backend/semantic_program.go:85` is hardcoded to
19
20```
21numeric: binary64, integer_width: unknown, truth: r_compatible,
22ownership: unknown, index_base: 1
23```
24
25and `semantic_document.go:1014` *validates* that every contract equals exactly
26that, while `typed_operation.go:46` rejects any value model that is not
27`tagged_dynamic_binary64`. There is no integer width and no ownership in the
28model at all. Code like `base16ct`'s constant-time decoder —
29
30```rust
31ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47);
32```
33
34— depends on exact 16-bit signed wrapping and arithmetic shift. Lowering that
35into a 1-indexed dynamic float64 model produces silently wrong answers. So the
36first rule of this project is the one that codebase broke:
37
38> **Never approximate a semantic you cannot represent. Fail loudly instead.**
39
40`src/ty.rs` already does this: `i128`/`u128` are rejected with a reason rather
41than widened or truncated.
42
43## Architecture
44
45```
46Rust source ──syn──> syn AST ──lower──> Nim source ──nim c──> binary
47```
48
49**The frontend is `syn`, deliberately.** Hand-rolling a Rust grammar is how the
50other project went wrong; a correct parser is not the interesting part of this
51problem. The interesting part is the lowering, which is where all the work goes.
52
53Planned modules:
54
55| file | role | state |
56|---|---|---|
57| `src/ty.rs` | Rust type → Nim type, exact widths, explicit rejections | written |
58| `src/lower.rs` | items, statements, expressions → Nim | todo |
59| `src/fmt.rs` | `println!`/`format!` format-string handling | todo |
60| `src/prelude.nim` | `Option`/`Result`/panic runtime, embedded in output | todo |
61| `src/main.rs` | CLI: `rustnim <in.rs> -o <out.nim>` | todo |
62
63## Mapping decisions made so far
64
65- **Integers**: exact width. `i32``int32`, `usize``uint`, etc. `i128`/`u128`
66 rejected.
67- **Indexing**: both 0-based. Direct.
68- **`&T`** → plain value. **`&mut T`** → `var T` parameter.
69- **`&[T]`** → `openArray[T]` in parameter position, `seq[T]` when owned.
70 `Nim::owned()` performs that conversion.
71- **Ownership/borrowck**: ignored. Nim is GC'd; for safe Rust this is sound.
72- **`Option`/`Result`** → object variants in the prelude.
73- **`match`** → Nim `case` where the arms are simple, `if`/`elif` when arms have
74 guards or bindings.
75- **Rust's expression-orientation** maps well: Nim `if`/`case` are expressions
76 too, and a proc's trailing expression is its return value.
77
78### Open questions to settle empirically (the Nim toolchain is already here)
79
801. Is Nim's `shr` on a **signed** integer arithmetic or logical? `base16ct`
81 needs arithmetic. Write the test before relying on either answer.
822. Rust debug builds **panic** on integer overflow; release builds wrap. Nim
83 raises `OverflowDefect` by default. Decide which Rust profile we model, say
84 so in the README, and map `wrapping_*`/`checked_*`/`saturating_*` explicitly.
853. `char`: Rust `char` is a Unicode scalar; mapped to `Rune`, which needs
86 `std/unicode`. Confirm round-tripping.
87
88## Testing: differential, not golden
89
90The bar is **behavioural equivalence with rustc**, not that the output looks
91plausible. For each case in `tests/cases/`:
92
93```
94rustc case.rs && ./case > expected
95rustnim case.rs -o case.nim && nim c -r case.nim > actual
96diff expected actual
97```
98
99A case only counts as passing when both binaries build *and* produce identical
100stdout. `tests/` currently has no runner — writing it is the next step, and it
101should come before any more of the lowering, so that progress is measured
102rather than asserted.
103
104## Toolchain
105
106- `rustc` / `cargo` 1.98.1 — system.
107- Nim 2.2.4 — vendored at `.nim-toolchain/` (gitignored; downloaded from
108 nim-lang.org, not installed system-wide). Binary: `.nim-toolchain/bin/nim`.
109
110## Milestone 1
111
112Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and
113have its decoder produce byte-identical output to the Rust original. It is a
114good target: 613 lines, `no_std`, no dependencies, and its constant-time
115integer arithmetic is exactly the kind of thing a sloppy transpiler gets wrong.