Add closures and unsafe; base16ct's lower.rs and upper.rs go through
`unsafe` is a permission marker, not a semantic change -- it does not alter
what the enclosed operations mean -- so the block is transparent and every
operation inside still goes through the ordinary lowering and is still
rejected if it has no faithful mapping. `unsafe fn` lowers like any proc.
A closure becomes a Nim anonymous proc. Nim's closures capture by reference,
as Rust's non-`move` closures do; a `move` closure captures by value, which
is a different thing, so it is rejected rather than lowered to the same
construct. `impl Fn(A) -> B` is left at Nim's default calling convention,
which accepts both a plain top-level proc and a capturing closure, just as
Rust's `impl Fn` does. `.map`/`.and_then` over an Option/Result are expanded
inline with the closure's parameter aliased to the payload, which keeps the
whole thing an expression and keeps a view a view.
`&str` is a borrowed view of someone else's bytes, so it now maps to
openArray[char] rather than an owned string; Nim accepts a string argument
for an openArray[char] parameter, so literals still pass through.
from_utf8_unchecked reinterprets a byte view as a character view over the
same memory -- probed first, including that writes through the original are
visible and that the empty case is safe.
Modules are now real. Rust keeps lower::decode and mixed::decode apart by
module, and flattening merged them -- they are different functions, so this
was a miscompile waiting to happen, not a cosmetic issue. The first input is
the crate root, each later one is a module named by its file stem, items are
emitted as <module>_<name>, and a call resolves through an explicit
qualifier, then the current module, then what `use` brought into scope, then
the root.
Three bugs found while getting there: a window alias re-evaluated its offset
and length expression at each use site, so a loop pattern that shadowed a
name silently changed what those bounds meant -- they are now hoisted and
computed once, as Rust computes them once; windows did not compose, so a
chunks_exact_mut over a get_mut view rebuilt the outer view instead of
indexing through it; and `?` now applies a crate's `impl From` to convert the
error type instead of refusing, while still never assuming the conversion is
the identity.
base16ct's lower.rs and upper.rs now go through byte-for-byte, which makes
four of its six modules. encode_str lowers as written --
`encode(src, dst).map(|r| unsafe { core::str::from_utf8_unchecked(r) })` --
returning a &str view of the bytes just written rather than a copy. Remaining
are display.rs, which needs the formatter to accumulate writes across a loop,
and the alloc half, which now reaches debug_assert_eq!.
32 differential cases and 6 integration tests, all green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>0a375d8 parent: 428f374 modified
DESIGN.md +70 -29 | @@ -2,13 +2,13 @@ | ||
| 2 | 2 | |
| 3 | 3 | ## Status |
| 4 | 4 | |
| 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`. | |
| 5 | +**Milestone 1 is reached, and then some.** Four of base16ct's six modules go | |
| 6 | +through byte-for-byte. 32 differential cases, 28 behavioural and 4 rejections, | |
| 7 | +plus 6 unit/integration tests. All green. Run `cargo test`. | |
| 8 | 8 | |
| 9 | 9 | Passing today: functions, `impl` methods, trait impls (formatting traits and |
| 10 | 10 | `From`), structs, enums (C-like and data-carrying), `Option`/`Result` with |
| 11 | -`?`, slice iterators (`iter`/`iter_mut`/`enumerate`/`zip`/`chunks_exact`/ | |
| 11 | +`?`, closures, `unsafe`, slice iterators (`iter`/`iter_mut`/`enumerate`/`zip`/`chunks_exact`/ | |
| 12 | 12 | `chunks_exact_mut`/`windows`), borrowed slices as values and return types, |
| 13 | 13 | `let`/`let mut`, the full integer |
| 14 | 14 | and float operator set at exact widths, `as` casts, `if`/`while`/`loop`/`for`, |
| @@ -155,6 +155,39 @@ the view and its validity condition travel together through `ok_or` until a | ||
| 155 | 155 | `?` or `unwrap` resolves them into a bounds check plus a binding. Keeping such |
| 156 | 156 | an `Option` in a variable is rejected with a message saying so. |
| 157 | 157 | |
| 158 | +### Closures and `unsafe` | |
| 159 | + | |
| 160 | +`unsafe` is a permission marker, not a semantic change: it does not alter what | |
| 161 | +the enclosed operations mean. So the block is transparent, and every operation | |
| 162 | +inside still goes through the ordinary lowering and is still rejected if it has | |
| 163 | +no faithful mapping. `unsafe fn` lowers like any other proc. | |
| 164 | + | |
| 165 | +A closure becomes a Nim anonymous proc. Nim's closures capture by reference, as | |
| 166 | +Rust's non-`move` closures do; a `move` closure captures by value, which is a | |
| 167 | +different thing, so it is rejected rather than lowered to the same construct. | |
| 168 | +`impl Fn(A) -> B` is left at Nim's default calling convention, which accepts | |
| 169 | +both a plain top-level proc and a capturing closure — as Rust's `impl Fn` does. | |
| 170 | + | |
| 171 | +`.map`/`.and_then` over an `Option`/`Result` are expanded inline with the | |
| 172 | +closure's parameter aliased to the payload, rather than handed to a generic | |
| 173 | +proc. That keeps the whole thing an expression and keeps a view a view. | |
| 174 | + | |
| 175 | +`&str` is a borrowed view of someone else's bytes, so it maps to | |
| 176 | +`openArray[char]`, not to an owned `string`. Nim accepts a `string` argument | |
| 177 | +for an `openArray[char]` parameter, so a literal still passes straight through. | |
| 178 | +`from_utf8_unchecked` reinterprets a byte view as a character view over the | |
| 179 | +same memory — no copy, no validation, and writes through the original are | |
| 180 | +visible, as in Rust. | |
| 181 | + | |
| 182 | +### Modules | |
| 183 | + | |
| 184 | +Rust keeps `lower::decode` and `mixed::decode` apart by module; flattening into | |
| 185 | +one Nim module would merge them — they are *different functions*. So the first | |
| 186 | +input is the crate root and each later one is a module named by its file stem, | |
| 187 | +items are emitted as `<module>_<name>`, and a call resolves through an explicit | |
| 188 | +qualifier, then the current module, then what `use` brought into scope, then | |
| 189 | +the root. | |
| 190 | + | |
| 158 | 191 | ### Declaration order |
| 159 | 192 | |
| 160 | 193 | Rust has no declaration-before-use rule and Nim does, so every proc is |
| @@ -213,16 +246,17 @@ runner) rather than a wrong answer. | ||
| 213 | 246 | |
| 214 | 247 | 5. `checked_*` and `saturating_*` are not mapped yet; they are currently |
| 215 | 248 | rejected as unsupported methods rather than approximated. |
| 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. | |
| 249 | +6. Generics (type and const parameters), `move` closures, closure bodies with | |
| 250 | + statements, and trait impls other than the formatting traits and `From` are | |
| 251 | + rejected with a reason. | |
| 218 | 252 | Lifetime parameters are *not* a rejection: they carry no runtime meaning |
| 219 | 253 | and Nim is GC'd, so `fn encode<'a>(..)` lowers fine. |
| 220 | 254 | 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but |
| 221 | 255 | the exponent-form thresholds have only been checked at `1e21`. |
| 222 | -8. Flattening several files into one module can collide a crate's own | |
| 223 | - `type Result<T>` with the builtin `Result<T, E>`. Rust kept them apart by | |
| 224 | - module; we tell them apart by arity. That is a real difference from Rust's | |
| 225 | - resolution and would need proper module scoping to fix. | |
| 256 | +8. Functions are scoped by module now, but *types* are still global: two | |
| 257 | + modules declaring the same type name would collide. Relatedly, a crate's | |
| 258 | + own `type Result<T>` is told apart from the builtin `Result<T, E>` by | |
| 259 | + arity, which is not how Rust resolves it. | |
| 226 | 260 | |
| 227 | 261 | ## Testing: differential, not golden |
| 228 | 262 | |
| @@ -278,21 +312,22 @@ or any parent, or via `RUSTNIM_NIM`. | ||
| 278 | 312 | Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and |
| 279 | 313 | have its decoder produce byte-identical output to the Rust original. |
| 280 | 314 | |
| 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 | |
| 315 | +**Reached, for four of the crate's six modules.** | |
| 316 | +`tests/cases/026-base16ct-crate/` transpiles base16ct's `error.rs`, | |
| 317 | +`lower.rs`, `upper.rs` and `mixed.rs` **byte-for-byte as published on | |
| 283 | 318 | 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 | |
| 319 | +`decoded_len`, `encoded_len` and `decode_inner` verbatim. Output is | |
| 285 | 320 | byte-identical to rustc's: |
| 286 | 321 | |
| 287 | 322 | ``` |
| 288 | -mixed-l ok abcd1234 len=4 lower, upper and mixed hex all decode | |
| 289 | -mixed-u ok abcd1234 len=4 | |
| 323 | +lower ok abcd1234 len=4 decode: lower, upper, mixed | |
| 290 | 324 | 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 | |
| 325 | +upper ok abcd1234 len=4 | |
| 326 | +upper-rej err InvalidEncoding ... upper correctly rejects lowercase | |
| 327 | +oddlen err InvalidLength / invalid Base16 length <- Debug and Display | |
| 328 | +encode ok 6162636431323334 len=8 encode, both cases | |
| 329 | +encode-up ok 4142434431323334 len=8 | |
| 330 | +encode_str ok abcd1234 len=8 closure over unsafe, borrowed &str | |
| 296 | 331 | ``` |
| 297 | 332 | |
| 298 | 333 | `decode_inner` goes through as written: `dst.get_mut(..decoded_len(src)?)`, |
| @@ -300,14 +335,20 @@ short-dst err InvalidLength / invalid Base16 length | ||
| 300 | 335 | returned `&'a [u8]` view into the caller's buffer. The `Display` line in that |
| 301 | 336 | output comes from the crate's own `impl fmt::Display for Error`. |
| 302 | 337 | |
| 338 | +`encode_str` goes through as written — `encode(src, dst).map(|r| unsafe { | |
| 339 | +core::str::from_utf8_unchecked(r) })` — returning a `&str` view of the bytes | |
| 340 | +just written, not a copy. | |
| 341 | + | |
| 303 | 342 | ### Still to do for the whole crate |
| 304 | 343 | |
| 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. | |
| 344 | +Two modules remain, and both are close: | |
| 345 | + | |
| 346 | +- `display.rs` needs the formatter to *accumulate* writes. Our model maps a | |
| 347 | + formatting impl to a proc returning the string it wrote, which handles one | |
| 348 | + write or a `match` over several; `UpperHex` here calls `f.write_str(..)?` in | |
| 349 | + a loop, so the writes have to append instead of being the value. It also | |
| 350 | + needs `HexDisplay<'a>(pub &'a [u8])` — a struct *field* of view type, the | |
| 351 | + same wall as `Option<&[T]>`. | |
| 352 | +- The `alloc` half (`--cfg feature=alloc`) now gets as far as | |
| 353 | + `debug_assert_eq!`, then needs `String::from_utf8_unchecked` to produce an | |
| 354 | + owned `String` from a `Vec<u8>`. | |
| @@ -2,13 +2,13 @@ | |||
| 2 | 2 | ||
| 3 | ## Status | 3 | ## Status |
| 4 | 4 | ||
| 5 | -**Milestone 1's decoder goal is reached.** 31 differential cases, 27 | 5 | +**Milestone 1 is reached, and then some.** Four of base16ct's six modules go |
| 6 | -behavioural and 4 rejections, plus 5 unit/integration tests. All green. Run | 6 | +through byte-for-byte. 32 differential cases, 28 behavioural and 4 rejections, |
| 7 | -`cargo test`. | 7 | +plus 6 unit/integration tests. All green. Run `cargo test`. |
| 8 | 8 | ||
| 9 | Passing today: functions, `impl` methods, trait impls (formatting traits and | 9 | Passing today: functions, `impl` methods, trait impls (formatting traits and |
| 10 | `From`), structs, enums (C-like and data-carrying), `Option`/`Result` with | 10 | `From`), structs, enums (C-like and data-carrying), `Option`/`Result` with |
| 11 | -`?`, slice iterators (`iter`/`iter_mut`/`enumerate`/`zip`/`chunks_exact`/ | 11 | +`?`, closures, `unsafe`, slice iterators (`iter`/`iter_mut`/`enumerate`/`zip`/`chunks_exact`/ |
| 12 | `chunks_exact_mut`/`windows`), borrowed slices as values and return types, | 12 | `chunks_exact_mut`/`windows`), borrowed slices as values and return types, |
| 13 | `let`/`let mut`, the full integer | 13 | `let`/`let mut`, the full integer |
| 14 | 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`, |
| @@ -155,6 +155,39 @@ 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 | 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. | 156 | an `Option` in a variable is rejected with a message saying so. |
| 157 | 157 | ||
| 158 | +### Closures and `unsafe` | ||
| 159 | + | ||
| 160 | +`unsafe` is a permission marker, not a semantic change: it does not alter what | ||
| 161 | +the enclosed operations mean. So the block is transparent, and every operation | ||
| 162 | +inside still goes through the ordinary lowering and is still rejected if it has | ||
| 163 | +no faithful mapping. `unsafe fn` lowers like any other proc. | ||
| 164 | + | ||
| 165 | +A closure becomes a Nim anonymous proc. Nim's closures capture by reference, as | ||
| 166 | +Rust's non-`move` closures do; a `move` closure captures by value, which is a | ||
| 167 | +different thing, so it is rejected rather than lowered to the same construct. | ||
| 168 | +`impl Fn(A) -> B` is left at Nim's default calling convention, which accepts | ||
| 169 | +both a plain top-level proc and a capturing closure — as Rust's `impl Fn` does. | ||
| 170 | + | ||
| 171 | +`.map`/`.and_then` over an `Option`/`Result` are expanded inline with the | ||
| 172 | +closure's parameter aliased to the payload, rather than handed to a generic | ||
| 173 | +proc. That keeps the whole thing an expression and keeps a view a view. | ||
| 174 | + | ||
| 175 | +`&str` is a borrowed view of someone else's bytes, so it maps to | ||
| 176 | +`openArray[char]`, not to an owned `string`. Nim accepts a `string` argument | ||
| 177 | +for an `openArray[char]` parameter, so a literal still passes straight through. | ||
| 178 | +`from_utf8_unchecked` reinterprets a byte view as a character view over the | ||
| 179 | +same memory — no copy, no validation, and writes through the original are | ||
| 180 | +visible, as in Rust. | ||
| 181 | + | ||
| 182 | +### Modules | ||
| 183 | + | ||
| 184 | +Rust keeps `lower::decode` and `mixed::decode` apart by module; flattening into | ||
| 185 | +one Nim module would merge them — they are *different functions*. So the first | ||
| 186 | +input is the crate root and each later one is a module named by its file stem, | ||
| 187 | +items are emitted as `<module>_<name>`, and a call resolves through an explicit | ||
| 188 | +qualifier, then the current module, then what `use` brought into scope, then | ||
| 189 | +the root. | ||
| 190 | + | ||
| 158 | ### Declaration order | 191 | ### Declaration order |
| 159 | 192 | ||
| 160 | Rust has no declaration-before-use rule and Nim does, so every proc is | 193 | Rust has no declaration-before-use rule and Nim does, so every proc is |
| @@ -213,16 +246,17 @@ runner) rather than a wrong answer. | |||
| 213 | 246 | ||
| 214 | 5. `checked_*` and `saturating_*` are not mapped yet; they are currently | 247 | 5. `checked_*` and `saturating_*` are not mapped yet; they are currently |
| 215 | rejected as unsupported methods rather than approximated. | 248 | rejected as unsupported methods rather than approximated. |
| 216 | -6. Generics (type and const parameters), closures, `unsafe`, and trait impls | 249 | +6. Generics (type and const parameters), `move` closures, closure bodies with |
| 217 | - other than the formatting traits and `From` are rejected with a reason. | 250 | + statements, and trait impls other than the formatting traits and `From` are |
| 251 | + rejected with a reason. | ||
| 218 | Lifetime parameters are *not* a rejection: they carry no runtime meaning | 252 | Lifetime parameters are *not* a rejection: they carry no runtime meaning |
| 219 | and Nim is GC'd, so `fn encode<'a>(..)` lowers fine. | 253 | and Nim is GC'd, so `fn encode<'a>(..)` lowers fine. |
| 220 | 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but | 254 | 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but |
| 221 | the exponent-form thresholds have only been checked at `1e21`. | 255 | the exponent-form thresholds have only been checked at `1e21`. |
| 222 | -8. Flattening several files into one module can collide a crate's own | 256 | +8. Functions are scoped by module now, but *types* are still global: two |
| 223 | - `type Result<T>` with the builtin `Result<T, E>`. Rust kept them apart by | 257 | + modules declaring the same type name would collide. Relatedly, a crate's |
| 224 | - module; we tell them apart by arity. That is a real difference from Rust's | 258 | + own `type Result<T>` is told apart from the builtin `Result<T, E>` by |
| 225 | - resolution and would need proper module scoping to fix. | 259 | + arity, which is not how Rust resolves it. |
| 226 | 260 | ||
| 227 | ## Testing: differential, not golden | 261 | ## Testing: differential, not golden |
| 228 | 262 | ||
| @@ -278,21 +312,22 @@ or any parent, or via `RUSTNIM_NIM`. | |||
| 278 | Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and | 312 | Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and |
| 279 | have its decoder produce byte-identical output to the Rust original. | 313 | have its decoder produce byte-identical output to the Rust original. |
| 280 | 314 | ||
| 281 | -**The decoder is reached.** `tests/cases/026-base16ct-crate/` transpiles | 315 | +**Reached, for four of the crate's six modules.** |
| 282 | -base16ct's `error.rs` and `mixed.rs` **byte-for-byte as published on | 316 | +`tests/cases/026-base16ct-crate/` transpiles base16ct's `error.rs`, |
| 317 | +`lower.rs`, `upper.rs` and `mixed.rs` **byte-for-byte as published on | ||
| 283 | crates.io** — verified with `cmp`, not by eye — together with `lib.rs`'s | 318 | 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 | 319 | +`decoded_len`, `encoded_len` and `decode_inner` verbatim. Output is |
| 285 | byte-identical to rustc's: | 320 | byte-identical to rustc's: |
| 286 | 321 | ||
| 287 | ``` | 322 | ``` |
| 288 | -mixed-l ok abcd1234 len=4 lower, upper and mixed hex all decode | 323 | +lower ok abcd1234 len=4 decode: lower, upper, mixed |
| 289 | -mixed-u ok abcd1234 len=4 | ||
| 290 | mixed-m ok abcd1234 len=4 | 324 | mixed-m ok abcd1234 len=4 |
| 291 | -edge ok 00ff7f80 len=4 | 325 | +upper ok abcd1234 len=4 |
| 292 | -oddlen err InvalidLength / invalid Base16 length <- Debug and Display | 326 | +upper-rej err InvalidEncoding ... upper correctly rejects lowercase |
| 293 | -bad err InvalidEncoding / invalid Base16 encoding | 327 | +oddlen err InvalidLength / invalid Base16 length <- Debug and Display |
| 294 | -empty ok len=0 | 328 | +encode ok 6162636431323334 len=8 encode, both cases |
| 295 | -short-dst err InvalidLength / invalid Base16 length | 329 | +encode-up ok 4142434431323334 len=8 |
| 330 | +encode_str ok abcd1234 len=8 closure over unsafe, borrowed &str | ||
| 296 | ``` | 331 | ``` |
| 297 | 332 | ||
| 298 | `decode_inner` goes through as written: `dst.get_mut(..decoded_len(src)?)`, | 333 | `decode_inner` goes through as written: `dst.get_mut(..decoded_len(src)?)`, |
| @@ -300,14 +335,20 @@ short-dst err InvalidLength / invalid Base16 length | |||
| 300 | returned `&'a [u8]` view into the caller's buffer. The `Display` line in that | 335 | 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`. | 336 | output comes from the crate's own `impl fmt::Display for Error`. |
| 302 | 337 | ||
| 338 | +`encode_str` goes through as written — `encode(src, dst).map(|r| unsafe { | ||
| 339 | +core::str::from_utf8_unchecked(r) })` — returning a `&str` view of the bytes | ||
| 340 | +just written, not a copy. | ||
| 341 | + | ||
| 303 | ### Still to do for the whole crate | 342 | ### Still to do for the whole crate |
| 304 | 343 | ||
| 305 | -- `lower.rs` / `upper.rs`: `encode` itself lowers, but the same file defines | 344 | +Two modules remain, and both are close: |
| 306 | - `encode_str`, whose body is a **closure** over an **`unsafe`** block. Both | 345 | + |
| 307 | - are unimplemented, and a file is all-or-nothing, so neither module is in the | 346 | +- `display.rs` needs the formatter to *accumulate* writes. Our model maps a |
| 308 | - case yet. | 347 | + formatting impl to a proc returning the string it wrote, which handles one |
| 309 | -- `display.rs`: `HexDisplay<'a>(pub &'a [u8])` is a tuple struct holding a | 348 | + write or a `match` over several; `UpperHex` here calls `f.write_str(..)?` in |
| 310 | - borrowed slice. A struct *field* of view type is what Nim's view types do | 349 | + a loop, so the writes have to append instead of being the value. It also |
| 311 | - not allow, which is the same wall as `Option<&[T]>`. | 350 | + needs `HexDisplay<'a>(pub &'a [u8])` — a struct *field* of view type, the |
| 312 | -- The `alloc` half (`decode_vec`, `encode_string`) needs `--cfg feature=alloc` | 351 | + same wall as `Option<&[T]>`. |
| 313 | - and then `String::from_utf8_unchecked`, i.e. `unsafe` again. | 352 | +- The `alloc` half (`--cfg feature=alloc`) now gets as far as |
| 353 | + `debug_assert_eq!`, then needs `String::from_utf8_unchecked` to produce an | ||
| 354 | + owned `String` from a `Vec<u8>`. | ||
modified
README.md +17 -11 | @@ -44,22 +44,28 @@ into a single index loop where each binding is an lvalue into the original | ||
| 44 | 44 | container, so `*d = v` through `iter_mut()` reaches the caller's slice. |
| 45 | 45 | Borrowed slices are views, not copies, including as return types. |
| 46 | 46 | |
| 47 | +Closures and `unsafe` blocks, and `&str` as a borrowed view rather than an | |
| 48 | +owned copy. Modules: pass the crate root first and each further file after it, | |
| 49 | +and items are scoped by module, so `lower::decode` and `mixed::decode` stay | |
| 50 | +distinct. | |
| 51 | + | |
| 47 | 52 | Rejected with a reason, rather than guessed at: `i128`/`u128`, generics, |
| 48 | -closures, `unsafe`, trait impls other than the ones above, iterator adaptors | |
| 49 | -with no index-loop equivalent (`map`, `filter`, `take_while`), float→int casts, | |
| 50 | -and any standard-library method that isn't mapped. | |
| 53 | +`move` closures, trait impls other than the ones above, iterator adaptors with | |
| 54 | +no index-loop equivalent (`map`, `filter`, `take_while`), float→int casts, and | |
| 55 | +any standard-library method that isn't mapped. | |
| 51 | 56 | |
| 52 | 57 | ## base16ct |
| 53 | 58 | |
| 54 | -`tests/cases/026-base16ct-crate/` transpiles base16ct 1.0.0's `error.rs` and | |
| 55 | -`mixed.rs` byte-for-byte as published on crates.io, plus `lib.rs`'s | |
| 56 | -`decoded_len`, `encoded_len` and `decode_inner` verbatim. Its decoder output is | |
| 57 | -byte-identical to rustc's — lower, upper and mixed hex, both error variants, | |
| 58 | -and the `Display` strings. That is the crate the transpiler in | |
| 59 | -[`findings/`](findings/) emitted an empty file for. | |
| 59 | +`tests/cases/026-base16ct-crate/` transpiles four of base16ct 1.0.0's six | |
| 60 | +modules — `error.rs`, `lower.rs`, `upper.rs`, `mixed.rs` — byte-for-byte as | |
| 61 | +published on crates.io, plus `lib.rs`'s `decoded_len`, `encoded_len` and | |
| 62 | +`decode_inner` verbatim. Decoding and encoding are byte-identical to rustc's, | |
| 63 | +including `encode_str`, which is a closure over an `unsafe` block returning a | |
| 64 | +borrowed `&str` view of the bytes just written. That is the crate the | |
| 65 | +transpiler in [`findings/`](findings/) emitted an empty file for. | |
| 60 | 66 | |
| 61 | -The rest of the crate needs closures, `unsafe`, and struct fields of view type; | |
| 62 | -[`DESIGN.md`](DESIGN.md) says which file needs which. | |
| 67 | +`display.rs` and the `alloc` half are not through yet; | |
| 68 | +[`DESIGN.md`](DESIGN.md) says exactly what each still needs. | |
| 63 | 69 | |
| 64 | 70 | ## Tests |
| 65 | 71 | |
| @@ -44,22 +44,28 @@ into a single index loop where each binding is an lvalue into the original | |||
| 44 | container, so `*d = v` through `iter_mut()` reaches the caller's slice. | 44 | container, so `*d = v` through `iter_mut()` reaches the caller's slice. |
| 45 | Borrowed slices are views, not copies, including as return types. | 45 | Borrowed slices are views, not copies, including as return types. |
| 46 | 46 | ||
| 47 | +Closures and `unsafe` blocks, and `&str` as a borrowed view rather than an | ||
| 48 | +owned copy. Modules: pass the crate root first and each further file after it, | ||
| 49 | +and items are scoped by module, so `lower::decode` and `mixed::decode` stay | ||
| 50 | +distinct. | ||
| 51 | + | ||
| 47 | Rejected with a reason, rather than guessed at: `i128`/`u128`, generics, | 52 | Rejected with a reason, rather than guessed at: `i128`/`u128`, generics, |
| 48 | -closures, `unsafe`, trait impls other than the ones above, iterator adaptors | 53 | +`move` closures, trait impls other than the ones above, iterator adaptors with |
| 49 | -with no index-loop equivalent (`map`, `filter`, `take_while`), float→int casts, | 54 | +no index-loop equivalent (`map`, `filter`, `take_while`), float→int casts, and |
| 50 | -and any standard-library method that isn't mapped. | 55 | +any standard-library method that isn't mapped. |
| 51 | 56 | ||
| 52 | ## base16ct | 57 | ## base16ct |
| 53 | 58 | ||
| 54 | -`tests/cases/026-base16ct-crate/` transpiles base16ct 1.0.0's `error.rs` and | 59 | +`tests/cases/026-base16ct-crate/` transpiles four of base16ct 1.0.0's six |
| 55 | -`mixed.rs` byte-for-byte as published on crates.io, plus `lib.rs`'s | 60 | +modules — `error.rs`, `lower.rs`, `upper.rs`, `mixed.rs` — byte-for-byte as |
| 56 | -`decoded_len`, `encoded_len` and `decode_inner` verbatim. Its decoder output is | 61 | +published on crates.io, plus `lib.rs`'s `decoded_len`, `encoded_len` and |
| 57 | -byte-identical to rustc's — lower, upper and mixed hex, both error variants, | 62 | +`decode_inner` verbatim. Decoding and encoding are byte-identical to rustc's, |
| 58 | -and the `Display` strings. That is the crate the transpiler in | 63 | +including `encode_str`, which is a closure over an `unsafe` block returning a |
| 59 | -[`findings/`](findings/) emitted an empty file for. | 64 | +borrowed `&str` view of the bytes just written. That is the crate the |
| 65 | +transpiler in [`findings/`](findings/) emitted an empty file for. | ||
| 60 | 66 | ||
| 61 | -The rest of the crate needs closures, `unsafe`, and struct fields of view type; | 67 | +`display.rs` and the `alloc` half are not through yet; |
| 62 | -[`DESIGN.md`](DESIGN.md) says which file needs which. | 68 | +[`DESIGN.md`](DESIGN.md) says exactly what each still needs. |
| 63 | 69 | ||
| 64 | ## Tests | 70 | ## Tests |
| 65 | 71 | ||
modified
src/lower.rs +478 -75 | @@ -76,9 +76,9 @@ enum Iter { | ||
| 76 | 76 | Elems { code: String, off: String, len: String, elem: Option<Nim>, mutable: bool }, |
| 77 | 77 | /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of |
| 78 | 78 | /// `k` elements starting at `k * i`. |
| 79 | - Chunks { code: String, k: String, elem: Option<Nim>, mutable: bool }, | |
| 79 | + Chunks { code: String, base: String, len: String, k: String, elem: Option<Nim>, mutable: bool }, | |
| 80 | 80 | /// `a.windows(k)`: like `Chunks` but advancing one element at a time. |
| 81 | - Windows { code: String, k: String, elem: Option<Nim> }, | |
| 81 | + Windows { code: String, base: String, len: String, k: String, elem: Option<Nim> }, | |
| 82 | 82 | /// `.enumerate()` — the index is the first half of the pair. |
| 83 | 83 | Enumerate(Box<Iter>), |
| 84 | 84 | /// `.zip(other)` — stops at the shorter, as Rust's does. |
| @@ -95,10 +95,8 @@ impl Iter { | ||
| 95 | 95 | if *closed { format!("({n} + 1)") } else { n } |
| 96 | 96 | } |
| 97 | 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 | - } | |
| 98 | + Iter::Chunks { k, len, .. } => format!("({} div int({}))", len, k), | |
| 99 | + Iter::Windows { len, k, .. } => format!("(max(0, {} - int({}) + 1))", len, k), | |
| 102 | 100 | Iter::Enumerate(i) => i.len(), |
| 103 | 101 | Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()), |
| 104 | 102 | } |
| @@ -188,7 +186,16 @@ pub struct Lowerer { | ||
| 188 | 186 | /// Names introduced by a `for` pattern that stand for an lvalue or a |
| 189 | 187 | /// window into a container, rather than for a variable of their own. |
| 190 | 188 | alias_scopes: Vec<HashMap<String, Alias>>, |
| 191 | - fns: HashMap<String, Sig>, | |
| 189 | + /// `(module, name) -> signature`. Rust keeps `lower::decode` and | |
| 190 | + /// `mixed::decode` apart by module; flattening into one Nim module would | |
| 191 | + /// merge them, so the module is part of the key and of the emitted name. | |
| 192 | + fns: HashMap<(String, String), Sig>, | |
| 193 | + /// Module being lowered: the file stem, or empty for the crate root. | |
| 194 | + cur_mod: String, | |
| 195 | + /// `use` brings a name into scope from another module. Flattening loses | |
| 196 | + /// the module structure, so the mapping is recorded and consulted when a | |
| 197 | + /// bare call is resolved. | |
| 198 | + use_map: HashMap<String, String>, | |
| 192 | 199 | /// struct name -> (field, type) |
| 193 | 200 | structs: HashMap<String, Vec<(String, Nim)>>, |
| 194 | 201 | enums: HashMap<String, EnumDef>, |
| @@ -209,6 +216,8 @@ pub struct Lowerer { | ||
| 209 | 216 | /// proc is declared up front rather than the input being reordered -- |
| 210 | 217 | /// which would not work for mutual recursion anyway. |
| 211 | 218 | forwards: Vec<String>, |
| 219 | + /// Element type a `vec![..]` should build, from the binding's annotation. | |
| 220 | + vec_expect: Option<Nim>, | |
| 212 | 221 | /// While lowering a formatting impl: the `Formatter` parameter's name. |
| 213 | 222 | /// Writes through it produce the proc's string result. |
| 214 | 223 | fmt_param: Option<String>, |
| @@ -242,6 +251,8 @@ impl Lowerer { | ||
| 242 | 251 | scopes: vec![HashMap::new()], |
| 243 | 252 | alias_scopes: vec![HashMap::new()], |
| 244 | 253 | fns: HashMap::new(), |
| 254 | + cur_mod: String::new(), | |
| 255 | + use_map: HashMap::new(), | |
| 245 | 256 | structs: HashMap::new(), |
| 246 | 257 | enums: HashMap::new(), |
| 247 | 258 | variant_owner: HashMap::new(), |
| @@ -249,6 +260,7 @@ impl Lowerer { | ||
| 249 | 260 | fmt_impls: HashMap::new(), |
| 250 | 261 | from_impls: HashMap::new(), |
| 251 | 262 | fmt_param: None, |
| 263 | + vec_expect: None, | |
| 252 | 264 | forwards: Vec::new(), |
| 253 | 265 | aliases: HashMap::new(), |
| 254 | 266 | modules: Vec::new(), |
| @@ -311,25 +323,34 @@ impl Lowerer { | ||
| 311 | 323 | |
| 312 | 324 | // ---------------------------------------------------------------- file |
| 313 | 325 | |
| 314 | - pub fn lower_file(&mut self, file: &syn::File) -> Result<String, String> { | |
| 326 | + pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> { | |
| 315 | 327 | self.out.push_str(include_str!("prelude.nim")); |
| 316 | 328 | self.blank(); |
| 317 | 329 | |
| 318 | 330 | // Pass 0: type aliases. A signature in one file may use an alias |
| 319 | 331 | // declared in another, and inputs are given in whatever order suits |
| 320 | 332 | // the caller, so aliases are registered before anything is mapped. |
| 321 | - for item in &file.items { | |
| 322 | - self.collect_aliases(item)?; | |
| 333 | + for (m, f) in files { | |
| 334 | + self.cur_mod = m.clone(); | |
| 335 | + for item in &f.items { | |
| 336 | + self.collect_aliases(item)?; | |
| 337 | + } | |
| 323 | 338 | } |
| 324 | 339 | |
| 325 | 340 | // Pass 1: signatures and struct shapes, so that a call can be typed |
| 326 | 341 | // regardless of declaration order (Rust has no forward declarations). |
| 327 | - for item in &file.items { | |
| 328 | - self.collect(item)?; | |
| 342 | + for (m, f) in files { | |
| 343 | + self.cur_mod = m.clone(); | |
| 344 | + for item in &f.items { | |
| 345 | + self.collect(item)?; | |
| 346 | + } | |
| 329 | 347 | } |
| 330 | 348 | // Pass 2: type definitions, which every signature may mention. |
| 331 | - for item in &file.items { | |
| 332 | - self.item_types(item)?; | |
| 349 | + for (m, f) in files { | |
| 350 | + self.cur_mod = m.clone(); | |
| 351 | + for item in &f.items { | |
| 352 | + self.item_types(item)?; | |
| 353 | + } | |
| 333 | 354 | } |
| 334 | 355 | |
| 335 | 356 | // Pass 3: forward declarations. Rust imposes no declaration order and |
| @@ -343,11 +364,14 @@ impl Lowerer { | ||
| 343 | 364 | } |
| 344 | 365 | |
| 345 | 366 | // Pass 4: bodies. |
| 346 | - for item in &file.items { | |
| 347 | - self.item(item)?; | |
| 367 | + for (m, f) in files { | |
| 368 | + self.cur_mod = m.clone(); | |
| 369 | + for item in &f.items { | |
| 370 | + self.item(item)?; | |
| 371 | + } | |
| 348 | 372 | } |
| 349 | 373 | |
| 350 | - if self.fns.contains_key("main") { | |
| 374 | + if self.fns.contains_key(&(String::new(), "main".to_string())) { | |
| 351 | 375 | self.blank(); |
| 352 | 376 | self.line("when isMainModule:"); |
| 353 | 377 | self.indent += 1; |
| @@ -372,6 +396,7 @@ impl Lowerer { | ||
| 372 | 396 | return Ok(()); |
| 373 | 397 | } |
| 374 | 398 | match item { |
| 399 | + Item::Use(u) => self.collect_use(&u.tree, &[]), | |
| 375 | 400 | Item::Type(t) => { |
| 376 | 401 | let params: Vec<String> = t |
| 377 | 402 | .generics |
| @@ -396,6 +421,34 @@ impl Lowerer { | ||
| 396 | 421 | Ok(()) |
| 397 | 422 | } |
| 398 | 423 | |
| 424 | + /// Record what a `use` brings into scope, as `name -> module`. | |
| 425 | + fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) { | |
| 426 | + use syn::UseTree; | |
| 427 | + match t { | |
| 428 | + UseTree::Path(p) => { | |
| 429 | + let mut pre = prefix.to_vec(); | |
| 430 | + pre.push(p.ident.to_string()); | |
| 431 | + self.collect_use(&p.tree, &pre); | |
| 432 | + } | |
| 433 | + UseTree::Group(g) => { | |
| 434 | + for t in &g.items { | |
| 435 | + self.collect_use(t, prefix); | |
| 436 | + } | |
| 437 | + } | |
| 438 | + UseTree::Name(n) => { | |
| 439 | + let m = module_of(prefix); | |
| 440 | + self.use_map.insert(n.ident.to_string(), m); | |
| 441 | + } | |
| 442 | + UseTree::Rename(r) => { | |
| 443 | + let m = module_of(prefix); | |
| 444 | + self.use_map.insert(r.rename.to_string(), m); | |
| 445 | + } | |
| 446 | + // A glob brings in an unknown set of names; resolution falls back | |
| 447 | + // to the current module and the root, as it would without it. | |
| 448 | + UseTree::Glob(_) => {} | |
| 449 | + } | |
| 450 | + } | |
| 451 | + | |
| 399 | 452 | fn collect(&mut self, item: &Item) -> Result<(), String> { |
| 400 | 453 | // A `#[cfg(..)]` item exists only under some feature set. Dropping it |
| 401 | 454 | // silently would change what the program does; picking a feature set |
| @@ -408,9 +461,11 @@ impl Lowerer { | ||
| 408 | 461 | match item { |
| 409 | 462 | Item::Fn(f) => { |
| 410 | 463 | 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); | |
| 413 | - self.fns.insert(f.sig.ident.to_string(), Sig { params, ret }); | |
| 464 | + let name = f.sig.ident.to_string(); | |
| 465 | + let nim = self.fn_name(&self.cur_mod, &name); | |
| 466 | + self.forwards.push(self.head_of(&nim, &f.sig, None)?); | |
| 467 | + self.fns | |
| 468 | + .insert((self.cur_mod.clone(), name), Sig { params, ret }); | |
| 414 | 469 | } |
| 415 | 470 | Item::Struct(s) => { |
| 416 | 471 | let mut fields = Vec::new(); |
| @@ -637,6 +692,38 @@ impl Lowerer { | ||
| 637 | 692 | self.expand(&substitute(target, params, &args), depth + 1) |
| 638 | 693 | } |
| 639 | 694 | |
| 695 | + /// The Nim name for a function, qualified by its module. | |
| 696 | + fn fn_name(&self, module: &str, name: &str) -> String { | |
| 697 | + if module.is_empty() { | |
| 698 | + ident(name) | |
| 699 | + } else { | |
| 700 | + format!("{}_{}", module, ident(name)) | |
| 701 | + } | |
| 702 | + } | |
| 703 | + | |
| 704 | + /// Resolve a call path to the module and name it refers to: an explicit | |
| 705 | + /// `mixed::decode`, then the current module, then the crate root. | |
| 706 | + fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> { | |
| 707 | + let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect(); | |
| 708 | + let last = segs.last()?.clone(); | |
| 709 | + if segs.len() >= 2 { | |
| 710 | + let q = &segs[segs.len() - 2]; | |
| 711 | + if self.fns.contains_key(&(q.clone(), last.clone())) { | |
| 712 | + return Some((q.clone(), last)); | |
| 713 | + } | |
| 714 | + } | |
| 715 | + let imported = self.use_map.get(&last).cloned(); | |
| 716 | + for m in [Some(self.cur_mod.clone()), imported, Some(String::new())] | |
| 717 | + .into_iter() | |
| 718 | + .flatten() | |
| 719 | + { | |
| 720 | + if self.fns.contains_key(&(m.clone(), last.clone())) { | |
| 721 | + return Some((m, last)); | |
| 722 | + } | |
| 723 | + } | |
| 724 | + None | |
| 725 | + } | |
| 726 | + | |
| 640 | 727 | /// The Nim `proc` head for a Rust signature, used both for the forward |
| 641 | 728 | /// declaration and for the definition, so the two cannot drift apart. |
| 642 | 729 | fn head_of( |
| @@ -681,6 +768,8 @@ impl Lowerer { | ||
| 681 | 768 | } |
| 682 | 769 | |
| 683 | 770 | fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> { |
| 771 | + // `unsafe fn` marks a contract for callers; it does not change what | |
| 772 | + // the body means, so it lowers like any other proc. | |
| 684 | 773 | if sig.asyncness.is_some() { |
| 685 | 774 | return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident)); |
| 686 | 775 | } |
| @@ -749,7 +838,10 @@ impl Lowerer { | ||
| 749 | 838 | |
| 750 | 839 | fn item_inner(&mut self, item: &Item) -> Result<(), String> { |
| 751 | 840 | match item { |
| 752 | - Item::Fn(f) => self.func(&f.sig, &f.block, None), | |
| 841 | + Item::Fn(f) => { | |
| 842 | + let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string()); | |
| 843 | + self.func_named(&nim, &f.sig, &f.block, None) | |
| 844 | + } | |
| 753 | 845 | Item::Struct(s) => { |
| 754 | 846 | let name = s.ident.to_string(); |
| 755 | 847 | let fields = self.structs[&name].clone(); |
| @@ -1384,6 +1476,11 @@ impl Lowerer { | ||
| 1384 | 1476 | self.nested_block(&b.block)?; |
| 1385 | 1477 | Ok(None) |
| 1386 | 1478 | } |
| 1479 | + Expr::Unsafe(u) => { | |
| 1480 | + // Transparent in statement position too, for the same reason. | |
| 1481 | + self.nested_block_flat(&u.block)?; | |
| 1482 | + Ok(None) | |
| 1483 | + } | |
| 1387 | 1484 | Expr::Match(_) => { |
| 1388 | 1485 | self.match_stmt(e)?; |
| 1389 | 1486 | Ok(None) |
| @@ -1597,23 +1694,21 @@ impl Lowerer { | ||
| 1597 | 1694 | Ok(Iter::Zip(Box::new(a), Box::new(b))) |
| 1598 | 1695 | } |
| 1599 | 1696 | "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => { |
| 1600 | - let recv = self.expr(&m.receiver)?; | |
| 1697 | + let (code, base, len, elem) = self.slice_parts(&m.receiver)?; | |
| 1601 | 1698 | let k = self.expr(&m.args[0])?; |
| 1602 | 1699 | Ok(Iter::Chunks { |
| 1603 | - code: recv.code, | |
| 1700 | + code, | |
| 1701 | + base, | |
| 1702 | + len, | |
| 1604 | 1703 | k: k.code, |
| 1605 | - elem: elem_of(&recv.ty), | |
| 1704 | + elem, | |
| 1606 | 1705 | mutable: name.ends_with("_mut"), |
| 1607 | 1706 | }) |
| 1608 | 1707 | } |
| 1609 | 1708 | "windows" if m.args.len() == 1 => { |
| 1610 | - let recv = self.expr(&m.receiver)?; | |
| 1709 | + let (code, base, len, elem) = self.slice_parts(&m.receiver)?; | |
| 1611 | 1710 | 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 | - }) | |
| 1711 | + Ok(Iter::Windows { code, base, len, k: k.code, elem }) | |
| 1617 | 1712 | } |
| 1618 | 1713 | other => Err(format!( |
| 1619 | 1714 | "iterator adaptor `.{other}()` is not implemented; it has \ |
| @@ -1626,13 +1721,7 @@ impl Lowerer { | ||
| 1626 | 1721 | // A `for` binding that is itself a window iterates that window, |
| 1627 | 1722 | // not the whole container it points into. |
| 1628 | 1723 | 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 | - }); | |
| 1724 | + return Ok(Iter::Elems { code, off, len, elem, mutable: false }); | |
| 1636 | 1725 | } |
| 1637 | 1726 | let v = self.expr(other)?; |
| 1638 | 1727 | Ok(Iter::Elems { |
| @@ -1666,6 +1755,10 @@ impl Lowerer { | ||
| 1666 | 1755 | "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(), |
| 1667 | 1756 | ), |
| 1668 | 1757 | (Pat::Wild(_), _) => Ok(()), |
| 1758 | + // `for &byte in xs` — the `&` destructures the reference, which in | |
| 1759 | + // Nim is already the value. | |
| 1760 | + (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i), | |
| 1761 | + (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i), | |
| 1669 | 1762 | (Pat::Ident(id), _) => { |
| 1670 | 1763 | let name = id.ident.to_string(); |
| 1671 | 1764 | match it { |
| @@ -1711,24 +1804,24 @@ impl Lowerer { | ||
| 1711 | 1804 | } |
| 1712 | 1805 | Ok(()) |
| 1713 | 1806 | } |
| 1714 | - Iter::Chunks { code, k, elem, .. } => { | |
| 1807 | + Iter::Chunks { code, base, k, elem, .. } => { | |
| 1715 | 1808 | self.bind_alias( |
| 1716 | 1809 | &name, |
| 1717 | 1810 | Alias::Window { |
| 1718 | 1811 | code: code.clone(), |
| 1719 | - off: format!("({} * int({}))", i, k), | |
| 1812 | + off: format!("({} + {} * int({}))", base, i, k), | |
| 1720 | 1813 | len: format!("int({})", k), |
| 1721 | 1814 | elem: elem.clone(), |
| 1722 | 1815 | }, |
| 1723 | 1816 | ); |
| 1724 | 1817 | Ok(()) |
| 1725 | 1818 | } |
| 1726 | - Iter::Windows { code, k, elem } => { | |
| 1819 | + Iter::Windows { code, base, k, elem, .. } => { | |
| 1727 | 1820 | self.bind_alias( |
| 1728 | 1821 | &name, |
| 1729 | 1822 | Alias::Window { |
| 1730 | 1823 | code: code.clone(), |
| 1731 | - off: i.to_string(), | |
| 1824 | + off: format!("({} + {})", base, i), | |
| 1732 | 1825 | len: format!("int({})", k), |
| 1733 | 1826 | elem: elem.clone(), |
| 1734 | 1827 | }, |
| @@ -2140,9 +2233,10 @@ impl Lowerer { | ||
| 2140 | 2233 | } |
| 2141 | 2234 | // A top-level function used as a value, e.g. passed to a |
| 2142 | 2235 | // parameter of `impl Fn(..)` type. |
| 2143 | - if let Some(sig) = self.fns.get(&name) { | |
| 2236 | + if let Some(k) = self.resolve_fn(&p.path) { | |
| 2237 | + let sig = &self.fns[&k]; | |
| 2144 | 2238 | let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone())); |
| 2145 | - return Ok(Val::new(ident(&name), Some(t))); | |
| 2239 | + return Ok(Val::new(self.fn_name(&k.0, &k.1), Some(t))); | |
| 2146 | 2240 | } |
| 2147 | 2241 | Ok(Val::new(ident(&name), None)) |
| 2148 | 2242 | } |
| @@ -2220,9 +2314,38 @@ impl Lowerer { | ||
| 2220 | 2314 | }; |
| 2221 | 2315 | Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t)) |
| 2222 | 2316 | } |
| 2317 | + // `unsafe` is a permission marker, not a semantic change: it does | |
| 2318 | + // not alter what the enclosed operations mean. So the block is | |
| 2319 | + // transparent here, and each operation inside still goes through | |
| 2320 | + // the ordinary lowering -- and is still rejected if it has no | |
| 2321 | + // faithful mapping. | |
| 2322 | + Expr::Unsafe(u) => match single_expr(&u.block) { | |
| 2323 | + Some(e) => self.expr_at(e, expect), | |
| 2324 | + None => Err("an `unsafe` block used as a value must be a single \ | |
| 2325 | + expression" | |
| 2326 | + .into()), | |
| 2327 | + }, | |
| 2328 | + Expr::Closure(c) => self.closure(c, expect), | |
| 2223 | 2329 | Expr::Try(t) => self.try_op(t), |
| 2224 | 2330 | Expr::Call(c) => self.call(c, expect), |
| 2225 | 2331 | Expr::MethodCall(m) => self.method(m, expect), |
| 2332 | + Expr::Macro(m) if path_name(&m.mac.path) == "vec" => { | |
| 2333 | + // `vec![..]`'s elements take their type from the annotation on | |
| 2334 | + // the binding, exactly as Rust's would. | |
| 2335 | + let want = match expect { | |
| 2336 | + Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()), | |
| 2337 | + _ => None, | |
| 2338 | + }; | |
| 2339 | + let saved = std::mem::replace(&mut self.vec_expect, want.clone()); | |
| 2340 | + let code = self.macro_call(&m.mac); | |
| 2341 | + self.vec_expect = saved; | |
| 2342 | + let code = code?; | |
| 2343 | + let ty = match want { | |
| 2344 | + Some(e) => Some(Nim::Seq(Box::new(e))), | |
| 2345 | + None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))), | |
| 2346 | + }; | |
| 2347 | + Ok(Val::new(code, ty)) | |
| 2348 | + } | |
| 2226 | 2349 | Expr::Macro(m) => { |
| 2227 | 2350 | let code = self.macro_call(&m.mac)?; |
| 2228 | 2351 | Ok(Val::new(code, None)) |
| @@ -2567,6 +2690,190 @@ impl Lowerer { | ||
| 2567 | 2690 | /// The early return is statements, not an expression, so they are emitted |
| 2568 | 2691 | /// ahead of the line being built. Every caller lowers its sub-expressions |
| 2569 | 2692 | /// before emitting its own line, which is what makes that ordering hold. |
| 2693 | + /// The container, start offset, length and element type an expression | |
| 2694 | + /// denotes as a slice. A window alias contributes its own offset, so | |
| 2695 | + /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight | |
| 2696 | + /// into the original buffer rather than through a rebuilt view. | |
| 2697 | + fn slice_parts( | |
| 2698 | + &mut self, | |
| 2699 | + e: &Expr, | |
| 2700 | + ) -> Result<(String, String, String, Option<Nim>), String> { | |
| 2701 | + if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) { | |
| 2702 | + return Ok((code, off, len, elem)); | |
| 2703 | + } | |
| 2704 | + let v = self.expr(e)?; | |
| 2705 | + let len = format!("{}.len", v.code); | |
| 2706 | + Ok((v.code, "0".to_string(), len, elem_of(&v.ty))) | |
| 2707 | + } | |
| 2708 | + | |
| 2709 | + /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline. | |
| 2710 | + fn map_closure( | |
| 2711 | + &mut self, | |
| 2712 | + what: &str, | |
| 2713 | + recv: &Val, | |
| 2714 | + kind: &str, | |
| 2715 | + targs: &[Nim], | |
| 2716 | + c: &syn::ExprClosure, | |
| 2717 | + ) -> Result<Val, String> { | |
| 2718 | + if c.capture.is_some() { | |
| 2719 | + return Err("a `move` closure captures by value; Nim's closures \ | |
| 2720 | + capture by reference, and the two are not the same" | |
| 2721 | + .into()); | |
| 2722 | + } | |
| 2723 | + if c.inputs.len() != 1 { | |
| 2724 | + return Err(format!("`.{what}()` takes a one-argument closure")); | |
| 2725 | + } | |
| 2726 | + let pname = match &c.inputs[0] { | |
| 2727 | + Pat::Ident(i) => i.ident.to_string(), | |
| 2728 | + Pat::Wild(_) => "unused0".into(), | |
| 2729 | + _ => return Err("only plain identifier closure parameters are supported".into()), | |
| 2730 | + }; | |
| 2731 | + | |
| 2732 | + let is_opt = kind == "Option"; | |
| 2733 | + let tmp = self.fresh("Map"); | |
| 2734 | + let recv_ty = Nim::Named(kind.to_string(), targs.to_vec()); | |
| 2735 | + self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code)); | |
| 2736 | + | |
| 2737 | + let body = match &*c.body { | |
| 2738 | + Expr::Block(b) => single_expr(&b.block) | |
| 2739 | + .ok_or("a closure body with statements is not implemented yet")?, | |
| 2740 | + other => other, | |
| 2741 | + }; | |
| 2742 | + self.push_scope(); | |
| 2743 | + // The parameter names the payload itself, so a view stays a view. | |
| 2744 | + self.bind_alias( | |
| 2745 | + &pname, | |
| 2746 | + Alias::Value { | |
| 2747 | + code: format!("{}.val", tmp), | |
| 2748 | + ty: Some(targs[0].clone()), | |
| 2749 | + }, | |
| 2750 | + ); | |
| 2751 | + let v = self.expr(body)?; | |
| 2752 | + self.pop_scope(); | |
| 2753 | + | |
| 2754 | + let inner = v | |
| 2755 | + .ty | |
| 2756 | + .clone() | |
| 2757 | + .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?; | |
| 2758 | + // `and_then`'s closure already returns the wrapped type; `map`'s does | |
| 2759 | + // not and has to be re-wrapped. | |
| 2760 | + let (test, some_branch, none_branch, out_ty) = if is_opt { | |
| 2761 | + let out = if what == "map" { | |
| 2762 | + Nim::Named("Option".into(), vec![inner.clone()]) | |
| 2763 | + } else { | |
| 2764 | + inner.clone() | |
| 2765 | + }; | |
| 2766 | + let body_code = if what == "map" { | |
| 2767 | + format!("rsSome[{}]({})", inner.render(), v.code) | |
| 2768 | + } else { | |
| 2769 | + v.code.clone() | |
| 2770 | + }; | |
| 2771 | + ( | |
| 2772 | + format!("{}.has", tmp), | |
| 2773 | + body_code, | |
| 2774 | + format!("rsNone[{}]()", elem_arg(&out).render()), | |
| 2775 | + out, | |
| 2776 | + ) | |
| 2777 | + } else { | |
| 2778 | + let e = targs[1].clone(); | |
| 2779 | + let out = if what == "map" { | |
| 2780 | + Nim::Named("Result".into(), vec![inner.clone(), e.clone()]) | |
| 2781 | + } else { | |
| 2782 | + inner.clone() | |
| 2783 | + }; | |
| 2784 | + let ok_ty = elem_arg(&out); | |
| 2785 | + let body_code = if what == "map" { | |
| 2786 | + format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code) | |
| 2787 | + } else { | |
| 2788 | + v.code.clone() | |
| 2789 | + }; | |
| 2790 | + ( | |
| 2791 | + format!("{}.ok", tmp), | |
| 2792 | + body_code, | |
| 2793 | + format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp), | |
| 2794 | + out, | |
| 2795 | + ) | |
| 2796 | + }; | |
| 2797 | + Ok(Val::new( | |
| 2798 | + format!("(if {}: {} else: {})", test, some_branch, none_branch), | |
| 2799 | + Some(out_ty), | |
| 2800 | + )) | |
| 2801 | + } | |
| 2802 | + | |
| 2803 | + /// `|x| x + 1` -> a Nim anonymous proc. | |
| 2804 | + /// | |
| 2805 | + /// Nim's closures capture by reference, as Rust's non-`move` closures do. | |
| 2806 | + /// A `move` closure captures by value, which is a different thing, so it | |
| 2807 | + /// is rejected rather than lowered to the same construct. | |
| 2808 | + fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> { | |
| 2809 | + if c.capture.is_some() { | |
| 2810 | + return Err("a `move` closure captures by value; Nim's closures \ | |
| 2811 | + capture by reference, and the two are not the same" | |
| 2812 | + .into()); | |
| 2813 | + } | |
| 2814 | + let want: Option<&Vec<Nim>> = match expect { | |
| 2815 | + Some(Nim::Proc(a, _)) => Some(a), | |
| 2816 | + _ => None, | |
| 2817 | + }; | |
| 2818 | + | |
| 2819 | + self.push_scope(); | |
| 2820 | + let mut parts = Vec::new(); | |
| 2821 | + let mut ptys = Vec::new(); | |
| 2822 | + for (i, p) in c.inputs.iter().enumerate() { | |
| 2823 | + let (name, ann) = match p { | |
| 2824 | + Pat::Ident(id) => (id.ident.to_string(), None), | |
| 2825 | + Pat::Type(t) => match &*t.pat { | |
| 2826 | + Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)), | |
| 2827 | + _ => return Err("only plain identifier closure parameters are supported".into()), | |
| 2828 | + }, | |
| 2829 | + Pat::Wild(_) => (format!("unused{i}"), None), | |
| 2830 | + _ => return Err("only plain identifier closure parameters are supported".into()), | |
| 2831 | + }; | |
| 2832 | + let t = ann | |
| 2833 | + .or_else(|| want.and_then(|w| w.get(i).cloned())) | |
| 2834 | + .ok_or_else(|| { | |
| 2835 | + format!( | |
| 2836 | + "cannot infer the type of closure parameter `{name}`; \ | |
| 2837 | + annotate it" | |
| 2838 | + ) | |
| 2839 | + })?; | |
| 2840 | + parts.push(format!("{}: {}", ident(&name), t.render())); | |
| 2841 | + self.bind(&name, t.clone()); | |
| 2842 | + ptys.push(t); | |
| 2843 | + } | |
| 2844 | + | |
| 2845 | + let ret_ann = match &c.output { | |
| 2846 | + ReturnType::Default => None, | |
| 2847 | + ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()), | |
| 2848 | + }; | |
| 2849 | + let body = match &*c.body { | |
| 2850 | + Expr::Block(b) => single_expr(&b.block) | |
| 2851 | + .ok_or("a closure body with statements is not implemented yet")?, | |
| 2852 | + other => other, | |
| 2853 | + }; | |
| 2854 | + let v = self.expr_at(body, ret_ann.as_ref())?; | |
| 2855 | + self.pop_scope(); | |
| 2856 | + | |
| 2857 | + let ret = ret_ann | |
| 2858 | + .or_else(|| v.ty.clone()) | |
| 2859 | + .ok_or("cannot infer a closure's return type; annotate it")?; | |
| 2860 | + Ok(Val::new( | |
| 2861 | + format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code), | |
| 2862 | + Some(Nim::Proc(ptys, Box::new(ret))), | |
| 2863 | + )) | |
| 2864 | + } | |
| 2865 | + | |
| 2866 | + /// Lower a block's statements at the current indentation, without opening | |
| 2867 | + /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope | |
| 2868 | + /// of its own in the generated code. | |
| 2869 | + fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> { | |
| 2870 | + self.push_scope(); | |
| 2871 | + let tail = self.block_body(b)?; | |
| 2872 | + self.emit_tail(tail); | |
| 2873 | + self.pop_scope(); | |
| 2874 | + Ok(()) | |
| 2875 | + } | |
| 2876 | + | |
| 2570 | 2877 | fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> { |
| 2571 | 2878 | if self.in_loop_cond { |
| 2572 | 2879 | return Err("`?` in a loop condition is not implemented yet: the \ |
| @@ -2613,24 +2920,29 @@ impl Lowerer { | ||
| 2613 | 2920 | (Nim::Named(a, ai), Nim::Named(b, bi)) |
| 2614 | 2921 | if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 => |
| 2615 | 2922 | { |
| 2616 | - // Rust inserts a `From::from` on the error here. We only accept | |
| 2617 | - // the case where the error types already agree, rather than | |
| 2618 | - // silently dropping a conversion that might not be the identity. | |
| 2619 | - if ai[1] != bi[1] { | |
| 2620 | - return Err(format!( | |
| 2621 | - "`?` would need `From<{}> for {}`: an error-type conversion \ | |
| 2622 | - is not implemented, and assuming it is the identity would \ | |
| 2623 | - be a guess", | |
| 2624 | - ai[1].render(), | |
| 2625 | - bi[1].render() | |
| 2626 | - )); | |
| 2627 | - } | |
| 2923 | + // Rust inserts a `From::from` on the error here. Where the | |
| 2924 | + // types differ we call the crate's own `impl From`; we never | |
| 2925 | + // assume the conversion is the identity. | |
| 2926 | + let err = if ai[1] == bi[1] { | |
| 2927 | + format!("{}.err", tmp) | |
| 2928 | + } else { | |
| 2929 | + let key = (type_name(&ai[1]), type_name(&bi[1])); | |
| 2930 | + let f = self.from_impls.get(&key).cloned().ok_or_else(|| { | |
| 2931 | + format!( | |
| 2932 | + "`?` needs `From<{}> for {}` to convert the error, and \ | |
| 2933 | + no such `impl` is in scope; assuming the conversion is \ | |
| 2934 | + the identity would be a guess", | |
| 2935 | + key.0, key.1 | |
| 2936 | + ) | |
| 2937 | + })?; | |
| 2938 | + format!("{}({}.err)", f, tmp) | |
| 2939 | + }; | |
| 2628 | 2940 | self.line(&format!("if not {}.ok:", tmp)); |
| 2629 | 2941 | self.line(&format!( |
| 2630 | - " return rsErr[{}, {}]({}.err)", | |
| 2942 | + " return rsErr[{}, {}]({})", | |
| 2631 | 2943 | bi[0].render(), |
| 2632 | 2944 | bi[1].render(), |
| 2633 | - tmp | |
| 2945 | + err | |
| 2634 | 2946 | )); |
| 2635 | 2947 | Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone()))) |
| 2636 | 2948 | } |
| @@ -2655,9 +2967,10 @@ impl Lowerer { | ||
| 2655 | 2967 | return Err("only calls to named functions are supported".into()); |
| 2656 | 2968 | }; |
| 2657 | 2969 | let name = path_name(&p.path); |
| 2658 | - let ptys: Vec<Nim> = self | |
| 2659 | - .fns | |
| 2660 | - .get(&name) | |
| 2970 | + let target = self.resolve_fn(&p.path); | |
| 2971 | + let ptys: Vec<Nim> = target | |
| 2972 | + .as_ref() | |
| 2973 | + .and_then(|k| self.fns.get(k)) | |
| 2661 | 2974 | .map(|s| s.params.clone()) |
| 2662 | 2975 | .unwrap_or_default(); |
| 2663 | 2976 | let mut args = Vec::new(); |
| @@ -2707,6 +3020,15 @@ impl Lowerer { | ||
| 2707 | 3020 | _ => {} |
| 2708 | 3021 | } |
| 2709 | 3022 | |
| 3023 | + // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a | |
| 3024 | + // string view; no copy, no validation, same memory. | |
| 3025 | + if name == "from_utf8_unchecked" && codes.len() == 1 { | |
| 3026 | + return Ok(Val::new( | |
| 3027 | + format!("rsStrView({})", codes[0]), | |
| 3028 | + Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))), | |
| 3029 | + )); | |
| 3030 | + } | |
| 3031 | + | |
| 2710 | 3032 | // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. |
| 2711 | 3033 | if let Some((def, v)) = self.resolve_variant(&p.path) { |
| 2712 | 3034 | return Ok(Val::new( |
| @@ -2725,17 +3047,18 @@ impl Lowerer { | ||
| 2725 | 3047 | Some((*ret).clone()), |
| 2726 | 3048 | )); |
| 2727 | 3049 | } |
| 2728 | - let ret = self.fns.get(&name).map(|s| s.ret.clone()); | |
| 3050 | + let ret = target.as_ref().and_then(|k| self.fns.get(k)).map(|s| s.ret.clone()); | |
| 2729 | 3051 | if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) { |
| 2730 | 3052 | return Err(format!( |
| 2731 | 3053 | "call to unknown function `{name}`; only functions defined in \ |
| 2732 | 3054 | this file and the supported standard-library subset can be lowered" |
| 2733 | 3055 | )); |
| 2734 | 3056 | } |
| 2735 | - Ok(Val::new( | |
| 2736 | - format!("{}({})", ident(&name), codes.join(", ")), | |
| 2737 | - ret, | |
| 2738 | - )) | |
| 3057 | + let nim = match &target { | |
| 3058 | + Some((m, n)) => self.fn_name(m, n), | |
| 3059 | + None => ident(&name), | |
| 3060 | + }; | |
| 3061 | + Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret)) | |
| 2739 | 3062 | } |
| 2740 | 3063 | |
| 2741 | 3064 | fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> { |
| @@ -2768,6 +3091,7 @@ impl Lowerer { | ||
| 2768 | 3091 | && matches!(m.args.first(), Some(Expr::Range(_))) |
| 2769 | 3092 | { |
| 2770 | 3093 | let Some(Expr::Range(r)) = m.args.first() else { unreachable!() }; |
| 3094 | + let (code, base, blen, belem) = self.slice_parts(&m.receiver)?; | |
| 2771 | 3095 | let lo = match &r.start { |
| 2772 | 3096 | Some(e) => format!("int({})", self.expr(e)?.code), |
| 2773 | 3097 | None => "0".into(), |
| @@ -2779,9 +3103,18 @@ impl Lowerer { | ||
| 2779 | 3103 | (Some(e), syn::RangeLimits::Closed(_)) => { |
| 2780 | 3104 | format!("(int({}) - {} + 1)", self.expr(e)?.code, lo) |
| 2781 | 3105 | } |
| 2782 | - (None, _) => format!("({}.len - {})", recv.code, lo), | |
| 3106 | + (None, _) => format!("({} - {})", blen, lo), | |
| 2783 | 3107 | }; |
| 2784 | - let elem = elem_of(&rt0).ok_or("cannot infer the element type of this slice")?; | |
| 3108 | + // Hoisted, so the bounds are computed once -- as Rust computes | |
| 3109 | + // them once -- and cannot be re-evaluated later in a scope where | |
| 3110 | + // the names they mention have been shadowed by a loop pattern. | |
| 3111 | + let off_t = self.fresh("Off"); | |
| 3112 | + let len_t = self.fresh("Len"); | |
| 3113 | + self.line(&format!("let {}: int = {} + {}", off_t, base, lo)); | |
| 3114 | + self.line(&format!("let {}: int = {}", len_t, len)); | |
| 3115 | + let elem = belem | |
| 3116 | + .or_else(|| elem_of(&rt0)) | |
| 3117 | + .ok_or("cannot infer the element type of this slice")?; | |
| 2785 | 3118 | let mut v = Val::new( |
| 2786 | 3119 | String::new(), |
| 2787 | 3120 | Some(Nim::Named( |
| @@ -2789,16 +3122,32 @@ impl Lowerer { | ||
| 2789 | 3122 | vec![Nim::OpenArray(Box::new(elem.clone()))], |
| 2790 | 3123 | )), |
| 2791 | 3124 | ); |
| 2792 | - v.guard = Some(format!("({} + {} <= {}.len)", lo, len, recv.code)); | |
| 3125 | + v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen)); | |
| 2793 | 3126 | v.window = Some(Alias::Window { |
| 2794 | - code: recv.code.clone(), | |
| 2795 | - off: lo, | |
| 2796 | - len, | |
| 3127 | + code, | |
| 3128 | + off: off_t, | |
| 3129 | + len: len_t, | |
| 2797 | 3130 | elem: Some(elem), |
| 2798 | 3131 | }); |
| 2799 | 3132 | return Ok(v); |
| 2800 | 3133 | } |
| 2801 | 3134 | |
| 3135 | + // `.map`/`.and_then` over an `Option`/`Result` take a closure whose | |
| 3136 | + // parameter type comes from the receiver, so they are handled before | |
| 3137 | + // the arguments are lowered. The closure is expanded inline, with its | |
| 3138 | + // parameter aliased to the payload: that keeps the whole thing an | |
| 3139 | + // expression and avoids handing a view to a generic proc. | |
| 3140 | + if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 { | |
| 3141 | + if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) = | |
| 3142 | + (recv.ty.clone(), &m.args[0]) | |
| 3143 | + { | |
| 3144 | + if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2) | |
| 3145 | + { | |
| 3146 | + return self.map_closure(&name, &recv, &kind, &targs, c); | |
| 3147 | + } | |
| 3148 | + } | |
| 3149 | + } | |
| 3150 | + | |
| 2802 | 3151 | // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's |
| 2803 | 3152 | // own type; `v.push(e)` takes the element type. |
| 2804 | 3153 | let arg_want = match (name.as_str(), &recv.ty) { |
| @@ -2967,6 +3316,35 @@ impl Lowerer { | ||
| 2967 | 3316 | |
| 2968 | 3317 | // -------------------------------------------------------------- macros |
| 2969 | 3318 | |
| 3319 | + /// The element type of a `vec![..]`, from its first element. | |
| 3320 | + fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> { | |
| 3321 | + let body = mac.tokens.to_string(); | |
| 3322 | + if body.trim().is_empty() { | |
| 3323 | + return Ok(None); | |
| 3324 | + } | |
| 3325 | + let first: Option<Expr> = if body.contains(';') { | |
| 3326 | + // The whole body must be consumed or the parse fails, so the | |
| 3327 | + // length is parsed too even though only the element is wanted. | |
| 3328 | + mac.parse_body_with(|input: syn::parse::ParseStream| { | |
| 3329 | + let v: Expr = input.parse()?; | |
| 3330 | + input.parse::<syn::Token![;]>()?; | |
| 3331 | + let _len: Expr = input.parse()?; | |
| 3332 | + Ok(v) | |
| 3333 | + }) | |
| 3334 | + .ok() | |
| 3335 | + } else { | |
| 3336 | + mac.parse_body_with( | |
| 3337 | + syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated, | |
| 3338 | + ) | |
| 3339 | + .ok() | |
| 3340 | + .and_then(|p| p.into_iter().next()) | |
| 3341 | + }; | |
| 3342 | + match first { | |
| 3343 | + Some(e) => Ok(self.expr(&e)?.ty), | |
| 3344 | + None => Ok(None), | |
| 3345 | + } | |
| 3346 | + } | |
| 3347 | + | |
| 2970 | 3348 | fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> { |
| 2971 | 3349 | let name = path_name(&mac.path); |
| 2972 | 3350 | match name.as_str() { |
| @@ -3030,16 +3408,18 @@ impl Lowerer { | ||
| 3030 | 3408 | Ok((v, n)) |
| 3031 | 3409 | }) |
| 3032 | 3410 | .map_err(|e| format!("vec![elem; n]: {e}"))?; |
| 3033 | - let v = self.expr(&v)?; | |
| 3411 | + let want = self.vec_expect.clone(); | |
| 3412 | + let v = self.expr_at(&v, want.as_ref())?; | |
| 3034 | 3413 | let n = self.expr(&n)?; |
| 3035 | 3414 | return Ok(format!("newSeqWith(int({}), {})", n.code, v.code)); |
| 3036 | 3415 | } |
| 3037 | 3416 | let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac |
| 3038 | 3417 | .parse_body_with(syn::punctuated::Punctuated::parse_terminated) |
| 3039 | 3418 | .map_err(|e| format!("vec!: {e}"))?; |
| 3419 | + let want = self.vec_expect.clone(); | |
| 3040 | 3420 | let mut parts = Vec::new(); |
| 3041 | 3421 | for e in &elems { |
| 3042 | - parts.push(self.expr(e)?.code); | |
| 3422 | + parts.push(self.expr_at(e, want.as_ref())?.code); | |
| 3043 | 3423 | } |
| 3044 | 3424 | Ok(format!("@[{}]", parts.join(", "))) |
| 3045 | 3425 | } |
| @@ -3231,13 +3611,37 @@ fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type | ||
| 3231 | 3611 | /// models with a view rather than with an owned copy. |
| 3232 | 3612 | fn returns_borrow(t: &syn::Type) -> bool { |
| 3233 | 3613 | match t { |
| 3234 | - syn::Type::Reference(r) => matches!(&*r.elem, syn::Type::Slice(_)), | |
| 3614 | + syn::Type::Reference(r) => match &*r.elem { | |
| 3615 | + syn::Type::Slice(_) => true, | |
| 3616 | + // `&str` is a borrow of someone else's bytes too, and returning it | |
| 3617 | + // means returning a view, not an owned string. | |
| 3618 | + syn::Type::Path(p) => p.path.is_ident("str"), | |
| 3619 | + _ => false, | |
| 3620 | + }, | |
| 3235 | 3621 | syn::Type::Paren(p) => returns_borrow(&p.elem), |
| 3236 | 3622 | syn::Type::Group(g) => returns_borrow(&g.elem), |
| 3237 | 3623 | _ => false, |
| 3238 | 3624 | } |
| 3239 | 3625 | } |
| 3240 | 3626 | |
| 3627 | +/// The module a `use` prefix names. `crate`, `self` and `super` all resolve | |
| 3628 | +/// to the crate root, which is where a flattened module's items live unless | |
| 3629 | +/// they came from one of the extra input files. | |
| 3630 | +fn module_of(prefix: &[String]) -> String { | |
| 3631 | + match prefix.last() { | |
| 3632 | + Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(), | |
| 3633 | + _ => String::new(), | |
| 3634 | + } | |
| 3635 | +} | |
| 3636 | + | |
| 3637 | +/// The first type argument of an `Option[T]` / `Result[T, E]`. | |
| 3638 | +fn elem_arg(t: &Nim) -> Nim { | |
| 3639 | + match t { | |
| 3640 | + Nim::Named(_, a) if !a.is_empty() => a[0].clone(), | |
| 3641 | + other => other.clone(), | |
| 3642 | + } | |
| 3643 | +} | |
| 3644 | + | |
| 3241 | 3645 | /// The element type of a sequence-like Nim type. |
| 3242 | 3646 | fn elem_of(t: &Option<Nim>) -> Option<Nim> { |
| 3243 | 3647 | match t { |
| @@ -3371,7 +3775,6 @@ fn item_kind(i: &Item) -> &'static str { | ||
| 3371 | 3775 | |
| 3372 | 3776 | fn expr_kind(e: &Expr) -> &'static str { |
| 3373 | 3777 | match e { |
| 3374 | - Expr::Closure(_) => "closure", | |
| 3375 | 3778 | Expr::Async(_) => "`async` block", |
| 3376 | 3779 | Expr::Await(_) => "`.await`", |
| 3377 | 3780 | Expr::Try(_) => "`?`", |
| @@ -76,9 +76,9 @@ enum Iter { | |||
| 76 | Elems { code: String, off: String, len: String, elem: Option<Nim>, mutable: bool }, | 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 | 77 | /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of |
| 78 | /// `k` elements starting at `k * i`. | 78 | /// `k` elements starting at `k * i`. |
| 79 | - Chunks { code: String, k: String, elem: Option<Nim>, mutable: bool }, | 79 | + Chunks { code: String, base: String, len: String, k: String, elem: Option<Nim>, mutable: bool }, |
| 80 | /// `a.windows(k)`: like `Chunks` but advancing one element at a time. | 80 | /// `a.windows(k)`: like `Chunks` but advancing one element at a time. |
| 81 | - Windows { code: String, k: String, elem: Option<Nim> }, | 81 | + Windows { code: String, base: String, len: String, k: String, elem: Option<Nim> }, |
| 82 | /// `.enumerate()` — the index is the first half of the pair. | 82 | /// `.enumerate()` — the index is the first half of the pair. |
| 83 | Enumerate(Box<Iter>), | 83 | Enumerate(Box<Iter>), |
| 84 | /// `.zip(other)` — stops at the shorter, as Rust's does. | 84 | /// `.zip(other)` — stops at the shorter, as Rust's does. |
| @@ -95,10 +95,8 @@ impl Iter { | |||
| 95 | if *closed { format!("({n} + 1)") } else { n } | 95 | if *closed { format!("({n} + 1)") } else { n } |
| 96 | } | 96 | } |
| 97 | Iter::Elems { len, .. } => len.clone(), | 97 | Iter::Elems { len, .. } => len.clone(), |
| 98 | - Iter::Chunks { code, k, .. } => format!("({}.len div int({}))", code, k), | 98 | + Iter::Chunks { k, len, .. } => format!("({} div int({}))", len, k), |
| 99 | - Iter::Windows { code, k, .. } => { | 99 | + Iter::Windows { len, k, .. } => format!("(max(0, {} - int({}) + 1))", len, k), |
| 100 | - format!("(max(0, {}.len - int({}) + 1))", code, k) | ||
| 101 | - } | ||
| 102 | Iter::Enumerate(i) => i.len(), | 100 | Iter::Enumerate(i) => i.len(), |
| 103 | Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()), | 101 | Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()), |
| 104 | } | 102 | } |
| @@ -188,7 +186,16 @@ pub struct Lowerer { | |||
| 188 | /// Names introduced by a `for` pattern that stand for an lvalue or a | 186 | /// 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. | 187 | /// window into a container, rather than for a variable of their own. |
| 190 | alias_scopes: Vec<HashMap<String, Alias>>, | 188 | alias_scopes: Vec<HashMap<String, Alias>>, |
| 191 | - fns: HashMap<String, Sig>, | 189 | + /// `(module, name) -> signature`. Rust keeps `lower::decode` and |
| 190 | + /// `mixed::decode` apart by module; flattening into one Nim module would | ||
| 191 | + /// merge them, so the module is part of the key and of the emitted name. | ||
| 192 | + fns: HashMap<(String, String), Sig>, | ||
| 193 | + /// Module being lowered: the file stem, or empty for the crate root. | ||
| 194 | + cur_mod: String, | ||
| 195 | + /// `use` brings a name into scope from another module. Flattening loses | ||
| 196 | + /// the module structure, so the mapping is recorded and consulted when a | ||
| 197 | + /// bare call is resolved. | ||
| 198 | + use_map: HashMap<String, String>, | ||
| 192 | /// struct name -> (field, type) | 199 | /// struct name -> (field, type) |
| 193 | structs: HashMap<String, Vec<(String, Nim)>>, | 200 | structs: HashMap<String, Vec<(String, Nim)>>, |
| 194 | enums: HashMap<String, EnumDef>, | 201 | enums: HashMap<String, EnumDef>, |
| @@ -209,6 +216,8 @@ pub struct Lowerer { | |||
| 209 | /// proc is declared up front rather than the input being reordered -- | 216 | /// proc is declared up front rather than the input being reordered -- |
| 210 | /// which would not work for mutual recursion anyway. | 217 | /// which would not work for mutual recursion anyway. |
| 211 | forwards: Vec<String>, | 218 | forwards: Vec<String>, |
| 219 | + /// Element type a `vec![..]` should build, from the binding's annotation. | ||
| 220 | + vec_expect: Option<Nim>, | ||
| 212 | /// While lowering a formatting impl: the `Formatter` parameter's name. | 221 | /// While lowering a formatting impl: the `Formatter` parameter's name. |
| 213 | /// Writes through it produce the proc's string result. | 222 | /// Writes through it produce the proc's string result. |
| 214 | fmt_param: Option<String>, | 223 | fmt_param: Option<String>, |
| @@ -242,6 +251,8 @@ impl Lowerer { | |||
| 242 | scopes: vec![HashMap::new()], | 251 | scopes: vec![HashMap::new()], |
| 243 | alias_scopes: vec![HashMap::new()], | 252 | alias_scopes: vec![HashMap::new()], |
| 244 | fns: HashMap::new(), | 253 | fns: HashMap::new(), |
| 254 | + cur_mod: String::new(), | ||
| 255 | + use_map: HashMap::new(), | ||
| 245 | structs: HashMap::new(), | 256 | structs: HashMap::new(), |
| 246 | enums: HashMap::new(), | 257 | enums: HashMap::new(), |
| 247 | variant_owner: HashMap::new(), | 258 | variant_owner: HashMap::new(), |
| @@ -249,6 +260,7 @@ impl Lowerer { | |||
| 249 | fmt_impls: HashMap::new(), | 260 | fmt_impls: HashMap::new(), |
| 250 | from_impls: HashMap::new(), | 261 | from_impls: HashMap::new(), |
| 251 | fmt_param: None, | 262 | fmt_param: None, |
| 263 | + vec_expect: None, | ||
| 252 | forwards: Vec::new(), | 264 | forwards: Vec::new(), |
| 253 | aliases: HashMap::new(), | 265 | aliases: HashMap::new(), |
| 254 | modules: Vec::new(), | 266 | modules: Vec::new(), |
| @@ -311,25 +323,34 @@ impl Lowerer { | |||
| 311 | 323 | ||
| 312 | // ---------------------------------------------------------------- file | 324 | // ---------------------------------------------------------------- file |
| 313 | 325 | ||
| 314 | - pub fn lower_file(&mut self, file: &syn::File) -> Result<String, String> { | 326 | + pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> { |
| 315 | self.out.push_str(include_str!("prelude.nim")); | 327 | self.out.push_str(include_str!("prelude.nim")); |
| 316 | self.blank(); | 328 | self.blank(); |
| 317 | 329 | ||
| 318 | // Pass 0: type aliases. A signature in one file may use an alias | 330 | // 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 | 331 | // declared in another, and inputs are given in whatever order suits |
| 320 | // the caller, so aliases are registered before anything is mapped. | 332 | // the caller, so aliases are registered before anything is mapped. |
| 321 | - for item in &file.items { | 333 | + for (m, f) in files { |
| 322 | - self.collect_aliases(item)?; | 334 | + self.cur_mod = m.clone(); |
| 335 | + for item in &f.items { | ||
| 336 | + self.collect_aliases(item)?; | ||
| 337 | + } | ||
| 323 | } | 338 | } |
| 324 | 339 | ||
| 325 | // Pass 1: signatures and struct shapes, so that a call can be typed | 340 | // Pass 1: signatures and struct shapes, so that a call can be typed |
| 326 | // regardless of declaration order (Rust has no forward declarations). | 341 | // regardless of declaration order (Rust has no forward declarations). |
| 327 | - for item in &file.items { | 342 | + for (m, f) in files { |
| 328 | - self.collect(item)?; | 343 | + self.cur_mod = m.clone(); |
| 344 | + for item in &f.items { | ||
| 345 | + self.collect(item)?; | ||
| 346 | + } | ||
| 329 | } | 347 | } |
| 330 | // Pass 2: type definitions, which every signature may mention. | 348 | // Pass 2: type definitions, which every signature may mention. |
| 331 | - for item in &file.items { | 349 | + for (m, f) in files { |
| 332 | - self.item_types(item)?; | 350 | + self.cur_mod = m.clone(); |
| 351 | + for item in &f.items { | ||
| 352 | + self.item_types(item)?; | ||
| 353 | + } | ||
| 333 | } | 354 | } |
| 334 | 355 | ||
| 335 | // Pass 3: forward declarations. Rust imposes no declaration order and | 356 | // Pass 3: forward declarations. Rust imposes no declaration order and |
| @@ -343,11 +364,14 @@ impl Lowerer { | |||
| 343 | } | 364 | } |
| 344 | 365 | ||
| 345 | // Pass 4: bodies. | 366 | // Pass 4: bodies. |
| 346 | - for item in &file.items { | 367 | + for (m, f) in files { |
| 347 | - self.item(item)?; | 368 | + self.cur_mod = m.clone(); |
| 369 | + for item in &f.items { | ||
| 370 | + self.item(item)?; | ||
| 371 | + } | ||
| 348 | } | 372 | } |
| 349 | 373 | ||
| 350 | - if self.fns.contains_key("main") { | 374 | + if self.fns.contains_key(&(String::new(), "main".to_string())) { |
| 351 | self.blank(); | 375 | self.blank(); |
| 352 | self.line("when isMainModule:"); | 376 | self.line("when isMainModule:"); |
| 353 | self.indent += 1; | 377 | self.indent += 1; |
| @@ -372,6 +396,7 @@ impl Lowerer { | |||
| 372 | return Ok(()); | 396 | return Ok(()); |
| 373 | } | 397 | } |
| 374 | match item { | 398 | match item { |
| 399 | + Item::Use(u) => self.collect_use(&u.tree, &[]), | ||
| 375 | Item::Type(t) => { | 400 | Item::Type(t) => { |
| 376 | let params: Vec<String> = t | 401 | let params: Vec<String> = t |
| 377 | .generics | 402 | .generics |
| @@ -396,6 +421,34 @@ impl Lowerer { | |||
| 396 | Ok(()) | 421 | Ok(()) |
| 397 | } | 422 | } |
| 398 | 423 | ||
| 424 | + /// Record what a `use` brings into scope, as `name -> module`. | ||
| 425 | + fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) { | ||
| 426 | + use syn::UseTree; | ||
| 427 | + match t { | ||
| 428 | + UseTree::Path(p) => { | ||
| 429 | + let mut pre = prefix.to_vec(); | ||
| 430 | + pre.push(p.ident.to_string()); | ||
| 431 | + self.collect_use(&p.tree, &pre); | ||
| 432 | + } | ||
| 433 | + UseTree::Group(g) => { | ||
| 434 | + for t in &g.items { | ||
| 435 | + self.collect_use(t, prefix); | ||
| 436 | + } | ||
| 437 | + } | ||
| 438 | + UseTree::Name(n) => { | ||
| 439 | + let m = module_of(prefix); | ||
| 440 | + self.use_map.insert(n.ident.to_string(), m); | ||
| 441 | + } | ||
| 442 | + UseTree::Rename(r) => { | ||
| 443 | + let m = module_of(prefix); | ||
| 444 | + self.use_map.insert(r.rename.to_string(), m); | ||
| 445 | + } | ||
| 446 | + // A glob brings in an unknown set of names; resolution falls back | ||
| 447 | + // to the current module and the root, as it would without it. | ||
| 448 | + UseTree::Glob(_) => {} | ||
| 449 | + } | ||
| 450 | + } | ||
| 451 | + | ||
| 399 | fn collect(&mut self, item: &Item) -> Result<(), String> { | 452 | fn collect(&mut self, item: &Item) -> Result<(), String> { |
| 400 | // A `#[cfg(..)]` item exists only under some feature set. Dropping it | 453 | // A `#[cfg(..)]` item exists only under some feature set. Dropping it |
| 401 | // silently would change what the program does; picking a feature set | 454 | // silently would change what the program does; picking a feature set |
| @@ -408,9 +461,11 @@ impl Lowerer { | |||
| 408 | match item { | 461 | match item { |
| 409 | Item::Fn(f) => { | 462 | Item::Fn(f) => { |
| 410 | let (params, ret) = self.signature(&f.sig)?; | 463 | let (params, ret) = self.signature(&f.sig)?; |
| 411 | - let head = self.head_of(&f.sig.ident.to_string(), &f.sig, None)?; | 464 | + let name = f.sig.ident.to_string(); |
| 412 | - self.forwards.push(head); | 465 | + let nim = self.fn_name(&self.cur_mod, &name); |
| 413 | - self.fns.insert(f.sig.ident.to_string(), Sig { params, ret }); | 466 | + self.forwards.push(self.head_of(&nim, &f.sig, None)?); |
| 467 | + self.fns | ||
| 468 | + .insert((self.cur_mod.clone(), name), Sig { params, ret }); | ||
| 414 | } | 469 | } |
| 415 | Item::Struct(s) => { | 470 | Item::Struct(s) => { |
| 416 | let mut fields = Vec::new(); | 471 | let mut fields = Vec::new(); |
| @@ -637,6 +692,38 @@ impl Lowerer { | |||
| 637 | self.expand(&substitute(target, params, &args), depth + 1) | 692 | self.expand(&substitute(target, params, &args), depth + 1) |
| 638 | } | 693 | } |
| 639 | 694 | ||
| 695 | + /// The Nim name for a function, qualified by its module. | ||
| 696 | + fn fn_name(&self, module: &str, name: &str) -> String { | ||
| 697 | + if module.is_empty() { | ||
| 698 | + ident(name) | ||
| 699 | + } else { | ||
| 700 | + format!("{}_{}", module, ident(name)) | ||
| 701 | + } | ||
| 702 | + } | ||
| 703 | + | ||
| 704 | + /// Resolve a call path to the module and name it refers to: an explicit | ||
| 705 | + /// `mixed::decode`, then the current module, then the crate root. | ||
| 706 | + fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> { | ||
| 707 | + let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect(); | ||
| 708 | + let last = segs.last()?.clone(); | ||
| 709 | + if segs.len() >= 2 { | ||
| 710 | + let q = &segs[segs.len() - 2]; | ||
| 711 | + if self.fns.contains_key(&(q.clone(), last.clone())) { | ||
| 712 | + return Some((q.clone(), last)); | ||
| 713 | + } | ||
| 714 | + } | ||
| 715 | + let imported = self.use_map.get(&last).cloned(); | ||
| 716 | + for m in [Some(self.cur_mod.clone()), imported, Some(String::new())] | ||
| 717 | + .into_iter() | ||
| 718 | + .flatten() | ||
| 719 | + { | ||
| 720 | + if self.fns.contains_key(&(m.clone(), last.clone())) { | ||
| 721 | + return Some((m, last)); | ||
| 722 | + } | ||
| 723 | + } | ||
| 724 | + None | ||
| 725 | + } | ||
| 726 | + | ||
| 640 | /// The Nim `proc` head for a Rust signature, used both for the forward | 727 | /// 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. | 728 | /// declaration and for the definition, so the two cannot drift apart. |
| 642 | fn head_of( | 729 | fn head_of( |
| @@ -681,6 +768,8 @@ impl Lowerer { | |||
| 681 | } | 768 | } |
| 682 | 769 | ||
| 683 | fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> { | 770 | fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> { |
| 771 | + // `unsafe fn` marks a contract for callers; it does not change what | ||
| 772 | + // the body means, so it lowers like any other proc. | ||
| 684 | if sig.asyncness.is_some() { | 773 | if sig.asyncness.is_some() { |
| 685 | return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident)); | 774 | return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident)); |
| 686 | } | 775 | } |
| @@ -749,7 +838,10 @@ impl Lowerer { | |||
| 749 | 838 | ||
| 750 | fn item_inner(&mut self, item: &Item) -> Result<(), String> { | 839 | fn item_inner(&mut self, item: &Item) -> Result<(), String> { |
| 751 | match item { | 840 | match item { |
| 752 | - Item::Fn(f) => self.func(&f.sig, &f.block, None), | 841 | + Item::Fn(f) => { |
| 842 | + let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string()); | ||
| 843 | + self.func_named(&nim, &f.sig, &f.block, None) | ||
| 844 | + } | ||
| 753 | Item::Struct(s) => { | 845 | Item::Struct(s) => { |
| 754 | let name = s.ident.to_string(); | 846 | let name = s.ident.to_string(); |
| 755 | let fields = self.structs[&name].clone(); | 847 | let fields = self.structs[&name].clone(); |
| @@ -1384,6 +1476,11 @@ impl Lowerer { | |||
| 1384 | self.nested_block(&b.block)?; | 1476 | self.nested_block(&b.block)?; |
| 1385 | Ok(None) | 1477 | Ok(None) |
| 1386 | } | 1478 | } |
| 1479 | + Expr::Unsafe(u) => { | ||
| 1480 | + // Transparent in statement position too, for the same reason. | ||
| 1481 | + self.nested_block_flat(&u.block)?; | ||
| 1482 | + Ok(None) | ||
| 1483 | + } | ||
| 1387 | Expr::Match(_) => { | 1484 | Expr::Match(_) => { |
| 1388 | self.match_stmt(e)?; | 1485 | self.match_stmt(e)?; |
| 1389 | Ok(None) | 1486 | Ok(None) |
| @@ -1597,23 +1694,21 @@ impl Lowerer { | |||
| 1597 | Ok(Iter::Zip(Box::new(a), Box::new(b))) | 1694 | Ok(Iter::Zip(Box::new(a), Box::new(b))) |
| 1598 | } | 1695 | } |
| 1599 | "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => { | 1696 | "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => { |
| 1600 | - let recv = self.expr(&m.receiver)?; | 1697 | + let (code, base, len, elem) = self.slice_parts(&m.receiver)?; |
| 1601 | let k = self.expr(&m.args[0])?; | 1698 | let k = self.expr(&m.args[0])?; |
| 1602 | Ok(Iter::Chunks { | 1699 | Ok(Iter::Chunks { |
| 1603 | - code: recv.code, | 1700 | + code, |
| 1701 | + base, | ||
| 1702 | + len, | ||
| 1604 | k: k.code, | 1703 | k: k.code, |
| 1605 | - elem: elem_of(&recv.ty), | 1704 | + elem, |
| 1606 | mutable: name.ends_with("_mut"), | 1705 | mutable: name.ends_with("_mut"), |
| 1607 | }) | 1706 | }) |
| 1608 | } | 1707 | } |
| 1609 | "windows" if m.args.len() == 1 => { | 1708 | "windows" if m.args.len() == 1 => { |
| 1610 | - let recv = self.expr(&m.receiver)?; | 1709 | + let (code, base, len, elem) = self.slice_parts(&m.receiver)?; |
| 1611 | let k = self.expr(&m.args[0])?; | 1710 | let k = self.expr(&m.args[0])?; |
| 1612 | - Ok(Iter::Windows { | 1711 | + Ok(Iter::Windows { code, base, len, k: k.code, elem }) |
| 1613 | - code: recv.code, | ||
| 1614 | - k: k.code, | ||
| 1615 | - elem: elem_of(&recv.ty), | ||
| 1616 | - }) | ||
| 1617 | } | 1712 | } |
| 1618 | other => Err(format!( | 1713 | other => Err(format!( |
| 1619 | "iterator adaptor `.{other}()` is not implemented; it has \ | 1714 | "iterator adaptor `.{other}()` is not implemented; it has \ |
| @@ -1626,13 +1721,7 @@ impl Lowerer { | |||
| 1626 | // A `for` binding that is itself a window iterates that window, | 1721 | // A `for` binding that is itself a window iterates that window, |
| 1627 | // not the whole container it points into. | 1722 | // not the whole container it points into. |
| 1628 | if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) { | 1723 | if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) { |
| 1629 | - return Ok(Iter::Elems { | 1724 | + return Ok(Iter::Elems { code, off, len, elem, mutable: false }); |
| 1630 | - code, | ||
| 1631 | - off, | ||
| 1632 | - len, | ||
| 1633 | - elem, | ||
| 1634 | - mutable: false, | ||
| 1635 | - }); | ||
| 1636 | } | 1725 | } |
| 1637 | let v = self.expr(other)?; | 1726 | let v = self.expr(other)?; |
| 1638 | Ok(Iter::Elems { | 1727 | Ok(Iter::Elems { |
| @@ -1666,6 +1755,10 @@ impl Lowerer { | |||
| 1666 | "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(), | 1755 | "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(), |
| 1667 | ), | 1756 | ), |
| 1668 | (Pat::Wild(_), _) => Ok(()), | 1757 | (Pat::Wild(_), _) => Ok(()), |
| 1758 | + // `for &byte in xs` — the `&` destructures the reference, which in | ||
| 1759 | + // Nim is already the value. | ||
| 1760 | + (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i), | ||
| 1761 | + (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i), | ||
| 1669 | (Pat::Ident(id), _) => { | 1762 | (Pat::Ident(id), _) => { |
| 1670 | let name = id.ident.to_string(); | 1763 | let name = id.ident.to_string(); |
| 1671 | match it { | 1764 | match it { |
| @@ -1711,24 +1804,24 @@ impl Lowerer { | |||
| 1711 | } | 1804 | } |
| 1712 | Ok(()) | 1805 | Ok(()) |
| 1713 | } | 1806 | } |
| 1714 | - Iter::Chunks { code, k, elem, .. } => { | 1807 | + Iter::Chunks { code, base, k, elem, .. } => { |
| 1715 | self.bind_alias( | 1808 | self.bind_alias( |
| 1716 | &name, | 1809 | &name, |
| 1717 | Alias::Window { | 1810 | Alias::Window { |
| 1718 | code: code.clone(), | 1811 | code: code.clone(), |
| 1719 | - off: format!("({} * int({}))", i, k), | 1812 | + off: format!("({} + {} * int({}))", base, i, k), |
| 1720 | len: format!("int({})", k), | 1813 | len: format!("int({})", k), |
| 1721 | elem: elem.clone(), | 1814 | elem: elem.clone(), |
| 1722 | }, | 1815 | }, |
| 1723 | ); | 1816 | ); |
| 1724 | Ok(()) | 1817 | Ok(()) |
| 1725 | } | 1818 | } |
| 1726 | - Iter::Windows { code, k, elem } => { | 1819 | + Iter::Windows { code, base, k, elem, .. } => { |
| 1727 | self.bind_alias( | 1820 | self.bind_alias( |
| 1728 | &name, | 1821 | &name, |
| 1729 | Alias::Window { | 1822 | Alias::Window { |
| 1730 | code: code.clone(), | 1823 | code: code.clone(), |
| 1731 | - off: i.to_string(), | 1824 | + off: format!("({} + {})", base, i), |
| 1732 | len: format!("int({})", k), | 1825 | len: format!("int({})", k), |
| 1733 | elem: elem.clone(), | 1826 | elem: elem.clone(), |
| 1734 | }, | 1827 | }, |
| @@ -2140,9 +2233,10 @@ impl Lowerer { | |||
| 2140 | } | 2233 | } |
| 2141 | // A top-level function used as a value, e.g. passed to a | 2234 | // A top-level function used as a value, e.g. passed to a |
| 2142 | // parameter of `impl Fn(..)` type. | 2235 | // parameter of `impl Fn(..)` type. |
| 2143 | - if let Some(sig) = self.fns.get(&name) { | 2236 | + if let Some(k) = self.resolve_fn(&p.path) { |
| 2237 | + let sig = &self.fns[&k]; | ||
| 2144 | let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone())); | 2238 | let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone())); |
| 2145 | - return Ok(Val::new(ident(&name), Some(t))); | 2239 | + return Ok(Val::new(self.fn_name(&k.0, &k.1), Some(t))); |
| 2146 | } | 2240 | } |
| 2147 | Ok(Val::new(ident(&name), None)) | 2241 | Ok(Val::new(ident(&name), None)) |
| 2148 | } | 2242 | } |
| @@ -2220,9 +2314,38 @@ impl Lowerer { | |||
| 2220 | }; | 2314 | }; |
| 2221 | Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t)) | 2315 | Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t)) |
| 2222 | } | 2316 | } |
| 2317 | + // `unsafe` is a permission marker, not a semantic change: it does | ||
| 2318 | + // not alter what the enclosed operations mean. So the block is | ||
| 2319 | + // transparent here, and each operation inside still goes through | ||
| 2320 | + // the ordinary lowering -- and is still rejected if it has no | ||
| 2321 | + // faithful mapping. | ||
| 2322 | + Expr::Unsafe(u) => match single_expr(&u.block) { | ||
| 2323 | + Some(e) => self.expr_at(e, expect), | ||
| 2324 | + None => Err("an `unsafe` block used as a value must be a single \ | ||
| 2325 | + expression" | ||
| 2326 | + .into()), | ||
| 2327 | + }, | ||
| 2328 | + Expr::Closure(c) => self.closure(c, expect), | ||
| 2223 | Expr::Try(t) => self.try_op(t), | 2329 | Expr::Try(t) => self.try_op(t), |
| 2224 | Expr::Call(c) => self.call(c, expect), | 2330 | Expr::Call(c) => self.call(c, expect), |
| 2225 | Expr::MethodCall(m) => self.method(m, expect), | 2331 | Expr::MethodCall(m) => self.method(m, expect), |
| 2332 | + Expr::Macro(m) if path_name(&m.mac.path) == "vec" => { | ||
| 2333 | + // `vec![..]`'s elements take their type from the annotation on | ||
| 2334 | + // the binding, exactly as Rust's would. | ||
| 2335 | + let want = match expect { | ||
| 2336 | + Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()), | ||
| 2337 | + _ => None, | ||
| 2338 | + }; | ||
| 2339 | + let saved = std::mem::replace(&mut self.vec_expect, want.clone()); | ||
| 2340 | + let code = self.macro_call(&m.mac); | ||
| 2341 | + self.vec_expect = saved; | ||
| 2342 | + let code = code?; | ||
| 2343 | + let ty = match want { | ||
| 2344 | + Some(e) => Some(Nim::Seq(Box::new(e))), | ||
| 2345 | + None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))), | ||
| 2346 | + }; | ||
| 2347 | + Ok(Val::new(code, ty)) | ||
| 2348 | + } | ||
| 2226 | Expr::Macro(m) => { | 2349 | Expr::Macro(m) => { |
| 2227 | let code = self.macro_call(&m.mac)?; | 2350 | let code = self.macro_call(&m.mac)?; |
| 2228 | Ok(Val::new(code, None)) | 2351 | Ok(Val::new(code, None)) |
| @@ -2567,6 +2690,190 @@ impl Lowerer { | |||
| 2567 | /// The early return is statements, not an expression, so they are emitted | 2690 | /// The early return is statements, not an expression, so they are emitted |
| 2568 | /// ahead of the line being built. Every caller lowers its sub-expressions | 2691 | /// ahead of the line being built. Every caller lowers its sub-expressions |
| 2569 | /// before emitting its own line, which is what makes that ordering hold. | 2692 | /// before emitting its own line, which is what makes that ordering hold. |
| 2693 | + /// The container, start offset, length and element type an expression | ||
| 2694 | + /// denotes as a slice. A window alias contributes its own offset, so | ||
| 2695 | + /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight | ||
| 2696 | + /// into the original buffer rather than through a rebuilt view. | ||
| 2697 | + fn slice_parts( | ||
| 2698 | + &mut self, | ||
| 2699 | + e: &Expr, | ||
| 2700 | + ) -> Result<(String, String, String, Option<Nim>), String> { | ||
| 2701 | + if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) { | ||
| 2702 | + return Ok((code, off, len, elem)); | ||
| 2703 | + } | ||
| 2704 | + let v = self.expr(e)?; | ||
| 2705 | + let len = format!("{}.len", v.code); | ||
| 2706 | + Ok((v.code, "0".to_string(), len, elem_of(&v.ty))) | ||
| 2707 | + } | ||
| 2708 | + | ||
| 2709 | + /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline. | ||
| 2710 | + fn map_closure( | ||
| 2711 | + &mut self, | ||
| 2712 | + what: &str, | ||
| 2713 | + recv: &Val, | ||
| 2714 | + kind: &str, | ||
| 2715 | + targs: &[Nim], | ||
| 2716 | + c: &syn::ExprClosure, | ||
| 2717 | + ) -> Result<Val, String> { | ||
| 2718 | + if c.capture.is_some() { | ||
| 2719 | + return Err("a `move` closure captures by value; Nim's closures \ | ||
| 2720 | + capture by reference, and the two are not the same" | ||
| 2721 | + .into()); | ||
| 2722 | + } | ||
| 2723 | + if c.inputs.len() != 1 { | ||
| 2724 | + return Err(format!("`.{what}()` takes a one-argument closure")); | ||
| 2725 | + } | ||
| 2726 | + let pname = match &c.inputs[0] { | ||
| 2727 | + Pat::Ident(i) => i.ident.to_string(), | ||
| 2728 | + Pat::Wild(_) => "unused0".into(), | ||
| 2729 | + _ => return Err("only plain identifier closure parameters are supported".into()), | ||
| 2730 | + }; | ||
| 2731 | + | ||
| 2732 | + let is_opt = kind == "Option"; | ||
| 2733 | + let tmp = self.fresh("Map"); | ||
| 2734 | + let recv_ty = Nim::Named(kind.to_string(), targs.to_vec()); | ||
| 2735 | + self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code)); | ||
| 2736 | + | ||
| 2737 | + let body = match &*c.body { | ||
| 2738 | + Expr::Block(b) => single_expr(&b.block) | ||
| 2739 | + .ok_or("a closure body with statements is not implemented yet")?, | ||
| 2740 | + other => other, | ||
| 2741 | + }; | ||
| 2742 | + self.push_scope(); | ||
| 2743 | + // The parameter names the payload itself, so a view stays a view. | ||
| 2744 | + self.bind_alias( | ||
| 2745 | + &pname, | ||
| 2746 | + Alias::Value { | ||
| 2747 | + code: format!("{}.val", tmp), | ||
| 2748 | + ty: Some(targs[0].clone()), | ||
| 2749 | + }, | ||
| 2750 | + ); | ||
| 2751 | + let v = self.expr(body)?; | ||
| 2752 | + self.pop_scope(); | ||
| 2753 | + | ||
| 2754 | + let inner = v | ||
| 2755 | + .ty | ||
| 2756 | + .clone() | ||
| 2757 | + .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?; | ||
| 2758 | + // `and_then`'s closure already returns the wrapped type; `map`'s does | ||
| 2759 | + // not and has to be re-wrapped. | ||
| 2760 | + let (test, some_branch, none_branch, out_ty) = if is_opt { | ||
| 2761 | + let out = if what == "map" { | ||
| 2762 | + Nim::Named("Option".into(), vec![inner.clone()]) | ||
| 2763 | + } else { | ||
| 2764 | + inner.clone() | ||
| 2765 | + }; | ||
| 2766 | + let body_code = if what == "map" { | ||
| 2767 | + format!("rsSome[{}]({})", inner.render(), v.code) | ||
| 2768 | + } else { | ||
| 2769 | + v.code.clone() | ||
| 2770 | + }; | ||
| 2771 | + ( | ||
| 2772 | + format!("{}.has", tmp), | ||
| 2773 | + body_code, | ||
| 2774 | + format!("rsNone[{}]()", elem_arg(&out).render()), | ||
| 2775 | + out, | ||
| 2776 | + ) | ||
| 2777 | + } else { | ||
| 2778 | + let e = targs[1].clone(); | ||
| 2779 | + let out = if what == "map" { | ||
| 2780 | + Nim::Named("Result".into(), vec![inner.clone(), e.clone()]) | ||
| 2781 | + } else { | ||
| 2782 | + inner.clone() | ||
| 2783 | + }; | ||
| 2784 | + let ok_ty = elem_arg(&out); | ||
| 2785 | + let body_code = if what == "map" { | ||
| 2786 | + format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code) | ||
| 2787 | + } else { | ||
| 2788 | + v.code.clone() | ||
| 2789 | + }; | ||
| 2790 | + ( | ||
| 2791 | + format!("{}.ok", tmp), | ||
| 2792 | + body_code, | ||
| 2793 | + format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp), | ||
| 2794 | + out, | ||
| 2795 | + ) | ||
| 2796 | + }; | ||
| 2797 | + Ok(Val::new( | ||
| 2798 | + format!("(if {}: {} else: {})", test, some_branch, none_branch), | ||
| 2799 | + Some(out_ty), | ||
| 2800 | + )) | ||
| 2801 | + } | ||
| 2802 | + | ||
| 2803 | + /// `|x| x + 1` -> a Nim anonymous proc. | ||
| 2804 | + /// | ||
| 2805 | + /// Nim's closures capture by reference, as Rust's non-`move` closures do. | ||
| 2806 | + /// A `move` closure captures by value, which is a different thing, so it | ||
| 2807 | + /// is rejected rather than lowered to the same construct. | ||
| 2808 | + fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> { | ||
| 2809 | + if c.capture.is_some() { | ||
| 2810 | + return Err("a `move` closure captures by value; Nim's closures \ | ||
| 2811 | + capture by reference, and the two are not the same" | ||
| 2812 | + .into()); | ||
| 2813 | + } | ||
| 2814 | + let want: Option<&Vec<Nim>> = match expect { | ||
| 2815 | + Some(Nim::Proc(a, _)) => Some(a), | ||
| 2816 | + _ => None, | ||
| 2817 | + }; | ||
| 2818 | + | ||
| 2819 | + self.push_scope(); | ||
| 2820 | + let mut parts = Vec::new(); | ||
| 2821 | + let mut ptys = Vec::new(); | ||
| 2822 | + for (i, p) in c.inputs.iter().enumerate() { | ||
| 2823 | + let (name, ann) = match p { | ||
| 2824 | + Pat::Ident(id) => (id.ident.to_string(), None), | ||
| 2825 | + Pat::Type(t) => match &*t.pat { | ||
| 2826 | + Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)), | ||
| 2827 | + _ => return Err("only plain identifier closure parameters are supported".into()), | ||
| 2828 | + }, | ||
| 2829 | + Pat::Wild(_) => (format!("unused{i}"), None), | ||
| 2830 | + _ => return Err("only plain identifier closure parameters are supported".into()), | ||
| 2831 | + }; | ||
| 2832 | + let t = ann | ||
| 2833 | + .or_else(|| want.and_then(|w| w.get(i).cloned())) | ||
| 2834 | + .ok_or_else(|| { | ||
| 2835 | + format!( | ||
| 2836 | + "cannot infer the type of closure parameter `{name}`; \ | ||
| 2837 | + annotate it" | ||
| 2838 | + ) | ||
| 2839 | + })?; | ||
| 2840 | + parts.push(format!("{}: {}", ident(&name), t.render())); | ||
| 2841 | + self.bind(&name, t.clone()); | ||
| 2842 | + ptys.push(t); | ||
| 2843 | + } | ||
| 2844 | + | ||
| 2845 | + let ret_ann = match &c.output { | ||
| 2846 | + ReturnType::Default => None, | ||
| 2847 | + ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()), | ||
| 2848 | + }; | ||
| 2849 | + let body = match &*c.body { | ||
| 2850 | + Expr::Block(b) => single_expr(&b.block) | ||
| 2851 | + .ok_or("a closure body with statements is not implemented yet")?, | ||
| 2852 | + other => other, | ||
| 2853 | + }; | ||
| 2854 | + let v = self.expr_at(body, ret_ann.as_ref())?; | ||
| 2855 | + self.pop_scope(); | ||
| 2856 | + | ||
| 2857 | + let ret = ret_ann | ||
| 2858 | + .or_else(|| v.ty.clone()) | ||
| 2859 | + .ok_or("cannot infer a closure's return type; annotate it")?; | ||
| 2860 | + Ok(Val::new( | ||
| 2861 | + format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code), | ||
| 2862 | + Some(Nim::Proc(ptys, Box::new(ret))), | ||
| 2863 | + )) | ||
| 2864 | + } | ||
| 2865 | + | ||
| 2866 | + /// Lower a block's statements at the current indentation, without opening | ||
| 2867 | + /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope | ||
| 2868 | + /// of its own in the generated code. | ||
| 2869 | + fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> { | ||
| 2870 | + self.push_scope(); | ||
| 2871 | + let tail = self.block_body(b)?; | ||
| 2872 | + self.emit_tail(tail); | ||
| 2873 | + self.pop_scope(); | ||
| 2874 | + Ok(()) | ||
| 2875 | + } | ||
| 2876 | + | ||
| 2570 | fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> { | 2877 | fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> { |
| 2571 | if self.in_loop_cond { | 2878 | if self.in_loop_cond { |
| 2572 | return Err("`?` in a loop condition is not implemented yet: the \ | 2879 | return Err("`?` in a loop condition is not implemented yet: the \ |
| @@ -2613,24 +2920,29 @@ impl Lowerer { | |||
| 2613 | (Nim::Named(a, ai), Nim::Named(b, bi)) | 2920 | (Nim::Named(a, ai), Nim::Named(b, bi)) |
| 2614 | if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 => | 2921 | if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 => |
| 2615 | { | 2922 | { |
| 2616 | - // Rust inserts a `From::from` on the error here. We only accept | 2923 | + // Rust inserts a `From::from` on the error here. Where the |
| 2617 | - // the case where the error types already agree, rather than | 2924 | + // types differ we call the crate's own `impl From`; we never |
| 2618 | - // silently dropping a conversion that might not be the identity. | 2925 | + // assume the conversion is the identity. |
| 2619 | - if ai[1] != bi[1] { | 2926 | + let err = if ai[1] == bi[1] { |
| 2620 | - return Err(format!( | 2927 | + format!("{}.err", tmp) |
| 2621 | - "`?` would need `From<{}> for {}`: an error-type conversion \ | 2928 | + } else { |
| 2622 | - is not implemented, and assuming it is the identity would \ | 2929 | + let key = (type_name(&ai[1]), type_name(&bi[1])); |
| 2623 | - be a guess", | 2930 | + let f = self.from_impls.get(&key).cloned().ok_or_else(|| { |
| 2624 | - ai[1].render(), | 2931 | + format!( |
| 2625 | - bi[1].render() | 2932 | + "`?` needs `From<{}> for {}` to convert the error, and \ |
| 2626 | - )); | 2933 | + no such `impl` is in scope; assuming the conversion is \ |
| 2627 | - } | 2934 | + the identity would be a guess", |
| 2935 | + key.0, key.1 | ||
| 2936 | + ) | ||
| 2937 | + })?; | ||
| 2938 | + format!("{}({}.err)", f, tmp) | ||
| 2939 | + }; | ||
| 2628 | self.line(&format!("if not {}.ok:", tmp)); | 2940 | self.line(&format!("if not {}.ok:", tmp)); |
| 2629 | self.line(&format!( | 2941 | self.line(&format!( |
| 2630 | - " return rsErr[{}, {}]({}.err)", | 2942 | + " return rsErr[{}, {}]({})", |
| 2631 | bi[0].render(), | 2943 | bi[0].render(), |
| 2632 | bi[1].render(), | 2944 | bi[1].render(), |
| 2633 | - tmp | 2945 | + err |
| 2634 | )); | 2946 | )); |
| 2635 | Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone()))) | 2947 | Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone()))) |
| 2636 | } | 2948 | } |
| @@ -2655,9 +2967,10 @@ impl Lowerer { | |||
| 2655 | return Err("only calls to named functions are supported".into()); | 2967 | return Err("only calls to named functions are supported".into()); |
| 2656 | }; | 2968 | }; |
| 2657 | let name = path_name(&p.path); | 2969 | let name = path_name(&p.path); |
| 2658 | - let ptys: Vec<Nim> = self | 2970 | + let target = self.resolve_fn(&p.path); |
| 2659 | - .fns | 2971 | + let ptys: Vec<Nim> = target |
| 2660 | - .get(&name) | 2972 | + .as_ref() |
| 2973 | + .and_then(|k| self.fns.get(k)) | ||
| 2661 | .map(|s| s.params.clone()) | 2974 | .map(|s| s.params.clone()) |
| 2662 | .unwrap_or_default(); | 2975 | .unwrap_or_default(); |
| 2663 | let mut args = Vec::new(); | 2976 | let mut args = Vec::new(); |
| @@ -2707,6 +3020,15 @@ impl Lowerer { | |||
| 2707 | _ => {} | 3020 | _ => {} |
| 2708 | } | 3021 | } |
| 2709 | 3022 | ||
| 3023 | + // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a | ||
| 3024 | + // string view; no copy, no validation, same memory. | ||
| 3025 | + if name == "from_utf8_unchecked" && codes.len() == 1 { | ||
| 3026 | + return Ok(Val::new( | ||
| 3027 | + format!("rsStrView({})", codes[0]), | ||
| 3028 | + Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))), | ||
| 3029 | + )); | ||
| 3030 | + } | ||
| 3031 | + | ||
| 2710 | // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. | 3032 | // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. |
| 2711 | if let Some((def, v)) = self.resolve_variant(&p.path) { | 3033 | if let Some((def, v)) = self.resolve_variant(&p.path) { |
| 2712 | return Ok(Val::new( | 3034 | return Ok(Val::new( |
| @@ -2725,17 +3047,18 @@ impl Lowerer { | |||
| 2725 | Some((*ret).clone()), | 3047 | Some((*ret).clone()), |
| 2726 | )); | 3048 | )); |
| 2727 | } | 3049 | } |
| 2728 | - let ret = self.fns.get(&name).map(|s| s.ret.clone()); | 3050 | + let ret = target.as_ref().and_then(|k| self.fns.get(k)).map(|s| s.ret.clone()); |
| 2729 | if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) { | 3051 | if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) { |
| 2730 | return Err(format!( | 3052 | return Err(format!( |
| 2731 | "call to unknown function `{name}`; only functions defined in \ | 3053 | "call to unknown function `{name}`; only functions defined in \ |
| 2732 | this file and the supported standard-library subset can be lowered" | 3054 | this file and the supported standard-library subset can be lowered" |
| 2733 | )); | 3055 | )); |
| 2734 | } | 3056 | } |
| 2735 | - Ok(Val::new( | 3057 | + let nim = match &target { |
| 2736 | - format!("{}({})", ident(&name), codes.join(", ")), | 3058 | + Some((m, n)) => self.fn_name(m, n), |
| 2737 | - ret, | 3059 | + None => ident(&name), |
| 2738 | - )) | 3060 | + }; |
| 3061 | + Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret)) | ||
| 2739 | } | 3062 | } |
| 2740 | 3063 | ||
| 2741 | fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> { | 3064 | fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> { |
| @@ -2768,6 +3091,7 @@ impl Lowerer { | |||
| 2768 | && matches!(m.args.first(), Some(Expr::Range(_))) | 3091 | && matches!(m.args.first(), Some(Expr::Range(_))) |
| 2769 | { | 3092 | { |
| 2770 | let Some(Expr::Range(r)) = m.args.first() else { unreachable!() }; | 3093 | let Some(Expr::Range(r)) = m.args.first() else { unreachable!() }; |
| 3094 | + let (code, base, blen, belem) = self.slice_parts(&m.receiver)?; | ||
| 2771 | let lo = match &r.start { | 3095 | let lo = match &r.start { |
| 2772 | Some(e) => format!("int({})", self.expr(e)?.code), | 3096 | Some(e) => format!("int({})", self.expr(e)?.code), |
| 2773 | None => "0".into(), | 3097 | None => "0".into(), |
| @@ -2779,9 +3103,18 @@ impl Lowerer { | |||
| 2779 | (Some(e), syn::RangeLimits::Closed(_)) => { | 3103 | (Some(e), syn::RangeLimits::Closed(_)) => { |
| 2780 | format!("(int({}) - {} + 1)", self.expr(e)?.code, lo) | 3104 | format!("(int({}) - {} + 1)", self.expr(e)?.code, lo) |
| 2781 | } | 3105 | } |
| 2782 | - (None, _) => format!("({}.len - {})", recv.code, lo), | 3106 | + (None, _) => format!("({} - {})", blen, lo), |
| 2783 | }; | 3107 | }; |
| 2784 | - let elem = elem_of(&rt0).ok_or("cannot infer the element type of this slice")?; | 3108 | + // Hoisted, so the bounds are computed once -- as Rust computes |
| 3109 | + // them once -- and cannot be re-evaluated later in a scope where | ||
| 3110 | + // the names they mention have been shadowed by a loop pattern. | ||
| 3111 | + let off_t = self.fresh("Off"); | ||
| 3112 | + let len_t = self.fresh("Len"); | ||
| 3113 | + self.line(&format!("let {}: int = {} + {}", off_t, base, lo)); | ||
| 3114 | + self.line(&format!("let {}: int = {}", len_t, len)); | ||
| 3115 | + let elem = belem | ||
| 3116 | + .or_else(|| elem_of(&rt0)) | ||
| 3117 | + .ok_or("cannot infer the element type of this slice")?; | ||
| 2785 | let mut v = Val::new( | 3118 | let mut v = Val::new( |
| 2786 | String::new(), | 3119 | String::new(), |
| 2787 | Some(Nim::Named( | 3120 | Some(Nim::Named( |
| @@ -2789,16 +3122,32 @@ impl Lowerer { | |||
| 2789 | vec![Nim::OpenArray(Box::new(elem.clone()))], | 3122 | vec![Nim::OpenArray(Box::new(elem.clone()))], |
| 2790 | )), | 3123 | )), |
| 2791 | ); | 3124 | ); |
| 2792 | - v.guard = Some(format!("({} + {} <= {}.len)", lo, len, recv.code)); | 3125 | + v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen)); |
| 2793 | v.window = Some(Alias::Window { | 3126 | v.window = Some(Alias::Window { |
| 2794 | - code: recv.code.clone(), | 3127 | + code, |
| 2795 | - off: lo, | 3128 | + off: off_t, |
| 2796 | - len, | 3129 | + len: len_t, |
| 2797 | elem: Some(elem), | 3130 | elem: Some(elem), |
| 2798 | }); | 3131 | }); |
| 2799 | return Ok(v); | 3132 | return Ok(v); |
| 2800 | } | 3133 | } |
| 2801 | 3134 | ||
| 3135 | + // `.map`/`.and_then` over an `Option`/`Result` take a closure whose | ||
| 3136 | + // parameter type comes from the receiver, so they are handled before | ||
| 3137 | + // the arguments are lowered. The closure is expanded inline, with its | ||
| 3138 | + // parameter aliased to the payload: that keeps the whole thing an | ||
| 3139 | + // expression and avoids handing a view to a generic proc. | ||
| 3140 | + if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 { | ||
| 3141 | + if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) = | ||
| 3142 | + (recv.ty.clone(), &m.args[0]) | ||
| 3143 | + { | ||
| 3144 | + if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2) | ||
| 3145 | + { | ||
| 3146 | + return self.map_closure(&name, &recv, &kind, &targs, c); | ||
| 3147 | + } | ||
| 3148 | + } | ||
| 3149 | + } | ||
| 3150 | + | ||
| 2802 | // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's | 3151 | // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's |
| 2803 | // own type; `v.push(e)` takes the element type. | 3152 | // own type; `v.push(e)` takes the element type. |
| 2804 | let arg_want = match (name.as_str(), &recv.ty) { | 3153 | let arg_want = match (name.as_str(), &recv.ty) { |
| @@ -2967,6 +3316,35 @@ impl Lowerer { | |||
| 2967 | 3316 | ||
| 2968 | // -------------------------------------------------------------- macros | 3317 | // -------------------------------------------------------------- macros |
| 2969 | 3318 | ||
| 3319 | + /// The element type of a `vec![..]`, from its first element. | ||
| 3320 | + fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> { | ||
| 3321 | + let body = mac.tokens.to_string(); | ||
| 3322 | + if body.trim().is_empty() { | ||
| 3323 | + return Ok(None); | ||
| 3324 | + } | ||
| 3325 | + let first: Option<Expr> = if body.contains(';') { | ||
| 3326 | + // The whole body must be consumed or the parse fails, so the | ||
| 3327 | + // length is parsed too even though only the element is wanted. | ||
| 3328 | + mac.parse_body_with(|input: syn::parse::ParseStream| { | ||
| 3329 | + let v: Expr = input.parse()?; | ||
| 3330 | + input.parse::<syn::Token![;]>()?; | ||
| 3331 | + let _len: Expr = input.parse()?; | ||
| 3332 | + Ok(v) | ||
| 3333 | + }) | ||
| 3334 | + .ok() | ||
| 3335 | + } else { | ||
| 3336 | + mac.parse_body_with( | ||
| 3337 | + syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated, | ||
| 3338 | + ) | ||
| 3339 | + .ok() | ||
| 3340 | + .and_then(|p| p.into_iter().next()) | ||
| 3341 | + }; | ||
| 3342 | + match first { | ||
| 3343 | + Some(e) => Ok(self.expr(&e)?.ty), | ||
| 3344 | + None => Ok(None), | ||
| 3345 | + } | ||
| 3346 | + } | ||
| 3347 | + | ||
| 2970 | fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> { | 3348 | fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> { |
| 2971 | let name = path_name(&mac.path); | 3349 | let name = path_name(&mac.path); |
| 2972 | match name.as_str() { | 3350 | match name.as_str() { |
| @@ -3030,16 +3408,18 @@ impl Lowerer { | |||
| 3030 | Ok((v, n)) | 3408 | Ok((v, n)) |
| 3031 | }) | 3409 | }) |
| 3032 | .map_err(|e| format!("vec![elem; n]: {e}"))?; | 3410 | .map_err(|e| format!("vec![elem; n]: {e}"))?; |
| 3033 | - let v = self.expr(&v)?; | 3411 | + let want = self.vec_expect.clone(); |
| 3412 | + let v = self.expr_at(&v, want.as_ref())?; | ||
| 3034 | let n = self.expr(&n)?; | 3413 | let n = self.expr(&n)?; |
| 3035 | return Ok(format!("newSeqWith(int({}), {})", n.code, v.code)); | 3414 | return Ok(format!("newSeqWith(int({}), {})", n.code, v.code)); |
| 3036 | } | 3415 | } |
| 3037 | let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac | 3416 | let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac |
| 3038 | .parse_body_with(syn::punctuated::Punctuated::parse_terminated) | 3417 | .parse_body_with(syn::punctuated::Punctuated::parse_terminated) |
| 3039 | .map_err(|e| format!("vec!: {e}"))?; | 3418 | .map_err(|e| format!("vec!: {e}"))?; |
| 3419 | + let want = self.vec_expect.clone(); | ||
| 3040 | let mut parts = Vec::new(); | 3420 | let mut parts = Vec::new(); |
| 3041 | for e in &elems { | 3421 | for e in &elems { |
| 3042 | - parts.push(self.expr(e)?.code); | 3422 | + parts.push(self.expr_at(e, want.as_ref())?.code); |
| 3043 | } | 3423 | } |
| 3044 | Ok(format!("@[{}]", parts.join(", "))) | 3424 | Ok(format!("@[{}]", parts.join(", "))) |
| 3045 | } | 3425 | } |
| @@ -3231,13 +3611,37 @@ fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type | |||
| 3231 | /// models with a view rather than with an owned copy. | 3611 | /// models with a view rather than with an owned copy. |
| 3232 | fn returns_borrow(t: &syn::Type) -> bool { | 3612 | fn returns_borrow(t: &syn::Type) -> bool { |
| 3233 | match t { | 3613 | match t { |
| 3234 | - syn::Type::Reference(r) => matches!(&*r.elem, syn::Type::Slice(_)), | 3614 | + syn::Type::Reference(r) => match &*r.elem { |
| 3615 | + syn::Type::Slice(_) => true, | ||
| 3616 | + // `&str` is a borrow of someone else's bytes too, and returning it | ||
| 3617 | + // means returning a view, not an owned string. | ||
| 3618 | + syn::Type::Path(p) => p.path.is_ident("str"), | ||
| 3619 | + _ => false, | ||
| 3620 | + }, | ||
| 3235 | syn::Type::Paren(p) => returns_borrow(&p.elem), | 3621 | syn::Type::Paren(p) => returns_borrow(&p.elem), |
| 3236 | syn::Type::Group(g) => returns_borrow(&g.elem), | 3622 | syn::Type::Group(g) => returns_borrow(&g.elem), |
| 3237 | _ => false, | 3623 | _ => false, |
| 3238 | } | 3624 | } |
| 3239 | } | 3625 | } |
| 3240 | 3626 | ||
| 3627 | +/// The module a `use` prefix names. `crate`, `self` and `super` all resolve | ||
| 3628 | +/// to the crate root, which is where a flattened module's items live unless | ||
| 3629 | +/// they came from one of the extra input files. | ||
| 3630 | +fn module_of(prefix: &[String]) -> String { | ||
| 3631 | + match prefix.last() { | ||
| 3632 | + Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(), | ||
| 3633 | + _ => String::new(), | ||
| 3634 | + } | ||
| 3635 | +} | ||
| 3636 | + | ||
| 3637 | +/// The first type argument of an `Option[T]` / `Result[T, E]`. | ||
| 3638 | +fn elem_arg(t: &Nim) -> Nim { | ||
| 3639 | + match t { | ||
| 3640 | + Nim::Named(_, a) if !a.is_empty() => a[0].clone(), | ||
| 3641 | + other => other.clone(), | ||
| 3642 | + } | ||
| 3643 | +} | ||
| 3644 | + | ||
| 3241 | /// The element type of a sequence-like Nim type. | 3645 | /// The element type of a sequence-like Nim type. |
| 3242 | fn elem_of(t: &Option<Nim>) -> Option<Nim> { | 3646 | fn elem_of(t: &Option<Nim>) -> Option<Nim> { |
| 3243 | match t { | 3647 | match t { |
| @@ -3371,7 +3775,6 @@ fn item_kind(i: &Item) -> &'static str { | |||
| 3371 | 3775 | ||
| 3372 | fn expr_kind(e: &Expr) -> &'static str { | 3776 | fn expr_kind(e: &Expr) -> &'static str { |
| 3373 | match e { | 3777 | match e { |
| 3374 | - Expr::Closure(_) => "closure", | ||
| 3375 | Expr::Async(_) => "`async` block", | 3778 | Expr::Async(_) => "`async` block", |
| 3376 | Expr::Await(_) => "`.await`", | 3779 | Expr::Await(_) => "`.await`", |
| 3377 | Expr::Try(_) => "`?`", | 3780 | Expr::Try(_) => "`?`", |
modified
src/main.rs +15 -5 | @@ -67,15 +67,25 @@ fn run() -> Result<(), String> { | ||
| 67 | 67 | // inside a single output file, so the items are flattened in argument |
| 68 | 68 | // order. A name collision between two files is a Nim compile error, which |
| 69 | 69 | // is a loud failure rather than a silently shadowed definition. |
| 70 | - let mut items = Vec::new(); | |
| 71 | - for input in &inputs { | |
| 70 | + // The first input is the crate root; each later one is a module named by | |
| 71 | + // its file stem. That is what keeps `lower::decode` and `mixed::decode` | |
| 72 | + // apart once everything is flattened into a single Nim module. | |
| 73 | + let mut files = Vec::new(); | |
| 74 | + for (i, input) in inputs.iter().enumerate() { | |
| 72 | 75 | let src = std::fs::read_to_string(input) |
| 73 | 76 | .map_err(|e| format!("cannot read {}: {e}", input.display()))?; |
| 74 | 77 | let parsed: syn::File = syn::parse_file(&src) |
| 75 | 78 | .map_err(|e| format!("{}: parse error: {e}", input.display()))?; |
| 76 | - items.extend(parsed.items); | |
| 79 | + let module = if i == 0 { | |
| 80 | + String::new() | |
| 81 | + } else { | |
| 82 | + input | |
| 83 | + .file_stem() | |
| 84 | + .map(|s| s.to_string_lossy().into_owned()) | |
| 85 | + .unwrap_or_default() | |
| 86 | + }; | |
| 87 | + files.push((module, parsed)); | |
| 77 | 88 | } |
| 78 | - let file = syn::File { shebang: None, frontmatter: None, attrs: Vec::new(), items }; | |
| 79 | 89 | |
| 80 | 90 | let mut lowerer = lower::Lowerer::new(); |
| 81 | 91 | lowerer.features = features; |
| @@ -84,7 +94,7 @@ fn run() -> Result<(), String> { | ||
| 84 | 94 | .iter() |
| 85 | 95 | .filter_map(|p| p.file_stem().map(|s| s.to_string_lossy().into_owned())) |
| 86 | 96 | .collect(); |
| 87 | - let nim = lowerer.lower_file(&file)?; | |
| 97 | + let nim = lowerer.lower_file(&files)?; | |
| 88 | 98 | |
| 89 | 99 | match output { |
| 90 | 100 | Some(p) => std::fs::write(&p, nim) |
| @@ -67,15 +67,25 @@ fn run() -> Result<(), String> { | |||
| 67 | // inside a single output file, so the items are flattened in argument | 67 | // inside a single output file, so the items are flattened in argument |
| 68 | // order. A name collision between two files is a Nim compile error, which | 68 | // order. A name collision between two files is a Nim compile error, which |
| 69 | // is a loud failure rather than a silently shadowed definition. | 69 | // is a loud failure rather than a silently shadowed definition. |
| 70 | - let mut items = Vec::new(); | 70 | + // The first input is the crate root; each later one is a module named by |
| 71 | - for input in &inputs { | 71 | + // its file stem. That is what keeps `lower::decode` and `mixed::decode` |
| 72 | + // apart once everything is flattened into a single Nim module. | ||
| 73 | + let mut files = Vec::new(); | ||
| 74 | + for (i, input) in inputs.iter().enumerate() { | ||
| 72 | let src = std::fs::read_to_string(input) | 75 | let src = std::fs::read_to_string(input) |
| 73 | .map_err(|e| format!("cannot read {}: {e}", input.display()))?; | 76 | .map_err(|e| format!("cannot read {}: {e}", input.display()))?; |
| 74 | let parsed: syn::File = syn::parse_file(&src) | 77 | let parsed: syn::File = syn::parse_file(&src) |
| 75 | .map_err(|e| format!("{}: parse error: {e}", input.display()))?; | 78 | .map_err(|e| format!("{}: parse error: {e}", input.display()))?; |
| 76 | - items.extend(parsed.items); | 79 | + let module = if i == 0 { |
| 80 | + String::new() | ||
| 81 | + } else { | ||
| 82 | + input | ||
| 83 | + .file_stem() | ||
| 84 | + .map(|s| s.to_string_lossy().into_owned()) | ||
| 85 | + .unwrap_or_default() | ||
| 86 | + }; | ||
| 87 | + files.push((module, parsed)); | ||
| 77 | } | 88 | } |
| 78 | - let file = syn::File { shebang: None, frontmatter: None, attrs: Vec::new(), items }; | ||
| 79 | 89 | ||
| 80 | let mut lowerer = lower::Lowerer::new(); | 90 | let mut lowerer = lower::Lowerer::new(); |
| 81 | lowerer.features = features; | 91 | lowerer.features = features; |
| @@ -84,7 +94,7 @@ fn run() -> Result<(), String> { | |||
| 84 | .iter() | 94 | .iter() |
| 85 | .filter_map(|p| p.file_stem().map(|s| s.to_string_lossy().into_owned())) | 95 | .filter_map(|p| p.file_stem().map(|s| s.to_string_lossy().into_owned())) |
| 86 | .collect(); | 96 | .collect(); |
| 87 | - let nim = lowerer.lower_file(&file)?; | 97 | + let nim = lowerer.lower_file(&files)?; |
| 88 | 98 | ||
| 89 | match output { | 99 | match output { |
| 90 | Some(p) => std::fs::write(&p, nim) | 100 | Some(p) => std::fs::write(&p, nim) |
modified
src/prelude.nim +16 -0 | @@ -152,6 +152,22 @@ proc rsPad*(s: string, width: int, zero: bool): string = | ||
| 152 | 152 | else: |
| 153 | 153 | repeat(' ', fill) & s |
| 154 | 154 | |
| 155 | +proc rsStrView*(b: openArray[uint8]): openArray[char] = | |
| 156 | + ## Rust's `str::from_utf8_unchecked` reinterprets a byte slice as a string | |
| 157 | + ## slice without copying or validating. Nim's `char` and `uint8` are both one | |
| 158 | + ## byte, so the same view is handed back over the same memory -- writes | |
| 159 | + ## through the original are visible here, as they are in Rust. | |
| 160 | + if b.len == 0: | |
| 161 | + result = toOpenArray(cast[ptr UncheckedArray[char]](nil), 0, -1) | |
| 162 | + else: | |
| 163 | + result = toOpenArray(cast[ptr UncheckedArray[char]](unsafeAddr b[0]), 0, b.len - 1) | |
| 164 | + | |
| 165 | +proc rsDisplay*(x: openArray[char]): string = | |
| 166 | + result = newStringOfCap(x.len) | |
| 167 | + for c in x: result.add(c) | |
| 168 | + | |
| 169 | +proc rsDebug*(x: openArray[char]): string = rsDebug(rsDisplay(x)) | |
| 170 | + | |
| 155 | 171 | proc rsBytes*(s: string): seq[uint8] = |
| 156 | 172 | ## Rust's `str::as_bytes` is a view of the UTF-8 encoding; Nim's `string` is |
| 157 | 173 | ## already those bytes, so this is a reinterpretation, not a conversion. |
| @@ -152,6 +152,22 @@ proc rsPad*(s: string, width: int, zero: bool): string = | |||
| 152 | else: | 152 | else: |
| 153 | repeat(' ', fill) & s | 153 | repeat(' ', fill) & s |
| 154 | 154 | ||
| 155 | +proc rsStrView*(b: openArray[uint8]): openArray[char] = | ||
| 156 | + ## Rust's `str::from_utf8_unchecked` reinterprets a byte slice as a string | ||
| 157 | + ## slice without copying or validating. Nim's `char` and `uint8` are both one | ||
| 158 | + ## byte, so the same view is handed back over the same memory -- writes | ||
| 159 | + ## through the original are visible here, as they are in Rust. | ||
| 160 | + if b.len == 0: | ||
| 161 | + result = toOpenArray(cast[ptr UncheckedArray[char]](nil), 0, -1) | ||
| 162 | + else: | ||
| 163 | + result = toOpenArray(cast[ptr UncheckedArray[char]](unsafeAddr b[0]), 0, b.len - 1) | ||
| 164 | + | ||
| 165 | +proc rsDisplay*(x: openArray[char]): string = | ||
| 166 | + result = newStringOfCap(x.len) | ||
| 167 | + for c in x: result.add(c) | ||
| 168 | + | ||
| 169 | +proc rsDebug*(x: openArray[char]): string = rsDebug(rsDisplay(x)) | ||
| 170 | + | ||
| 155 | proc rsBytes*(s: string): seq[uint8] = | 171 | proc rsBytes*(s: string): seq[uint8] = |
| 156 | ## Rust's `str::as_bytes` is a view of the UTF-8 encoding; Nim's `string` is | 172 | ## Rust's `str::as_bytes` is a view of the UTF-8 encoding; Nim's `string` is |
| 157 | ## already those bytes, so this is a reinterpretation, not a conversion. | 173 | ## already those bytes, so this is a reinterpretation, not a conversion. |
modified
src/ty.rs +13 -4 | @@ -16,8 +16,9 @@ pub enum Nim { | ||
| 16 | 16 | Tuple(Vec<Nim>), |
| 17 | 17 | Named(String, Vec<Nim>), |
| 18 | 18 | Var(Box<Nim>), |
| 19 | - /// `impl Fn(A) -> B` / `fn(A) -> B`. `nimcall` is the default calling | |
| 20 | - /// convention for a top-level proc, which is what Rust passes here. | |
| 19 | + /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling | |
| 20 | + /// convention (`closure`), which accepts both a plain top-level proc and | |
| 21 | + /// a closure that captures -- and Rust's `impl Fn` accepts both too. | |
| 21 | 22 | Proc(Vec<Nim>, Box<Nim>), |
| 22 | 23 | Unit, |
| 23 | 24 | } |
| @@ -48,8 +49,8 @@ impl Nim { | ||
| 48 | 49 | .map(|(i, t)| format!("a{}: {}", i, t.render())) |
| 49 | 50 | .collect(); |
| 50 | 51 | match &**ret { |
| 51 | - Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")), | |
| 52 | - r => format!("proc ({}): {} {{.nimcall.}}", inner.join(", "), r.render()), | |
| 52 | + Nim::Unit => format!("proc ({})", inner.join(", ")), | |
| 53 | + r => format!("proc ({}): {}", inner.join(", "), r.render()), | |
| 53 | 54 | } |
| 54 | 55 | } |
| 55 | 56 | Nim::Unit => "void".into(), |
| @@ -187,6 +188,14 @@ pub fn map(t: &Type) -> Result<Nim, String> { | ||
| 187 | 188 | // &T is a value in Nim; &mut T becomes a `var` parameter. The caller |
| 188 | 189 | // decides whether a `var` is legal in the position it is used. |
| 189 | 190 | Type::Reference(r) => { |
| 191 | + // `&str` is a borrowed view of characters, not an owned string. | |
| 192 | + // Nim accepts a `string` argument for an `openArray[char]` | |
| 193 | + // parameter, so a literal still passes straight through. | |
| 194 | + if let Type::Path(p) = &*r.elem { | |
| 195 | + if p.path.is_ident("str") { | |
| 196 | + return Ok(Nim::OpenArray(Box::new(Nim::Prim("char".into())))); | |
| 197 | + } | |
| 198 | + } | |
| 190 | 199 | let inner = map(&r.elem)?; |
| 191 | 200 | if r.mutability.is_some() { |
| 192 | 201 | Ok(Nim::Var(Box::new(inner))) |
| @@ -16,8 +16,9 @@ pub enum Nim { | |||
| 16 | Tuple(Vec<Nim>), | 16 | Tuple(Vec<Nim>), |
| 17 | Named(String, Vec<Nim>), | 17 | Named(String, Vec<Nim>), |
| 18 | Var(Box<Nim>), | 18 | Var(Box<Nim>), |
| 19 | - /// `impl Fn(A) -> B` / `fn(A) -> B`. `nimcall` is the default calling | 19 | + /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling |
| 20 | - /// convention for a top-level proc, which is what Rust passes here. | 20 | + /// convention (`closure`), which accepts both a plain top-level proc and |
| 21 | + /// a closure that captures -- and Rust's `impl Fn` accepts both too. | ||
| 21 | Proc(Vec<Nim>, Box<Nim>), | 22 | Proc(Vec<Nim>, Box<Nim>), |
| 22 | Unit, | 23 | Unit, |
| 23 | } | 24 | } |
| @@ -48,8 +49,8 @@ impl Nim { | |||
| 48 | .map(|(i, t)| format!("a{}: {}", i, t.render())) | 49 | .map(|(i, t)| format!("a{}: {}", i, t.render())) |
| 49 | .collect(); | 50 | .collect(); |
| 50 | match &**ret { | 51 | match &**ret { |
| 51 | - Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")), | 52 | + Nim::Unit => format!("proc ({})", inner.join(", ")), |
| 52 | - r => format!("proc ({}): {} {{.nimcall.}}", inner.join(", "), r.render()), | 53 | + r => format!("proc ({}): {}", inner.join(", "), r.render()), |
| 53 | } | 54 | } |
| 54 | } | 55 | } |
| 55 | Nim::Unit => "void".into(), | 56 | Nim::Unit => "void".into(), |
| @@ -187,6 +188,14 @@ pub fn map(t: &Type) -> Result<Nim, String> { | |||
| 187 | // &T is a value in Nim; &mut T becomes a `var` parameter. The caller | 188 | // &T is a value in Nim; &mut T becomes a `var` parameter. The caller |
| 188 | // decides whether a `var` is legal in the position it is used. | 189 | // decides whether a `var` is legal in the position it is used. |
| 189 | Type::Reference(r) => { | 190 | Type::Reference(r) => { |
| 191 | + // `&str` is a borrowed view of characters, not an owned string. | ||
| 192 | + // Nim accepts a `string` argument for an `openArray[char]` | ||
| 193 | + // parameter, so a literal still passes straight through. | ||
| 194 | + if let Type::Path(p) = &*r.elem { | ||
| 195 | + if p.path.is_ident("str") { | ||
| 196 | + return Ok(Nim::OpenArray(Box::new(Nim::Prim("char".into())))); | ||
| 197 | + } | ||
| 198 | + } | ||
| 190 | let inner = map(&r.elem)?; | 199 | let inner = map(&r.elem)?; |
| 191 | if r.mutability.is_some() { | 200 | if r.mutability.is_some() { |
| 192 | Ok(Nim::Var(Box::new(inner))) | 201 | Ok(Nim::Var(Box::new(inner))) |
added
tests/cases/026-base16ct-crate/lower.rs +78 -0 | new file mode 100644 | ||
| @@ -0,0 +1,78 @@ | ||
| 1 | +use crate::{Error, decode_inner, encoded_len}; | |
| 2 | +#[cfg(feature = "alloc")] | |
| 3 | +use crate::{String, Vec, decoded_len}; | |
| 4 | + | |
| 5 | +/// Decode a lower 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 lower 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 | +/// Encode the input byte slice as lower Base16. | |
| 19 | +/// | |
| 20 | +/// Writes the result into the provided destination slice, returning an | |
| 21 | +/// ASCII-encoded lower Base16 (hex) string value. | |
| 22 | +pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a [u8], Error> { | |
| 23 | + let dst = dst | |
| 24 | + .get_mut(..encoded_len(src)) | |
| 25 | + .ok_or(Error::InvalidLength)?; | |
| 26 | + for (src, dst) in src.iter().zip(dst.chunks_exact_mut(2)) { | |
| 27 | + dst[0] = encode_nibble(src >> 4); | |
| 28 | + dst[1] = encode_nibble(src & 0x0f); | |
| 29 | + } | |
| 30 | + Ok(dst) | |
| 31 | +} | |
| 32 | + | |
| 33 | +/// Encode input byte slice into a [`&str`] containing lower Base16 (hex). | |
| 34 | +pub fn encode_str<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, Error> { | |
| 35 | + encode(src, dst).map(|r| unsafe { core::str::from_utf8_unchecked(r) }) | |
| 36 | +} | |
| 37 | + | |
| 38 | +/// Encode input byte slice into a [`String`] containing lower Base16 (hex). | |
| 39 | +/// | |
| 40 | +/// # Panics | |
| 41 | +/// If `input` length is greater than `usize::MAX/2`. | |
| 42 | +#[cfg(feature = "alloc")] | |
| 43 | +pub fn encode_string(input: &[u8]) -> String { | |
| 44 | + let elen = encoded_len(input); | |
| 45 | + let mut dst = vec![0u8; elen]; | |
| 46 | + let res = encode(input, &mut dst).expect("dst length is correct"); | |
| 47 | + | |
| 48 | + debug_assert_eq!(elen, res.len()); | |
| 49 | + unsafe { String::from_utf8_unchecked(dst) } | |
| 50 | +} | |
| 51 | + | |
| 52 | +/// Decode a single nibble of lower hex | |
| 53 | +#[inline(always)] | |
| 54 | +fn decode_nibble(src: u8) -> u16 { | |
| 55 | + // 0-9 0x30-0x39 | |
| 56 | + // A-F 0x41-0x46 or a-f 0x61-0x66 | |
| 57 | + let byte = src as i16; | |
| 58 | + let mut ret: i16 = -1; | |
| 59 | + | |
| 60 | + // 0-9 0x30-0x39 | |
| 61 | + // if (byte > 0x2f && byte < 0x3a) ret += byte - 0x30 + 1; // -47 | |
| 62 | + ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47); | |
| 63 | + // a-f 0x61-0x66 | |
| 64 | + // if (byte > 0x60 && byte < 0x67) ret += byte - 0x61 + 10 + 1; // -86 | |
| 65 | + ret += (((0x60i16 - byte) & (byte - 0x67)) >> 8) & (byte - 86); | |
| 66 | + | |
| 67 | + ret as u16 | |
| 68 | +} | |
| 69 | + | |
| 70 | +/// Encode a single nibble of hex | |
| 71 | +#[inline(always)] | |
| 72 | +fn encode_nibble(src: u8) -> u8 { | |
| 73 | + let mut ret = src as i16 + 0x30; | |
| 74 | + // 0-9 0x30-0x39 | |
| 75 | + // a-f 0x61-0x66 | |
| 76 | + ret += ((0x39i16 - ret) >> 8) & (0x61i16 - 0x3a); | |
| 77 | + ret as u8 | |
| 78 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | +use crate::{Error, decode_inner, encoded_len}; | ||
| 2 | +#[cfg(feature = "alloc")] | ||
| 3 | +use crate::{String, Vec, decoded_len}; | ||
| 4 | + | ||
| 5 | +/// Decode a lower 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 lower 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 | +/// Encode the input byte slice as lower Base16. | ||
| 19 | +/// | ||
| 20 | +/// Writes the result into the provided destination slice, returning an | ||
| 21 | +/// ASCII-encoded lower Base16 (hex) string value. | ||
| 22 | +pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a [u8], Error> { | ||
| 23 | + let dst = dst | ||
| 24 | + .get_mut(..encoded_len(src)) | ||
| 25 | + .ok_or(Error::InvalidLength)?; | ||
| 26 | + for (src, dst) in src.iter().zip(dst.chunks_exact_mut(2)) { | ||
| 27 | + dst[0] = encode_nibble(src >> 4); | ||
| 28 | + dst[1] = encode_nibble(src & 0x0f); | ||
| 29 | + } | ||
| 30 | + Ok(dst) | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +/// Encode input byte slice into a [`&str`] containing lower Base16 (hex). | ||
| 34 | +pub fn encode_str<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, Error> { | ||
| 35 | + encode(src, dst).map(|r| unsafe { core::str::from_utf8_unchecked(r) }) | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +/// Encode input byte slice into a [`String`] containing lower Base16 (hex). | ||
| 39 | +/// | ||
| 40 | +/// # Panics | ||
| 41 | +/// If `input` length is greater than `usize::MAX/2`. | ||
| 42 | +#[cfg(feature = "alloc")] | ||
| 43 | +pub fn encode_string(input: &[u8]) -> String { | ||
| 44 | + let elen = encoded_len(input); | ||
| 45 | + let mut dst = vec![0u8; elen]; | ||
| 46 | + let res = encode(input, &mut dst).expect("dst length is correct"); | ||
| 47 | + | ||
| 48 | + debug_assert_eq!(elen, res.len()); | ||
| 49 | + unsafe { String::from_utf8_unchecked(dst) } | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +/// Decode a single nibble of lower hex | ||
| 53 | +#[inline(always)] | ||
| 54 | +fn decode_nibble(src: u8) -> u16 { | ||
| 55 | + // 0-9 0x30-0x39 | ||
| 56 | + // A-F 0x41-0x46 or a-f 0x61-0x66 | ||
| 57 | + let byte = src as i16; | ||
| 58 | + let mut ret: i16 = -1; | ||
| 59 | + | ||
| 60 | + // 0-9 0x30-0x39 | ||
| 61 | + // if (byte > 0x2f && byte < 0x3a) ret += byte - 0x30 + 1; // -47 | ||
| 62 | + ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47); | ||
| 63 | + // a-f 0x61-0x66 | ||
| 64 | + // if (byte > 0x60 && byte < 0x67) ret += byte - 0x61 + 10 + 1; // -86 | ||
| 65 | + ret += (((0x60i16 - byte) & (byte - 0x67)) >> 8) & (byte - 86); | ||
| 66 | + | ||
| 67 | + ret as u16 | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +/// Encode a single nibble of hex | ||
| 71 | +#[inline(always)] | ||
| 72 | +fn encode_nibble(src: u8) -> u8 { | ||
| 73 | + let mut ret = src as i16 + 0x30; | ||
| 74 | + // 0-9 0x30-0x39 | ||
| 75 | + // a-f 0x61-0x66 | ||
| 76 | + ret += ((0x39i16 - ret) >> 8) & (0x61i16 - 0x3a); | ||
| 77 | + ret as u8 | ||
| 78 | +} | ||
modified
tests/cases/026-base16ct-crate/main.rs +25 -3 | @@ -1,13 +1,16 @@ | ||
| 1 | 1 | //@ args: run |
| 2 | 2 | // base16ct 1.0.0, transpiled as a multi-file crate. |
| 3 | 3 | // |
| 4 | -// `error.rs` and `mixed.rs` are the crate's own files, byte-for-byte. | |
| 4 | +// `error.rs`, `lower.rs`, `upper.rs` and `mixed.rs` are the crate's own | |
| 5 | +// files, byte-for-byte. | |
| 5 | 6 | // This file carries `lib.rs`'s core -- `decoded_len`, `encoded_len` and |
| 6 | 7 | // `decode_inner` verbatim -- plus a driver, because the runner needs a `main`. |
| 7 | 8 | // The `alloc`-gated items are off, as they are by default in the crate. |
| 8 | 9 | |
| 9 | 10 | mod error; |
| 11 | +mod lower; | |
| 10 | 12 | mod mixed; |
| 13 | +mod upper; | |
| 11 | 14 | |
| 12 | 15 | pub use crate::error::{Error, Result}; |
| 13 | 16 | |
| @@ -65,6 +68,7 @@ fn show(tag: &str, r: Result<&[u8]>) { | ||
| 65 | 68 | fn main() { |
| 66 | 69 | let mut buf = [0u8; 16]; |
| 67 | 70 | |
| 71 | + show("lower", lower::decode(b"abcd1234", &mut buf)); | |
| 68 | 72 | show("mixed-l", mixed::decode(b"abcd1234", &mut buf)); |
| 69 | 73 | show("mixed-u", mixed::decode(b"ABCD1234", &mut buf)); |
| 70 | 74 | show("mixed-m", mixed::decode(b"abCD1234", &mut buf)); |
| @@ -76,8 +80,26 @@ fn main() { | ||
| 76 | 80 | let mut small = [0u8; 2]; |
| 77 | 81 | show("short-dst", mixed::decode(b"abcd1234", &mut small)); |
| 78 | 82 | |
| 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. | |
| 83 | + show("upper", upper::decode(b"ABCD1234", &mut buf)); | |
| 84 | + show("upper-rej", upper::decode(b"abcd1234", &mut buf)); | |
| 85 | + | |
| 86 | + let mut enc = [0u8; 8]; | |
| 87 | + show("encode", lower::encode(b"\xab\xcd\x12\x34", &mut enc)); | |
| 88 | + let mut encu = [0u8; 8]; | |
| 89 | + show("encode-up", upper::encode(b"\xab\xcd\x12\x34", &mut encu)); | |
| 90 | + | |
| 91 | + // `encode_str` is a closure over an `unsafe` block returning a borrowed | |
| 92 | + // `&str` -- a view of the bytes just written, not a copy. | |
| 93 | + let mut enc2 = [0u8; 8]; | |
| 94 | + match lower::encode_str(b"\xab\xcd\x12\x34", &mut enc2) { | |
| 95 | + Ok(s) => println!("encode_str ok {} len={}", s, s.len()), | |
| 96 | + Err(e) => println!("encode_str err {:?}", e), | |
| 97 | + } | |
| 98 | + let mut tiny = [0u8; 2]; | |
| 99 | + match lower::encode_str(b"\xab\xcd", &mut tiny) { | |
| 100 | + Ok(s) => println!("tiny ok {}", s), | |
| 101 | + Err(e) => println!("tiny err {:?} / {}", e, e), | |
| 102 | + } | |
| 81 | 103 | |
| 82 | 104 | println!("{} {}", decoded_len(b"abcd").unwrap(), encoded_len(b"\xab\xcd")); |
| 83 | 105 | } |
| @@ -1,13 +1,16 @@ | |||
| 1 | //@ args: run | 1 | //@ args: run |
| 2 | // base16ct 1.0.0, transpiled as a multi-file crate. | 2 | // base16ct 1.0.0, transpiled as a multi-file crate. |
| 3 | // | 3 | // |
| 4 | -// `error.rs` and `mixed.rs` are the crate's own files, byte-for-byte. | 4 | +// `error.rs`, `lower.rs`, `upper.rs` and `mixed.rs` are the crate's own |
| 5 | +// files, byte-for-byte. | ||
| 5 | // This file carries `lib.rs`'s core -- `decoded_len`, `encoded_len` and | 6 | // 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 | // `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 | // The `alloc`-gated items are off, as they are by default in the crate. |
| 8 | 9 | ||
| 9 | mod error; | 10 | mod error; |
| 11 | +mod lower; | ||
| 10 | mod mixed; | 12 | mod mixed; |
| 13 | +mod upper; | ||
| 11 | 14 | ||
| 12 | pub use crate::error::{Error, Result}; | 15 | pub use crate::error::{Error, Result}; |
| 13 | 16 | ||
| @@ -65,6 +68,7 @@ fn show(tag: &str, r: Result<&[u8]>) { | |||
| 65 | fn main() { | 68 | fn main() { |
| 66 | let mut buf = [0u8; 16]; | 69 | let mut buf = [0u8; 16]; |
| 67 | 70 | ||
| 71 | + show("lower", lower::decode(b"abcd1234", &mut buf)); | ||
| 68 | show("mixed-l", mixed::decode(b"abcd1234", &mut buf)); | 72 | show("mixed-l", mixed::decode(b"abcd1234", &mut buf)); |
| 69 | show("mixed-u", mixed::decode(b"ABCD1234", &mut buf)); | 73 | show("mixed-u", mixed::decode(b"ABCD1234", &mut buf)); |
| 70 | show("mixed-m", mixed::decode(b"abCD1234", &mut buf)); | 74 | show("mixed-m", mixed::decode(b"abCD1234", &mut buf)); |
| @@ -76,8 +80,26 @@ fn main() { | |||
| 76 | let mut small = [0u8; 2]; | 80 | let mut small = [0u8; 2]; |
| 77 | show("short-dst", mixed::decode(b"abcd1234", &mut small)); | 81 | show("short-dst", mixed::decode(b"abcd1234", &mut small)); |
| 78 | 82 | ||
| 79 | - // `lower::encode` is not here: `lower.rs` also defines `encode_str`, whose | 83 | + show("upper", upper::decode(b"ABCD1234", &mut buf)); |
| 80 | - // body is a closure over an `unsafe` block, and neither is implemented. | 84 | + show("upper-rej", upper::decode(b"abcd1234", &mut buf)); |
| 85 | + | ||
| 86 | + let mut enc = [0u8; 8]; | ||
| 87 | + show("encode", lower::encode(b"\xab\xcd\x12\x34", &mut enc)); | ||
| 88 | + let mut encu = [0u8; 8]; | ||
| 89 | + show("encode-up", upper::encode(b"\xab\xcd\x12\x34", &mut encu)); | ||
| 90 | + | ||
| 91 | + // `encode_str` is a closure over an `unsafe` block returning a borrowed | ||
| 92 | + // `&str` -- a view of the bytes just written, not a copy. | ||
| 93 | + let mut enc2 = [0u8; 8]; | ||
| 94 | + match lower::encode_str(b"\xab\xcd\x12\x34", &mut enc2) { | ||
| 95 | + Ok(s) => println!("encode_str ok {} len={}", s, s.len()), | ||
| 96 | + Err(e) => println!("encode_str err {:?}", e), | ||
| 97 | + } | ||
| 98 | + let mut tiny = [0u8; 2]; | ||
| 99 | + match lower::encode_str(b"\xab\xcd", &mut tiny) { | ||
| 100 | + Ok(s) => println!("tiny ok {}", s), | ||
| 101 | + Err(e) => println!("tiny err {:?} / {}", e, e), | ||
| 102 | + } | ||
| 81 | 103 | ||
| 82 | println!("{} {}", decoded_len(b"abcd").unwrap(), encoded_len(b"\xab\xcd")); | 104 | println!("{} {}", decoded_len(b"abcd").unwrap(), encoded_len(b"\xab\xcd")); |
| 83 | } | 105 | } |
added
tests/cases/026-base16ct-crate/upper.rs +78 -0 | new file mode 100644 | ||
| @@ -0,0 +1,78 @@ | ||
| 1 | +use crate::{Error, decode_inner, encoded_len}; | |
| 2 | +#[cfg(feature = "alloc")] | |
| 3 | +use crate::{String, Vec, decoded_len}; | |
| 4 | + | |
| 5 | +/// Decode an upper 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 an upper 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 | +/// Encode the input byte slice as upper Base16. | |
| 19 | +/// | |
| 20 | +/// Writes the result into the provided destination slice, returning an | |
| 21 | +/// ASCII-encoded upper Base16 (hex) string value. | |
| 22 | +pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a [u8], Error> { | |
| 23 | + let dst = dst | |
| 24 | + .get_mut(..encoded_len(src)) | |
| 25 | + .ok_or(Error::InvalidLength)?; | |
| 26 | + for (src, dst) in src.iter().zip(dst.chunks_exact_mut(2)) { | |
| 27 | + dst[0] = encode_nibble(src >> 4); | |
| 28 | + dst[1] = encode_nibble(src & 0x0f); | |
| 29 | + } | |
| 30 | + Ok(dst) | |
| 31 | +} | |
| 32 | + | |
| 33 | +/// Encode input byte slice into a [`&str`] containing upper Base16 (hex). | |
| 34 | +pub fn encode_str<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, Error> { | |
| 35 | + encode(src, dst).map(|r| unsafe { core::str::from_utf8_unchecked(r) }) | |
| 36 | +} | |
| 37 | + | |
| 38 | +/// Encode input byte slice into a [`String`] containing upper Base16 (hex). | |
| 39 | +/// | |
| 40 | +/// # Panics | |
| 41 | +/// If `input` length is greater than `usize::MAX/2`. | |
| 42 | +#[cfg(feature = "alloc")] | |
| 43 | +pub fn encode_string(input: &[u8]) -> String { | |
| 44 | + let elen = encoded_len(input); | |
| 45 | + let mut dst = vec![0u8; elen]; | |
| 46 | + let res = encode(input, &mut dst).expect("dst length is correct"); | |
| 47 | + | |
| 48 | + debug_assert_eq!(elen, res.len()); | |
| 49 | + unsafe { String::from_utf8_unchecked(dst) } | |
| 50 | +} | |
| 51 | + | |
| 52 | +/// Decode a single nibble of upper hex | |
| 53 | +#[inline(always)] | |
| 54 | +fn decode_nibble(src: u8) -> u16 { | |
| 55 | + // 0-9 0x30-0x39 | |
| 56 | + // A-F 0x41-0x46 or a-f 0x61-0x66 | |
| 57 | + let byte = src as i16; | |
| 58 | + let mut ret: i16 = -1; | |
| 59 | + | |
| 60 | + // 0-9 0x30-0x39 | |
| 61 | + // if (byte > 0x2f && byte < 0x3a) ret += byte - 0x30 + 1; // -47 | |
| 62 | + ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47); | |
| 63 | + // A-F 0x41-0x46 | |
| 64 | + // if (byte > 0x40 && byte < 0x47) ret += byte - 0x41 + 10 + 1; // -54 | |
| 65 | + ret += (((0x40i16 - byte) & (byte - 0x47)) >> 8) & (byte - 54); | |
| 66 | + | |
| 67 | + ret as u16 | |
| 68 | +} | |
| 69 | + | |
| 70 | +/// Encode a single nibble of hex | |
| 71 | +#[inline(always)] | |
| 72 | +fn encode_nibble(src: u8) -> u8 { | |
| 73 | + let mut ret = src as i16 + 0x30; | |
| 74 | + // 0-9 0x30-0x39 | |
| 75 | + // A-F 0x41-0x46 | |
| 76 | + ret += ((0x39i16 - ret) >> 8) & (0x41i16 - 0x3a); | |
| 77 | + ret as u8 | |
| 78 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | +use crate::{Error, decode_inner, encoded_len}; | ||
| 2 | +#[cfg(feature = "alloc")] | ||
| 3 | +use crate::{String, Vec, decoded_len}; | ||
| 4 | + | ||
| 5 | +/// Decode an upper 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 an upper 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 | +/// Encode the input byte slice as upper Base16. | ||
| 19 | +/// | ||
| 20 | +/// Writes the result into the provided destination slice, returning an | ||
| 21 | +/// ASCII-encoded upper Base16 (hex) string value. | ||
| 22 | +pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a [u8], Error> { | ||
| 23 | + let dst = dst | ||
| 24 | + .get_mut(..encoded_len(src)) | ||
| 25 | + .ok_or(Error::InvalidLength)?; | ||
| 26 | + for (src, dst) in src.iter().zip(dst.chunks_exact_mut(2)) { | ||
| 27 | + dst[0] = encode_nibble(src >> 4); | ||
| 28 | + dst[1] = encode_nibble(src & 0x0f); | ||
| 29 | + } | ||
| 30 | + Ok(dst) | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +/// Encode input byte slice into a [`&str`] containing upper Base16 (hex). | ||
| 34 | +pub fn encode_str<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, Error> { | ||
| 35 | + encode(src, dst).map(|r| unsafe { core::str::from_utf8_unchecked(r) }) | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +/// Encode input byte slice into a [`String`] containing upper Base16 (hex). | ||
| 39 | +/// | ||
| 40 | +/// # Panics | ||
| 41 | +/// If `input` length is greater than `usize::MAX/2`. | ||
| 42 | +#[cfg(feature = "alloc")] | ||
| 43 | +pub fn encode_string(input: &[u8]) -> String { | ||
| 44 | + let elen = encoded_len(input); | ||
| 45 | + let mut dst = vec![0u8; elen]; | ||
| 46 | + let res = encode(input, &mut dst).expect("dst length is correct"); | ||
| 47 | + | ||
| 48 | + debug_assert_eq!(elen, res.len()); | ||
| 49 | + unsafe { String::from_utf8_unchecked(dst) } | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +/// Decode a single nibble of upper hex | ||
| 53 | +#[inline(always)] | ||
| 54 | +fn decode_nibble(src: u8) -> u16 { | ||
| 55 | + // 0-9 0x30-0x39 | ||
| 56 | + // A-F 0x41-0x46 or a-f 0x61-0x66 | ||
| 57 | + let byte = src as i16; | ||
| 58 | + let mut ret: i16 = -1; | ||
| 59 | + | ||
| 60 | + // 0-9 0x30-0x39 | ||
| 61 | + // if (byte > 0x2f && byte < 0x3a) ret += byte - 0x30 + 1; // -47 | ||
| 62 | + ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47); | ||
| 63 | + // A-F 0x41-0x46 | ||
| 64 | + // if (byte > 0x40 && byte < 0x47) ret += byte - 0x41 + 10 + 1; // -54 | ||
| 65 | + ret += (((0x40i16 - byte) & (byte - 0x47)) >> 8) & (byte - 54); | ||
| 66 | + | ||
| 67 | + ret as u16 | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +/// Encode a single nibble of hex | ||
| 71 | +#[inline(always)] | ||
| 72 | +fn encode_nibble(src: u8) -> u8 { | ||
| 73 | + let mut ret = src as i16 + 0x30; | ||
| 74 | + // 0-9 0x30-0x39 | ||
| 75 | + // A-F 0x41-0x46 | ||
| 76 | + ret += ((0x39i16 - ret) >> 8) & (0x41i16 - 0x3a); | ||
| 77 | + ret as u8 | ||
| 78 | +} | ||
added
tests/cases/027-closures-and-unsafe.rs +45 -0 | new file mode 100644 | ||
| @@ -0,0 +1,45 @@ | ||
| 1 | +// `unsafe` is a permission marker, not a semantic change: it does not alter | |
| 2 | +// what the enclosed operations mean, so the block is transparent and each | |
| 3 | +// operation inside still goes through the ordinary lowering. | |
| 4 | +// | |
| 5 | +// Nim's closures capture by reference, as Rust's non-`move` closures do. | |
| 6 | + | |
| 7 | +fn apply(f: impl Fn(i32) -> i32, x: i32) -> i32 { | |
| 8 | + f(x) | |
| 9 | +} | |
| 10 | + | |
| 11 | +fn twice(x: i32) -> i32 { | |
| 12 | + x * 2 | |
| 13 | +} | |
| 14 | + | |
| 15 | +fn halve(n: i32) -> Result<i32, i32> { | |
| 16 | + if n % 2 == 0 { Ok(n / 2) } else { Err(n) } | |
| 17 | +} | |
| 18 | + | |
| 19 | +fn as_str(b: &[u8]) -> &str { | |
| 20 | + unsafe { core::str::from_utf8_unchecked(b) } | |
| 21 | +} | |
| 22 | + | |
| 23 | +fn main() { | |
| 24 | + let add_one = |x: i32| x + 1; | |
| 25 | + println!("{}", apply(add_one, 10)); | |
| 26 | + println!("{}", apply(twice, 10)); | |
| 27 | + println!("{}", apply(|x: i32| x * x, 7)); | |
| 28 | + | |
| 29 | + // A closure capturing an enclosing binding, by reference. | |
| 30 | + let base: i32 = 100; | |
| 31 | + let shift = |x: i32| x + base; | |
| 32 | + println!("{}", apply(shift, 5)); | |
| 33 | + | |
| 34 | + // `.map` over a Result: the closure's parameter type comes from the | |
| 35 | + // receiver, and the error branch is carried through untouched. | |
| 36 | + println!("{:?}", halve(8).map(|v| v * 10)); | |
| 37 | + println!("{:?}", halve(7).map(|v| v * 10)); | |
| 38 | + | |
| 39 | + // `&str` is a view of someone else's bytes, not a copy. | |
| 40 | + let bytes: Vec<u8> = vec![104, 105]; | |
| 41 | + println!("{} {}", as_str(&bytes), as_str(&bytes).len()); | |
| 42 | + | |
| 43 | + let n: i32 = unsafe { twice(21) }; | |
| 44 | + println!("{}", n); | |
| 45 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | +// `unsafe` is a permission marker, not a semantic change: it does not alter | ||
| 2 | +// what the enclosed operations mean, so the block is transparent and each | ||
| 3 | +// operation inside still goes through the ordinary lowering. | ||
| 4 | +// | ||
| 5 | +// Nim's closures capture by reference, as Rust's non-`move` closures do. | ||
| 6 | + | ||
| 7 | +fn apply(f: impl Fn(i32) -> i32, x: i32) -> i32 { | ||
| 8 | + f(x) | ||
| 9 | +} | ||
| 10 | + | ||
| 11 | +fn twice(x: i32) -> i32 { | ||
| 12 | + x * 2 | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +fn halve(n: i32) -> Result<i32, i32> { | ||
| 16 | + if n % 2 == 0 { Ok(n / 2) } else { Err(n) } | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +fn as_str(b: &[u8]) -> &str { | ||
| 20 | + unsafe { core::str::from_utf8_unchecked(b) } | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +fn main() { | ||
| 24 | + let add_one = |x: i32| x + 1; | ||
| 25 | + println!("{}", apply(add_one, 10)); | ||
| 26 | + println!("{}", apply(twice, 10)); | ||
| 27 | + println!("{}", apply(|x: i32| x * x, 7)); | ||
| 28 | + | ||
| 29 | + // A closure capturing an enclosing binding, by reference. | ||
| 30 | + let base: i32 = 100; | ||
| 31 | + let shift = |x: i32| x + base; | ||
| 32 | + println!("{}", apply(shift, 5)); | ||
| 33 | + | ||
| 34 | + // `.map` over a Result: the closure's parameter type comes from the | ||
| 35 | + // receiver, and the error branch is carried through untouched. | ||
| 36 | + println!("{:?}", halve(8).map(|v| v * 10)); | ||
| 37 | + println!("{:?}", halve(7).map(|v| v * 10)); | ||
| 38 | + | ||
| 39 | + // `&str` is a view of someone else's bytes, not a copy. | ||
| 40 | + let bytes: Vec<u8> = vec![104, 105]; | ||
| 41 | + println!("{} {}", as_str(&bytes), as_str(&bytes).len()); | ||
| 42 | + | ||
| 43 | + let n: i32 = unsafe { twice(21) }; | ||
| 44 | + println!("{}", n); | ||
| 45 | +} | ||
deleted
tests/cases/904-reject-closure.rs +0 -5 | deleted file mode 100644 | ||
| @@ -1,5 +0,0 @@ | ||
| 1 | -//@ reject: unsupported expression in value position: closure | |
| 2 | -fn main() { | |
| 3 | - let f = |x: i32| x + 1; | |
| 4 | - println!("{}", f(1)); | |
| 5 | -} | |
| deleted file mode 100644 | |||
| @@ -1,5 +0,0 @@ | |||
| 1 | -//@ reject: unsupported expression in value position: closure | ||
| 2 | -fn main() { | ||
| 3 | - let f = |x: i32| x + 1; | ||
| 4 | - println!("{}", f(1)); | ||
| 5 | -} | ||
added
tests/cases/904-reject-move-closure.rs +9 -0 | new file mode 100644 | ||
| @@ -0,0 +1,9 @@ | ||
| 1 | +//@ reject: captures by value | |
| 2 | +// Nim's closures capture by reference, as Rust's non-`move` closures do. A | |
| 3 | +// `move` closure is a different thing, so it is not lowered to the same | |
| 4 | +// construct. | |
| 5 | +fn main() { | |
| 6 | + let v: Vec<i32> = vec![1, 2, 3]; | |
| 7 | + let f = move || v.len(); | |
| 8 | + println!("{}", f()); | |
| 9 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | +//@ reject: captures by value | ||
| 2 | +// Nim's closures capture by reference, as Rust's non-`move` closures do. A | ||
| 3 | +// `move` closure is a different thing, so it is not lowered to the same | ||
| 4 | +// construct. | ||
| 5 | +fn main() { | ||
| 6 | + let v: Vec<i32> = vec![1, 2, 3]; | ||
| 7 | + let f = move || v.len(); | ||
| 8 | + println!("{}", f()); | ||
| 9 | +} | ||
modified
tests/multifile.rs +36 -11 | @@ -34,26 +34,51 @@ fn run(dir: &Path, args: &[&str]) -> (bool, String, String) { | ||
| 34 | 34 | #[test] |
| 35 | 35 | fn files_are_flattened_into_one_module() { |
| 36 | 36 | let d = work("multifile"); |
| 37 | + let root = d.join("root.rs"); | |
| 37 | 38 | let a = d.join("a.rs"); |
| 38 | - let b = d.join("b.rs"); | |
| 39 | 39 | fs::write(&a, "pub fn double(x: i32) -> i32 { x * 2 }\n").unwrap(); |
| 40 | 40 | fs::write( |
| 41 | - &b, | |
| 42 | - "fn main() { println!(\"{}\", double(21)); }\n", | |
| 41 | + &root, | |
| 42 | + "mod a;\nuse crate::a::double;\nfn main() { println!(\"{}\", double(21)); }\n", | |
| 43 | 43 | ) |
| 44 | 44 | .unwrap(); |
| 45 | 45 | |
| 46 | - // `double` is defined in a.rs and called from b.rs: it resolves only | |
| 47 | - // because both were passed in. | |
| 48 | - let (ok, out, err) = run(&d, &[a.to_str().unwrap(), b.to_str().unwrap()]); | |
| 46 | + // The first input is the crate root; `a.rs` becomes module `a`, and its | |
| 47 | + // items are emitted with that prefix so two modules may define the same | |
| 48 | + // name. | |
| 49 | + let (ok, out, err) = run(&d, &[root.to_str().unwrap(), a.to_str().unwrap()]); | |
| 49 | 50 | assert!(ok, "multi-file transpile failed: {err}"); |
| 50 | - assert!(out.contains("proc double"), "missing `double`:\n{out}"); | |
| 51 | + assert!(out.contains("proc a_double"), "missing `a_double`:\n{out}"); | |
| 51 | 52 | assert!(out.contains("proc main"), "missing `main`:\n{out}"); |
| 53 | + assert!(out.contains("a_double(21"), "call not qualified:\n{out}"); | |
| 52 | 54 | |
| 53 | - // b.rs alone must fail rather than emit a call to something undefined. | |
| 54 | - let (ok, _, err) = run(&d, &[b.to_str().unwrap()]); | |
| 55 | - assert!(!ok, "b.rs alone should not transpile"); | |
| 56 | - assert!(err.contains("unknown function `double`"), "unexpected: {err}"); | |
| 55 | + // The root alone must fail rather than emit a call to something undefined. | |
| 56 | + let (ok, _, err) = run(&d, &[root.to_str().unwrap()]); | |
| 57 | + assert!(!ok, "the root alone should not transpile"); | |
| 58 | + assert!(err.contains("mod a;"), "unexpected: {err}"); | |
| 59 | +} | |
| 60 | + | |
| 61 | +#[test] | |
| 62 | +fn two_modules_may_define_the_same_name() { | |
| 63 | + let d = work("modcollide"); | |
| 64 | + let root = d.join("root.rs"); | |
| 65 | + let x = d.join("x.rs"); | |
| 66 | + let y = d.join("y.rs"); | |
| 67 | + fs::write(&x, "pub fn go() -> i32 { 1 }\n").unwrap(); | |
| 68 | + fs::write(&y, "pub fn go() -> i32 { 2 }\n").unwrap(); | |
| 69 | + fs::write( | |
| 70 | + &root, | |
| 71 | + "mod x;\nmod y;\nfn main() { println!(\"{} {}\", x::go(), y::go()); }\n", | |
| 72 | + ) | |
| 73 | + .unwrap(); | |
| 74 | + | |
| 75 | + let (ok, out, err) = run( | |
| 76 | + &d, | |
| 77 | + &[root.to_str().unwrap(), x.to_str().unwrap(), y.to_str().unwrap()], | |
| 78 | + ); | |
| 79 | + assert!(ok, "{err}"); | |
| 80 | + assert!(out.contains("proc x_go") && out.contains("proc y_go"), "{out}"); | |
| 81 | + assert!(out.contains("x_go()") && out.contains("y_go()"), "{out}"); | |
| 57 | 82 | } |
| 58 | 83 | |
| 59 | 84 | #[test] |
| @@ -34,26 +34,51 @@ fn run(dir: &Path, args: &[&str]) -> (bool, String, String) { | |||
| 34 | #[test] | 34 | #[test] |
| 35 | fn files_are_flattened_into_one_module() { | 35 | fn files_are_flattened_into_one_module() { |
| 36 | let d = work("multifile"); | 36 | let d = work("multifile"); |
| 37 | + let root = d.join("root.rs"); | ||
| 37 | let a = d.join("a.rs"); | 38 | let a = d.join("a.rs"); |
| 38 | - let b = d.join("b.rs"); | ||
| 39 | fs::write(&a, "pub fn double(x: i32) -> i32 { x * 2 }\n").unwrap(); | 39 | fs::write(&a, "pub fn double(x: i32) -> i32 { x * 2 }\n").unwrap(); |
| 40 | fs::write( | 40 | fs::write( |
| 41 | - &b, | 41 | + &root, |
| 42 | - "fn main() { println!(\"{}\", double(21)); }\n", | 42 | + "mod a;\nuse crate::a::double;\nfn main() { println!(\"{}\", double(21)); }\n", |
| 43 | ) | 43 | ) |
| 44 | .unwrap(); | 44 | .unwrap(); |
| 45 | 45 | ||
| 46 | - // `double` is defined in a.rs and called from b.rs: it resolves only | 46 | + // The first input is the crate root; `a.rs` becomes module `a`, and its |
| 47 | - // because both were passed in. | 47 | + // items are emitted with that prefix so two modules may define the same |
| 48 | - let (ok, out, err) = run(&d, &[a.to_str().unwrap(), b.to_str().unwrap()]); | 48 | + // name. |
| 49 | + let (ok, out, err) = run(&d, &[root.to_str().unwrap(), a.to_str().unwrap()]); | ||
| 49 | assert!(ok, "multi-file transpile failed: {err}"); | 50 | assert!(ok, "multi-file transpile failed: {err}"); |
| 50 | - assert!(out.contains("proc double"), "missing `double`:\n{out}"); | 51 | + assert!(out.contains("proc a_double"), "missing `a_double`:\n{out}"); |
| 51 | assert!(out.contains("proc main"), "missing `main`:\n{out}"); | 52 | assert!(out.contains("proc main"), "missing `main`:\n{out}"); |
| 53 | + assert!(out.contains("a_double(21"), "call not qualified:\n{out}"); | ||
| 52 | 54 | ||
| 53 | - // b.rs alone must fail rather than emit a call to something undefined. | 55 | + // The root alone must fail rather than emit a call to something undefined. |
| 54 | - let (ok, _, err) = run(&d, &[b.to_str().unwrap()]); | 56 | + let (ok, _, err) = run(&d, &[root.to_str().unwrap()]); |
| 55 | - assert!(!ok, "b.rs alone should not transpile"); | 57 | + assert!(!ok, "the root alone should not transpile"); |
| 56 | - assert!(err.contains("unknown function `double`"), "unexpected: {err}"); | 58 | + assert!(err.contains("mod a;"), "unexpected: {err}"); |
| 59 | +} | ||
| 60 | + | ||
| 61 | +#[test] | ||
| 62 | +fn two_modules_may_define_the_same_name() { | ||
| 63 | + let d = work("modcollide"); | ||
| 64 | + let root = d.join("root.rs"); | ||
| 65 | + let x = d.join("x.rs"); | ||
| 66 | + let y = d.join("y.rs"); | ||
| 67 | + fs::write(&x, "pub fn go() -> i32 { 1 }\n").unwrap(); | ||
| 68 | + fs::write(&y, "pub fn go() -> i32 { 2 }\n").unwrap(); | ||
| 69 | + fs::write( | ||
| 70 | + &root, | ||
| 71 | + "mod x;\nmod y;\nfn main() { println!(\"{} {}\", x::go(), y::go()); }\n", | ||
| 72 | + ) | ||
| 73 | + .unwrap(); | ||
| 74 | + | ||
| 75 | + let (ok, out, err) = run( | ||
| 76 | + &d, | ||
| 77 | + &[root.to_str().unwrap(), x.to_str().unwrap(), y.to_str().unwrap()], | ||
| 78 | + ); | ||
| 79 | + assert!(ok, "{err}"); | ||
| 80 | + assert!(out.contains("proc x_go") && out.contains("proc y_go"), "{out}"); | ||
| 81 | + assert!(out.contains("x_go()") && out.contains("y_go()"), "{out}"); | ||
| 57 | } | 82 | } |
| 58 | 83 | ||
| 59 | #[test] | 84 | #[test] |