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>ae9f986 parent: b66da6b modified
DESIGN.md +104 -28 | @@ -2,11 +2,15 @@ | ||
| 2 | 2 | |
| 3 | 3 | ## Status |
| 4 | 4 | |
| 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 | |
| 10 | 14 | and float operator set at exact widths, `as` casts, `if`/`while`/`loop`/`for`, |
| 11 | 15 | `match` including patterns that bind, `Vec`/slices/arrays, type aliases |
| 12 | 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 | 101 | `while` condition is rejected: the early return would run once before the |
| 98 | 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 | 164 | ### Type propagation is load-bearing |
| 101 | 165 | |
| 102 | 166 | 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 | 214 | 5. `checked_*` and `saturating_*` are not mapped yet; they are currently |
| 151 | 215 | 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. | |
| 156 | 220 | 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but |
| 157 | 221 | the exponent-form thresholds have only been checked at `1e21`. |
| 158 | 222 | 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 | 278 | Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and |
| 215 | 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`: 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: | |
| 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 | | |
| 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 | ## Status | 3 | ## Status |
| 4 | 4 | ||
| 5 | -**Transpiling, and measured.** 27 differential cases, 23 behavioural and 4 | 5 | +**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 and | 8 | + |
| 9 | -data-carrying), `Option`/`Result` with `?`, `let`/`let mut`, the full integer | 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 | ||
| 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 aliases | 15 | `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 the | 101 | `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-bearing | 164 | ### Type propagation is load-bearing |
| 101 | 165 | ||
| 102 | Rust infers an unsuffixed integer literal's type from context and falls back | 166 | 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 currently | 214 | 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 and | 216 | +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, so | 218 | + 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`, but | 220 | 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 own | 222 | 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 — and | 278 | 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`: the | 281 | +**The decoder is reached.** `tests/cases/026-base16ct-crate/` transpiles |
| 218 | -`Error` enum, `decoded_len` and `decode_nibble` **verbatim from base16ct | 282 | +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 output | 283 | +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 other | 285 | +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**, and | 305 | +- `lower.rs` / `upper.rs`: `encode` itself lowers, but the same file defines |
| 235 | -**iterator adaptors over slices** together with the mutable slice views they | 306 | + `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 files | 307 | + 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] = &[ | ||
| 29 | 29 | |
| 30 | 30 | fn ident(name: &str) -> String { |
| 31 | 31 | 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 | + } | |
| 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 | 117 | /// A lowered expression: its Nim text, and its type where we know it. |
| 39 | 118 | /// |
| 40 | 119 | /// 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 | 124 | struct Val { |
| 46 | 125 | code: String, |
| 47 | 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 | 139 | impl Val { |
| 51 | 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 | 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 | 185 | out: String, |
| 97 | 186 | indent: usize, |
| 98 | 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 | 191 | fns: HashMap<String, Sig>, |
| 100 | 192 | /// struct name -> (field, type) |
| 101 | 193 | structs: HashMap<String, Vec<(String, Nim)>>, |
| @@ -103,8 +195,28 @@ pub struct Lowerer { | ||
| 103 | 195 | /// variant name -> enums declaring it. A variant named by more than one |
| 104 | 196 | /// enum must be written qualified, or it is rejected as ambiguous. |
| 105 | 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 | 215 | /// `type X<T> = ...`, expanded before any type is mapped. |
| 107 | 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 | 220 | /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is |
| 109 | 221 | /// evaluated against these exactly as rustc would, so an item that is |
| 110 | 222 | /// dropped here is genuinely not part of the program being compiled. |
| @@ -128,11 +240,18 @@ impl Lowerer { | ||
| 128 | 240 | out: String::new(), |
| 129 | 241 | indent: 0, |
| 130 | 242 | scopes: vec![HashMap::new()], |
| 243 | + alias_scopes: vec![HashMap::new()], | |
| 131 | 244 | fns: HashMap::new(), |
| 132 | 245 | structs: HashMap::new(), |
| 133 | 246 | enums: HashMap::new(), |
| 134 | 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 | 253 | aliases: HashMap::new(), |
| 254 | + modules: Vec::new(), | |
| 136 | 255 | features: Vec::new(), |
| 137 | 256 | dropped_by_cfg: 0, |
| 138 | 257 | ret: None, |
| @@ -165,9 +284,23 @@ impl Lowerer { | ||
| 165 | 284 | |
| 166 | 285 | fn push_scope(&mut self) { |
| 167 | 286 | self.scopes.push(HashMap::new()); |
| 287 | + self.alias_scopes.push(HashMap::new()); | |
| 168 | 288 | } |
| 169 | 289 | fn pop_scope(&mut self) { |
| 170 | 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 | 305 | fn bind(&mut self, name: &str, t: Nim) { |
| 173 | 306 | self.scopes.last_mut().unwrap().insert(name.to_string(), t); |
| @@ -182,12 +315,34 @@ impl Lowerer { | ||
| 182 | 315 | self.out.push_str(include_str!("prelude.nim")); |
| 183 | 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 | 325 | // Pass 1: signatures and struct shapes, so that a call can be typed |
| 186 | 326 | // regardless of declaration order (Rust has no forward declarations). |
| 187 | 327 | for item in &file.items { |
| 188 | 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 | 346 | for item in &file.items { |
| 192 | 347 | self.item(item)?; |
| 193 | 348 | } |
| @@ -212,6 +367,35 @@ impl Lowerer { | ||
| 212 | 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 | 399 | fn collect(&mut self, item: &Item) -> Result<(), String> { |
| 216 | 400 | // A `#[cfg(..)]` item exists only under some feature set. Dropping it |
| 217 | 401 | // silently would change what the program does; picking a feature set |
| @@ -224,6 +408,8 @@ impl Lowerer { | ||
| 224 | 408 | match item { |
| 225 | 409 | Item::Fn(f) => { |
| 226 | 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 | 413 | self.fns.insert(f.sig.ident.to_string(), Sig { params, ret }); |
| 228 | 414 | } |
| 229 | 415 | Item::Struct(s) => { |
| @@ -237,6 +423,12 @@ impl Lowerer { | ||
| 237 | 423 | } |
| 238 | 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 | 432 | Item::Type(t) => { |
| 241 | 433 | let params: Vec<String> = t |
| 242 | 434 | .generics |
| @@ -291,13 +483,57 @@ impl Lowerer { | ||
| 291 | 483 | } |
| 292 | 484 | Item::Impl(im) => { |
| 293 | 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 | 526 | for it in &im.items { |
| 295 | 527 | if let syn::ImplItem::Fn(m) = it { |
| 296 | 528 | let (mut params, ret) = self.signature(&m.sig)?; |
| 297 | 529 | if takes_self(&m.sig) { |
| 298 | 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 | 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 | 683 | fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> { |
| 405 | 684 | if sig.asyncness.is_some() { |
| 406 | 685 | return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident)); |
| @@ -426,17 +705,49 @@ impl Lowerer { | ||
| 426 | 705 | } |
| 427 | 706 | let ret = match &sig.output { |
| 428 | 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 | 716 | Ok((params, ret)) |
| 432 | 717 | } |
| 433 | 718 | |
| 434 | 719 | // --------------------------------------------------------------- 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 | 739 | fn item(&mut self, item: &Item) -> Result<(), String> { |
| 437 | 740 | if !self.cfg_keeps(item_attrs(item))? { |
| 438 | 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 | 751 | match item { |
| 441 | 752 | Item::Fn(f) => self.func(&f.sig, &f.block, None), |
| 442 | 753 | Item::Struct(s) => { |
| @@ -471,11 +782,26 @@ impl Lowerer { | ||
| 471 | 782 | } |
| 472 | 783 | Item::Impl(im) => { |
| 473 | 784 | 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")); | |
| 479 | 805 | } |
| 480 | 806 | for it in &im.items { |
| 481 | 807 | match it { |
| @@ -495,19 +821,23 @@ impl Lowerer { | ||
| 495 | 821 | // An inline `mod` is flattened; Nim has no nested modules in a |
| 496 | 822 | // single file. |
| 497 | 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 | 824 | for i in &items { |
| 502 | 825 | self.item(i)?; |
| 503 | 826 | } |
| 504 | 827 | Ok(()) |
| 505 | 828 | } |
| 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 | + } | |
| 511 | 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 | 1067 | fn func( |
| 651 | 1068 | &mut self, |
| 652 | 1069 | sig: &syn::Signature, |
| @@ -654,6 +1071,16 @@ impl Lowerer { | ||
| 654 | 1071 | recv: Option<Nim>, |
| 655 | 1072 | ) -> Result<(), String> { |
| 656 | 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 | 1084 | let (ptys, ret) = self.signature(sig)?; |
| 658 | 1085 | |
| 659 | 1086 | self.push_scope(); |
| @@ -684,6 +1111,9 @@ impl Lowerer { | ||
| 684 | 1111 | for (p, t) in typed.iter().zip(ptys.iter()) { |
| 685 | 1112 | let pname = match &*p.pat { |
| 686 | 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 | 1117 | _ => return Err("only plain identifier parameters are supported".into()), |
| 688 | 1118 | }; |
| 689 | 1119 | rendered.push(format!("{}: {}", ident(&pname), t.render())); |
| @@ -692,9 +1122,9 @@ impl Lowerer { | ||
| 692 | 1122 | } |
| 693 | 1123 | |
| 694 | 1124 | let head = if ret == Nim::Unit { |
| 695 | - format!("proc {}*({}) =", ident(&name), rendered.join(", ")) | |
| 1125 | + format!("proc {}*({}) =", ident(name), rendered.join(", ")) | |
| 696 | 1126 | } else { |
| 697 | - format!("proc {}*({}): {} =", ident(&name), rendered.join(", "), ret.render()) | |
| 1127 | + format!("proc {}*({}): {} =", ident(name), rendered.join(", "), ret.render()) | |
| 698 | 1128 | }; |
| 699 | 1129 | self.line(&head); |
| 700 | 1130 | self.indent += 1; |
| @@ -869,6 +1299,21 @@ impl Lowerer { | ||
| 869 | 1299 | } |
| 870 | 1300 | |
| 871 | 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 | 1317 | let t = match (ann, &v.ty) { |
| 873 | 1318 | (Some(a), _) => a.owned(), |
| 874 | 1319 | (None, Some(t)) => t.clone().owned(), |
| @@ -1075,30 +1520,43 @@ impl Lowerer { | ||
| 1075 | 1520 | if f.label.is_some() { |
| 1076 | 1521 | return Err("loop labels are not implemented yet".into()); |
| 1077 | 1522 | } |
| 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)?; | |
| 1083 | 1524 | |
| 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"); | |
| 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 | 1560 | Expr::Range(r) => { |
| 1103 | 1561 | let lo = match &r.start { |
| 1104 | 1562 | Some(e) => self.expr(e)?, |
| @@ -1106,45 +1564,184 @@ impl Lowerer { | ||
| 1106 | 1564 | }; |
| 1107 | 1565 | let hi = match &r.end { |
| 1108 | 1566 | 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 | + } | |
| 1114 | 1570 | }; |
| 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 | + } | |
| 1117 | 1624 | } |
| 1118 | 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 | 1637 | 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 | + }) | |
| 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; | |
| 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()), | |
| 1144 | 1744 | } |
| 1145 | - self.indent -= 1; | |
| 1146 | - self.pop_scope(); | |
| 1147 | - Ok(()) | |
| 1148 | 1745 | } |
| 1149 | 1746 | |
| 1150 | 1747 | fn match_stmt(&mut self, e: &Expr) -> Result<(), String> { |
| @@ -1499,6 +2096,22 @@ impl Lowerer { | ||
| 1499 | 2096 | if name == "None" { |
| 1500 | 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 | 2115 | // A unit enum variant used as a value: `Error::InvalidLength`. |
| 1503 | 2116 | if let Some((def, v)) = self.resolve_variant(&p.path) { |
| 1504 | 2117 | let ty = Some(Nim::Named(def.name.clone(), vec![])); |
| @@ -1508,6 +2121,20 @@ impl Lowerer { | ||
| 1508 | 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 | 2138 | if let Some(t) = self.lookup(&name) { |
| 1512 | 2139 | return Ok(Val::new(ident(&name), Some(t))); |
| 1513 | 2140 | } |
| @@ -1530,7 +2157,38 @@ impl Lowerer { | ||
| 1530 | 2157 | Expr::Unary(u) => self.unary(u, expect), |
| 1531 | 2158 | Expr::Binary(b) => self.binary(b, expect), |
| 1532 | 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 | 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 | 2192 | let base = self.expr(&i.expr)?; |
| 1535 | 2193 | let idx = self.expr(&i.index)?; |
| 1536 | 2194 | // Rust indexes with usize; Nim wants an `int`, and a `uint` |
| @@ -1564,7 +2222,7 @@ impl Lowerer { | ||
| 1564 | 2222 | } |
| 1565 | 2223 | Expr::Try(t) => self.try_op(t), |
| 1566 | 2224 | Expr::Call(c) => self.call(c, expect), |
| 1567 | - Expr::MethodCall(m) => self.method(m), | |
| 2225 | + Expr::MethodCall(m) => self.method(m, expect), | |
| 1568 | 2226 | Expr::Macro(m) => { |
| 1569 | 2227 | let code = self.macro_call(&m.mac)?; |
| 1570 | 2228 | Ok(Val::new(code, None)) |
| @@ -1917,6 +2575,30 @@ impl Lowerer { | ||
| 1917 | 2575 | .into()); |
| 1918 | 2576 | } |
| 1919 | 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 | 2602 | let vt = v.ty.clone().ok_or( |
| 1921 | 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> { | |
| 2060 | - let recv = self.expr(&m.receiver)?; | |
| 2741 | + fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> { | |
| 2061 | 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 | 2802 | // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's |
| 2063 | 2803 | // own type; `v.push(e)` takes the element type. |
| 2064 | 2804 | let arg_want = match (name.as_str(), &recv.ty) { |
| @@ -2089,6 +2829,19 @@ impl Lowerer { | ||
| 2089 | 2829 | }; |
| 2090 | 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 | 2845 | "ok_or" => { |
| 2093 | 2846 | let inner = match &rt { |
| 2094 | 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 | 2920 | "abs" => (format!("abs({})", recv.code), rt.clone()), |
| 2162 | 2921 | "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), |
| 2163 | 2922 | "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), |
| @@ -2167,11 +2926,30 @@ impl Lowerer { | ||
| 2167 | 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 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 { | |
| 2175 | 2953 | let mut all = vec![recv.code.clone()]; |
| 2176 | 2954 | all.extend(args.iter().map(|a| a.code.clone())); |
| 2177 | 2955 | (format!("{}({})", ident(&name), all.join(", ")), Some(ret)) |
| @@ -2203,6 +2981,27 @@ impl Lowerer { | ||
| 2203 | 2981 | }) |
| 2204 | 2982 | } |
| 2205 | 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 | 3005 | "panic" => { |
| 2207 | 3006 | let s = self.format_args(mac)?; |
| 2208 | 3007 | Ok(format!("rsPanic({s})")) |
| @@ -2253,17 +3052,23 @@ impl Lowerer { | ||
| 2253 | 3052 | |
| 2254 | 3053 | /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression. |
| 2255 | 3054 | 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 { | |
| 2261 | 3066 | if args.is_empty() { |
| 2262 | 3067 | return Ok("\"\"".into()); |
| 2263 | 3068 | } |
| 2264 | 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 | 3073 | let pieces = fmt::parse(&s.value())?; |
| 2269 | 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 | 3228 | // --------------------------------------------------------------- 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 | 3275 | fn takes_self(sig: &syn::Signature) -> bool { |
| 2426 | 3276 | matches!(sig.inputs.first(), Some(FnArg::Receiver(_))) |
| 2427 | 3277 | } |
| @@ -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 a | 119 | /// 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 one | 195 | /// 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]` is | 220 | /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is |
| 109 | /// evaluated against these exactly as rustc would, so an item that is | 221 | /// 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 typed | 325 | // 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 it | 400 | // A `#[cfg(..)]` item exists only under some feature set. Dropping it |
| 217 | // silently would change what the program does; picking a feature set | 401 | // 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> = t | 433 | let params: Vec<String> = t |
| 242 | .generics | 434 | .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 | // --------------------------------------------------------------- items | 719 | // --------------------------------------------------------------- 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 a | 821 | // 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.ident | 832 | + 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 a | 1525 | + // 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.receiver | 1535 | + |
| 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's | 2802 | // `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 makes | 2948 | + // 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![,]> = mac | 3055 | + 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 | // --------------------------------------------------------------- utilities | 3228 | // --------------------------------------------------------------- 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> { | ||
| 79 | 79 | |
| 80 | 80 | let mut lowerer = lower::Lowerer::new(); |
| 81 | 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 | 87 | let nim = lowerer.lower_file(&file)?; |
| 83 | 88 | |
| 84 | 89 | 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 @@ | ||
| 6 | 6 | ## truncate toward zero in both) there is deliberately nothing here: the |
| 7 | 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 | 15 | import std/[unicode, strutils] |
| 10 | 16 | |
| 11 | 17 | type |
| 12 | 18 | 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 | 27 | Option*[T] = object |
| 15 | 28 | case has*: bool |
| 16 | 29 | of true: val*: T |
| @@ -6,11 +6,24 @@ | |||
| 6 | ## truncate toward zero in both) there is deliberately nothing here: the | 6 | ## 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 | type | 17 | type |
| 12 | RustPanic* = object of CatchableError | 18 | 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] = object | 27 | Option*[T] = object |
| 15 | case has*: bool | 28 | case has*: bool |
| 16 | of true: val*: T | 29 | of true: val*: T |
modified
src/ty.rs +37 -1 | @@ -40,7 +40,13 @@ impl Nim { | ||
| 40 | 40 | } |
| 41 | 41 | Nim::Var(t) => format!("var {}", t.render()), |
| 42 | 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 | 50 | match &**ret { |
| 45 | 51 | Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")), |
| 46 | 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 | 144 | pub fn map(t: &Type) -> Result<Nim, String> { |
| 112 | 145 | match t { |
| 113 | 146 | Type::Path(p) => { |
| 147 | + if let Some(n) = std_qualified(&p.path) { | |
| 148 | + return Ok(n); | |
| 149 | + } | |
| 114 | 150 | let seg = p |
| 115 | 151 | .path |
| 116 | 152 | .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 = p | 150 | let seg = p |
| 115 | .path | 151 | .path |
| 116 | .segments | 152 | .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 { | ||
| 181 | 181 | |
| 182 | 182 | fn run_case(case: &Path, work: &Path, nim: &Path) -> Outcome { |
| 183 | 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 | 203 | Ok(s) => s, |
| 186 | 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 | 223 | name.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }).collect::<String>() |
| 206 | 224 | ); |
| 207 | 225 | 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) { | |
| 212 | 233 | Ok(r) => r, |
| 213 | 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 | 273 | Command::new("rustc") |
| 253 | 274 | .arg("--edition=2021") |
| 254 | 275 | .arg("-A").arg("warnings") |
| 255 | - .arg(case) | |
| 276 | + .arg(&root) | |
| 256 | 277 | .arg("-o").arg(&rs_bin) |
| 257 | 278 | .env("TMPDIR", &dir), |
| 258 | 279 | None, |
| @@ -346,10 +367,14 @@ fn differential() { | ||
| 346 | 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 | 372 | let mut cases: Vec<PathBuf> = fs::read_dir(&cases_dir) |
| 350 | 373 | .unwrap_or_else(|e| panic!("no {}: {e}", cases_dir.display())) |
| 351 | 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 | 378 | .collect(); |
| 354 | 379 | cases.sort(); |
| 355 | 380 | |
| @@ -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 | ||