nandi/rustnimpublic Fork 0
87c9cc836cadb6a4453f18af3eafbe61e2c4f20c
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 · 123 lines · 5.0 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
Settle signed-shr and unsigned-wrap semantics against both compilers 87c9cc8 nandi 8h ago78### Settled empirically (Nim 2.2.4 vs rustc 1.98.1, both run)
79
801. **Nim's `shr` on a signed integer is arithmetic**, matching Rust.
81 `int16(-256) shr 8` = `-1` in Nim; `(-256i16) >> 8` = `-1` in Rust.
82 `base16ct`'s decoder depends on this, so it maps directly with no helper.
832. **Nim's fixed-width unsigned arithmetic wraps silently**, matching Rust's
84 `wrapping_*`. `uint8(200) + 100` = `44` in Nim; `200u8.wrapping_add(100)`
85 = `44` in Rust. So `wrapping_add` on an unsigned type is just `+`.
86
87### Still open
88
893. Rust debug builds **panic** on signed integer overflow; release builds wrap.
90 Nim raises `OverflowDefect` on signed overflow. Item 2 settles the *unsigned*
91 case only. Decide which Rust profile we model, state it in the README, and
92 map `checked_*`/`saturating_*` explicitly.
934. `char`: Rust `char` is a Unicode scalar; mapped to `Rune`, which needs
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 8h ago94 `std/unicode`. Confirm round-tripping.
95
96## Testing: differential, not golden
97
98The bar is **behavioural equivalence with rustc**, not that the output looks
99plausible. For each case in `tests/cases/`:
100
101```
102rustc case.rs && ./case > expected
103rustnim case.rs -o case.nim && nim c -r case.nim > actual
104diff expected actual
105```
106
107A case only counts as passing when both binaries build *and* produce identical
108stdout. `tests/` currently has no runner — writing it is the next step, and it
109should come before any more of the lowering, so that progress is measured
110rather than asserted.
111
112## Toolchain
113
114- `rustc` / `cargo` 1.98.1 — system.
115- Nim 2.2.4 — vendored at `.nim-toolchain/` (gitignored; downloaded from
116 nim-lang.org, not installed system-wide). Binary: `.nim-toolchain/bin/nim`.
117
118## Milestone 1
119
120Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and
121have its decoder produce byte-identical output to the Rust original. It is a
122good target: 613 lines, `no_std`, no dependencies, and its constant-time
123integer arithmetic is exactly the kind of thing a sloppy transpiler gets wrong.