nandi/rustnimpublic Fork 0
ae9f986
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 trait impls and slice iterators; base16ct's decoder now goes through

Trait impls. A Display impl becomes `proc rsDisplay(self: T): string`.
Rust's Formatter is a sink and the observable result of `{}` is exactly the
bytes written into it, so writes through the formatter produce that string
and the existing match/trailing-expression machinery assembles it. A body
that does anything else with the formatter -- padding, precision,
debug_struct -- is rejected, since those change the output and this model
does not carry them. Debug/LowerHex/UpperHex/Binary/Octal follow. `impl From`
becomes a conversion proc that `.into()` resolves through, and a marker trait
with no items generates nothing, because trait resolution is not modelled
anywhere and a use that needed it is rejected where it appears.

Two soundness bugs surfaced on the way. Methods were keyed by bare name, so
two types defining the same method collided; they are now keyed by receiver
type, which is also how Nim tells them apart. And a qualified path was
collapsed to its last segment, merging `fmt::Error` with a crate's own
`Error` -- core::fmt's types are now recognised by qualified name.

Slice iterators. Rust's are lazy and compose; Nim's `for` is over one
sequence. A chain of adaptors is resolved into a small IR and emitted as a
single index loop in which each binding is an *lvalue* into the original
container. That is what makes `*d = v` through iter_mut() write back to the
caller's slice rather than to a copy, and what lets chunks_exact(2) hand out
a window that indexes into the source with an offset. Adaptors without an
exact index-loop equivalent (map, filter, take_while) are rejected rather
than partially honoured: dropping one would change which elements are
visited. zip stopping at the shorter side is a test, not an assumption.

Borrowed slices are now views rather than copies, using Nim's experimental
view types -- probed against Nim 2.2.4 first, since copying into a seq would
print the right bytes while silently changing aliasing. `s.get(a..b)` is the
one leak: it is an Option of a view, and Nim cannot put a view in an object,
so the view and its validity condition travel together through `ok_or` until
a `?` resolves them into a bounds check plus a binding. Keeping such an
Option in a variable is rejected with a message saying so.

Also: forward declarations for every proc, since Rust has no
declaration-before-use rule and Nim does (reordering would not handle mutual
recursion); type aliases collected in their own pass, since a signature in
one file may use an alias declared in another; `mod x;` satisfied by passing
x.rs as another input; and the differential runner now accepts a directory of
.rs files with main.rs as the crate root.

Milestone 1's decoder goal is reached. tests/cases/026-base16ct-crate/
transpiles base16ct's error.rs and mixed.rs byte-for-byte as published on
crates.io -- verified with cmp -- plus lib.rs's decoded_len, encoded_len and
decode_inner verbatim, and the output is byte-identical to rustc's for lower,
upper and mixed hex, both error variants, and the Display strings. Remaining
for the whole crate is closures, `unsafe`, and view-typed struct fields;
DESIGN.md records which file needs which.

31 differential cases and 5 integration tests, all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-18T19:55:59-07:00 Browse files
ae9f986 parent: b66da6b
modified DESIGN.md +104 -28
@@ -2,11 +2,15 @@
22
33 ## Status
44
5-**Transpiling, and measured.** 27 differential cases, 23 behavioural and 4
6-rejections, plus 5 unit/integration tests. All green. Run `cargo test`.
7-
8-Passing today: functions, `impl` methods, structs, enums (C-like and
9-data-carrying), `Option`/`Result` with `?`, `let`/`let mut`, the full integer
5+**Milestone 1's decoder goal is reached.** 31 differential cases, 27
6+behavioural and 4 rejections, plus 5 unit/integration tests. All green. Run
7+`cargo test`.
8+
9+Passing today: functions, `impl` methods, trait impls (formatting traits and
10+`From`), structs, enums (C-like and data-carrying), `Option`/`Result` with
11+`?`, slice iterators (`iter`/`iter_mut`/`enumerate`/`zip`/`chunks_exact`/
12+`chunks_exact_mut`/`windows`), borrowed slices as values and return types,
13+`let`/`let mut`, the full integer
1014 and float operator set at exact widths, `as` casts, `if`/`while`/`loop`/`for`,
1115 `match` including patterns that bind, `Vec`/slices/arrays, type aliases
1216 (including generic ones), function-typed parameters (`impl Fn(A) -> B`),
@@ -97,6 +101,66 @@ types already agree, rather than assume a conversion is the identity. `?` in a
97101 `while` condition is rejected: the early return would run once before the
98102 loop rather than on each iteration.
99103
104+### Trait impls
105+
106+A `Display` impl becomes `proc rsDisplay(self: T): string`. Rust's `Formatter`
107+is a sink and the observable result of `{}` is exactly the bytes written into
108+it, so every write through the formatter produces that string and the existing
109+`match`/trailing-expression machinery assembles it. A `fmt` body that does
110+anything else with the formatter — padding, precision, `debug_struct` — is
111+rejected, because those change the output and this model does not carry them.
112+`Debug`, `LowerHex`, `UpperHex`, `Binary` and `Octal` work the same way.
113+
114+`impl From<A> for B` becomes a conversion proc that `.into()` resolves
115+through. A marker trait with no items generates nothing: we do not model trait
116+resolution anywhere, so there is nothing for it to affect; a use that actually
117+needed the trait (a `dyn`, a bound) is rejected where it appears. Any other
118+trait impl is rejected.
119+
120+Methods are keyed by `(receiver type, name)`, not by name alone — two types
121+may define the same method, and Nim tells them apart by overload resolution on
122+the first parameter.
123+
124+`fmt::Error` is *not* the same type as a crate's own `Error`. Collapsing a
125+qualified path to its last segment merged them, which was a real soundness
126+bug; `core::fmt`'s types are now recognised by their qualified name.
127+
128+### Slice iterators are resolved to one index loop
129+
130+Rust's slice iterators are lazy and compose. Nim's `for` is over one sequence,
131+so a chain of adaptors is resolved into a small IR and emitted as a single
132+index loop in which **each binding is an lvalue into the original container**.
133+That is what makes `*d = v` through `iter_mut()` write back to the caller's
134+slice instead of to a copy, and what lets `chunks_exact(2)` hand out a window
135+that indexes straight into the source with an offset.
136+
137+Only adaptors with an exact index-loop equivalent are accepted. `map`,
138+`filter` and `take_while` are rejected rather than partially honoured:
139+silently dropping an adaptor would change which elements the loop visits.
140+
141+`zip` stops at the shorter side, as Rust's does — that is a test, not an
142+assumption (`tests/cases/023`).
143+
144+### Borrowed slices are views, not copies
145+
146+`&[T]` is a borrow. Nim's experimental view types model exactly that,
147+including returning one from a proc: writing through the returned view is
148+visible in the original buffer. That was probed against Nim 2.2.4 before being
149+relied on, because copying into a `seq` would print the right bytes while
150+silently changing aliasing.
151+
152+`s.get(a..b)` is the one place this leaks. It is an `Option<&[T]>`, and Nim
153+cannot put a view inside an object, so there is no value to hand back. Instead
154+the view and its validity condition travel together through `ok_or` until a
155+`?` or `unwrap` resolves them into a bounds check plus a binding. Keeping such
156+an `Option` in a variable is rejected with a message saying so.
157+
158+### Declaration order
159+
160+Rust has no declaration-before-use rule and Nim does, so every proc is
161+forward-declared between the type definitions and the bodies. Reordering the
162+input instead would not handle mutual recursion.
163+
100164 ### Type propagation is load-bearing
101165
102166 Rust infers an unsuffixed integer literal's type from context and falls back
@@ -149,10 +213,10 @@ runner) rather than a wrong answer.
149213
150214 5. `checked_*` and `saturating_*` are not mapped yet; they are currently
151215 rejected as unsupported methods rather than approximated.
152-6. Generics (type and const parameters), traits and trait impls, closures and
153- iterator adaptors are rejected with a reason. Lifetime parameters are *not*
154- a rejection: they carry no runtime meaning and Nim is GC'd, so
155- `fn encode<'a>(..)` lowers fine.
216+6. Generics (type and const parameters), closures, `unsafe`, and trait impls
217+ other than the formatting traits and `From` are rejected with a reason.
218+ Lifetime parameters are *not* a rejection: they carry no runtime meaning
219+ and Nim is GC'd, so `fn encode<'a>(..)` lowers fine.
156220 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but
157221 the exponent-form thresholds have only been checked at `1e21`.
158222 8. Flattening several files into one module can collide a crate's own
@@ -214,24 +278,36 @@ or any parent, or via `RUSTNIM_NIM`.
214278 Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and
215279 have its decoder produce byte-identical output to the Rust original.
216280
217-**Not reached.** What is reached, and measured, is `tests/cases/022`: the
218-`Error` enum, `decoded_len` and `decode_nibble` **verbatim from base16ct
219-1.0.0**, decoding lower, upper and mixed hex and both error cases, with output
220-byte-identical to rustc's. That covers the constant-time nibble arithmetic —
221-the i16 wrapping and arithmetic shift whose exact semantics the other
222-transpiler's float64 universal AST cannot represent at all. The loop around it
223-is rewritten with indexing, and the case says so.
281+**The decoder is reached.** `tests/cases/026-base16ct-crate/` transpiles
282+base16ct's `error.rs` and `mixed.rs` **byte-for-byte as published on
283+crates.io** — verified with `cmp`, not by eye — together with `lib.rs`'s
284+`decoded_len`, `encoded_len` and `decode_inner` verbatim, and its output is
285+byte-identical to rustc's:
224286
225-Running the real crate now gives these diagnostics, which are the todo list:
287+```
288+mixed-l ok abcd1234 len=4 lower, upper and mixed hex all decode
289+mixed-u ok abcd1234 len=4
290+mixed-m ok abcd1234 len=4
291+edge ok 00ff7f80 len=4
292+oddlen err InvalidLength / invalid Base16 length <- Debug and Display
293+bad err InvalidEncoding / invalid Base16 encoding
294+empty ok len=0
295+short-dst err InvalidLength / invalid Base16 length
296+```
226297
227-| file | blocker |
228-|---|---|
229-| `error.rs` | `impl fmt::Display for Error`, `impl core::error::Error`, `impl From<Error> for fmt::Error` — trait impls |
230-| `lib.rs` | `decode_inner`: `dst.get_mut(..n)` (a mutable subslice view), `chunks_exact(2)`, `zip`, `iter_mut`, and `*dst = ..` |
231-| `lower.rs`, `upper.rs`, `mixed.rs` | the same, plus `encode`'s `chunks_exact_mut` |
232-| `display.rs` | `impl fmt::UpperHex for HexDisplay` — trait impls again |
233-
234-So the remaining work is two features, not a long tail: **trait impls**, and
235-**iterator adaptors over slices** together with the mutable slice views they
236-borrow from. `mod`/multi-file and `#[cfg]` are done; pass the crate's files
237-together and add `--cfg feature=alloc` for the `alloc` half.
298+`decode_inner` goes through as written: `dst.get_mut(..decoded_len(src)?)`,
299+`src.chunks_exact(2).zip(dst.iter_mut())`, `*dst = byte as u8`, and the
300+returned `&'a [u8]` view into the caller's buffer. The `Display` line in that
301+output comes from the crate's own `impl fmt::Display for Error`.
302+
303+### Still to do for the whole crate
304+
305+- `lower.rs` / `upper.rs`: `encode` itself lowers, but the same file defines
306+ `encode_str`, whose body is a **closure** over an **`unsafe`** block. Both
307+ are unimplemented, and a file is all-or-nothing, so neither module is in the
308+ case yet.
309+- `display.rs`: `HexDisplay<'a>(pub &'a [u8])` is a tuple struct holding a
310+ borrowed slice. A struct *field* of view type is what Nim's view types do
311+ not allow, which is the same wall as `Option<&[T]>`.
312+- The `alloc` half (`decode_vec`, `encode_string`) needs `--cfg feature=alloc`
313+ and then `String::from_utf8_unchecked`, i.e. `unsafe` again.
@@ -2,11 +2,15 @@
2 2
3 ## Status3 ## Status
4 4
5-**Transpiling, and measured.** 27 differential cases, 23 behavioural and 45+**Milestone 1's decoder goal is reached.** 31 differential cases, 27
6-rejections, plus 5 unit/integration tests. All green. Run `cargo test`.6+behavioural and 4 rejections, plus 5 unit/integration tests. All green. Run
7-7+`cargo test`.
8-Passing today: functions, `impl` methods, structs, enums (C-like and8+
9-data-carrying), `Option`/`Result` with `?`, `let`/`let mut`, the full integer9+Passing today: functions, `impl` methods, trait impls (formatting traits and
10+`From`), structs, enums (C-like and data-carrying), `Option`/`Result` with
11+`?`, slice iterators (`iter`/`iter_mut`/`enumerate`/`zip`/`chunks_exact`/
12+`chunks_exact_mut`/`windows`), borrowed slices as values and return types,
13+`let`/`let mut`, the full integer
10 and float operator set at exact widths, `as` casts, `if`/`while`/`loop`/`for`,14 and float operator set at exact widths, `as` casts, `if`/`while`/`loop`/`for`,
11 `match` including patterns that bind, `Vec`/slices/arrays, type aliases15 `match` including patterns that bind, `Vec`/slices/arrays, type aliases
12 (including generic ones), function-typed parameters (`impl Fn(A) -> B`),16 (including generic ones), function-typed parameters (`impl Fn(A) -> B`),
@@ -97,6 +101,66 @@ types already agree, rather than assume a conversion is the identity. `?` in a
97 `while` condition is rejected: the early return would run once before the101 `while` condition is rejected: the early return would run once before the
98 loop rather than on each iteration.102 loop rather than on each iteration.
99 103
104+### Trait impls
105+
106+A `Display` impl becomes `proc rsDisplay(self: T): string`. Rust's `Formatter`
107+is a sink and the observable result of `{}` is exactly the bytes written into
108+it, so every write through the formatter produces that string and the existing
109+`match`/trailing-expression machinery assembles it. A `fmt` body that does
110+anything else with the formatter — padding, precision, `debug_struct` — is
111+rejected, because those change the output and this model does not carry them.
112+`Debug`, `LowerHex`, `UpperHex`, `Binary` and `Octal` work the same way.
113+
114+`impl From<A> for B` becomes a conversion proc that `.into()` resolves
115+through. A marker trait with no items generates nothing: we do not model trait
116+resolution anywhere, so there is nothing for it to affect; a use that actually
117+needed the trait (a `dyn`, a bound) is rejected where it appears. Any other
118+trait impl is rejected.
119+
120+Methods are keyed by `(receiver type, name)`, not by name alone — two types
121+may define the same method, and Nim tells them apart by overload resolution on
122+the first parameter.
123+
124+`fmt::Error` is *not* the same type as a crate's own `Error`. Collapsing a
125+qualified path to its last segment merged them, which was a real soundness
126+bug; `core::fmt`'s types are now recognised by their qualified name.
127+
128+### Slice iterators are resolved to one index loop
129+
130+Rust's slice iterators are lazy and compose. Nim's `for` is over one sequence,
131+so a chain of adaptors is resolved into a small IR and emitted as a single
132+index loop in which **each binding is an lvalue into the original container**.
133+That is what makes `*d = v` through `iter_mut()` write back to the caller's
134+slice instead of to a copy, and what lets `chunks_exact(2)` hand out a window
135+that indexes straight into the source with an offset.
136+
137+Only adaptors with an exact index-loop equivalent are accepted. `map`,
138+`filter` and `take_while` are rejected rather than partially honoured:
139+silently dropping an adaptor would change which elements the loop visits.
140+
141+`zip` stops at the shorter side, as Rust's does — that is a test, not an
142+assumption (`tests/cases/023`).
143+
144+### Borrowed slices are views, not copies
145+
146+`&[T]` is a borrow. Nim's experimental view types model exactly that,
147+including returning one from a proc: writing through the returned view is
148+visible in the original buffer. That was probed against Nim 2.2.4 before being
149+relied on, because copying into a `seq` would print the right bytes while
150+silently changing aliasing.
151+
152+`s.get(a..b)` is the one place this leaks. It is an `Option<&[T]>`, and Nim
153+cannot put a view inside an object, so there is no value to hand back. Instead
154+the view and its validity condition travel together through `ok_or` until a
155+`?` or `unwrap` resolves them into a bounds check plus a binding. Keeping such
156+an `Option` in a variable is rejected with a message saying so.
157+
158+### Declaration order
159+
160+Rust has no declaration-before-use rule and Nim does, so every proc is
161+forward-declared between the type definitions and the bodies. Reordering the
162+input instead would not handle mutual recursion.
163+
100 ### Type propagation is load-bearing164 ### Type propagation is load-bearing
101 165
102 Rust infers an unsuffixed integer literal's type from context and falls back166 Rust infers an unsuffixed integer literal's type from context and falls back
@@ -149,10 +213,10 @@ runner) rather than a wrong answer.
149 213
150 5. `checked_*` and `saturating_*` are not mapped yet; they are currently214 5. `checked_*` and `saturating_*` are not mapped yet; they are currently
151 rejected as unsupported methods rather than approximated.215 rejected as unsupported methods rather than approximated.
152-6. Generics (type and const parameters), traits and trait impls, closures and216+6. Generics (type and const parameters), closures, `unsafe`, and trait impls
153- iterator adaptors are rejected with a reason. Lifetime parameters are *not*217+ other than the formatting traits and `From` are rejected with a reason.
154- a rejection: they carry no runtime meaning and Nim is GC'd, so218+ Lifetime parameters are *not* a rejection: they carry no runtime meaning
155- `fn encode<'a>(..)` lowers fine.219+ and Nim is GC'd, so `fn encode<'a>(..)` lowers fine.
156 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but220 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but
157 the exponent-form thresholds have only been checked at `1e21`.221 the exponent-form thresholds have only been checked at `1e21`.
158 8. Flattening several files into one module can collide a crate's own222 8. Flattening several files into one module can collide a crate's own
@@ -214,24 +278,36 @@ or any parent, or via `RUSTNIM_NIM`.
214 Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and278 Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and
215 have its decoder produce byte-identical output to the Rust original.279 have its decoder produce byte-identical output to the Rust original.
216 280
217-**Not reached.** What is reached, and measured, is `tests/cases/022`: the281+**The decoder is reached.** `tests/cases/026-base16ct-crate/` transpiles
218-`Error` enum, `decoded_len` and `decode_nibble` **verbatim from base16ct282+base16ct's `error.rs` and `mixed.rs` **byte-for-byte as published on
219-1.0.0**, decoding lower, upper and mixed hex and both error cases, with output283+crates.io** — verified with `cmp`, not by eye — together with `lib.rs`'s
220-byte-identical to rustc's. That covers the constant-time nibble arithmetic —284+`decoded_len`, `encoded_len` and `decode_inner` verbatim, and its output is
221-the i16 wrapping and arithmetic shift whose exact semantics the other285+byte-identical to rustc's:
222-transpiler's float64 universal AST cannot represent at all. The loop around it
223-is rewritten with indexing, and the case says so.
224 286
225-Running the real crate now gives these diagnostics, which are the todo list:287+```
288+mixed-l ok abcd1234 len=4 lower, upper and mixed hex all decode
289+mixed-u ok abcd1234 len=4
290+mixed-m ok abcd1234 len=4
291+edge ok 00ff7f80 len=4
292+oddlen err InvalidLength / invalid Base16 length <- Debug and Display
293+bad err InvalidEncoding / invalid Base16 encoding
294+empty ok len=0
295+short-dst err InvalidLength / invalid Base16 length
296+```
226 297
227-| file | blocker |298+`decode_inner` goes through as written: `dst.get_mut(..decoded_len(src)?)`,
228-|---|---|299+`src.chunks_exact(2).zip(dst.iter_mut())`, `*dst = byte as u8`, and the
229-| `error.rs` | `impl fmt::Display for Error`, `impl core::error::Error`, `impl From<Error> for fmt::Error` — trait impls |300+returned `&'a [u8]` view into the caller's buffer. The `Display` line in that
230-| `lib.rs` | `decode_inner`: `dst.get_mut(..n)` (a mutable subslice view), `chunks_exact(2)`, `zip`, `iter_mut`, and `*dst = ..` |301+output comes from the crate's own `impl fmt::Display for Error`.
231-| `lower.rs`, `upper.rs`, `mixed.rs` | the same, plus `encode`'s `chunks_exact_mut` |302+
232-| `display.rs` | `impl fmt::UpperHex for HexDisplay` — trait impls again |303+### Still to do for the whole crate
233-304+
234-So the remaining work is two features, not a long tail: **trait impls**, and305+- `lower.rs` / `upper.rs`: `encode` itself lowers, but the same file defines
235-**iterator adaptors over slices** together with the mutable slice views they306+ `encode_str`, whose body is a **closure** over an **`unsafe`** block. Both
236-borrow from. `mod`/multi-file and `#[cfg]` are done; pass the crate's files307+ are unimplemented, and a file is all-or-nothing, so neither module is in the
237-together and add `--cfg feature=alloc` for the `alloc` half.308+ case yet.
309+- `display.rs`: `HexDisplay<'a>(pub &'a [u8])` is a tuple struct holding a
310+ borrowed slice. A struct *field* of view type is what Nim's view types do
311+ not allow, which is the same wall as `Option<&[T]>`.
312+- The `alloc` half (`decode_vec`, `encode_string`) needs `--cfg feature=alloc`
313+ and then `String::from_utf8_unchecked`, i.e. `unsafe` again.
modified src/lower.rs +939 -89
@@ -29,12 +29,91 @@ const NIM_KEYWORDS: &[&str] = &[
2929
3030 fn ident(name: &str) -> String {
3131 if NIM_KEYWORDS.contains(&name) {
32- format!("{name}_r")
33- } else {
34- name.to_string()
32+ return format!("{name}_r");
33+ }
34+ // Nim identifiers may not begin with an underscore, and may not contain
35+ // two in a row. Rust uses both freely (`_unused`, `__private`).
36+ let mut out = String::new();
37+ let mut last_us = false;
38+ for (i, c) in name.chars().enumerate() {
39+ if c == '_' {
40+ if i == 0 {
41+ out.push('u');
42+ out.push('_');
43+ last_us = true;
44+ continue;
45+ }
46+ if last_us {
47+ continue;
48+ }
49+ last_us = true;
50+ out.push('_');
51+ } else {
52+ last_us = false;
53+ out.push(c);
54+ }
55+ }
56+ if out.ends_with('_') {
57+ out.push('x');
58+ }
59+ out
60+}
61+
62+/// A `for`-loop source, resolved from a chain of iterator adaptors.
63+///
64+/// Rust's slice iterators are lazy and compose; Nim's `for` is over one
65+/// sequence. So a chain is resolved into this shape and then emitted as a
66+/// single index loop, with each binding becoming an *lvalue* into the original
67+/// container. That is what makes `*dst = v` through `iter_mut()` write back to
68+/// the caller's slice rather than to a copy.
69+#[derive(Clone, Debug)]
70+enum Iter {
71+ /// `a..b` / `a..=b`.
72+ Range { lo: String, hi: String, closed: bool, ty: Option<Nim> },
73+ /// `for x in a`, `a.iter()`, `a.iter_mut()`. `off`/`len` let the same
74+ /// shape cover a subslice view. `mutable` only affects whether the binding
75+ /// may be assigned through.
76+ Elems { code: String, off: String, len: String, elem: Option<Nim>, mutable: bool },
77+ /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of
78+ /// `k` elements starting at `k * i`.
79+ Chunks { code: String, k: String, elem: Option<Nim>, mutable: bool },
80+ /// `a.windows(k)`: like `Chunks` but advancing one element at a time.
81+ Windows { code: String, k: String, elem: Option<Nim> },
82+ /// `.enumerate()` — the index is the first half of the pair.
83+ Enumerate(Box<Iter>),
84+ /// `.zip(other)` — stops at the shorter, as Rust's does.
85+ Zip(Box<Iter>, Box<Iter>),
86+}
87+
88+impl Iter {
89+ /// The number of iterations, as a Nim expression in terms of the loop's
90+ /// own containers.
91+ fn len(&self) -> String {
92+ match self {
93+ Iter::Range { lo, hi, closed, .. } => {
94+ let n = format!("(int({hi}) - int({lo}))");
95+ if *closed { format!("({n} + 1)") } else { n }
96+ }
97+ Iter::Elems { len, .. } => len.clone(),
98+ Iter::Chunks { code, k, .. } => format!("({}.len div int({}))", code, k),
99+ Iter::Windows { code, k, .. } => {
100+ format!("(max(0, {}.len - int({}) + 1))", code, k)
101+ }
102+ Iter::Enumerate(i) => i.len(),
103+ Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),
104+ }
35105 }
36106 }
37107
108+/// How a `for`-loop pattern name refers back into the container it came from.
109+#[derive(Clone, Debug)]
110+enum Alias {
111+ /// The name stands for this Nim lvalue expression.
112+ Value { code: String, ty: Option<Nim> },
113+ /// The name stands for a window: `code[off .. off + len - 1]`.
114+ Window { code: String, off: String, len: String, elem: Option<Nim> },
115+}
116+
38117 /// A lowered expression: its Nim text, and its type where we know it.
39118 ///
40119 /// The type is not decoration. Nim needs it to pick `div` over `/`, to size a
@@ -45,14 +124,24 @@ fn ident(name: &str) -> String {
45124 struct Val {
46125 code: String,
47126 ty: Option<Nim>,
127+ /// Set when the value *is* a slice view rather than a Nim value: binding
128+ /// it introduces an alias, not a copy.
129+ window: Option<Alias>,
130+ /// For `get`/`get_mut`: the condition under which the `Option` is `Some`,
131+ /// carried until an `ok_or`/`?` or an `unwrap` consumes it. Nim's view
132+ /// types cannot live inside an object, so an `Option` of a view has no
133+ /// runtime representation -- it is tracked here instead.
134+ guard: Option<String>,
135+ /// The error an `ok_or` attached to that guard.
136+ guard_err: Option<String>,
48137 }
49138
50139 impl Val {
51140 fn new(code: impl Into<String>, ty: Option<Nim>) -> Self {
52- Val { code: code.into(), ty }
141+ Val { code: code.into(), ty, window: None, guard: None, guard_err: None }
53142 }
54143 fn untyped(code: impl Into<String>) -> Self {
55- Val { code: code.into(), ty: None }
144+ Val::new(code, None)
56145 }
57146 }
58147
@@ -96,6 +185,9 @@ pub struct Lowerer {
96185 out: String,
97186 indent: usize,
98187 scopes: Vec<HashMap<String, Nim>>,
188+ /// Names introduced by a `for` pattern that stand for an lvalue or a
189+ /// window into a container, rather than for a variable of their own.
190+ alias_scopes: Vec<HashMap<String, Alias>>,
99191 fns: HashMap<String, Sig>,
100192 /// struct name -> (field, type)
101193 structs: HashMap<String, Vec<(String, Nim)>>,
@@ -103,8 +195,28 @@ pub struct Lowerer {
103195 /// variant name -> enums declaring it. A variant named by more than one
104196 /// enum must be written qualified, or it is rejected as ambiguous.
105197 variant_owner: HashMap<String, Vec<String>>,
198+ /// `(receiver type, method) -> signature`. Keyed by type because two
199+ /// types may define the same method name, and Nim tells them apart by
200+ /// overload resolution on the first parameter.
201+ methods: HashMap<(String, String), Sig>,
202+ /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}`
203+ /// on a user type can be checked rather than assumed.
204+ fmt_impls: HashMap<(String, String), ()>,
205+ /// `(from, to)` conversions declared by `impl From<A> for B`.
206+ from_impls: HashMap<(String, String), String>,
207+ /// Forward declarations, emitted between the type definitions and the
208+ /// bodies. Rust has no declaration-before-use rule and Nim does, so every
209+ /// proc is declared up front rather than the input being reordered --
210+ /// which would not work for mutual recursion anyway.
211+ forwards: Vec<String>,
212+ /// While lowering a formatting impl: the `Formatter` parameter's name.
213+ /// Writes through it produce the proc's string result.
214+ fmt_param: Option<String>,
106215 /// `type X<T> = ...`, expanded before any type is mapped.
107216 aliases: HashMap<String, (Vec<String>, syn::Type)>,
217+ /// Module names supplied as separate input files. A `mod x;` naming one
218+ /// of these is satisfied by that file having been passed in.
219+ pub modules: Vec<String>,
108220 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
109221 /// evaluated against these exactly as rustc would, so an item that is
110222 /// dropped here is genuinely not part of the program being compiled.
@@ -128,11 +240,18 @@ impl Lowerer {
128240 out: String::new(),
129241 indent: 0,
130242 scopes: vec![HashMap::new()],
243+ alias_scopes: vec![HashMap::new()],
131244 fns: HashMap::new(),
132245 structs: HashMap::new(),
133246 enums: HashMap::new(),
134247 variant_owner: HashMap::new(),
248+ methods: HashMap::new(),
249+ fmt_impls: HashMap::new(),
250+ from_impls: HashMap::new(),
251+ fmt_param: None,
252+ forwards: Vec::new(),
135253 aliases: HashMap::new(),
254+ modules: Vec::new(),
136255 features: Vec::new(),
137256 dropped_by_cfg: 0,
138257 ret: None,
@@ -165,9 +284,23 @@ impl Lowerer {
165284
166285 fn push_scope(&mut self) {
167286 self.scopes.push(HashMap::new());
287+ self.alias_scopes.push(HashMap::new());
168288 }
169289 fn pop_scope(&mut self) {
170290 self.scopes.pop();
291+ self.alias_scopes.pop();
292+ }
293+ fn bind_alias(&mut self, name: &str, a: Alias) {
294+ self.alias_scopes
295+ .last_mut()
296+ .unwrap()
297+ .insert(name.to_string(), a);
298+ }
299+ fn lookup_alias(&self, name: &str) -> Option<Alias> {
300+ self.alias_scopes
301+ .iter()
302+ .rev()
303+ .find_map(|s| s.get(name).cloned())
171304 }
172305 fn bind(&mut self, name: &str, t: Nim) {
173306 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
@@ -182,12 +315,34 @@ impl Lowerer {
182315 self.out.push_str(include_str!("prelude.nim"));
183316 self.blank();
184317
318+ // Pass 0: type aliases. A signature in one file may use an alias
319+ // declared in another, and inputs are given in whatever order suits
320+ // the caller, so aliases are registered before anything is mapped.
321+ for item in &file.items {
322+ self.collect_aliases(item)?;
323+ }
324+
185325 // Pass 1: signatures and struct shapes, so that a call can be typed
186326 // regardless of declaration order (Rust has no forward declarations).
187327 for item in &file.items {
188328 self.collect(item)?;
189329 }
190- // Pass 2: bodies.
330+ // Pass 2: type definitions, which every signature may mention.
331+ for item in &file.items {
332+ self.item_types(item)?;
333+ }
334+
335+ // Pass 3: forward declarations. Rust imposes no declaration order and
336+ // Nim does, so everything is declared before any body is emitted;
337+ // reordering the input would not handle mutual recursion anyway.
338+ if !self.forwards.is_empty() {
339+ for f in self.forwards.clone() {
340+ self.line(&f);
341+ }
342+ self.blank();
343+ }
344+
345+ // Pass 4: bodies.
191346 for item in &file.items {
192347 self.item(item)?;
193348 }
@@ -212,6 +367,35 @@ impl Lowerer {
212367 Ok(std::mem::take(&mut self.out))
213368 }
214369
370+ fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
371+ if !self.cfg_keeps(item_attrs(item))? {
372+ return Ok(());
373+ }
374+ match item {
375+ Item::Type(t) => {
376+ let params: Vec<String> = t
377+ .generics
378+ .params
379+ .iter()
380+ .filter_map(|g| match g {
381+ syn::GenericParam::Type(t) => Some(t.ident.to_string()),
382+ _ => None,
383+ })
384+ .collect();
385+ self.aliases
386+ .insert(t.ident.to_string(), (params, (*t.ty).clone()));
387+ }
388+ Item::Mod(m) if m.content.is_some() => {
389+ let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
390+ for i in &items {
391+ self.collect_aliases(i)?;
392+ }
393+ }
394+ _ => {}
395+ }
396+ Ok(())
397+ }
398+
215399 fn collect(&mut self, item: &Item) -> Result<(), String> {
216400 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
217401 // silently would change what the program does; picking a feature set
@@ -224,6 +408,8 @@ impl Lowerer {
224408 match item {
225409 Item::Fn(f) => {
226410 let (params, ret) = self.signature(&f.sig)?;
411+ let head = self.head_of(&f.sig.ident.to_string(), &f.sig, None)?;
412+ self.forwards.push(head);
227413 self.fns.insert(f.sig.ident.to_string(), Sig { params, ret });
228414 }
229415 Item::Struct(s) => {
@@ -237,6 +423,12 @@ impl Lowerer {
237423 }
238424 self.structs.insert(s.ident.to_string(), fields);
239425 }
426+ Item::Mod(m) if m.content.is_some() => {
427+ let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
428+ for i in &items {
429+ self.collect(i)?;
430+ }
431+ }
240432 Item::Type(t) => {
241433 let params: Vec<String> = t
242434 .generics
@@ -291,13 +483,57 @@ impl Lowerer {
291483 }
292484 Item::Impl(im) => {
293485 let self_ty = self.map_ty(&im.self_ty)?;
486+ let tyname = type_name(&self_ty);
487+ if let Some((path, _)) = &im.trait_ {
488+ let tr = path_name(path);
489+ if im.items.is_empty() {
490+ // A marker trait with no items. We do not model trait
491+ // resolution at all, so it generates nothing; any use
492+ // that actually needed the trait (a `dyn`, a bound) is
493+ // rejected where it appears.
494+ return Ok(());
495+ }
496+ if is_fmt_trait(&tr) {
497+ self.forwards.push(format!(
498+ "proc {}*(self: {}): string",
499+ fmt_proc(&tr),
500+ self_ty.render()
501+ ));
502+ self.fmt_impls.insert((tyname, tr), ());
503+ return Ok(());
504+ }
505+ if tr == "From" {
506+ let syn::ImplItem::Fn(m) = &im.items[0] else {
507+ return Err("`impl From` must contain `fn from`".into());
508+ };
509+ let (params, _) = self.signature(&m.sig)?;
510+ let src = params
511+ .first()
512+ .ok_or("`fn from` takes one argument")?
513+ .clone();
514+ let name = format!("rsFrom{}{}", tyname, type_name(&src));
515+ self.forwards.push(self.head_of(&name, &m.sig, None)?);
516+ self.from_impls
517+ .insert((type_name(&src), tyname), name);
518+ return Ok(());
519+ }
520+ return Err(format!(
521+ "`impl {tr} for {tyname}`: only formatting traits \
522+ (Display, Debug, LowerHex, UpperHex, Binary, Octal), \
523+ `From`, and marker traits with no items are implemented"
524+ ));
525+ }
294526 for it in &im.items {
295527 if let syn::ImplItem::Fn(m) = it {
296528 let (mut params, ret) = self.signature(&m.sig)?;
297529 if takes_self(&m.sig) {
298530 params.insert(0, self_ty.clone());
299531 }
300- self.fns.insert(m.sig.ident.to_string(), Sig { params, ret });
532+ let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
533+ let head = self.head_of(&m.sig.ident.to_string(), &m.sig, recv.as_ref())?;
534+ self.forwards.push(head);
535+ self.methods
536+ .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret });
301537 }
302538 }
303539 }
@@ -401,6 +637,49 @@ impl Lowerer {
401637 self.expand(&substitute(target, params, &args), depth + 1)
402638 }
403639
640+ /// The Nim `proc` head for a Rust signature, used both for the forward
641+ /// declaration and for the definition, so the two cannot drift apart.
642+ fn head_of(
643+ &self,
644+ name: &str,
645+ sig: &syn::Signature,
646+ recv: Option<&Nim>,
647+ ) -> Result<String, String> {
648+ let (ptys, ret) = self.signature(sig)?;
649+ let mut parts = Vec::new();
650+ if let Some(self_ty) = recv {
651+ let mutable = matches!(
652+ sig.inputs.first(),
653+ Some(FnArg::Receiver(r))
654+ if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
655+ );
656+ let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
657+ parts.push(format!("self: {}", t.render()));
658+ }
659+ let typed: Vec<&syn::PatType> = sig
660+ .inputs
661+ .iter()
662+ .filter_map(|a| match a {
663+ FnArg::Typed(t) => Some(t),
664+ _ => None,
665+ })
666+ .collect();
667+ for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
668+ let pname = match &*p.pat {
669+ Pat::Ident(id) => id.ident.to_string(),
670+ Pat::Wild(_) => format!("unused{}", parts.len()),
671+ _ => return Err("only plain identifier parameters are supported".into()),
672+ };
673+ let _ = i;
674+ parts.push(format!("{}: {}", ident(&pname), t.render()));
675+ }
676+ Ok(if ret == Nim::Unit {
677+ format!("proc {}*({})", ident(name), parts.join(", "))
678+ } else {
679+ format!("proc {}*({}): {}", ident(name), parts.join(", "), ret.render())
680+ })
681+ }
682+
404683 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
405684 if sig.asyncness.is_some() {
406685 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
@@ -426,17 +705,49 @@ impl Lowerer {
426705 }
427706 let ret = match &sig.output {
428707 ReturnType::Default => Nim::Unit,
429- ReturnType::Type(_, t) => self.map_ty(t)?.owned(),
708+ // A returned `&[T]` is a borrow of the caller's buffer, so it
709+ // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
710+ // a `seq`, which `owned()` would do to both.
711+ ReturnType::Type(_, t) => {
712+ let n = self.map_ty(t)?;
713+ if returns_borrow(t) { n } else { n.owned() }
714+ }
430715 };
431716 Ok((params, ret))
432717 }
433718
434719 // --------------------------------------------------------------- items
435720
721+ /// Emit the type definitions only: they must precede every signature.
722+ fn item_types(&mut self, item: &Item) -> Result<(), String> {
723+ if !self.cfg_keeps(item_attrs(item))? {
724+ return Ok(());
725+ }
726+ match item {
727+ Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
728+ Item::Mod(m) if m.content.is_some() => {
729+ let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
730+ for i in &items {
731+ self.item_types(i)?;
732+ }
733+ Ok(())
734+ }
735+ _ => Ok(()),
736+ }
737+ }
738+
436739 fn item(&mut self, item: &Item) -> Result<(), String> {
437740 if !self.cfg_keeps(item_attrs(item))? {
438741 return Ok(());
439742 }
743+ // Types were emitted in their own pass.
744+ if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
745+ return Ok(());
746+ }
747+ self.item_inner(item)
748+ }
749+
750+ fn item_inner(&mut self, item: &Item) -> Result<(), String> {
440751 match item {
441752 Item::Fn(f) => self.func(&f.sig, &f.block, None),
442753 Item::Struct(s) => {
@@ -471,11 +782,26 @@ impl Lowerer {
471782 }
472783 Item::Impl(im) => {
473784 let self_ty = self.map_ty(&im.self_ty)?;
474- if im.trait_.is_some() {
475- return Err(format!(
476- "`impl Trait for {}`: trait impls are not implemented yet",
477- self_ty.render()
478- ));
785+ if let Some((path, _)) = &im.trait_ {
786+ let tr = path_name(path);
787+ if im.items.is_empty() {
788+ return Ok(());
789+ }
790+ let syn::ImplItem::Fn(m) = &im.items[0] else {
791+ return Err(format!("unsupported item in `impl {tr}`"));
792+ };
793+ if is_fmt_trait(&tr) {
794+ return self.fmt_impl(&tr, &self_ty, &m.sig, &m.block);
795+ }
796+ if tr == "From" {
797+ let name = {
798+ let (params, _) = self.signature(&m.sig)?;
799+ let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
800+ self.from_impls[&(type_name(&src), type_name(&self_ty))].clone()
801+ };
802+ return self.func_named(&name, &m.sig, &m.block, None);
803+ }
804+ return Err(format!("`impl {tr}` is not implemented"));
479805 }
480806 for it in &im.items {
481807 match it {
@@ -495,19 +821,23 @@ impl Lowerer {
495821 // An inline `mod` is flattened; Nim has no nested modules in a
496822 // single file.
497823 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
498- for i in &items {
499- self.collect(i)?;
500- }
501824 for i in &items {
502825 self.item(i)?;
503826 }
504827 Ok(())
505828 }
506- Item::Mod(m) => Err(format!(
507- "`mod {};` refers to another file; pass that file to rustnim as \
508- an additional input instead",
509- m.ident
510- )),
829+ Item::Mod(m) => {
830+ // Satisfied if that file was passed in too; everything is one
831+ // Nim module, so the declaration itself emits nothing.
832+ if self.modules.iter().any(|x| *x == m.ident.to_string()) {
833+ return Ok(());
834+ }
835+ Err(format!(
836+ "`mod {};` refers to another file that was not passed to \
837+ rustnim; add it to the input list",
838+ m.ident
839+ ))
840+ }
511841 other => Err(format!("unsupported item: {}", item_kind(other))),
512842 }
513843 }
@@ -647,6 +977,93 @@ impl Lowerer {
647977 }
648978 }
649979
980+ /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
981+ ///
982+ /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
983+ /// observable result of `{}` is exactly the bytes written. So the method
984+ /// becomes `proc rsDisplay(self: T): string` and every write through the
985+ /// formatter produces that string. A `fmt` body that does anything else
986+ /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
987+ /// because those affect the output and this model does not carry them.
988+ /// The window an expression names, if it names one.
989+ fn window_of(&self, e: &Expr) -> Option<Alias> {
990+ match e {
991+ Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
992+ Some(a @ Alias::Window { .. }) => Some(a),
993+ _ => None,
994+ },
995+ Expr::Reference(r) => self.window_of(&r.expr),
996+ Expr::Paren(p) => self.window_of(&p.expr),
997+ Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
998+ _ => None,
999+ }
1000+ }
1001+
1002+ /// Whether an expression is the `Formatter` parameter of the formatting
1003+ /// impl currently being lowered.
1004+ fn is_fmt_param(&self, e: &Expr) -> bool {
1005+ let Some(f) = &self.fmt_param else { return false };
1006+ match e {
1007+ Expr::Path(p) => path_name(&p.path) == *f,
1008+ Expr::Reference(r) => self.is_fmt_param(&r.expr),
1009+ Expr::Paren(p) => self.is_fmt_param(&p.expr),
1010+ _ => false,
1011+ }
1012+ }
1013+
1014+ fn fmt_impl(
1015+ &mut self,
1016+ tr: &str,
1017+ self_ty: &Nim,
1018+ sig: &syn::Signature,
1019+ body: &syn::Block,
1020+ ) -> Result<(), String> {
1021+ let proc_name = fmt_proc(tr);
1022+ // The formatter is the parameter after `self`.
1023+ let f = sig
1024+ .inputs
1025+ .iter()
1026+ .filter_map(|a| match a {
1027+ FnArg::Typed(t) => match &*t.pat {
1028+ Pat::Ident(i) => Some(i.ident.to_string()),
1029+ _ => None,
1030+ },
1031+ _ => None,
1032+ })
1033+ .next()
1034+ .ok_or("`fn fmt` needs a `Formatter` parameter")?;
1035+
1036+ self.push_scope();
1037+ self.bind("self", self_ty.clone());
1038+ let saved = self.fmt_param.replace(f);
1039+ let outer_ret = self.ret.replace(Nim::Prim("string".into()));
1040+ let outer_target = self
1041+ .target
1042+ .replace(("result".to_string(), Some(Nim::Prim("string".into()))));
1043+
1044+ self.line(&format!(
1045+ "proc {}*(self: {}): string =",
1046+ proc_name,
1047+ self_ty.render()
1048+ ));
1049+ self.indent += 1;
1050+ let before = self.out.len();
1051+ let want = Nim::Prim("string".into());
1052+ let tail = self.block_body_at(body, Some(&want))?;
1053+ self.emit_tail(tail);
1054+ if self.out.len() == before {
1055+ self.line("discard");
1056+ }
1057+ self.indent -= 1;
1058+
1059+ self.target = outer_target;
1060+ self.ret = outer_ret;
1061+ self.fmt_param = saved;
1062+ self.pop_scope();
1063+ self.blank();
1064+ Ok(())
1065+ }
1066+
6501067 fn func(
6511068 &mut self,
6521069 sig: &syn::Signature,
@@ -654,6 +1071,16 @@ impl Lowerer {
6541071 recv: Option<Nim>,
6551072 ) -> Result<(), String> {
6561073 let name = sig.ident.to_string();
1074+ self.func_named(&name.clone(), sig, body, recv)
1075+ }
1076+
1077+ fn func_named(
1078+ &mut self,
1079+ name: &str,
1080+ sig: &syn::Signature,
1081+ body: &syn::Block,
1082+ recv: Option<Nim>,
1083+ ) -> Result<(), String> {
6571084 let (ptys, ret) = self.signature(sig)?;
6581085
6591086 self.push_scope();
@@ -684,6 +1111,9 @@ impl Lowerer {
6841111 for (p, t) in typed.iter().zip(ptys.iter()) {
6851112 let pname = match &*p.pat {
6861113 Pat::Ident(i) => i.ident.to_string(),
1114+ // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
1115+ // still needs a name for it.
1116+ Pat::Wild(_) => format!("unused{}", rendered.len()),
6871117 _ => return Err("only plain identifier parameters are supported".into()),
6881118 };
6891119 rendered.push(format!("{}: {}", ident(&pname), t.render()));
@@ -692,9 +1122,9 @@ impl Lowerer {
6921122 }
6931123
6941124 let head = if ret == Nim::Unit {
695- format!("proc {}*({}) =", ident(&name), rendered.join(", "))
1125+ format!("proc {}*({}) =", ident(name), rendered.join(", "))
6961126 } else {
697- format!("proc {}*({}): {} =", ident(&name), rendered.join(", "), ret.render())
1127+ format!("proc {}*({}): {} =", ident(name), rendered.join(", "), ret.render())
6981128 };
6991129 self.line(&head);
7001130 self.indent += 1;
@@ -869,6 +1299,21 @@ impl Lowerer {
8691299 }
8701300
8711301 let v = self.expr_at(&init.expr, ann.as_ref())?;
1302+ if let Some(w) = v.window.clone() {
1303+ // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
1304+ // view into the caller's buffer. Copying it into a `seq` would
1305+ // still print the right bytes but would stop writes reaching the
1306+ // caller, so it is bound as an alias.
1307+ if v.guard.is_some() && v.guard_err.is_some() {
1308+ return Err(format!(
1309+ "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
1310+ which Nim cannot represent; apply `?` or `unwrap()` to it \
1311+ in the same expression"
1312+ ));
1313+ }
1314+ self.bind_alias(&name, w);
1315+ return Ok(());
1316+ }
8721317 let t = match (ann, &v.ty) {
8731318 (Some(a), _) => a.owned(),
8741319 (None, Some(t)) => t.clone().owned(),
@@ -1075,30 +1520,43 @@ impl Lowerer {
10751520 if f.label.is_some() {
10761521 return Err("loop labels are not implemented yet".into());
10771522 }
1078- let name = match &*f.pat {
1079- Pat::Ident(i) => i.ident.to_string(),
1080- Pat::Wild(_) => "_".into(),
1081- _ => return Err("destructuring `for` patterns are not implemented yet".into()),
1082- };
1523+ let it = self.resolve_iter(&f.expr)?;
10831524
1084- // Strip the iterator adaptors that are no-ops once we are iterating a
1085- // Nim container directly. Anything else (`.map`, `.filter`, `.rev`)
1086- // is a real iterator and is rejected rather than silently dropped.
1087- let mut src = &*f.expr;
1088- loop {
1089- match src {
1090- Expr::MethodCall(m)
1091- if matches!(m.method.to_string().as_str(), "iter" | "into_iter" | "iter_mut")
1092- && m.args.is_empty() =>
1093- {
1094- src = &m.receiver
1095- }
1096- Expr::Reference(r) => src = &r.expr,
1097- _ => break,
1098- }
1525+ // One index loop drives the whole chain. Rust's adaptors are lazy and
1526+ // compose; resolving them to an index and binding each name to an
1527+ // lvalue reproduces that without materialising anything.
1528+ let i = self.fresh("Idx");
1529+ self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
1530+ self.indent += 1;
1531+ self.push_scope();
1532+ let before = self.out.len();
1533+
1534+ self.bind_pattern(&f.pat, &it, &i)?;
1535+
1536+ let saved = self.target.take();
1537+ if let Some(v) = self.block_body(&f.body)? {
1538+ let code = v.code.clone();
1539+ self.line(&format!("discard {code}"));
1540+ }
1541+ self.target = saved;
1542+ if self.out.len() == before {
1543+ self.line("discard");
10991544 }
1545+ self.pop_scope();
1546+ self.indent -= 1;
1547+ Ok(())
1548+ }
11001549
1101- let (header, elem) = match src {
1550+ /// Resolve a chain of iterator adaptors into a single `Iter`.
1551+ ///
1552+ /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
1553+ /// `filter`, `take_while` and friends are rejected rather than partially
1554+ /// honoured: silently dropping an adaptor would change which elements the
1555+ /// loop visits.
1556+ fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
1557+ match e {
1558+ Expr::Reference(r) => self.resolve_iter(&r.expr),
1559+ Expr::Paren(p) => self.resolve_iter(&p.expr),
11021560 Expr::Range(r) => {
11031561 let lo = match &r.start {
11041562 Some(e) => self.expr(e)?,
@@ -1106,45 +1564,184 @@ impl Lowerer {
11061564 };
11071565 let hi = match &r.end {
11081566 Some(e) => self.expr(e)?,
1109- None => return Err("a `for` over an unbounded range would not terminate".into()),
1110- };
1111- let op = match r.limits {
1112- syn::RangeLimits::HalfOpen(_) => "..<",
1113- syn::RangeLimits::Closed(_) => "..",
1567+ None => {
1568+ return Err("a `for` over an unbounded range would not terminate".into())
1569+ }
11141570 };
1115- let t = lo.ty.clone().or(hi.ty.clone());
1116- (format!("{} {} {}", lo.code, op, hi.code), t)
1571+ let ty = lo.ty.clone().or(hi.ty.clone());
1572+ Ok(Iter::Range {
1573+ lo: lo.code,
1574+ hi: hi.code,
1575+ closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
1576+ ty,
1577+ })
1578+ }
1579+ Expr::MethodCall(m) => {
1580+ let name = m.method.to_string();
1581+ match name.as_str() {
1582+ "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
1583+ let mut it = self.resolve_iter(&m.receiver)?;
1584+ if name == "iter_mut" {
1585+ if let Iter::Elems { mutable, .. } = &mut it {
1586+ *mutable = true;
1587+ }
1588+ }
1589+ Ok(it)
1590+ }
1591+ "enumerate" if m.args.is_empty() => {
1592+ Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
1593+ }
1594+ "zip" if m.args.len() == 1 => {
1595+ let a = self.resolve_iter(&m.receiver)?;
1596+ let b = self.resolve_iter(&m.args[0])?;
1597+ Ok(Iter::Zip(Box::new(a), Box::new(b)))
1598+ }
1599+ "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => {
1600+ let recv = self.expr(&m.receiver)?;
1601+ let k = self.expr(&m.args[0])?;
1602+ Ok(Iter::Chunks {
1603+ code: recv.code,
1604+ k: k.code,
1605+ elem: elem_of(&recv.ty),
1606+ mutable: name.ends_with("_mut"),
1607+ })
1608+ }
1609+ "windows" if m.args.len() == 1 => {
1610+ let recv = self.expr(&m.receiver)?;
1611+ let k = self.expr(&m.args[0])?;
1612+ Ok(Iter::Windows {
1613+ code: recv.code,
1614+ k: k.code,
1615+ elem: elem_of(&recv.ty),
1616+ })
1617+ }
1618+ other => Err(format!(
1619+ "iterator adaptor `.{other}()` is not implemented; it has \
1620+ no index-loop equivalent here, and dropping it would \
1621+ change which elements the loop visits"
1622+ )),
1623+ }
11171624 }
11181625 other => {
1626+ // A `for` binding that is itself a window iterates that window,
1627+ // not the whole container it points into.
1628+ if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) {
1629+ return Ok(Iter::Elems {
1630+ code,
1631+ off,
1632+ len,
1633+ elem,
1634+ mutable: false,
1635+ });
1636+ }
11191637 let v = self.expr(other)?;
1120- let elem = match v.ty.clone() {
1121- Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
1122- Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
1123- _ => None,
1124- };
1125- (v.code, elem)
1638+ Ok(Iter::Elems {
1639+ len: format!("{}.len", v.code),
1640+ elem: elem_of(&v.ty),
1641+ code: v.code,
1642+ off: "0".into(),
1643+ mutable: false,
1644+ })
11261645 }
1127- };
1128-
1129- self.line(&format!("for {} in {}:", ident(&name), header));
1130- self.push_scope();
1131- if let Some(t) = elem {
1132- self.bind(&name, t);
11331646 }
1134- self.indent += 1;
1135- let before = self.out.len();
1136- let saved = self.target.take();
1137- if let Some(v) = self.block_body(&f.body)? {
1138- let code = v.code.clone();
1139- self.line(&format!("discard {code}"));
1140- }
1141- self.target = saved;
1142- if self.out.len() == before {
1143- self.line("discard");
1647+ }
1648+
1649+ /// Bind a `for` pattern against a resolved iterator at index `i`.
1650+ fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
1651+ match (p, it) {
1652+ (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
1653+ self.bind_pattern(&t.elems[0], a, i)?;
1654+ self.bind_pattern(&t.elems[1], b, i)
1655+ }
1656+ (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
1657+ if let Pat::Ident(id) = &t.elems[0] {
1658+ let n = id.ident.to_string();
1659+ // Rust's `enumerate` counts in `usize`.
1660+ self.line(&format!("let {}: uint = uint({})", ident(&n), i));
1661+ self.bind(&n, Nim::Prim("uint".into()));
1662+ }
1663+ self.bind_pattern(&t.elems[1], inner, i)
1664+ }
1665+ (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
1666+ "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
1667+ ),
1668+ (Pat::Wild(_), _) => Ok(()),
1669+ (Pat::Ident(id), _) => {
1670+ let name = id.ident.to_string();
1671+ match it {
1672+ Iter::Range { lo, ty, .. } => {
1673+ let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
1674+ // The loop counts from zero; the range's own start is
1675+ // added back so the binding has Rust's value and type.
1676+ self.line(&format!(
1677+ "let {}: {} = {}({}) + {}",
1678+ ident(&name),
1679+ t.render(),
1680+ t.render(),
1681+ i,
1682+ lo
1683+ ));
1684+ self.bind(&name, t);
1685+ Ok(())
1686+ }
1687+ Iter::Elems { code, off, elem, mutable, .. } => {
1688+ let access = if off == "0" {
1689+ format!("{}[{}]", code, i)
1690+ } else {
1691+ format!("{}[{} + {}]", code, off, i)
1692+ };
1693+ if *mutable {
1694+ // An alias, not a copy: assigning through the
1695+ // binding must reach the original element.
1696+ self.bind_alias(
1697+ &name,
1698+ Alias::Value { code: access, ty: elem.clone() },
1699+ );
1700+ } else {
1701+ let t = elem
1702+ .clone()
1703+ .ok_or("cannot infer the element type of this `for`")?;
1704+ self.line(&format!(
1705+ "let {}: {} = {}",
1706+ ident(&name),
1707+ t.render(),
1708+ access
1709+ ));
1710+ self.bind(&name, t);
1711+ }
1712+ Ok(())
1713+ }
1714+ Iter::Chunks { code, k, elem, .. } => {
1715+ self.bind_alias(
1716+ &name,
1717+ Alias::Window {
1718+ code: code.clone(),
1719+ off: format!("({} * int({}))", i, k),
1720+ len: format!("int({})", k),
1721+ elem: elem.clone(),
1722+ },
1723+ );
1724+ Ok(())
1725+ }
1726+ Iter::Windows { code, k, elem } => {
1727+ self.bind_alias(
1728+ &name,
1729+ Alias::Window {
1730+ code: code.clone(),
1731+ off: i.to_string(),
1732+ len: format!("int({})", k),
1733+ elem: elem.clone(),
1734+ },
1735+ );
1736+ Ok(())
1737+ }
1738+ // Handled above: a zip or enumerate needs a tuple pattern,
1739+ // and binding one name to the pair is not supported.
1740+ Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
1741+ }
1742+ }
1743+ _ => Err("unsupported `for` pattern".into()),
11441744 }
1145- self.indent -= 1;
1146- self.pop_scope();
1147- Ok(())
11481745 }
11491746
11501747 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
@@ -1499,6 +2096,22 @@ impl Lowerer {
14992096 if name == "None" {
15002097 return Ok(Val::new(self.none_of(expect), expect.cloned()));
15012098 }
2099+ // A unit struct used as a value: `fmt::Error`, or a `struct S;`
2100+ // declared here. In Nim that is a constructor call.
2101+ if p.path.segments.len() > 1 {
2102+ let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
2103+ if let Ok(Nim::Prim(n)) = ty::map(&ty) {
2104+ if n == "FmtError" {
2105+ return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
2106+ }
2107+ }
2108+ }
2109+ if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
2110+ return Ok(Val::new(
2111+ format!("{}()", ident(&name)),
2112+ Some(Nim::Named(name.clone(), vec![])),
2113+ ));
2114+ }
15022115 // A unit enum variant used as a value: `Error::InvalidLength`.
15032116 if let Some((def, v)) = self.resolve_variant(&p.path) {
15042117 let ty = Some(Nim::Named(def.name.clone(), vec![]));
@@ -1508,6 +2121,20 @@ impl Lowerer {
15082121 Val::new(format!("{}()", def.ctor_ident(&v)), ty)
15092122 });
15102123 }
2124+ // A `for` binding that stands for an element of the container
2125+ // it came from: using it must read (and assigning through it
2126+ // must write) that element, not a copy.
2127+ if let Some(a) = self.lookup_alias(&name) {
2128+ return Ok(match a {
2129+ Alias::Value { code, ty } => Val::new(code, ty),
2130+ // A window *is* a slice; as a value it is the view it
2131+ // denotes, which is what Rust's `&[T]` means too.
2132+ Alias::Window { code, off, len, elem } => Val::new(
2133+ format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
2134+ elem.map(|e| Nim::OpenArray(Box::new(e))),
2135+ ),
2136+ });
2137+ }
15112138 if let Some(t) = self.lookup(&name) {
15122139 return Ok(Val::new(ident(&name), Some(t)));
15132140 }
@@ -1530,7 +2157,38 @@ impl Lowerer {
15302157 Expr::Unary(u) => self.unary(u, expect),
15312158 Expr::Binary(b) => self.binary(b, expect),
15322159 Expr::Cast(c) => self.cast(c),
2160+ Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
2161+ let Expr::Range(r) = &*i.index else { unreachable!() };
2162+ let base = self.expr(&i.expr)?;
2163+ let lo = match &r.start {
2164+ Some(e) => format!("int({})", self.expr(e)?.code),
2165+ None => "0".into(),
2166+ };
2167+ // Nim's `toOpenArray` takes an inclusive upper bound.
2168+ let hi = match (&r.end, r.limits) {
2169+ (Some(e), syn::RangeLimits::HalfOpen(_)) => {
2170+ format!("int({}) - 1", self.expr(e)?.code)
2171+ }
2172+ (Some(e), syn::RangeLimits::Closed(_)) => {
2173+ format!("int({})", self.expr(e)?.code)
2174+ }
2175+ (None, _) => format!("{}.len - 1", base.code),
2176+ };
2177+ let elem = elem_of(&base.ty)
2178+ .ok_or("cannot infer the element type of this slice")?;
2179+ Ok(Val::new(
2180+ format!("{}.toOpenArray({}, {})", base.code, lo, hi),
2181+ Some(Nim::OpenArray(Box::new(elem))),
2182+ ))
2183+ }
15332184 Expr::Index(i) => {
2185+ if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
2186+ let idx = self.expr(&i.index)?;
2187+ return Ok(Val::new(
2188+ format!("{}[{} + int({})]", code, off, idx.code),
2189+ elem,
2190+ ));
2191+ }
15342192 let base = self.expr(&i.expr)?;
15352193 let idx = self.expr(&i.index)?;
15362194 // Rust indexes with usize; Nim wants an `int`, and a `uint`
@@ -1564,7 +2222,7 @@ impl Lowerer {
15642222 }
15652223 Expr::Try(t) => self.try_op(t),
15662224 Expr::Call(c) => self.call(c, expect),
1567- Expr::MethodCall(m) => self.method(m),
2225+ Expr::MethodCall(m) => self.method(m, expect),
15682226 Expr::Macro(m) => {
15692227 let code = self.macro_call(&m.mac)?;
15702228 Ok(Val::new(code, None))
@@ -1917,6 +2575,30 @@ impl Lowerer {
19172575 .into());
19182576 }
19192577 let v = self.expr(&t.expr)?;
2578+ if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
2579+ // An `Option`/`Result` of a view: the check is emitted here and the
2580+ // view itself survives as an alias, since it has no value form.
2581+ let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
2582+ let err = v.guard_err.clone().ok_or(
2583+ "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
2584+ )?;
2585+ let Nim::Named(n, ra) = &ret else {
2586+ return Err(format!("`?` in a function returning `{}`", ret.render()));
2587+ };
2588+ if n != "Result" || ra.len() != 2 {
2589+ return Err(format!("`?` in a function returning `{}`", ret.render()));
2590+ }
2591+ self.line(&format!("if not {}:", guard));
2592+ self.line(&format!(
2593+ " return rsErr[{}, {}]({})",
2594+ ra[0].render(),
2595+ ra[1].render(),
2596+ err
2597+ ));
2598+ let mut out = Val::new(String::new(), None);
2599+ out.window = Some(w);
2600+ return Ok(out);
2601+ }
19202602 let vt = v.ty.clone().ok_or(
19212603 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
19222604 )?;
@@ -2056,9 +2738,67 @@ impl Lowerer {
20562738 ))
20572739 }
20582740
2059- fn method(&mut self, m: &syn::ExprMethodCall) -> Result<Val, String> {
2060- let recv = self.expr(&m.receiver)?;
2741+ fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
20612742 let name = m.method.to_string();
2743+ if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
2744+ match name.as_str() {
2745+ "len" => {
2746+ return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
2747+ }
2748+ "is_empty" => {
2749+ return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
2750+ }
2751+ other => {
2752+ return Err(format!(
2753+ "`.{other}()` on a slice window from `chunks_exact`/\
2754+ `windows` is not implemented; only indexing and \
2755+ `len()` are"
2756+ ))
2757+ }
2758+ }
2759+ }
2760+ let recv = self.expr(&m.receiver)?;
2761+ let rt0 = recv.ty.clone();
2762+
2763+// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
2764+ // way to put a view in an object, so instead of materialising an
2765+ // Option the view and its validity condition travel together until
2766+ // an `ok_or`/`?`/`unwrap` resolves them.
2767+ if matches!(name.as_str(), "get" | "get_mut")
2768+ && matches!(m.args.first(), Some(Expr::Range(_)))
2769+ {
2770+ let Some(Expr::Range(r)) = m.args.first() else { unreachable!() };
2771+ let lo = match &r.start {
2772+ Some(e) => format!("int({})", self.expr(e)?.code),
2773+ None => "0".into(),
2774+ };
2775+ let len = match (&r.end, r.limits) {
2776+ (Some(e), syn::RangeLimits::HalfOpen(_)) => {
2777+ format!("(int({}) - {})", self.expr(e)?.code, lo)
2778+ }
2779+ (Some(e), syn::RangeLimits::Closed(_)) => {
2780+ format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
2781+ }
2782+ (None, _) => format!("({}.len - {})", recv.code, lo),
2783+ };
2784+ let elem = elem_of(&rt0).ok_or("cannot infer the element type of this slice")?;
2785+ let mut v = Val::new(
2786+ String::new(),
2787+ Some(Nim::Named(
2788+ "Option".into(),
2789+ vec![Nim::OpenArray(Box::new(elem.clone()))],
2790+ )),
2791+ );
2792+ v.guard = Some(format!("({} + {} <= {}.len)", lo, len, recv.code));
2793+ v.window = Some(Alias::Window {
2794+ code: recv.code.clone(),
2795+ off: lo,
2796+ len,
2797+ elem: Some(elem),
2798+ });
2799+ return Ok(v);
2800+ }
2801+
20622802 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
20632803 // own type; `v.push(e)` takes the element type.
20642804 let arg_want = match (name.as_str(), &recv.ty) {
@@ -2089,6 +2829,19 @@ impl Lowerer {
20892829 };
20902830 (format!("unwrap({})", recv.code), inner)
20912831 }
2832+ "ok_or" if recv.guard.is_some() => {
2833+ let e = args.first().ok_or("`ok_or` takes one argument")?;
2834+ let ety = e.ty.clone();
2835+ let mut v = recv.clone();
2836+ v.guard_err = Some(e.code.clone());
2837+ v.ty = match (&recv.ty, ety) {
2838+ (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
2839+ Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
2840+ }
2841+ _ => None,
2842+ };
2843+ return Ok(v);
2844+ }
20922845 "ok_or" => {
20932846 let inner = match &rt {
20942847 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
@@ -2158,6 +2911,12 @@ impl Lowerer {
21582911 )
21592912 }
21602913 }
2914+ // Inside a formatting impl, a write through the `Formatter` *is*
2915+ // the value the proc returns, so it lowers to the string written.
2916+ "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => (
2917+ a0.ok_or("`write_str` takes one argument")?,
2918+ Some(Nim::Prim("string".into())),
2919+ ),
21612920 "abs" => (format!("abs({})", recv.code), rt.clone()),
21622921 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
21632922 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
@@ -2167,11 +2926,30 @@ impl Lowerer {
21672926 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
21682927 ),
21692928
2929+ "into" => {
2930+ // `.into()` resolves through the `impl From` declarations, and
2931+ // needs the target type to pick one.
2932+ let from = rt
2933+ .clone()
2934+ .ok_or("`.into()` needs a known receiver type")?;
2935+ let to = expect
2936+ .ok_or("`.into()` needs a known target type; annotate the binding")?;
2937+ let key = (type_name(&from), type_name(to));
2938+ let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
2939+ format!(
2940+ "no `impl From<{}> for {}` in this file, so `.into()` has \
2941+ no conversion to call",
2942+ key.0, key.1
2943+ )
2944+ })?;
2945+ (format!("{}({})", f, recv.code), Some(to.clone()))
2946+ }
21702947 _ => {
2171- // A method defined in this file via `impl`. Nim's UFCS makes
2172- // the call site spelling identical.
2173- if let Some(sig) = self.fns.get(&name) {
2174- let ret = sig.ret.clone();
2948+ // A method defined in this file via `impl`, found by the
2949+ // receiver's type rather than by name alone.
2950+ let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
2951+ let sig = key.and_then(|k| self.methods.get(&k)).map(|s| s.ret.clone());
2952+ if let Some(ret) = sig {
21752953 let mut all = vec![recv.code.clone()];
21762954 all.extend(args.iter().map(|a| a.code.clone()));
21772955 (format!("{}({})", ident(&name), all.join(", ")), Some(ret))
@@ -2203,6 +2981,27 @@ impl Lowerer {
22032981 })
22042982 }
22052983 "format" => self.format_args(mac),
2984+ "write" | "writeln" => {
2985+ // `write!(f, "..", ..)` inside a formatting impl: the first
2986+ // argument is the sink, the rest is an ordinary format call.
2987+ let args: Vec<Expr> = mac
2988+ .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
2989+ .map_err(|e| format!("write!: {e}"))?
2990+ .into_iter()
2991+ .collect();
2992+ let sink = args.first().ok_or("`write!` needs a sink")?;
2993+ if !self.is_fmt_param(sink) {
2994+ return Err("`write!` to anything but the `Formatter` of the \
2995+ enclosing formatting impl is not implemented"
2996+ .into());
2997+ }
2998+ let s = self.format_pieces(&args[1..])?;
2999+ Ok(if name == "writeln" {
3000+ format!("({} & \"\\n\")", s)
3001+ } else {
3002+ s
3003+ })
3004+ }
22063005 "panic" => {
22073006 let s = self.format_args(mac)?;
22083007 Ok(format!("rsPanic({s})"))
@@ -2253,17 +3052,23 @@ impl Lowerer {
22533052
22543053 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
22553054 fn format_args(&mut self, mac: &syn::Macro) -> Result<String, String> {
2256- let args: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
2257- .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
2258- .map_err(|e| format!("format arguments: {e}"))?;
2259- let mut it = args.iter();
2260- let Some(Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. })) = it.next() else {
3055+ let args: Vec<Expr> = mac
3056+ .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
3057+ .map_err(|e| format!("format arguments: {e}"))?
3058+ .into_iter()
3059+ .collect();
3060+ self.format_pieces(&args)
3061+ }
3062+
3063+ /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
3064+ fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
3065+ let Some(Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. })) = args.first() else {
22613066 if args.is_empty() {
22623067 return Ok("\"\"".into());
22633068 }
22643069 return Err("the first argument must be a literal format string".into());
22653070 };
2266- let rest: Vec<&Expr> = it.collect();
3071+ let rest: Vec<&Expr> = args[1..].iter().collect();
22673072
22683073 let pieces = fmt::parse(&s.value())?;
22693074 let mut parts: Vec<String> = Vec::new();
@@ -2422,6 +3227,51 @@ fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type
24223227
24233228 // --------------------------------------------------------------- utilities
24243229
3230+/// Whether a return type is a borrow of one of the arguments, which Nim
3231+/// models with a view rather than with an owned copy.
3232+fn returns_borrow(t: &syn::Type) -> bool {
3233+ match t {
3234+ syn::Type::Reference(r) => matches!(&*r.elem, syn::Type::Slice(_)),
3235+ syn::Type::Paren(p) => returns_borrow(&p.elem),
3236+ syn::Type::Group(g) => returns_borrow(&g.elem),
3237+ _ => false,
3238+ }
3239+}
3240+
3241+/// The element type of a sequence-like Nim type.
3242+fn elem_of(t: &Option<Nim>) -> Option<Nim> {
3243+ match t {
3244+ Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
3245+ Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
3246+ _ => None,
3247+ }
3248+}
3249+
3250+/// The short name a Nim type is known by, for keying method tables.
3251+fn type_name(t: &Nim) -> String {
3252+ match t {
3253+ Nim::Named(n, _) => n.clone(),
3254+ Nim::Prim(p) => p.clone(),
3255+ other => other.render(),
3256+ }
3257+}
3258+
3259+fn is_fmt_trait(t: &str) -> bool {
3260+ matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
3261+}
3262+
3263+/// The prelude proc a formatting trait's output is produced by.
3264+fn fmt_proc(t: &str) -> &'static str {
3265+ match t {
3266+ "Display" => "rsDisplay",
3267+ "Debug" => "rsDebug",
3268+ "LowerHex" => "rsLowerHex",
3269+ "UpperHex" => "rsUpperHex",
3270+ "Binary" => "rsBinary",
3271+ _ => "rsOctal",
3272+ }
3273+}
3274+
24253275 fn takes_self(sig: &syn::Signature) -> bool {
24263276 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
24273277 }
@@ -29,12 +29,91 @@ const NIM_KEYWORDS: &[&str] = &[
29 29
30 fn ident(name: &str) -> String {30 fn ident(name: &str) -> String {
31 if NIM_KEYWORDS.contains(&name) {31 if NIM_KEYWORDS.contains(&name) {
32- format!("{name}_r")32+ return format!("{name}_r");
33- } else {33+ }
34- name.to_string()34+ // Nim identifiers may not begin with an underscore, and may not contain
35+ // two in a row. Rust uses both freely (`_unused`, `__private`).
36+ let mut out = String::new();
37+ let mut last_us = false;
38+ for (i, c) in name.chars().enumerate() {
39+ if c == '_' {
40+ if i == 0 {
41+ out.push('u');
42+ out.push('_');
43+ last_us = true;
44+ continue;
45+ }
46+ if last_us {
47+ continue;
48+ }
49+ last_us = true;
50+ out.push('_');
51+ } else {
52+ last_us = false;
53+ out.push(c);
54+ }
55+ }
56+ if out.ends_with('_') {
57+ out.push('x');
58+ }
59+ out
60+}
61+
62+/// A `for`-loop source, resolved from a chain of iterator adaptors.
63+///
64+/// Rust's slice iterators are lazy and compose; Nim's `for` is over one
65+/// sequence. So a chain is resolved into this shape and then emitted as a
66+/// single index loop, with each binding becoming an *lvalue* into the original
67+/// container. That is what makes `*dst = v` through `iter_mut()` write back to
68+/// the caller's slice rather than to a copy.
69+#[derive(Clone, Debug)]
70+enum Iter {
71+ /// `a..b` / `a..=b`.
72+ Range { lo: String, hi: String, closed: bool, ty: Option<Nim> },
73+ /// `for x in a`, `a.iter()`, `a.iter_mut()`. `off`/`len` let the same
74+ /// shape cover a subslice view. `mutable` only affects whether the binding
75+ /// may be assigned through.
76+ Elems { code: String, off: String, len: String, elem: Option<Nim>, mutable: bool },
77+ /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of
78+ /// `k` elements starting at `k * i`.
79+ Chunks { code: String, k: String, elem: Option<Nim>, mutable: bool },
80+ /// `a.windows(k)`: like `Chunks` but advancing one element at a time.
81+ Windows { code: String, k: String, elem: Option<Nim> },
82+ /// `.enumerate()` — the index is the first half of the pair.
83+ Enumerate(Box<Iter>),
84+ /// `.zip(other)` — stops at the shorter, as Rust's does.
85+ Zip(Box<Iter>, Box<Iter>),
86+}
87+
88+impl Iter {
89+ /// The number of iterations, as a Nim expression in terms of the loop's
90+ /// own containers.
91+ fn len(&self) -> String {
92+ match self {
93+ Iter::Range { lo, hi, closed, .. } => {
94+ let n = format!("(int({hi}) - int({lo}))");
95+ if *closed { format!("({n} + 1)") } else { n }
96+ }
97+ Iter::Elems { len, .. } => len.clone(),
98+ Iter::Chunks { code, k, .. } => format!("({}.len div int({}))", code, k),
99+ Iter::Windows { code, k, .. } => {
100+ format!("(max(0, {}.len - int({}) + 1))", code, k)
101+ }
102+ Iter::Enumerate(i) => i.len(),
103+ Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),
104+ }
35 }105 }
36 }106 }
37 107
108+/// How a `for`-loop pattern name refers back into the container it came from.
109+#[derive(Clone, Debug)]
110+enum Alias {
111+ /// The name stands for this Nim lvalue expression.
112+ Value { code: String, ty: Option<Nim> },
113+ /// The name stands for a window: `code[off .. off + len - 1]`.
114+ Window { code: String, off: String, len: String, elem: Option<Nim> },
115+}
116+
38 /// A lowered expression: its Nim text, and its type where we know it.117 /// A lowered expression: its Nim text, and its type where we know it.
39 ///118 ///
40 /// The type is not decoration. Nim needs it to pick `div` over `/`, to size a119 /// The type is not decoration. Nim needs it to pick `div` over `/`, to size a
@@ -45,14 +124,24 @@ fn ident(name: &str) -> String {
45 struct Val {124 struct Val {
46 code: String,125 code: String,
47 ty: Option<Nim>,126 ty: Option<Nim>,
127+ /// Set when the value *is* a slice view rather than a Nim value: binding
128+ /// it introduces an alias, not a copy.
129+ window: Option<Alias>,
130+ /// For `get`/`get_mut`: the condition under which the `Option` is `Some`,
131+ /// carried until an `ok_or`/`?` or an `unwrap` consumes it. Nim's view
132+ /// types cannot live inside an object, so an `Option` of a view has no
133+ /// runtime representation -- it is tracked here instead.
134+ guard: Option<String>,
135+ /// The error an `ok_or` attached to that guard.
136+ guard_err: Option<String>,
48 }137 }
49 138
50 impl Val {139 impl Val {
51 fn new(code: impl Into<String>, ty: Option<Nim>) -> Self {140 fn new(code: impl Into<String>, ty: Option<Nim>) -> Self {
52- Val { code: code.into(), ty }141+ Val { code: code.into(), ty, window: None, guard: None, guard_err: None }
53 }142 }
54 fn untyped(code: impl Into<String>) -> Self {143 fn untyped(code: impl Into<String>) -> Self {
55- Val { code: code.into(), ty: None }144+ Val::new(code, None)
56 }145 }
57 }146 }
58 147
@@ -96,6 +185,9 @@ pub struct Lowerer {
96 out: String,185 out: String,
97 indent: usize,186 indent: usize,
98 scopes: Vec<HashMap<String, Nim>>,187 scopes: Vec<HashMap<String, Nim>>,
188+ /// Names introduced by a `for` pattern that stand for an lvalue or a
189+ /// window into a container, rather than for a variable of their own.
190+ alias_scopes: Vec<HashMap<String, Alias>>,
99 fns: HashMap<String, Sig>,191 fns: HashMap<String, Sig>,
100 /// struct name -> (field, type)192 /// struct name -> (field, type)
101 structs: HashMap<String, Vec<(String, Nim)>>,193 structs: HashMap<String, Vec<(String, Nim)>>,
@@ -103,8 +195,28 @@ pub struct Lowerer {
103 /// variant name -> enums declaring it. A variant named by more than one195 /// variant name -> enums declaring it. A variant named by more than one
104 /// enum must be written qualified, or it is rejected as ambiguous.196 /// enum must be written qualified, or it is rejected as ambiguous.
105 variant_owner: HashMap<String, Vec<String>>,197 variant_owner: HashMap<String, Vec<String>>,
198+ /// `(receiver type, method) -> signature`. Keyed by type because two
199+ /// types may define the same method name, and Nim tells them apart by
200+ /// overload resolution on the first parameter.
201+ methods: HashMap<(String, String), Sig>,
202+ /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}`
203+ /// on a user type can be checked rather than assumed.
204+ fmt_impls: HashMap<(String, String), ()>,
205+ /// `(from, to)` conversions declared by `impl From<A> for B`.
206+ from_impls: HashMap<(String, String), String>,
207+ /// Forward declarations, emitted between the type definitions and the
208+ /// bodies. Rust has no declaration-before-use rule and Nim does, so every
209+ /// proc is declared up front rather than the input being reordered --
210+ /// which would not work for mutual recursion anyway.
211+ forwards: Vec<String>,
212+ /// While lowering a formatting impl: the `Formatter` parameter's name.
213+ /// Writes through it produce the proc's string result.
214+ fmt_param: Option<String>,
106 /// `type X<T> = ...`, expanded before any type is mapped.215 /// `type X<T> = ...`, expanded before any type is mapped.
107 aliases: HashMap<String, (Vec<String>, syn::Type)>,216 aliases: HashMap<String, (Vec<String>, syn::Type)>,
217+ /// Module names supplied as separate input files. A `mod x;` naming one
218+ /// of these is satisfied by that file having been passed in.
219+ pub modules: Vec<String>,
108 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is220 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
109 /// evaluated against these exactly as rustc would, so an item that is221 /// evaluated against these exactly as rustc would, so an item that is
110 /// dropped here is genuinely not part of the program being compiled.222 /// dropped here is genuinely not part of the program being compiled.
@@ -128,11 +240,18 @@ impl Lowerer {
128 out: String::new(),240 out: String::new(),
129 indent: 0,241 indent: 0,
130 scopes: vec![HashMap::new()],242 scopes: vec![HashMap::new()],
243+ alias_scopes: vec![HashMap::new()],
131 fns: HashMap::new(),244 fns: HashMap::new(),
132 structs: HashMap::new(),245 structs: HashMap::new(),
133 enums: HashMap::new(),246 enums: HashMap::new(),
134 variant_owner: HashMap::new(),247 variant_owner: HashMap::new(),
248+ methods: HashMap::new(),
249+ fmt_impls: HashMap::new(),
250+ from_impls: HashMap::new(),
251+ fmt_param: None,
252+ forwards: Vec::new(),
135 aliases: HashMap::new(),253 aliases: HashMap::new(),
254+ modules: Vec::new(),
136 features: Vec::new(),255 features: Vec::new(),
137 dropped_by_cfg: 0,256 dropped_by_cfg: 0,
138 ret: None,257 ret: None,
@@ -165,9 +284,23 @@ impl Lowerer {
165 284
166 fn push_scope(&mut self) {285 fn push_scope(&mut self) {
167 self.scopes.push(HashMap::new());286 self.scopes.push(HashMap::new());
287+ self.alias_scopes.push(HashMap::new());
168 }288 }
169 fn pop_scope(&mut self) {289 fn pop_scope(&mut self) {
170 self.scopes.pop();290 self.scopes.pop();
291+ self.alias_scopes.pop();
292+ }
293+ fn bind_alias(&mut self, name: &str, a: Alias) {
294+ self.alias_scopes
295+ .last_mut()
296+ .unwrap()
297+ .insert(name.to_string(), a);
298+ }
299+ fn lookup_alias(&self, name: &str) -> Option<Alias> {
300+ self.alias_scopes
301+ .iter()
302+ .rev()
303+ .find_map(|s| s.get(name).cloned())
171 }304 }
172 fn bind(&mut self, name: &str, t: Nim) {305 fn bind(&mut self, name: &str, t: Nim) {
173 self.scopes.last_mut().unwrap().insert(name.to_string(), t);306 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
@@ -182,12 +315,34 @@ impl Lowerer {
182 self.out.push_str(include_str!("prelude.nim"));315 self.out.push_str(include_str!("prelude.nim"));
183 self.blank();316 self.blank();
184 317
318+ // Pass 0: type aliases. A signature in one file may use an alias
319+ // declared in another, and inputs are given in whatever order suits
320+ // the caller, so aliases are registered before anything is mapped.
321+ for item in &file.items {
322+ self.collect_aliases(item)?;
323+ }
324+
185 // Pass 1: signatures and struct shapes, so that a call can be typed325 // Pass 1: signatures and struct shapes, so that a call can be typed
186 // regardless of declaration order (Rust has no forward declarations).326 // regardless of declaration order (Rust has no forward declarations).
187 for item in &file.items {327 for item in &file.items {
188 self.collect(item)?;328 self.collect(item)?;
189 }329 }
190- // Pass 2: bodies.330+ // Pass 2: type definitions, which every signature may mention.
331+ for item in &file.items {
332+ self.item_types(item)?;
333+ }
334+
335+ // Pass 3: forward declarations. Rust imposes no declaration order and
336+ // Nim does, so everything is declared before any body is emitted;
337+ // reordering the input would not handle mutual recursion anyway.
338+ if !self.forwards.is_empty() {
339+ for f in self.forwards.clone() {
340+ self.line(&f);
341+ }
342+ self.blank();
343+ }
344+
345+ // Pass 4: bodies.
191 for item in &file.items {346 for item in &file.items {
192 self.item(item)?;347 self.item(item)?;
193 }348 }
@@ -212,6 +367,35 @@ impl Lowerer {
212 Ok(std::mem::take(&mut self.out))367 Ok(std::mem::take(&mut self.out))
213 }368 }
214 369
370+ fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
371+ if !self.cfg_keeps(item_attrs(item))? {
372+ return Ok(());
373+ }
374+ match item {
375+ Item::Type(t) => {
376+ let params: Vec<String> = t
377+ .generics
378+ .params
379+ .iter()
380+ .filter_map(|g| match g {
381+ syn::GenericParam::Type(t) => Some(t.ident.to_string()),
382+ _ => None,
383+ })
384+ .collect();
385+ self.aliases
386+ .insert(t.ident.to_string(), (params, (*t.ty).clone()));
387+ }
388+ Item::Mod(m) if m.content.is_some() => {
389+ let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
390+ for i in &items {
391+ self.collect_aliases(i)?;
392+ }
393+ }
394+ _ => {}
395+ }
396+ Ok(())
397+ }
398+
215 fn collect(&mut self, item: &Item) -> Result<(), String> {399 fn collect(&mut self, item: &Item) -> Result<(), String> {
216 // A `#[cfg(..)]` item exists only under some feature set. Dropping it400 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
217 // silently would change what the program does; picking a feature set401 // silently would change what the program does; picking a feature set
@@ -224,6 +408,8 @@ impl Lowerer {
224 match item {408 match item {
225 Item::Fn(f) => {409 Item::Fn(f) => {
226 let (params, ret) = self.signature(&f.sig)?;410 let (params, ret) = self.signature(&f.sig)?;
411+ let head = self.head_of(&f.sig.ident.to_string(), &f.sig, None)?;
412+ self.forwards.push(head);
227 self.fns.insert(f.sig.ident.to_string(), Sig { params, ret });413 self.fns.insert(f.sig.ident.to_string(), Sig { params, ret });
228 }414 }
229 Item::Struct(s) => {415 Item::Struct(s) => {
@@ -237,6 +423,12 @@ impl Lowerer {
237 }423 }
238 self.structs.insert(s.ident.to_string(), fields);424 self.structs.insert(s.ident.to_string(), fields);
239 }425 }
426+ Item::Mod(m) if m.content.is_some() => {
427+ let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
428+ for i in &items {
429+ self.collect(i)?;
430+ }
431+ }
240 Item::Type(t) => {432 Item::Type(t) => {
241 let params: Vec<String> = t433 let params: Vec<String> = t
242 .generics434 .generics
@@ -291,13 +483,57 @@ impl Lowerer {
291 }483 }
292 Item::Impl(im) => {484 Item::Impl(im) => {
293 let self_ty = self.map_ty(&im.self_ty)?;485 let self_ty = self.map_ty(&im.self_ty)?;
486+ let tyname = type_name(&self_ty);
487+ if let Some((path, _)) = &im.trait_ {
488+ let tr = path_name(path);
489+ if im.items.is_empty() {
490+ // A marker trait with no items. We do not model trait
491+ // resolution at all, so it generates nothing; any use
492+ // that actually needed the trait (a `dyn`, a bound) is
493+ // rejected where it appears.
494+ return Ok(());
495+ }
496+ if is_fmt_trait(&tr) {
497+ self.forwards.push(format!(
498+ "proc {}*(self: {}): string",
499+ fmt_proc(&tr),
500+ self_ty.render()
501+ ));
502+ self.fmt_impls.insert((tyname, tr), ());
503+ return Ok(());
504+ }
505+ if tr == "From" {
506+ let syn::ImplItem::Fn(m) = &im.items[0] else {
507+ return Err("`impl From` must contain `fn from`".into());
508+ };
509+ let (params, _) = self.signature(&m.sig)?;
510+ let src = params
511+ .first()
512+ .ok_or("`fn from` takes one argument")?
513+ .clone();
514+ let name = format!("rsFrom{}{}", tyname, type_name(&src));
515+ self.forwards.push(self.head_of(&name, &m.sig, None)?);
516+ self.from_impls
517+ .insert((type_name(&src), tyname), name);
518+ return Ok(());
519+ }
520+ return Err(format!(
521+ "`impl {tr} for {tyname}`: only formatting traits \
522+ (Display, Debug, LowerHex, UpperHex, Binary, Octal), \
523+ `From`, and marker traits with no items are implemented"
524+ ));
525+ }
294 for it in &im.items {526 for it in &im.items {
295 if let syn::ImplItem::Fn(m) = it {527 if let syn::ImplItem::Fn(m) = it {
296 let (mut params, ret) = self.signature(&m.sig)?;528 let (mut params, ret) = self.signature(&m.sig)?;
297 if takes_self(&m.sig) {529 if takes_self(&m.sig) {
298 params.insert(0, self_ty.clone());530 params.insert(0, self_ty.clone());
299 }531 }
300- self.fns.insert(m.sig.ident.to_string(), Sig { params, ret });532+ let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
533+ let head = self.head_of(&m.sig.ident.to_string(), &m.sig, recv.as_ref())?;
534+ self.forwards.push(head);
535+ self.methods
536+ .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret });
301 }537 }
302 }538 }
303 }539 }
@@ -401,6 +637,49 @@ impl Lowerer {
401 self.expand(&substitute(target, params, &args), depth + 1)637 self.expand(&substitute(target, params, &args), depth + 1)
402 }638 }
403 639
640+ /// The Nim `proc` head for a Rust signature, used both for the forward
641+ /// declaration and for the definition, so the two cannot drift apart.
642+ fn head_of(
643+ &self,
644+ name: &str,
645+ sig: &syn::Signature,
646+ recv: Option<&Nim>,
647+ ) -> Result<String, String> {
648+ let (ptys, ret) = self.signature(sig)?;
649+ let mut parts = Vec::new();
650+ if let Some(self_ty) = recv {
651+ let mutable = matches!(
652+ sig.inputs.first(),
653+ Some(FnArg::Receiver(r))
654+ if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
655+ );
656+ let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
657+ parts.push(format!("self: {}", t.render()));
658+ }
659+ let typed: Vec<&syn::PatType> = sig
660+ .inputs
661+ .iter()
662+ .filter_map(|a| match a {
663+ FnArg::Typed(t) => Some(t),
664+ _ => None,
665+ })
666+ .collect();
667+ for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
668+ let pname = match &*p.pat {
669+ Pat::Ident(id) => id.ident.to_string(),
670+ Pat::Wild(_) => format!("unused{}", parts.len()),
671+ _ => return Err("only plain identifier parameters are supported".into()),
672+ };
673+ let _ = i;
674+ parts.push(format!("{}: {}", ident(&pname), t.render()));
675+ }
676+ Ok(if ret == Nim::Unit {
677+ format!("proc {}*({})", ident(name), parts.join(", "))
678+ } else {
679+ format!("proc {}*({}): {}", ident(name), parts.join(", "), ret.render())
680+ })
681+ }
682+
404 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {683 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
405 if sig.asyncness.is_some() {684 if sig.asyncness.is_some() {
406 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));685 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
@@ -426,17 +705,49 @@ impl Lowerer {
426 }705 }
427 let ret = match &sig.output {706 let ret = match &sig.output {
428 ReturnType::Default => Nim::Unit,707 ReturnType::Default => Nim::Unit,
429- ReturnType::Type(_, t) => self.map_ty(t)?.owned(),708+ // A returned `&[T]` is a borrow of the caller's buffer, so it
709+ // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
710+ // a `seq`, which `owned()` would do to both.
711+ ReturnType::Type(_, t) => {
712+ let n = self.map_ty(t)?;
713+ if returns_borrow(t) { n } else { n.owned() }
714+ }
430 };715 };
431 Ok((params, ret))716 Ok((params, ret))
432 }717 }
433 718
434 // --------------------------------------------------------------- items719 // --------------------------------------------------------------- items
435 720
721+ /// Emit the type definitions only: they must precede every signature.
722+ fn item_types(&mut self, item: &Item) -> Result<(), String> {
723+ if !self.cfg_keeps(item_attrs(item))? {
724+ return Ok(());
725+ }
726+ match item {
727+ Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
728+ Item::Mod(m) if m.content.is_some() => {
729+ let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
730+ for i in &items {
731+ self.item_types(i)?;
732+ }
733+ Ok(())
734+ }
735+ _ => Ok(()),
736+ }
737+ }
738+
436 fn item(&mut self, item: &Item) -> Result<(), String> {739 fn item(&mut self, item: &Item) -> Result<(), String> {
437 if !self.cfg_keeps(item_attrs(item))? {740 if !self.cfg_keeps(item_attrs(item))? {
438 return Ok(());741 return Ok(());
439 }742 }
743+ // Types were emitted in their own pass.
744+ if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
745+ return Ok(());
746+ }
747+ self.item_inner(item)
748+ }
749+
750+ fn item_inner(&mut self, item: &Item) -> Result<(), String> {
440 match item {751 match item {
441 Item::Fn(f) => self.func(&f.sig, &f.block, None),752 Item::Fn(f) => self.func(&f.sig, &f.block, None),
442 Item::Struct(s) => {753 Item::Struct(s) => {
@@ -471,11 +782,26 @@ impl Lowerer {
471 }782 }
472 Item::Impl(im) => {783 Item::Impl(im) => {
473 let self_ty = self.map_ty(&im.self_ty)?;784 let self_ty = self.map_ty(&im.self_ty)?;
474- if im.trait_.is_some() {785+ if let Some((path, _)) = &im.trait_ {
475- return Err(format!(786+ let tr = path_name(path);
476- "`impl Trait for {}`: trait impls are not implemented yet",787+ if im.items.is_empty() {
477- self_ty.render()788+ return Ok(());
478- ));789+ }
790+ let syn::ImplItem::Fn(m) = &im.items[0] else {
791+ return Err(format!("unsupported item in `impl {tr}`"));
792+ };
793+ if is_fmt_trait(&tr) {
794+ return self.fmt_impl(&tr, &self_ty, &m.sig, &m.block);
795+ }
796+ if tr == "From" {
797+ let name = {
798+ let (params, _) = self.signature(&m.sig)?;
799+ let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
800+ self.from_impls[&(type_name(&src), type_name(&self_ty))].clone()
801+ };
802+ return self.func_named(&name, &m.sig, &m.block, None);
803+ }
804+ return Err(format!("`impl {tr}` is not implemented"));
479 }805 }
480 for it in &im.items {806 for it in &im.items {
481 match it {807 match it {
@@ -495,19 +821,23 @@ impl Lowerer {
495 // An inline `mod` is flattened; Nim has no nested modules in a821 // An inline `mod` is flattened; Nim has no nested modules in a
496 // single file.822 // single file.
497 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();823 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
498- for i in &items {
499- self.collect(i)?;
500- }
501 for i in &items {824 for i in &items {
502 self.item(i)?;825 self.item(i)?;
503 }826 }
504 Ok(())827 Ok(())
505 }828 }
506- Item::Mod(m) => Err(format!(829+ Item::Mod(m) => {
507- "`mod {};` refers to another file; pass that file to rustnim as \830+ // Satisfied if that file was passed in too; everything is one
508- an additional input instead",831+ // Nim module, so the declaration itself emits nothing.
509- m.ident832+ if self.modules.iter().any(|x| *x == m.ident.to_string()) {
510- )),833+ return Ok(());
834+ }
835+ Err(format!(
836+ "`mod {};` refers to another file that was not passed to \
837+ rustnim; add it to the input list",
838+ m.ident
839+ ))
840+ }
511 other => Err(format!("unsupported item: {}", item_kind(other))),841 other => Err(format!("unsupported item: {}", item_kind(other))),
512 }842 }
513 }843 }
@@ -647,6 +977,93 @@ impl Lowerer {
647 }977 }
648 }978 }
649 979
980+ /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
981+ ///
982+ /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
983+ /// observable result of `{}` is exactly the bytes written. So the method
984+ /// becomes `proc rsDisplay(self: T): string` and every write through the
985+ /// formatter produces that string. A `fmt` body that does anything else
986+ /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
987+ /// because those affect the output and this model does not carry them.
988+ /// The window an expression names, if it names one.
989+ fn window_of(&self, e: &Expr) -> Option<Alias> {
990+ match e {
991+ Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
992+ Some(a @ Alias::Window { .. }) => Some(a),
993+ _ => None,
994+ },
995+ Expr::Reference(r) => self.window_of(&r.expr),
996+ Expr::Paren(p) => self.window_of(&p.expr),
997+ Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
998+ _ => None,
999+ }
1000+ }
1001+
1002+ /// Whether an expression is the `Formatter` parameter of the formatting
1003+ /// impl currently being lowered.
1004+ fn is_fmt_param(&self, e: &Expr) -> bool {
1005+ let Some(f) = &self.fmt_param else { return false };
1006+ match e {
1007+ Expr::Path(p) => path_name(&p.path) == *f,
1008+ Expr::Reference(r) => self.is_fmt_param(&r.expr),
1009+ Expr::Paren(p) => self.is_fmt_param(&p.expr),
1010+ _ => false,
1011+ }
1012+ }
1013+
1014+ fn fmt_impl(
1015+ &mut self,
1016+ tr: &str,
1017+ self_ty: &Nim,
1018+ sig: &syn::Signature,
1019+ body: &syn::Block,
1020+ ) -> Result<(), String> {
1021+ let proc_name = fmt_proc(tr);
1022+ // The formatter is the parameter after `self`.
1023+ let f = sig
1024+ .inputs
1025+ .iter()
1026+ .filter_map(|a| match a {
1027+ FnArg::Typed(t) => match &*t.pat {
1028+ Pat::Ident(i) => Some(i.ident.to_string()),
1029+ _ => None,
1030+ },
1031+ _ => None,
1032+ })
1033+ .next()
1034+ .ok_or("`fn fmt` needs a `Formatter` parameter")?;
1035+
1036+ self.push_scope();
1037+ self.bind("self", self_ty.clone());
1038+ let saved = self.fmt_param.replace(f);
1039+ let outer_ret = self.ret.replace(Nim::Prim("string".into()));
1040+ let outer_target = self
1041+ .target
1042+ .replace(("result".to_string(), Some(Nim::Prim("string".into()))));
1043+
1044+ self.line(&format!(
1045+ "proc {}*(self: {}): string =",
1046+ proc_name,
1047+ self_ty.render()
1048+ ));
1049+ self.indent += 1;
1050+ let before = self.out.len();
1051+ let want = Nim::Prim("string".into());
1052+ let tail = self.block_body_at(body, Some(&want))?;
1053+ self.emit_tail(tail);
1054+ if self.out.len() == before {
1055+ self.line("discard");
1056+ }
1057+ self.indent -= 1;
1058+
1059+ self.target = outer_target;
1060+ self.ret = outer_ret;
1061+ self.fmt_param = saved;
1062+ self.pop_scope();
1063+ self.blank();
1064+ Ok(())
1065+ }
1066+
650 fn func(1067 fn func(
651 &mut self,1068 &mut self,
652 sig: &syn::Signature,1069 sig: &syn::Signature,
@@ -654,6 +1071,16 @@ impl Lowerer {
654 recv: Option<Nim>,1071 recv: Option<Nim>,
655 ) -> Result<(), String> {1072 ) -> Result<(), String> {
656 let name = sig.ident.to_string();1073 let name = sig.ident.to_string();
1074+ self.func_named(&name.clone(), sig, body, recv)
1075+ }
1076+
1077+ fn func_named(
1078+ &mut self,
1079+ name: &str,
1080+ sig: &syn::Signature,
1081+ body: &syn::Block,
1082+ recv: Option<Nim>,
1083+ ) -> Result<(), String> {
657 let (ptys, ret) = self.signature(sig)?;1084 let (ptys, ret) = self.signature(sig)?;
658 1085
659 self.push_scope();1086 self.push_scope();
@@ -684,6 +1111,9 @@ impl Lowerer {
684 for (p, t) in typed.iter().zip(ptys.iter()) {1111 for (p, t) in typed.iter().zip(ptys.iter()) {
685 let pname = match &*p.pat {1112 let pname = match &*p.pat {
686 Pat::Ident(i) => i.ident.to_string(),1113 Pat::Ident(i) => i.ident.to_string(),
1114+ // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
1115+ // still needs a name for it.
1116+ Pat::Wild(_) => format!("unused{}", rendered.len()),
687 _ => return Err("only plain identifier parameters are supported".into()),1117 _ => return Err("only plain identifier parameters are supported".into()),
688 };1118 };
689 rendered.push(format!("{}: {}", ident(&pname), t.render()));1119 rendered.push(format!("{}: {}", ident(&pname), t.render()));
@@ -692,9 +1122,9 @@ impl Lowerer {
692 }1122 }
693 1123
694 let head = if ret == Nim::Unit {1124 let head = if ret == Nim::Unit {
695- format!("proc {}*({}) =", ident(&name), rendered.join(", "))1125+ format!("proc {}*({}) =", ident(name), rendered.join(", "))
696 } else {1126 } else {
697- format!("proc {}*({}): {} =", ident(&name), rendered.join(", "), ret.render())1127+ format!("proc {}*({}): {} =", ident(name), rendered.join(", "), ret.render())
698 };1128 };
699 self.line(&head);1129 self.line(&head);
700 self.indent += 1;1130 self.indent += 1;
@@ -869,6 +1299,21 @@ impl Lowerer {
869 }1299 }
870 1300
871 let v = self.expr_at(&init.expr, ann.as_ref())?;1301 let v = self.expr_at(&init.expr, ann.as_ref())?;
1302+ if let Some(w) = v.window.clone() {
1303+ // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
1304+ // view into the caller's buffer. Copying it into a `seq` would
1305+ // still print the right bytes but would stop writes reaching the
1306+ // caller, so it is bound as an alias.
1307+ if v.guard.is_some() && v.guard_err.is_some() {
1308+ return Err(format!(
1309+ "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
1310+ which Nim cannot represent; apply `?` or `unwrap()` to it \
1311+ in the same expression"
1312+ ));
1313+ }
1314+ self.bind_alias(&name, w);
1315+ return Ok(());
1316+ }
872 let t = match (ann, &v.ty) {1317 let t = match (ann, &v.ty) {
873 (Some(a), _) => a.owned(),1318 (Some(a), _) => a.owned(),
874 (None, Some(t)) => t.clone().owned(),1319 (None, Some(t)) => t.clone().owned(),
@@ -1075,30 +1520,43 @@ impl Lowerer {
1075 if f.label.is_some() {1520 if f.label.is_some() {
1076 return Err("loop labels are not implemented yet".into());1521 return Err("loop labels are not implemented yet".into());
1077 }1522 }
1078- let name = match &*f.pat {1523+ let it = self.resolve_iter(&f.expr)?;
1079- Pat::Ident(i) => i.ident.to_string(),
1080- Pat::Wild(_) => "_".into(),
1081- _ => return Err("destructuring `for` patterns are not implemented yet".into()),
1082- };
1083 1524
1084- // Strip the iterator adaptors that are no-ops once we are iterating a1525+ // One index loop drives the whole chain. Rust's adaptors are lazy and
1085- // Nim container directly. Anything else (`.map`, `.filter`, `.rev`)1526+ // compose; resolving them to an index and binding each name to an
1086- // is a real iterator and is rejected rather than silently dropped.1527+ // lvalue reproduces that without materialising anything.
1087- let mut src = &*f.expr;1528+ let i = self.fresh("Idx");
1088- loop {1529+ self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
1089- match src {1530+ self.indent += 1;
1090- Expr::MethodCall(m)1531+ self.push_scope();
1091- if matches!(m.method.to_string().as_str(), "iter" | "into_iter" | "iter_mut")1532+ let before = self.out.len();
1092- && m.args.is_empty() =>1533+
1093- {1534+ self.bind_pattern(&f.pat, &it, &i)?;
1094- src = &m.receiver1535+
1095- }1536+ let saved = self.target.take();
1096- Expr::Reference(r) => src = &r.expr,1537+ if let Some(v) = self.block_body(&f.body)? {
1097- _ => break,1538+ let code = v.code.clone();
1098- }1539+ self.line(&format!("discard {code}"));
1540+ }
1541+ self.target = saved;
1542+ if self.out.len() == before {
1543+ self.line("discard");
1099 }1544 }
1545+ self.pop_scope();
1546+ self.indent -= 1;
1547+ Ok(())
1548+ }
1100 1549
1101- let (header, elem) = match src {1550+ /// Resolve a chain of iterator adaptors into a single `Iter`.
1551+ ///
1552+ /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
1553+ /// `filter`, `take_while` and friends are rejected rather than partially
1554+ /// honoured: silently dropping an adaptor would change which elements the
1555+ /// loop visits.
1556+ fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
1557+ match e {
1558+ Expr::Reference(r) => self.resolve_iter(&r.expr),
1559+ Expr::Paren(p) => self.resolve_iter(&p.expr),
1102 Expr::Range(r) => {1560 Expr::Range(r) => {
1103 let lo = match &r.start {1561 let lo = match &r.start {
1104 Some(e) => self.expr(e)?,1562 Some(e) => self.expr(e)?,
@@ -1106,45 +1564,184 @@ impl Lowerer {
1106 };1564 };
1107 let hi = match &r.end {1565 let hi = match &r.end {
1108 Some(e) => self.expr(e)?,1566 Some(e) => self.expr(e)?,
1109- None => return Err("a `for` over an unbounded range would not terminate".into()),1567+ None => {
1110- };1568+ return Err("a `for` over an unbounded range would not terminate".into())
1111- let op = match r.limits {1569+ }
1112- syn::RangeLimits::HalfOpen(_) => "..<",
1113- syn::RangeLimits::Closed(_) => "..",
1114 };1570 };
1115- let t = lo.ty.clone().or(hi.ty.clone());1571+ let ty = lo.ty.clone().or(hi.ty.clone());
1116- (format!("{} {} {}", lo.code, op, hi.code), t)1572+ Ok(Iter::Range {
1573+ lo: lo.code,
1574+ hi: hi.code,
1575+ closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
1576+ ty,
1577+ })
1578+ }
1579+ Expr::MethodCall(m) => {
1580+ let name = m.method.to_string();
1581+ match name.as_str() {
1582+ "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
1583+ let mut it = self.resolve_iter(&m.receiver)?;
1584+ if name == "iter_mut" {
1585+ if let Iter::Elems { mutable, .. } = &mut it {
1586+ *mutable = true;
1587+ }
1588+ }
1589+ Ok(it)
1590+ }
1591+ "enumerate" if m.args.is_empty() => {
1592+ Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
1593+ }
1594+ "zip" if m.args.len() == 1 => {
1595+ let a = self.resolve_iter(&m.receiver)?;
1596+ let b = self.resolve_iter(&m.args[0])?;
1597+ Ok(Iter::Zip(Box::new(a), Box::new(b)))
1598+ }
1599+ "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => {
1600+ let recv = self.expr(&m.receiver)?;
1601+ let k = self.expr(&m.args[0])?;
1602+ Ok(Iter::Chunks {
1603+ code: recv.code,
1604+ k: k.code,
1605+ elem: elem_of(&recv.ty),
1606+ mutable: name.ends_with("_mut"),
1607+ })
1608+ }
1609+ "windows" if m.args.len() == 1 => {
1610+ let recv = self.expr(&m.receiver)?;
1611+ let k = self.expr(&m.args[0])?;
1612+ Ok(Iter::Windows {
1613+ code: recv.code,
1614+ k: k.code,
1615+ elem: elem_of(&recv.ty),
1616+ })
1617+ }
1618+ other => Err(format!(
1619+ "iterator adaptor `.{other}()` is not implemented; it has \
1620+ no index-loop equivalent here, and dropping it would \
1621+ change which elements the loop visits"
1622+ )),
1623+ }
1117 }1624 }
1118 other => {1625 other => {
1626+ // A `for` binding that is itself a window iterates that window,
1627+ // not the whole container it points into.
1628+ if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) {
1629+ return Ok(Iter::Elems {
1630+ code,
1631+ off,
1632+ len,
1633+ elem,
1634+ mutable: false,
1635+ });
1636+ }
1119 let v = self.expr(other)?;1637 let v = self.expr(other)?;
1120- let elem = match v.ty.clone() {1638+ Ok(Iter::Elems {
1121- Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),1639+ len: format!("{}.len", v.code),
1122- Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),1640+ elem: elem_of(&v.ty),
1123- _ => None,1641+ code: v.code,
1124- };1642+ off: "0".into(),
1125- (v.code, elem)1643+ mutable: false,
1644+ })
1126 }1645 }
1127- };
1128-
1129- self.line(&format!("for {} in {}:", ident(&name), header));
1130- self.push_scope();
1131- if let Some(t) = elem {
1132- self.bind(&name, t);
1133 }1646 }
1134- self.indent += 1;1647+ }
1135- let before = self.out.len();1648+
1136- let saved = self.target.take();1649+ /// Bind a `for` pattern against a resolved iterator at index `i`.
1137- if let Some(v) = self.block_body(&f.body)? {1650+ fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
1138- let code = v.code.clone();1651+ match (p, it) {
1139- self.line(&format!("discard {code}"));1652+ (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
1140- }1653+ self.bind_pattern(&t.elems[0], a, i)?;
1141- self.target = saved;1654+ self.bind_pattern(&t.elems[1], b, i)
1142- if self.out.len() == before {1655+ }
1143- self.line("discard");1656+ (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
1657+ if let Pat::Ident(id) = &t.elems[0] {
1658+ let n = id.ident.to_string();
1659+ // Rust's `enumerate` counts in `usize`.
1660+ self.line(&format!("let {}: uint = uint({})", ident(&n), i));
1661+ self.bind(&n, Nim::Prim("uint".into()));
1662+ }
1663+ self.bind_pattern(&t.elems[1], inner, i)
1664+ }
1665+ (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
1666+ "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
1667+ ),
1668+ (Pat::Wild(_), _) => Ok(()),
1669+ (Pat::Ident(id), _) => {
1670+ let name = id.ident.to_string();
1671+ match it {
1672+ Iter::Range { lo, ty, .. } => {
1673+ let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
1674+ // The loop counts from zero; the range's own start is
1675+ // added back so the binding has Rust's value and type.
1676+ self.line(&format!(
1677+ "let {}: {} = {}({}) + {}",
1678+ ident(&name),
1679+ t.render(),
1680+ t.render(),
1681+ i,
1682+ lo
1683+ ));
1684+ self.bind(&name, t);
1685+ Ok(())
1686+ }
1687+ Iter::Elems { code, off, elem, mutable, .. } => {
1688+ let access = if off == "0" {
1689+ format!("{}[{}]", code, i)
1690+ } else {
1691+ format!("{}[{} + {}]", code, off, i)
1692+ };
1693+ if *mutable {
1694+ // An alias, not a copy: assigning through the
1695+ // binding must reach the original element.
1696+ self.bind_alias(
1697+ &name,
1698+ Alias::Value { code: access, ty: elem.clone() },
1699+ );
1700+ } else {
1701+ let t = elem
1702+ .clone()
1703+ .ok_or("cannot infer the element type of this `for`")?;
1704+ self.line(&format!(
1705+ "let {}: {} = {}",
1706+ ident(&name),
1707+ t.render(),
1708+ access
1709+ ));
1710+ self.bind(&name, t);
1711+ }
1712+ Ok(())
1713+ }
1714+ Iter::Chunks { code, k, elem, .. } => {
1715+ self.bind_alias(
1716+ &name,
1717+ Alias::Window {
1718+ code: code.clone(),
1719+ off: format!("({} * int({}))", i, k),
1720+ len: format!("int({})", k),
1721+ elem: elem.clone(),
1722+ },
1723+ );
1724+ Ok(())
1725+ }
1726+ Iter::Windows { code, k, elem } => {
1727+ self.bind_alias(
1728+ &name,
1729+ Alias::Window {
1730+ code: code.clone(),
1731+ off: i.to_string(),
1732+ len: format!("int({})", k),
1733+ elem: elem.clone(),
1734+ },
1735+ );
1736+ Ok(())
1737+ }
1738+ // Handled above: a zip or enumerate needs a tuple pattern,
1739+ // and binding one name to the pair is not supported.
1740+ Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
1741+ }
1742+ }
1743+ _ => Err("unsupported `for` pattern".into()),
1144 }1744 }
1145- self.indent -= 1;
1146- self.pop_scope();
1147- Ok(())
1148 }1745 }
1149 1746
1150 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {1747 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
@@ -1499,6 +2096,22 @@ impl Lowerer {
1499 if name == "None" {2096 if name == "None" {
1500 return Ok(Val::new(self.none_of(expect), expect.cloned()));2097 return Ok(Val::new(self.none_of(expect), expect.cloned()));
1501 }2098 }
2099+ // A unit struct used as a value: `fmt::Error`, or a `struct S;`
2100+ // declared here. In Nim that is a constructor call.
2101+ if p.path.segments.len() > 1 {
2102+ let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
2103+ if let Ok(Nim::Prim(n)) = ty::map(&ty) {
2104+ if n == "FmtError" {
2105+ return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
2106+ }
2107+ }
2108+ }
2109+ if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
2110+ return Ok(Val::new(
2111+ format!("{}()", ident(&name)),
2112+ Some(Nim::Named(name.clone(), vec![])),
2113+ ));
2114+ }
1502 // A unit enum variant used as a value: `Error::InvalidLength`.2115 // A unit enum variant used as a value: `Error::InvalidLength`.
1503 if let Some((def, v)) = self.resolve_variant(&p.path) {2116 if let Some((def, v)) = self.resolve_variant(&p.path) {
1504 let ty = Some(Nim::Named(def.name.clone(), vec![]));2117 let ty = Some(Nim::Named(def.name.clone(), vec![]));
@@ -1508,6 +2121,20 @@ impl Lowerer {
1508 Val::new(format!("{}()", def.ctor_ident(&v)), ty)2121 Val::new(format!("{}()", def.ctor_ident(&v)), ty)
1509 });2122 });
1510 }2123 }
2124+ // A `for` binding that stands for an element of the container
2125+ // it came from: using it must read (and assigning through it
2126+ // must write) that element, not a copy.
2127+ if let Some(a) = self.lookup_alias(&name) {
2128+ return Ok(match a {
2129+ Alias::Value { code, ty } => Val::new(code, ty),
2130+ // A window *is* a slice; as a value it is the view it
2131+ // denotes, which is what Rust's `&[T]` means too.
2132+ Alias::Window { code, off, len, elem } => Val::new(
2133+ format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
2134+ elem.map(|e| Nim::OpenArray(Box::new(e))),
2135+ ),
2136+ });
2137+ }
1511 if let Some(t) = self.lookup(&name) {2138 if let Some(t) = self.lookup(&name) {
1512 return Ok(Val::new(ident(&name), Some(t)));2139 return Ok(Val::new(ident(&name), Some(t)));
1513 }2140 }
@@ -1530,7 +2157,38 @@ impl Lowerer {
1530 Expr::Unary(u) => self.unary(u, expect),2157 Expr::Unary(u) => self.unary(u, expect),
1531 Expr::Binary(b) => self.binary(b, expect),2158 Expr::Binary(b) => self.binary(b, expect),
1532 Expr::Cast(c) => self.cast(c),2159 Expr::Cast(c) => self.cast(c),
2160+ Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
2161+ let Expr::Range(r) = &*i.index else { unreachable!() };
2162+ let base = self.expr(&i.expr)?;
2163+ let lo = match &r.start {
2164+ Some(e) => format!("int({})", self.expr(e)?.code),
2165+ None => "0".into(),
2166+ };
2167+ // Nim's `toOpenArray` takes an inclusive upper bound.
2168+ let hi = match (&r.end, r.limits) {
2169+ (Some(e), syn::RangeLimits::HalfOpen(_)) => {
2170+ format!("int({}) - 1", self.expr(e)?.code)
2171+ }
2172+ (Some(e), syn::RangeLimits::Closed(_)) => {
2173+ format!("int({})", self.expr(e)?.code)
2174+ }
2175+ (None, _) => format!("{}.len - 1", base.code),
2176+ };
2177+ let elem = elem_of(&base.ty)
2178+ .ok_or("cannot infer the element type of this slice")?;
2179+ Ok(Val::new(
2180+ format!("{}.toOpenArray({}, {})", base.code, lo, hi),
2181+ Some(Nim::OpenArray(Box::new(elem))),
2182+ ))
2183+ }
1533 Expr::Index(i) => {2184 Expr::Index(i) => {
2185+ if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
2186+ let idx = self.expr(&i.index)?;
2187+ return Ok(Val::new(
2188+ format!("{}[{} + int({})]", code, off, idx.code),
2189+ elem,
2190+ ));
2191+ }
1534 let base = self.expr(&i.expr)?;2192 let base = self.expr(&i.expr)?;
1535 let idx = self.expr(&i.index)?;2193 let idx = self.expr(&i.index)?;
1536 // Rust indexes with usize; Nim wants an `int`, and a `uint`2194 // Rust indexes with usize; Nim wants an `int`, and a `uint`
@@ -1564,7 +2222,7 @@ impl Lowerer {
1564 }2222 }
1565 Expr::Try(t) => self.try_op(t),2223 Expr::Try(t) => self.try_op(t),
1566 Expr::Call(c) => self.call(c, expect),2224 Expr::Call(c) => self.call(c, expect),
1567- Expr::MethodCall(m) => self.method(m),2225+ Expr::MethodCall(m) => self.method(m, expect),
1568 Expr::Macro(m) => {2226 Expr::Macro(m) => {
1569 let code = self.macro_call(&m.mac)?;2227 let code = self.macro_call(&m.mac)?;
1570 Ok(Val::new(code, None))2228 Ok(Val::new(code, None))
@@ -1917,6 +2575,30 @@ impl Lowerer {
1917 .into());2575 .into());
1918 }2576 }
1919 let v = self.expr(&t.expr)?;2577 let v = self.expr(&t.expr)?;
2578+ if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
2579+ // An `Option`/`Result` of a view: the check is emitted here and the
2580+ // view itself survives as an alias, since it has no value form.
2581+ let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
2582+ let err = v.guard_err.clone().ok_or(
2583+ "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
2584+ )?;
2585+ let Nim::Named(n, ra) = &ret else {
2586+ return Err(format!("`?` in a function returning `{}`", ret.render()));
2587+ };
2588+ if n != "Result" || ra.len() != 2 {
2589+ return Err(format!("`?` in a function returning `{}`", ret.render()));
2590+ }
2591+ self.line(&format!("if not {}:", guard));
2592+ self.line(&format!(
2593+ " return rsErr[{}, {}]({})",
2594+ ra[0].render(),
2595+ ra[1].render(),
2596+ err
2597+ ));
2598+ let mut out = Val::new(String::new(), None);
2599+ out.window = Some(w);
2600+ return Ok(out);
2601+ }
1920 let vt = v.ty.clone().ok_or(2602 let vt = v.ty.clone().ok_or(
1921 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",2603 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
1922 )?;2604 )?;
@@ -2056,9 +2738,67 @@ impl Lowerer {
2056 ))2738 ))
2057 }2739 }
2058 2740
2059- fn method(&mut self, m: &syn::ExprMethodCall) -> Result<Val, String> {2741+ fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
2060- let recv = self.expr(&m.receiver)?;
2061 let name = m.method.to_string();2742 let name = m.method.to_string();
2743+ if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
2744+ match name.as_str() {
2745+ "len" => {
2746+ return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
2747+ }
2748+ "is_empty" => {
2749+ return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
2750+ }
2751+ other => {
2752+ return Err(format!(
2753+ "`.{other}()` on a slice window from `chunks_exact`/\
2754+ `windows` is not implemented; only indexing and \
2755+ `len()` are"
2756+ ))
2757+ }
2758+ }
2759+ }
2760+ let recv = self.expr(&m.receiver)?;
2761+ let rt0 = recv.ty.clone();
2762+
2763+// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
2764+ // way to put a view in an object, so instead of materialising an
2765+ // Option the view and its validity condition travel together until
2766+ // an `ok_or`/`?`/`unwrap` resolves them.
2767+ if matches!(name.as_str(), "get" | "get_mut")
2768+ && matches!(m.args.first(), Some(Expr::Range(_)))
2769+ {
2770+ let Some(Expr::Range(r)) = m.args.first() else { unreachable!() };
2771+ let lo = match &r.start {
2772+ Some(e) => format!("int({})", self.expr(e)?.code),
2773+ None => "0".into(),
2774+ };
2775+ let len = match (&r.end, r.limits) {
2776+ (Some(e), syn::RangeLimits::HalfOpen(_)) => {
2777+ format!("(int({}) - {})", self.expr(e)?.code, lo)
2778+ }
2779+ (Some(e), syn::RangeLimits::Closed(_)) => {
2780+ format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
2781+ }
2782+ (None, _) => format!("({}.len - {})", recv.code, lo),
2783+ };
2784+ let elem = elem_of(&rt0).ok_or("cannot infer the element type of this slice")?;
2785+ let mut v = Val::new(
2786+ String::new(),
2787+ Some(Nim::Named(
2788+ "Option".into(),
2789+ vec![Nim::OpenArray(Box::new(elem.clone()))],
2790+ )),
2791+ );
2792+ v.guard = Some(format!("({} + {} <= {}.len)", lo, len, recv.code));
2793+ v.window = Some(Alias::Window {
2794+ code: recv.code.clone(),
2795+ off: lo,
2796+ len,
2797+ elem: Some(elem),
2798+ });
2799+ return Ok(v);
2800+ }
2801+
2062 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's2802 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
2063 // own type; `v.push(e)` takes the element type.2803 // own type; `v.push(e)` takes the element type.
2064 let arg_want = match (name.as_str(), &recv.ty) {2804 let arg_want = match (name.as_str(), &recv.ty) {
@@ -2089,6 +2829,19 @@ impl Lowerer {
2089 };2829 };
2090 (format!("unwrap({})", recv.code), inner)2830 (format!("unwrap({})", recv.code), inner)
2091 }2831 }
2832+ "ok_or" if recv.guard.is_some() => {
2833+ let e = args.first().ok_or("`ok_or` takes one argument")?;
2834+ let ety = e.ty.clone();
2835+ let mut v = recv.clone();
2836+ v.guard_err = Some(e.code.clone());
2837+ v.ty = match (&recv.ty, ety) {
2838+ (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
2839+ Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
2840+ }
2841+ _ => None,
2842+ };
2843+ return Ok(v);
2844+ }
2092 "ok_or" => {2845 "ok_or" => {
2093 let inner = match &rt {2846 let inner = match &rt {
2094 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),2847 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
@@ -2158,6 +2911,12 @@ impl Lowerer {
2158 )2911 )
2159 }2912 }
2160 }2913 }
2914+ // Inside a formatting impl, a write through the `Formatter` *is*
2915+ // the value the proc returns, so it lowers to the string written.
2916+ "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => (
2917+ a0.ok_or("`write_str` takes one argument")?,
2918+ Some(Nim::Prim("string".into())),
2919+ ),
2161 "abs" => (format!("abs({})", recv.code), rt.clone()),2920 "abs" => (format!("abs({})", recv.code), rt.clone()),
2162 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),2921 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
2163 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),2922 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
@@ -2167,11 +2926,30 @@ impl Lowerer {
2167 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),2926 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
2168 ),2927 ),
2169 2928
2929+ "into" => {
2930+ // `.into()` resolves through the `impl From` declarations, and
2931+ // needs the target type to pick one.
2932+ let from = rt
2933+ .clone()
2934+ .ok_or("`.into()` needs a known receiver type")?;
2935+ let to = expect
2936+ .ok_or("`.into()` needs a known target type; annotate the binding")?;
2937+ let key = (type_name(&from), type_name(to));
2938+ let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
2939+ format!(
2940+ "no `impl From<{}> for {}` in this file, so `.into()` has \
2941+ no conversion to call",
2942+ key.0, key.1
2943+ )
2944+ })?;
2945+ (format!("{}({})", f, recv.code), Some(to.clone()))
2946+ }
2170 _ => {2947 _ => {
2171- // A method defined in this file via `impl`. Nim's UFCS makes2948+ // A method defined in this file via `impl`, found by the
2172- // the call site spelling identical.2949+ // receiver's type rather than by name alone.
2173- if let Some(sig) = self.fns.get(&name) {2950+ let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
2174- let ret = sig.ret.clone();2951+ let sig = key.and_then(|k| self.methods.get(&k)).map(|s| s.ret.clone());
2952+ if let Some(ret) = sig {
2175 let mut all = vec![recv.code.clone()];2953 let mut all = vec![recv.code.clone()];
2176 all.extend(args.iter().map(|a| a.code.clone()));2954 all.extend(args.iter().map(|a| a.code.clone()));
2177 (format!("{}({})", ident(&name), all.join(", ")), Some(ret))2955 (format!("{}({})", ident(&name), all.join(", ")), Some(ret))
@@ -2203,6 +2981,27 @@ impl Lowerer {
2203 })2981 })
2204 }2982 }
2205 "format" => self.format_args(mac),2983 "format" => self.format_args(mac),
2984+ "write" | "writeln" => {
2985+ // `write!(f, "..", ..)` inside a formatting impl: the first
2986+ // argument is the sink, the rest is an ordinary format call.
2987+ let args: Vec<Expr> = mac
2988+ .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
2989+ .map_err(|e| format!("write!: {e}"))?
2990+ .into_iter()
2991+ .collect();
2992+ let sink = args.first().ok_or("`write!` needs a sink")?;
2993+ if !self.is_fmt_param(sink) {
2994+ return Err("`write!` to anything but the `Formatter` of the \
2995+ enclosing formatting impl is not implemented"
2996+ .into());
2997+ }
2998+ let s = self.format_pieces(&args[1..])?;
2999+ Ok(if name == "writeln" {
3000+ format!("({} & \"\\n\")", s)
3001+ } else {
3002+ s
3003+ })
3004+ }
2206 "panic" => {3005 "panic" => {
2207 let s = self.format_args(mac)?;3006 let s = self.format_args(mac)?;
2208 Ok(format!("rsPanic({s})"))3007 Ok(format!("rsPanic({s})"))
@@ -2253,17 +3052,23 @@ impl Lowerer {
2253 3052
2254 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.3053 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
2255 fn format_args(&mut self, mac: &syn::Macro) -> Result<String, String> {3054 fn format_args(&mut self, mac: &syn::Macro) -> Result<String, String> {
2256- let args: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac3055+ let args: Vec<Expr> = mac
2257- .parse_body_with(syn::punctuated::Punctuated::parse_terminated)3056+ .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
2258- .map_err(|e| format!("format arguments: {e}"))?;3057+ .map_err(|e| format!("format arguments: {e}"))?
2259- let mut it = args.iter();3058+ .into_iter()
2260- let Some(Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. })) = it.next() else {3059+ .collect();
3060+ self.format_pieces(&args)
3061+ }
3062+
3063+ /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
3064+ fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
3065+ let Some(Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. })) = args.first() else {
2261 if args.is_empty() {3066 if args.is_empty() {
2262 return Ok("\"\"".into());3067 return Ok("\"\"".into());
2263 }3068 }
2264 return Err("the first argument must be a literal format string".into());3069 return Err("the first argument must be a literal format string".into());
2265 };3070 };
2266- let rest: Vec<&Expr> = it.collect();3071+ let rest: Vec<&Expr> = args[1..].iter().collect();
2267 3072
2268 let pieces = fmt::parse(&s.value())?;3073 let pieces = fmt::parse(&s.value())?;
2269 let mut parts: Vec<String> = Vec::new();3074 let mut parts: Vec<String> = Vec::new();
@@ -2422,6 +3227,51 @@ fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type
2422 3227
2423 // --------------------------------------------------------------- utilities3228 // --------------------------------------------------------------- utilities
2424 3229
3230+/// Whether a return type is a borrow of one of the arguments, which Nim
3231+/// models with a view rather than with an owned copy.
3232+fn returns_borrow(t: &syn::Type) -> bool {
3233+ match t {
3234+ syn::Type::Reference(r) => matches!(&*r.elem, syn::Type::Slice(_)),
3235+ syn::Type::Paren(p) => returns_borrow(&p.elem),
3236+ syn::Type::Group(g) => returns_borrow(&g.elem),
3237+ _ => false,
3238+ }
3239+}
3240+
3241+/// The element type of a sequence-like Nim type.
3242+fn elem_of(t: &Option<Nim>) -> Option<Nim> {
3243+ match t {
3244+ Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
3245+ Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
3246+ _ => None,
3247+ }
3248+}
3249+
3250+/// The short name a Nim type is known by, for keying method tables.
3251+fn type_name(t: &Nim) -> String {
3252+ match t {
3253+ Nim::Named(n, _) => n.clone(),
3254+ Nim::Prim(p) => p.clone(),
3255+ other => other.render(),
3256+ }
3257+}
3258+
3259+fn is_fmt_trait(t: &str) -> bool {
3260+ matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
3261+}
3262+
3263+/// The prelude proc a formatting trait's output is produced by.
3264+fn fmt_proc(t: &str) -> &'static str {
3265+ match t {
3266+ "Display" => "rsDisplay",
3267+ "Debug" => "rsDebug",
3268+ "LowerHex" => "rsLowerHex",
3269+ "UpperHex" => "rsUpperHex",
3270+ "Binary" => "rsBinary",
3271+ _ => "rsOctal",
3272+ }
3273+}
3274+
2425 fn takes_self(sig: &syn::Signature) -> bool {3275 fn takes_self(sig: &syn::Signature) -> bool {
2426 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))3276 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
2427 }3277 }
modified src/main.rs +5 -0
@@ -79,6 +79,11 @@ fn run() -> Result<(), String> {
7979
8080 let mut lowerer = lower::Lowerer::new();
8181 lowerer.features = features;
82+ // A `mod x;` is satisfied when x.rs is one of the inputs.
83+ lowerer.modules = inputs
84+ .iter()
85+ .filter_map(|p| p.file_stem().map(|s| s.to_string_lossy().into_owned()))
86+ .collect();
8287 let nim = lowerer.lower_file(&file)?;
8388
8489 match output {
@@ -79,6 +79,11 @@ fn run() -> Result<(), String> {
79 79
80 let mut lowerer = lower::Lowerer::new();80 let mut lowerer = lower::Lowerer::new();
81 lowerer.features = features;81 lowerer.features = features;
82+ // A `mod x;` is satisfied when x.rs is one of the inputs.
83+ lowerer.modules = inputs
84+ .iter()
85+ .filter_map(|p| p.file_stem().map(|s| s.to_string_lossy().into_owned()))
86+ .collect();
82 let nim = lowerer.lower_file(&file)?;87 let nim = lowerer.lower_file(&file)?;
83 88
84 match output {89 match output {
modified src/prelude.nim +13 -0
@@ -6,11 +6,24 @@
66 ## truncate toward zero in both) there is deliberately nothing here: the
77 ## operator is mapped directly and no helper is involved.
88
9+# Rust's `&[T]` is a borrowed view, not a copy. Nim's view types model exactly
10+# that, including returning one from a proc: writing through the returned view
11+# is visible in the original buffer. Probed against Nim 2.2.4 before relying on
12+# it, because copying instead would silently change aliasing.
13+{.experimental: "views".}
14+
915 import std/[unicode, strutils]
1016
1117 type
1218 RustPanic* = object of CatchableError
1319
20+ ## `core::fmt`'s own error and sink, kept distinct from any user type that
21+ ## happens to be called `Error`. A formatting impl is lowered to a proc that
22+ ## returns the formatted string, so these appear only in signatures.
23+ FmtError* = object
24+ FmtResult* = object
25+ ok*: bool
26+
1427 Option*[T] = object
1528 case has*: bool
1629 of true: val*: T
@@ -6,11 +6,24 @@
6 ## truncate toward zero in both) there is deliberately nothing here: the6 ## truncate toward zero in both) there is deliberately nothing here: the
7 ## operator is mapped directly and no helper is involved.7 ## operator is mapped directly and no helper is involved.
8 8
9+# Rust's `&[T]` is a borrowed view, not a copy. Nim's view types model exactly
10+# that, including returning one from a proc: writing through the returned view
11+# is visible in the original buffer. Probed against Nim 2.2.4 before relying on
12+# it, because copying instead would silently change aliasing.
13+{.experimental: "views".}
14+
9 import std/[unicode, strutils]15 import std/[unicode, strutils]
10 16
11 type17 type
12 RustPanic* = object of CatchableError18 RustPanic* = object of CatchableError
13 19
20+ ## `core::fmt`'s own error and sink, kept distinct from any user type that
21+ ## happens to be called `Error`. A formatting impl is lowered to a proc that
22+ ## returns the formatted string, so these appear only in signatures.
23+ FmtError* = object
24+ FmtResult* = object
25+ ok*: bool
26+
14 Option*[T] = object27 Option*[T] = object
15 case has*: bool28 case has*: bool
16 of true: val*: T29 of true: val*: T
modified src/ty.rs +37 -1
@@ -40,7 +40,13 @@ impl Nim {
4040 }
4141 Nim::Var(t) => format!("var {}", t.render()),
4242 Nim::Proc(args, ret) => {
43- let inner: Vec<String> = args.iter().map(|t| t.render()).collect();
43+ // Nim's proc types name their parameters even when the name is
44+ // never used.
45+ let inner: Vec<String> = args
46+ .iter()
47+ .enumerate()
48+ .map(|(i, t)| format!("a{}: {}", i, t.render()))
49+ .collect();
4450 match &**ret {
4551 Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")),
4652 r => format!("proc ({}): {} {{.nimcall.}}", inner.join(", "), r.render()),
@@ -108,9 +114,39 @@ fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
108114 }
109115 }
110116
117+/// Types from `core::fmt` that must not be confused with a user type of the
118+/// same short name. `fmt::Error` and a crate's own `Error` are different
119+/// types, and collapsing a path to its last segment would merge them.
120+fn std_qualified(p: &syn::Path) -> Option<Nim> {
121+ let segs: Vec<String> = p.segments.iter().map(|s| s.ident.to_string()).collect();
122+ if segs.len() < 2 {
123+ return None;
124+ }
125+ let (q, name) = (&segs[segs.len() - 2], segs.last()?.as_str());
126+ if q != "fmt" {
127+ return None;
128+ }
129+ Some(Nim::Prim(
130+ match name {
131+ "Error" => "FmtError",
132+ "Formatter" => "Formatter",
133+ // `fmt::Result` is `Result<(), fmt::Error>`. A formatting impl is
134+ // lowered to a proc that returns the formatted string, so the
135+ // result type is erased there; this spelling exists so that a
136+ // signature mentioning it still maps to something.
137+ "Result" => "FmtResult",
138+ _ => return None,
139+ }
140+ .into(),
141+ ))
142+}
143+
111144 pub fn map(t: &Type) -> Result<Nim, String> {
112145 match t {
113146 Type::Path(p) => {
147+ if let Some(n) = std_qualified(&p.path) {
148+ return Ok(n);
149+ }
114150 let seg = p
115151 .path
116152 .segments
@@ -40,7 +40,13 @@ impl Nim {
40 }40 }
41 Nim::Var(t) => format!("var {}", t.render()),41 Nim::Var(t) => format!("var {}", t.render()),
42 Nim::Proc(args, ret) => {42 Nim::Proc(args, ret) => {
43- let inner: Vec<String> = args.iter().map(|t| t.render()).collect();43+ // Nim's proc types name their parameters even when the name is
44+ // never used.
45+ let inner: Vec<String> = args
46+ .iter()
47+ .enumerate()
48+ .map(|(i, t)| format!("a{}: {}", i, t.render()))
49+ .collect();
44 match &**ret {50 match &**ret {
45 Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")),51 Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")),
46 r => format!("proc ({}): {} {{.nimcall.}}", inner.join(", "), r.render()),52 r => format!("proc ({}): {} {{.nimcall.}}", inner.join(", "), r.render()),
@@ -108,9 +114,39 @@ fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
108 }114 }
109 }115 }
110 116
117+/// Types from `core::fmt` that must not be confused with a user type of the
118+/// same short name. `fmt::Error` and a crate's own `Error` are different
119+/// types, and collapsing a path to its last segment would merge them.
120+fn std_qualified(p: &syn::Path) -> Option<Nim> {
121+ let segs: Vec<String> = p.segments.iter().map(|s| s.ident.to_string()).collect();
122+ if segs.len() < 2 {
123+ return None;
124+ }
125+ let (q, name) = (&segs[segs.len() - 2], segs.last()?.as_str());
126+ if q != "fmt" {
127+ return None;
128+ }
129+ Some(Nim::Prim(
130+ match name {
131+ "Error" => "FmtError",
132+ "Formatter" => "Formatter",
133+ // `fmt::Result` is `Result<(), fmt::Error>`. A formatting impl is
134+ // lowered to a proc that returns the formatted string, so the
135+ // result type is erased there; this spelling exists so that a
136+ // signature mentioning it still maps to something.
137+ "Result" => "FmtResult",
138+ _ => return None,
139+ }
140+ .into(),
141+ ))
142+}
143+
111 pub fn map(t: &Type) -> Result<Nim, String> {144 pub fn map(t: &Type) -> Result<Nim, String> {
112 match t {145 match t {
113 Type::Path(p) => {146 Type::Path(p) => {
147+ if let Some(n) = std_qualified(&p.path) {
148+ return Ok(n);
149+ }
114 let seg = p150 let seg = p
115 .path151 .path
116 .segments152 .segments
added tests/cases/023-slice-iterators.rs +57 -0
new file mode 100644
@@ -0,0 +1,57 @@
1+// Rust's slice iterators are lazy and compose; a chain is resolved into one
2+// index loop, with each binding becoming an lvalue into the original container.
3+// That is what makes `*d = v` through `iter_mut()` write back to the caller's
4+// slice rather than to a copy.
5+fn main() {
6+ let v: Vec<i32> = vec![10, 20, 30, 40, 50];
7+
8+ for x in v.iter() {
9+ print!("{} ", x);
10+ }
11+ println!("");
12+
13+ for (i, x) in v.iter().enumerate() {
14+ print!("{}:{} ", i, x);
15+ }
16+ println!("");
17+
18+ let w: Vec<i32> = vec![1, 2, 3];
19+ for (a, b) in v.iter().zip(w.iter()) {
20+ print!("{} ", a + b);
21+ }
22+ println!("");
23+
24+ for c in v.chunks_exact(2) {
25+ print!("[{} {}] ", c[0], c[1]);
26+ }
27+ println!("");
28+
29+ for c in v.windows(3) {
30+ print!("{} ", c[0] + c[1] + c[2]);
31+ }
32+ println!("");
33+
34+ // Writing through iter_mut must reach the original.
35+ let mut m: Vec<i32> = vec![1, 2, 3, 4];
36+ for d in m.iter_mut() {
37+ *d = *d * 100;
38+ }
39+ println!("{:?}", m);
40+
41+ // chunks_exact_mut, zipped against a read-only source.
42+ let src: Vec<i32> = vec![7, 8];
43+ let mut dst: Vec<i32> = vec![0, 0, 0, 0];
44+ for (s, c) in src.iter().zip(dst.chunks_exact_mut(2)) {
45+ c[0] = *s;
46+ c[1] = *s + 1;
47+ }
48+ println!("{:?}", dst);
49+
50+ // zip stops at the shorter side, as Rust's does.
51+ let short: Vec<i32> = vec![1];
52+ let mut n: i32 = 0;
53+ for (_a, _b) in v.iter().zip(short.iter()) {
54+ n += 1;
55+ }
56+ println!("{}", n);
57+}
new file mode 100644
@@ -0,0 +1,57 @@
1+// Rust's slice iterators are lazy and compose; a chain is resolved into one
2+// index loop, with each binding becoming an lvalue into the original container.
3+// That is what makes `*d = v` through `iter_mut()` write back to the caller's
4+// slice rather than to a copy.
5+fn main() {
6+ let v: Vec<i32> = vec![10, 20, 30, 40, 50];
7+
8+ for x in v.iter() {
9+ print!("{} ", x);
10+ }
11+ println!("");
12+
13+ for (i, x) in v.iter().enumerate() {
14+ print!("{}:{} ", i, x);
15+ }
16+ println!("");
17+
18+ let w: Vec<i32> = vec![1, 2, 3];
19+ for (a, b) in v.iter().zip(w.iter()) {
20+ print!("{} ", a + b);
21+ }
22+ println!("");
23+
24+ for c in v.chunks_exact(2) {
25+ print!("[{} {}] ", c[0], c[1]);
26+ }
27+ println!("");
28+
29+ for c in v.windows(3) {
30+ print!("{} ", c[0] + c[1] + c[2]);
31+ }
32+ println!("");
33+
34+ // Writing through iter_mut must reach the original.
35+ let mut m: Vec<i32> = vec![1, 2, 3, 4];
36+ for d in m.iter_mut() {
37+ *d = *d * 100;
38+ }
39+ println!("{:?}", m);
40+
41+ // chunks_exact_mut, zipped against a read-only source.
42+ let src: Vec<i32> = vec![7, 8];
43+ let mut dst: Vec<i32> = vec![0, 0, 0, 0];
44+ for (s, c) in src.iter().zip(dst.chunks_exact_mut(2)) {
45+ c[0] = *s;
46+ c[1] = *s + 1;
47+ }
48+ println!("{:?}", dst);
49+
50+ // zip stops at the shorter side, as Rust's does.
51+ let short: Vec<i32> = vec![1];
52+ let mut n: i32 = 0;
53+ for (_a, _b) in v.iter().zip(short.iter()) {
54+ n += 1;
55+ }
56+ println!("{}", n);
57+}
added tests/cases/024-trait-impls.rs +47 -0
new file mode 100644
@@ -0,0 +1,47 @@
1+// A `Display` impl becomes a proc returning the string the formatter is
2+// written with, since the observable result of `{}` is exactly those bytes.
3+use core::fmt;
4+
5+#[derive(Debug, PartialEq)]
6+pub enum Error {
7+ InvalidEncoding,
8+ InvalidLength,
9+}
10+
11+impl fmt::Display for Error {
12+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13+ match self {
14+ Error::InvalidEncoding => f.write_str("invalid Base16 encoding"),
15+ Error::InvalidLength => f.write_str("invalid Base16 length"),
16+ }
17+ }
18+}
19+
20+// A marker trait with no items: we do not model trait resolution, so it
21+// generates nothing.
22+impl core::error::Error for Error {}
23+
24+struct Point {
25+ x: i32,
26+ y: i32,
27+}
28+
29+impl fmt::Display for Point {
30+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31+ write!(f, "({}, {})", self.x, self.y)
32+ }
33+}
34+
35+impl Point {
36+ fn sum(&self) -> i32 {
37+ self.x + self.y
38+ }
39+}
40+
41+fn main() {
42+ println!("{}", Error::InvalidEncoding);
43+ println!("{}", Error::InvalidLength);
44+ println!("{:?}", Error::InvalidLength);
45+ let p = Point { x: 3, y: -4 };
46+ println!("{} {}", p, p.sum());
47+}
new file mode 100644
@@ -0,0 +1,47 @@
1+// A `Display` impl becomes a proc returning the string the formatter is
2+// written with, since the observable result of `{}` is exactly those bytes.
3+use core::fmt;
4+
5+#[derive(Debug, PartialEq)]
6+pub enum Error {
7+ InvalidEncoding,
8+ InvalidLength,
9+}
10+
11+impl fmt::Display for Error {
12+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13+ match self {
14+ Error::InvalidEncoding => f.write_str("invalid Base16 encoding"),
15+ Error::InvalidLength => f.write_str("invalid Base16 length"),
16+ }
17+ }
18+}
19+
20+// A marker trait with no items: we do not model trait resolution, so it
21+// generates nothing.
22+impl core::error::Error for Error {}
23+
24+struct Point {
25+ x: i32,
26+ y: i32,
27+}
28+
29+impl fmt::Display for Point {
30+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31+ write!(f, "({}, {})", self.x, self.y)
32+ }
33+}
34+
35+impl Point {
36+ fn sum(&self) -> i32 {
37+ self.x + self.y
38+ }
39+}
40+
41+fn main() {
42+ println!("{}", Error::InvalidEncoding);
43+ println!("{}", Error::InvalidLength);
44+ println!("{:?}", Error::InvalidLength);
45+ let p = Point { x: 3, y: -4 };
46+ println!("{} {}", p, p.sum());
47+}
added tests/cases/025-borrowed-slice-return.rs +25 -0
new file mode 100644
@@ -0,0 +1,25 @@
1+// Rust's `&[T]` is a borrow, not a copy. Nim's view types model that,
2+// including returning one: writing through the view reaches the original.
3+fn head(xs: &[i32], n: usize) -> &[i32] {
4+ &xs[..n]
5+}
6+
7+fn middle(xs: &[i32]) -> &[i32] {
8+ &xs[1..3]
9+}
10+
11+fn sum(xs: &[i32]) -> i32 {
12+ let mut t: i32 = 0;
13+ for x in xs.iter() {
14+ t += x;
15+ }
16+ t
17+}
18+
19+fn main() {
20+ let v: Vec<i32> = vec![1, 2, 3, 4, 5];
21+ println!("{}", sum(head(&v, 3)));
22+ println!("{}", sum(middle(&v)));
23+ println!("{}", head(&v, 2).len());
24+ println!("{}", head(&v, 5)[4]);
25+}
new file mode 100644
@@ -0,0 +1,25 @@
1+// Rust's `&[T]` is a borrow, not a copy. Nim's view types model that,
2+// including returning one: writing through the view reaches the original.
3+fn head(xs: &[i32], n: usize) -> &[i32] {
4+ &xs[..n]
5+}
6+
7+fn middle(xs: &[i32]) -> &[i32] {
8+ &xs[1..3]
9+}
10+
11+fn sum(xs: &[i32]) -> i32 {
12+ let mut t: i32 = 0;
13+ for x in xs.iter() {
14+ t += x;
15+ }
16+ t
17+}
18+
19+fn main() {
20+ let v: Vec<i32> = vec![1, 2, 3, 4, 5];
21+ println!("{}", sum(head(&v, 3)));
22+ println!("{}", sum(middle(&v)));
23+ println!("{}", head(&v, 2).len());
24+ println!("{}", head(&v, 5)[4]);
25+}
added tests/cases/026-base16ct-crate/error.rs +31 -0
new file mode 100644
@@ -0,0 +1,31 @@
1+use core::fmt;
2+
3+/// Result type with the `base16ct` crate's [`Error`] type.
4+pub type Result<T> = core::result::Result<T, Error>;
5+
6+/// Error type
7+#[derive(Clone, Eq, PartialEq, Debug)]
8+pub enum Error {
9+ /// Invalid encoding of provided Base16 string.
10+ InvalidEncoding,
11+
12+ /// Insufficient output buffer length.
13+ InvalidLength,
14+}
15+
16+impl fmt::Display for Error {
17+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18+ match self {
19+ Error::InvalidEncoding => f.write_str("invalid Base16 encoding"),
20+ Error::InvalidLength => f.write_str("invalid Base16 length"),
21+ }
22+ }
23+}
24+
25+impl core::error::Error for Error {}
26+
27+impl From<Error> for fmt::Error {
28+ fn from(_: Error) -> fmt::Error {
29+ fmt::Error
30+ }
31+}
new file mode 100644
@@ -0,0 +1,31 @@
1+use core::fmt;
2+
3+/// Result type with the `base16ct` crate's [`Error`] type.
4+pub type Result<T> = core::result::Result<T, Error>;
5+
6+/// Error type
7+#[derive(Clone, Eq, PartialEq, Debug)]
8+pub enum Error {
9+ /// Invalid encoding of provided Base16 string.
10+ InvalidEncoding,
11+
12+ /// Insufficient output buffer length.
13+ InvalidLength,
14+}
15+
16+impl fmt::Display for Error {
17+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18+ match self {
19+ Error::InvalidEncoding => f.write_str("invalid Base16 encoding"),
20+ Error::InvalidLength => f.write_str("invalid Base16 length"),
21+ }
22+ }
23+}
24+
25+impl core::error::Error for Error {}
26+
27+impl From<Error> for fmt::Error {
28+ fn from(_: Error) -> fmt::Error {
29+ fmt::Error
30+ }
31+}
added tests/cases/026-base16ct-crate/main.rs +83 -0
new file mode 100644
@@ -0,0 +1,83 @@
1+//@ args: run
2+// base16ct 1.0.0, transpiled as a multi-file crate.
3+//
4+// `error.rs` and `mixed.rs` are the crate's own files, byte-for-byte.
5+// This file carries `lib.rs`'s core -- `decoded_len`, `encoded_len` and
6+// `decode_inner` verbatim -- plus a driver, because the runner needs a `main`.
7+// The `alloc`-gated items are off, as they are by default in the crate.
8+
9+mod error;
10+mod mixed;
11+
12+pub use crate::error::{Error, Result};
13+
14+/// Compute decoded length of the given hex-encoded input.
15+#[inline(always)]
16+pub fn decoded_len(bytes: &[u8]) -> Result<usize> {
17+ if bytes.len() & 1 == 0 {
18+ Ok(bytes.len() / 2)
19+ } else {
20+ Err(Error::InvalidLength)
21+ }
22+}
23+
24+/// Get the length of Base16 (hex) produced by encoding the given bytes.
25+#[inline(always)]
26+pub fn encoded_len(bytes: &[u8]) -> usize {
27+ bytes.len() * 2
28+}
29+
30+fn decode_inner<'a>(
31+ src: &[u8],
32+ dst: &'a mut [u8],
33+ decode_nibble: impl Fn(u8) -> u16,
34+) -> Result<&'a [u8]> {
35+ let dst = dst
36+ .get_mut(..decoded_len(src)?)
37+ .ok_or(Error::InvalidLength)?;
38+
39+ let mut err: u16 = 0;
40+ for (src, dst) in src.chunks_exact(2).zip(dst.iter_mut()) {
41+ let byte = (decode_nibble(src[0]) << 4) | decode_nibble(src[1]);
42+ err |= byte >> 8;
43+ *dst = byte as u8;
44+ }
45+
46+ match err {
47+ 0 => Ok(dst),
48+ _ => Err(Error::InvalidEncoding),
49+ }
50+}
51+
52+fn show(tag: &str, r: Result<&[u8]>) {
53+ match r {
54+ Ok(v) => {
55+ print!("{} ok ", tag);
56+ for b in v.iter() {
57+ print!("{:02x}", b);
58+ }
59+ println!(" len={}", v.len());
60+ }
61+ Err(e) => println!("{} err {:?} / {}", tag, e, e),
62+ }
63+}
64+
65+fn main() {
66+ let mut buf = [0u8; 16];
67+
68+ show("mixed-l", mixed::decode(b"abcd1234", &mut buf));
69+ show("mixed-u", mixed::decode(b"ABCD1234", &mut buf));
70+ show("mixed-m", mixed::decode(b"abCD1234", &mut buf));
71+ show("edge", mixed::decode(b"00ff7f80", &mut buf));
72+ show("oddlen", mixed::decode(b"abc", &mut buf));
73+ show("bad", mixed::decode(b"zzzz", &mut buf));
74+ show("empty", mixed::decode(b"", &mut buf));
75+
76+ let mut small = [0u8; 2];
77+ show("short-dst", mixed::decode(b"abcd1234", &mut small));
78+
79+ // `lower::encode` is not here: `lower.rs` also defines `encode_str`, whose
80+ // body is a closure over an `unsafe` block, and neither is implemented.
81+
82+ println!("{} {}", decoded_len(b"abcd").unwrap(), encoded_len(b"\xab\xcd"));
83+}
new file mode 100644
@@ -0,0 +1,83 @@
1+//@ args: run
2+// base16ct 1.0.0, transpiled as a multi-file crate.
3+//
4+// `error.rs` and `mixed.rs` are the crate's own files, byte-for-byte.
5+// This file carries `lib.rs`'s core -- `decoded_len`, `encoded_len` and
6+// `decode_inner` verbatim -- plus a driver, because the runner needs a `main`.
7+// The `alloc`-gated items are off, as they are by default in the crate.
8+
9+mod error;
10+mod mixed;
11+
12+pub use crate::error::{Error, Result};
13+
14+/// Compute decoded length of the given hex-encoded input.
15+#[inline(always)]
16+pub fn decoded_len(bytes: &[u8]) -> Result<usize> {
17+ if bytes.len() & 1 == 0 {
18+ Ok(bytes.len() / 2)
19+ } else {
20+ Err(Error::InvalidLength)
21+ }
22+}
23+
24+/// Get the length of Base16 (hex) produced by encoding the given bytes.
25+#[inline(always)]
26+pub fn encoded_len(bytes: &[u8]) -> usize {
27+ bytes.len() * 2
28+}
29+
30+fn decode_inner<'a>(
31+ src: &[u8],
32+ dst: &'a mut [u8],
33+ decode_nibble: impl Fn(u8) -> u16,
34+) -> Result<&'a [u8]> {
35+ let dst = dst
36+ .get_mut(..decoded_len(src)?)
37+ .ok_or(Error::InvalidLength)?;
38+
39+ let mut err: u16 = 0;
40+ for (src, dst) in src.chunks_exact(2).zip(dst.iter_mut()) {
41+ let byte = (decode_nibble(src[0]) << 4) | decode_nibble(src[1]);
42+ err |= byte >> 8;
43+ *dst = byte as u8;
44+ }
45+
46+ match err {
47+ 0 => Ok(dst),
48+ _ => Err(Error::InvalidEncoding),
49+ }
50+}
51+
52+fn show(tag: &str, r: Result<&[u8]>) {
53+ match r {
54+ Ok(v) => {
55+ print!("{} ok ", tag);
56+ for b in v.iter() {
57+ print!("{:02x}", b);
58+ }
59+ println!(" len={}", v.len());
60+ }
61+ Err(e) => println!("{} err {:?} / {}", tag, e, e),
62+ }
63+}
64+
65+fn main() {
66+ let mut buf = [0u8; 16];
67+
68+ show("mixed-l", mixed::decode(b"abcd1234", &mut buf));
69+ show("mixed-u", mixed::decode(b"ABCD1234", &mut buf));
70+ show("mixed-m", mixed::decode(b"abCD1234", &mut buf));
71+ show("edge", mixed::decode(b"00ff7f80", &mut buf));
72+ show("oddlen", mixed::decode(b"abc", &mut buf));
73+ show("bad", mixed::decode(b"zzzz", &mut buf));
74+ show("empty", mixed::decode(b"", &mut buf));
75+
76+ let mut small = [0u8; 2];
77+ show("short-dst", mixed::decode(b"abcd1234", &mut small));
78+
79+ // `lower::encode` is not here: `lower.rs` also defines `encode_str`, whose
80+ // body is a closure over an `unsafe` block, and neither is implemented.
81+
82+ println!("{} {}", decoded_len(b"abcd").unwrap(), encoded_len(b"\xab\xcd"));
83+}
added tests/cases/026-base16ct-crate/mixed.rs +37 -0
new file mode 100644
@@ -0,0 +1,37 @@
1+use crate::{Error, decode_inner};
2+#[cfg(feature = "alloc")]
3+use crate::{Vec, decoded_len};
4+
5+/// Decode a mixed Base16 (hex) string into the provided destination buffer.
6+pub fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> {
7+ decode_inner(src.as_ref(), dst, decode_nibble)
8+}
9+
10+/// Decode a mixed Base16 (hex) string into a byte vector.
11+#[cfg(feature = "alloc")]
12+pub fn decode_vec(input: impl AsRef<[u8]>) -> Result<Vec<u8>, Error> {
13+ let mut output = vec![0u8; decoded_len(input.as_ref())?];
14+ decode(input, &mut output)?;
15+ Ok(output)
16+}
17+
18+/// Decode a single nibble of lower hex
19+#[inline(always)]
20+fn decode_nibble(src: u8) -> u16 {
21+ // 0-9 0x30-0x39
22+ // A-F 0x41-0x46 or a-f 0x61-0x66
23+ let byte = src as i16;
24+ let mut ret: i16 = -1;
25+
26+ // 0-9 0x30-0x39
27+ // if (byte > 0x2f && byte < 0x3a) ret += byte - 0x30 + 1; // -47
28+ ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47);
29+ // A-F 0x41-0x46
30+ // if (byte > 0x40 && byte < 0x47) ret += byte - 0x41 + 10 + 1; // -54
31+ ret += (((0x40i16 - byte) & (byte - 0x47)) >> 8) & (byte - 54);
32+ // a-f 0x61-0x66
33+ // if (byte > 0x60 && byte < 0x67) ret += byte - 0x61 + 10 + 1; // -86
34+ ret += (((0x60i16 - byte) & (byte - 0x67)) >> 8) & (byte - 86);
35+
36+ ret as u16
37+}
new file mode 100644
@@ -0,0 +1,37 @@
1+use crate::{Error, decode_inner};
2+#[cfg(feature = "alloc")]
3+use crate::{Vec, decoded_len};
4+
5+/// Decode a mixed Base16 (hex) string into the provided destination buffer.
6+pub fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> {
7+ decode_inner(src.as_ref(), dst, decode_nibble)
8+}
9+
10+/// Decode a mixed Base16 (hex) string into a byte vector.
11+#[cfg(feature = "alloc")]
12+pub fn decode_vec(input: impl AsRef<[u8]>) -> Result<Vec<u8>, Error> {
13+ let mut output = vec![0u8; decoded_len(input.as_ref())?];
14+ decode(input, &mut output)?;
15+ Ok(output)
16+}
17+
18+/// Decode a single nibble of lower hex
19+#[inline(always)]
20+fn decode_nibble(src: u8) -> u16 {
21+ // 0-9 0x30-0x39
22+ // A-F 0x41-0x46 or a-f 0x61-0x66
23+ let byte = src as i16;
24+ let mut ret: i16 = -1;
25+
26+ // 0-9 0x30-0x39
27+ // if (byte > 0x2f && byte < 0x3a) ret += byte - 0x30 + 1; // -47
28+ ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47);
29+ // A-F 0x41-0x46
30+ // if (byte > 0x40 && byte < 0x47) ret += byte - 0x41 + 10 + 1; // -54
31+ ret += (((0x40i16 - byte) & (byte - 0x47)) >> 8) & (byte - 54);
32+ // a-f 0x61-0x66
33+ // if (byte > 0x60 && byte < 0x67) ret += byte - 0x61 + 10 + 1; // -86
34+ ret += (((0x60i16 - byte) & (byte - 0x67)) >> 8) & (byte - 86);
35+
36+ ret as u16
37+}
modified tests/differential.rs +32 -7
@@ -181,7 +181,25 @@ fn tail(s: &str, lines: usize) -> String {
181181
182182 fn run_case(case: &Path, work: &Path, nim: &Path) -> Outcome {
183183 let name = case.file_stem().unwrap().to_string_lossy().into_owned();
184- let src = match fs::read_to_string(case) {
184+ // For a directory case, `main.rs` is the crate root and carries the
185+ // directives; every `.rs` beside it is passed to rustnim as well.
186+ let (root, extra) = if case.is_dir() {
187+ let mut others: Vec<PathBuf> = fs::read_dir(case)
188+ .map(|d| {
189+ d.filter_map(|e| e.ok().map(|e| e.path()))
190+ .filter(|p| {
191+ p.extension().is_some_and(|x| x == "rs")
192+ && p.file_name().is_some_and(|f| f != "main.rs")
193+ })
194+ .collect()
195+ })
196+ .unwrap_or_default();
197+ others.sort();
198+ (case.join("main.rs"), others)
199+ } else {
200+ (case.to_path_buf(), Vec::new())
201+ };
202+ let src = match fs::read_to_string(&root) {
185203 Ok(s) => s,
186204 Err(e) => return Outcome::fail("read", e.to_string()),
187205 };
@@ -205,10 +223,13 @@ fn run_case(case: &Path, work: &Path, nim: &Path) -> Outcome {
205223 name.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }).collect::<String>()
206224 );
207225 let nim_src = dir.join(format!("{mod_name}.nim"));
208- let transpile = match run(
209- Command::new(RUSTNIM).arg(case).arg("-o").arg(&nim_src).env("TMPDIR", &dir),
210- None,
211- ) {
226+ let mut cmd = Command::new(RUSTNIM);
227+ cmd.arg(&root);
228+ for e in &extra {
229+ cmd.arg(e);
230+ }
231+ cmd.arg("-o").arg(&nim_src).env("TMPDIR", &dir);
232+ let transpile = match run(&mut cmd, None) {
212233 Ok(r) => r,
213234 Err(e) => return Outcome::fail("rustnim", e),
214235 };
@@ -252,7 +273,7 @@ fn run_case(case: &Path, work: &Path, nim: &Path) -> Outcome {
252273 Command::new("rustc")
253274 .arg("--edition=2021")
254275 .arg("-A").arg("warnings")
255- .arg(case)
276+ .arg(&root)
256277 .arg("-o").arg(&rs_bin)
257278 .env("TMPDIR", &dir),
258279 None,
@@ -346,10 +367,14 @@ fn differential() {
346367 Err(e) => panic!("cannot locate Nim: {e}"),
347368 };
348369
370+ // A case is either a single `.rs` file or a directory of them whose
371+ // crate root is `main.rs` -- which is how a multi-file crate is tested.
349372 let mut cases: Vec<PathBuf> = fs::read_dir(&cases_dir)
350373 .unwrap_or_else(|e| panic!("no {}: {e}", cases_dir.display()))
351374 .filter_map(|e| e.ok().map(|e| e.path()))
352- .filter(|p| p.extension().is_some_and(|x| x == "rs"))
375+ .filter(|p| {
376+ p.extension().is_some_and(|x| x == "rs") || p.join("main.rs").is_file()
377+ })
353378 .collect();
354379 cases.sort();
355380
@@ -181,7 +181,25 @@ fn tail(s: &str, lines: usize) -> String {
181 181
182 fn run_case(case: &Path, work: &Path, nim: &Path) -> Outcome {182 fn run_case(case: &Path, work: &Path, nim: &Path) -> Outcome {
183 let name = case.file_stem().unwrap().to_string_lossy().into_owned();183 let name = case.file_stem().unwrap().to_string_lossy().into_owned();
184- let src = match fs::read_to_string(case) {184+ // For a directory case, `main.rs` is the crate root and carries the
185+ // directives; every `.rs` beside it is passed to rustnim as well.
186+ let (root, extra) = if case.is_dir() {
187+ let mut others: Vec<PathBuf> = fs::read_dir(case)
188+ .map(|d| {
189+ d.filter_map(|e| e.ok().map(|e| e.path()))
190+ .filter(|p| {
191+ p.extension().is_some_and(|x| x == "rs")
192+ && p.file_name().is_some_and(|f| f != "main.rs")
193+ })
194+ .collect()
195+ })
196+ .unwrap_or_default();
197+ others.sort();
198+ (case.join("main.rs"), others)
199+ } else {
200+ (case.to_path_buf(), Vec::new())
201+ };
202+ let src = match fs::read_to_string(&root) {
185 Ok(s) => s,203 Ok(s) => s,
186 Err(e) => return Outcome::fail("read", e.to_string()),204 Err(e) => return Outcome::fail("read", e.to_string()),
187 };205 };
@@ -205,10 +223,13 @@ fn run_case(case: &Path, work: &Path, nim: &Path) -> Outcome {
205 name.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }).collect::<String>()223 name.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }).collect::<String>()
206 );224 );
207 let nim_src = dir.join(format!("{mod_name}.nim"));225 let nim_src = dir.join(format!("{mod_name}.nim"));
208- let transpile = match run(226+ let mut cmd = Command::new(RUSTNIM);
209- Command::new(RUSTNIM).arg(case).arg("-o").arg(&nim_src).env("TMPDIR", &dir),227+ cmd.arg(&root);
210- None,228+ for e in &extra {
211- ) {229+ cmd.arg(e);
230+ }
231+ cmd.arg("-o").arg(&nim_src).env("TMPDIR", &dir);
232+ let transpile = match run(&mut cmd, None) {
212 Ok(r) => r,233 Ok(r) => r,
213 Err(e) => return Outcome::fail("rustnim", e),234 Err(e) => return Outcome::fail("rustnim", e),
214 };235 };
@@ -252,7 +273,7 @@ fn run_case(case: &Path, work: &Path, nim: &Path) -> Outcome {
252 Command::new("rustc")273 Command::new("rustc")
253 .arg("--edition=2021")274 .arg("--edition=2021")
254 .arg("-A").arg("warnings")275 .arg("-A").arg("warnings")
255- .arg(case)276+ .arg(&root)
256 .arg("-o").arg(&rs_bin)277 .arg("-o").arg(&rs_bin)
257 .env("TMPDIR", &dir),278 .env("TMPDIR", &dir),
258 None,279 None,
@@ -346,10 +367,14 @@ fn differential() {
346 Err(e) => panic!("cannot locate Nim: {e}"),367 Err(e) => panic!("cannot locate Nim: {e}"),
347 };368 };
348 369
370+ // A case is either a single `.rs` file or a directory of them whose
371+ // crate root is `main.rs` -- which is how a multi-file crate is tested.
349 let mut cases: Vec<PathBuf> = fs::read_dir(&cases_dir)372 let mut cases: Vec<PathBuf> = fs::read_dir(&cases_dir)
350 .unwrap_or_else(|e| panic!("no {}: {e}", cases_dir.display()))373 .unwrap_or_else(|e| panic!("no {}: {e}", cases_dir.display()))
351 .filter_map(|e| e.ok().map(|e| e.path()))374 .filter_map(|e| e.ok().map(|e| e.path()))
352- .filter(|p| p.extension().is_some_and(|x| x == "rs"))375+ .filter(|p| {
376+ p.extension().is_some_and(|x| x == "rs") || p.join("main.rs").is_file()
377+ })
353 .collect();378 .collect();
354 cases.sort();379 cases.sort();
355 380