Transpile a second crate, adler2, to test whether any of this generalises
base16ct is the crate this was built toward, so passing it proves less than it looks. adler2 2.0.1 was picked for being a different shape: a stateful struct with methods, operator-overload trait impls, and a hand-unrolled four-lane inner loop, where base16ct is free functions over byte slices. It works. tests/cases/029-adler2-crate/ transpiles algo.rs byte-for-byte as published, with lib.rs's items and a driver, and the checksums are identical to rustc's across every single byte, every length to 600 -- crossing both the 4-byte unrolling boundary and the 5552-byte chunk path -- and 144 incremental-write splits, which check that write_slice twice equals write_slice once on the concatenation. Getting there took ten features, which is the honest half of the answer. Trait impls are generalised past formatting and From: any trait's methods become procs named after the trait and the type, so two traits declaring the same method cannot collide, and the operator traits are additionally wired into `+=` and `+` dispatch with the impl's own parameter type deciding the width of the right operand. Then Self, Type::method() static calls, u32::from between primitives (lossless by definition, unlike `as`), tuple-destructuring let, split_at as two windows rather than a tuple of views, iterators bound to variables with .remainder(), `[0; 4]` as an array instead of a seq, and the bare #[cfg] flags. It also caught a regression I had introduced. The three-phase emission added for forward declarations was silently dropping `const` items declared inside a function body -- base16ct has none, so 33 passing cases said nothing about it. That is the argument for a second crate in one sentence. Surveyed three more without fixing anything, to find the wall rather than move it: siphasher and rustc-hash both stop at u128, which is the founding rule working rather than a gap -- they are told they cannot be translated instead of being handed a silently truncated hasher. hex needs associated types on `impl Iterator`, crc32fast needs mod directories and then SIMD intrinsics. DESIGN.md records the table. target_pointer_width and target_endian are now evaluated against the host, since the Nim is compiled for it. That is recorded as making the output host-shaped, because it does. 34 differential cases, all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5fdd5be parent: 99b8376 modified
DESIGN.md +48 -1 | @@ -291,10 +291,57 @@ runner) rather than a wrong answer. | ||
| 291 | 291 | modules declaring the same type name would collide. Relatedly, a crate's |
| 292 | 292 | own `type Result<T>` is told apart from the builtin `Result<T, E>` by |
| 293 | 293 | arity, which is not how Rust resolves it. |
| 294 | -10. `String::from_utf8_unchecked` copies, because Nim's `string` is an owned | |
| 294 | +10. `#[cfg(target_pointer_width)]` and `#[cfg(target_endian)]` are evaluated | |
| 295 | + against the *host*, since the generated Nim is compiled for it. That makes | |
| 296 | + the output host-shaped: a crate branching on pointer width has had that | |
| 297 | + branch decided at transpile time. | |
| 298 | +11. Associated types (`impl Iterator { type Item = .. }`) and `mod` | |
| 299 | + directories (`specialized/mod.rs`) are not implemented. | |
| 300 | +12. `String::from_utf8_unchecked` copies, because Nim's `string` is an owned | |
| 295 | 301 | value. Rust's consumes the `Vec` without copying. Observably the same from |
| 296 | 302 | the caller, but it is a copy where Rust has none. |
| 297 | 303 | |
| 304 | +## A second crate: does this generalise, or is it fitted to `base16ct`? | |
| 305 | + | |
| 306 | +`base16ct` is the crate this was built toward, so passing it proves less than | |
| 307 | +it looks. `adler2` 2.0.1 was picked as a deliberately different shape — | |
| 308 | +a stateful struct with methods, operator-overload trait impls, a hand-unrolled | |
| 309 | +four-lane inner loop — and it now works: `tests/cases/029-adler2-crate/` | |
| 310 | +transpiles `algo.rs` byte-for-byte as published, with `lib.rs`'s items and a | |
| 311 | +driver, and its checksums are byte-identical to rustc's across every single | |
| 312 | +byte, every length to 600 (crossing the 4-byte unrolling boundary and the | |
| 313 | +5552-chunk path), and 144 incremental-write splits. | |
| 314 | + | |
| 315 | +It needed real work, which is the honest part of the answer. Ten features: | |
| 316 | +trait impls generalised beyond formatting and `From` (any trait's methods | |
| 317 | +become procs on the type, with the operator traits wired into `+=`/`+` | |
| 318 | +dispatch), `Self`, `Type::method()` static calls, `u32::from` between | |
| 319 | +primitives, tuple-destructuring `let`, `split_at`, iterators bound to | |
| 320 | +variables and `.remainder()`, `[0; 4]` as an array rather than a `seq`, and | |
| 321 | +the bare `#[cfg]` flags. | |
| 322 | + | |
| 323 | +It also caught a **regression I had introduced**: the three-phase emission | |
| 324 | +added for forward declarations was silently dropping `const` items declared | |
| 325 | +*inside* a function body. `base16ct` has none, so 33 passing cases said | |
| 326 | +nothing about it. | |
| 327 | + | |
| 328 | +### What the other crates did | |
| 329 | + | |
| 330 | +Run without fixing anything, to see where the wall is rather than to move it: | |
| 331 | + | |
| 332 | +| crate | outcome | | |
| 333 | +|---|---| | |
| 334 | +| `adler2` 2.0.1 | **works**, byte-identical | | |
| 335 | +| `siphasher` 1.0.1 | rejected: `u128` | | |
| 336 | +| `rustc-hash` 2.1.1 | rejected: `u128` | | |
| 337 | +| `hex` 0.4.3 | rejected: `impl Iterator` needs an associated type | | |
| 338 | +| `crc32fast` 1.5.0 | rejected: directory modules (`specialized/mod.rs`), then SIMD intrinsics | | |
| 339 | + | |
| 340 | +Two of the five stop at `u128`, which is the founding rule doing its job | |
| 341 | +rather than a gap: they are told they cannot be translated instead of being | |
| 342 | +handed a silently truncated hasher. The other two are honest missing | |
| 343 | +features — associated types, and `mod` directories. | |
| 344 | + | |
| 298 | 345 | ## Proof of byte-identity for `base16ct` |
| 299 | 346 | |
| 300 | 347 | [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive |
| @@ -291,10 +291,57 @@ runner) rather than a wrong answer. | |||
| 291 | modules declaring the same type name would collide. Relatedly, a crate's | 291 | modules declaring the same type name would collide. Relatedly, a crate's |
| 292 | own `type Result<T>` is told apart from the builtin `Result<T, E>` by | 292 | own `type Result<T>` is told apart from the builtin `Result<T, E>` by |
| 293 | arity, which is not how Rust resolves it. | 293 | arity, which is not how Rust resolves it. |
| 294 | -10. `String::from_utf8_unchecked` copies, because Nim's `string` is an owned | 294 | +10. `#[cfg(target_pointer_width)]` and `#[cfg(target_endian)]` are evaluated |
| 295 | + against the *host*, since the generated Nim is compiled for it. That makes | ||
| 296 | + the output host-shaped: a crate branching on pointer width has had that | ||
| 297 | + branch decided at transpile time. | ||
| 298 | +11. Associated types (`impl Iterator { type Item = .. }`) and `mod` | ||
| 299 | + directories (`specialized/mod.rs`) are not implemented. | ||
| 300 | +12. `String::from_utf8_unchecked` copies, because Nim's `string` is an owned | ||
| 295 | value. Rust's consumes the `Vec` without copying. Observably the same from | 301 | value. Rust's consumes the `Vec` without copying. Observably the same from |
| 296 | the caller, but it is a copy where Rust has none. | 302 | the caller, but it is a copy where Rust has none. |
| 297 | 303 | ||
| 304 | +## A second crate: does this generalise, or is it fitted to `base16ct`? | ||
| 305 | + | ||
| 306 | +`base16ct` is the crate this was built toward, so passing it proves less than | ||
| 307 | +it looks. `adler2` 2.0.1 was picked as a deliberately different shape — | ||
| 308 | +a stateful struct with methods, operator-overload trait impls, a hand-unrolled | ||
| 309 | +four-lane inner loop — and it now works: `tests/cases/029-adler2-crate/` | ||
| 310 | +transpiles `algo.rs` byte-for-byte as published, with `lib.rs`'s items and a | ||
| 311 | +driver, and its checksums are byte-identical to rustc's across every single | ||
| 312 | +byte, every length to 600 (crossing the 4-byte unrolling boundary and the | ||
| 313 | +5552-chunk path), and 144 incremental-write splits. | ||
| 314 | + | ||
| 315 | +It needed real work, which is the honest part of the answer. Ten features: | ||
| 316 | +trait impls generalised beyond formatting and `From` (any trait's methods | ||
| 317 | +become procs on the type, with the operator traits wired into `+=`/`+` | ||
| 318 | +dispatch), `Self`, `Type::method()` static calls, `u32::from` between | ||
| 319 | +primitives, tuple-destructuring `let`, `split_at`, iterators bound to | ||
| 320 | +variables and `.remainder()`, `[0; 4]` as an array rather than a `seq`, and | ||
| 321 | +the bare `#[cfg]` flags. | ||
| 322 | + | ||
| 323 | +It also caught a **regression I had introduced**: the three-phase emission | ||
| 324 | +added for forward declarations was silently dropping `const` items declared | ||
| 325 | +*inside* a function body. `base16ct` has none, so 33 passing cases said | ||
| 326 | +nothing about it. | ||
| 327 | + | ||
| 328 | +### What the other crates did | ||
| 329 | + | ||
| 330 | +Run without fixing anything, to see where the wall is rather than to move it: | ||
| 331 | + | ||
| 332 | +| crate | outcome | | ||
| 333 | +|---|---| | ||
| 334 | +| `adler2` 2.0.1 | **works**, byte-identical | | ||
| 335 | +| `siphasher` 1.0.1 | rejected: `u128` | | ||
| 336 | +| `rustc-hash` 2.1.1 | rejected: `u128` | | ||
| 337 | +| `hex` 0.4.3 | rejected: `impl Iterator` needs an associated type | | ||
| 338 | +| `crc32fast` 1.5.0 | rejected: directory modules (`specialized/mod.rs`), then SIMD intrinsics | | ||
| 339 | + | ||
| 340 | +Two of the five stop at `u128`, which is the founding rule doing its job | ||
| 341 | +rather than a gap: they are told they cannot be translated instead of being | ||
| 342 | +handed a silently truncated hasher. The other two are honest missing | ||
| 343 | +features — associated types, and `mod` directories. | ||
| 344 | + | ||
| 298 | ## Proof of byte-identity for `base16ct` | 345 | ## Proof of byte-identity for `base16ct` |
| 299 | 346 | ||
| 300 | [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive | 347 | [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive |
modified
README.md +13 -0 | @@ -76,6 +76,19 @@ argument for longer inputs and 20,000 pseudorandom cases attacking it. | ||
| 76 | 76 | `cargo test --test proof` runs it — 151,463 cases, 9.1 MB of output, compared |
| 77 | 77 | byte for byte. |
| 78 | 78 | |
| 79 | +## Does it generalise? | |
| 80 | + | |
| 81 | +`base16ct` is the crate this was built toward, so a second one was tried. | |
| 82 | +`adler2` 2.0.1 — a stateful checksum with operator-overload trait impls and a | |
| 83 | +hand-unrolled loop, structurally nothing like `base16ct` — works, and is | |
| 84 | +byte-identical across every single byte, every length to 600, and 144 | |
| 85 | +incremental-write splits. It needed ten new features, and it caught a | |
| 86 | +regression that 33 passing cases had not. | |
| 87 | + | |
| 88 | +Of five crates tried, one works, two are rejected for `u128` (the founding | |
| 89 | +rule working as designed), and two need features that are genuinely missing. | |
| 90 | +[`DESIGN.md`](DESIGN.md) has the table. | |
| 91 | + | |
| 79 | 92 | ## Tests |
| 80 | 93 | |
| 81 | 94 | ```bash |
| @@ -76,6 +76,19 @@ argument for longer inputs and 20,000 pseudorandom cases attacking it. | |||
| 76 | `cargo test --test proof` runs it — 151,463 cases, 9.1 MB of output, compared | 76 | `cargo test --test proof` runs it — 151,463 cases, 9.1 MB of output, compared |
| 77 | byte for byte. | 77 | byte for byte. |
| 78 | 78 | ||
| 79 | +## Does it generalise? | ||
| 80 | + | ||
| 81 | +`base16ct` is the crate this was built toward, so a second one was tried. | ||
| 82 | +`adler2` 2.0.1 — a stateful checksum with operator-overload trait impls and a | ||
| 83 | +hand-unrolled loop, structurally nothing like `base16ct` — works, and is | ||
| 84 | +byte-identical across every single byte, every length to 600, and 144 | ||
| 85 | +incremental-write splits. It needed ten new features, and it caught a | ||
| 86 | +regression that 33 passing cases had not. | ||
| 87 | + | ||
| 88 | +Of five crates tried, one works, two are rejected for `u128` (the founding | ||
| 89 | +rule working as designed), and two need features that are genuinely missing. | ||
| 90 | +[`DESIGN.md`](DESIGN.md) has the table. | ||
| 91 | + | ||
| 79 | ## Tests | 92 | ## Tests |
| 80 | 93 | ||
| 81 | ```bash | 94 | ```bash |
modified
src/lower.rs +506 -50 | @@ -110,6 +110,10 @@ enum Alias { | ||
| 110 | 110 | Value { code: String, ty: Option<Nim> }, |
| 111 | 111 | /// The name stands for a window: `code[off .. off + len - 1]`. |
| 112 | 112 | Window { code: String, off: String, len: String, elem: Option<Nim> }, |
| 113 | + /// The name stands for an iterator that has not been consumed yet, as in | |
| 114 | + /// `let it = xs.chunks_exact(k);`. Rust's iterators are values; ours are | |
| 115 | + /// resolved chains, so the chain is carried until a `for` consumes it. | |
| 116 | + Iterator(Box<Iter>), | |
| 113 | 117 | } |
| 114 | 118 | |
| 115 | 119 | /// A lowered expression: its Nim text, and its type where we know it. |
| @@ -192,6 +196,8 @@ pub struct Lowerer { | ||
| 192 | 196 | fns: HashMap<(String, String), Sig>, |
| 193 | 197 | /// Module being lowered: the file stem, or empty for the crate root. |
| 194 | 198 | cur_mod: String, |
| 199 | + /// The type of the `impl` block being lowered, which `Self` names. | |
| 200 | + self_ty: Option<Nim>, | |
| 195 | 201 | /// `use` brings a name into scope from another module. Flattening loses |
| 196 | 202 | /// the module structure, so the mapping is recorded and consulted when a |
| 197 | 203 | /// bare call is resolved. |
| @@ -211,6 +217,11 @@ pub struct Lowerer { | ||
| 211 | 217 | fmt_impls: HashMap<(String, String), ()>, |
| 212 | 218 | /// `(from, to)` conversions declared by `impl From<A> for B`. |
| 213 | 219 | from_impls: HashMap<(String, String), String>, |
| 220 | + /// Operator traits implemented for a type, so `a += b` on a user type can | |
| 221 | + /// be dispatched to the impl rather than to Nim's built-in operator. | |
| 222 | + op_impls: HashMap<(String, String), ()>, | |
| 223 | + /// `(type, method) -> nim name`, for calls written as `Type::method(..)`. | |
| 224 | + statics: HashMap<(String, String), String>, | |
| 214 | 225 | /// Forward declarations, emitted between the type definitions and the |
| 215 | 226 | /// bodies. Rust has no declaration-before-use rule and Nim does, so every |
| 216 | 227 | /// proc is declared up front rather than the input being reordered -- |
| @@ -252,6 +263,7 @@ impl Lowerer { | ||
| 252 | 263 | alias_scopes: vec![HashMap::new()], |
| 253 | 264 | fns: HashMap::new(), |
| 254 | 265 | cur_mod: String::new(), |
| 266 | + self_ty: None, | |
| 255 | 267 | use_map: HashMap::new(), |
| 256 | 268 | structs: HashMap::new(), |
| 257 | 269 | enums: HashMap::new(), |
| @@ -259,6 +271,8 @@ impl Lowerer { | ||
| 259 | 271 | methods: HashMap::new(), |
| 260 | 272 | fmt_impls: HashMap::new(), |
| 261 | 273 | from_impls: HashMap::new(), |
| 274 | + op_impls: HashMap::new(), | |
| 275 | + statics: HashMap::new(), | |
| 262 | 276 | fmt_param: None, |
| 263 | 277 | vec_expect: None, |
| 264 | 278 | forwards: Vec::new(), |
| @@ -545,8 +559,21 @@ impl Lowerer { | ||
| 545 | 559 | } |
| 546 | 560 | Item::Impl(im) => { |
| 547 | 561 | let self_ty = self.map_ty(&im.self_ty)?; |
| 548 | - let tyname = type_name(&self_ty); | |
| 549 | - if let Some((path, _)) = &im.trait_ { | |
| 562 | + let outer_self = self.self_ty.replace(self_ty.clone()); | |
| 563 | + let r = self.collect_impl(im, &self_ty); | |
| 564 | + self.self_ty = outer_self; | |
| 565 | + return r; | |
| 566 | + } | |
| 567 | + _ => {} | |
| 568 | + } | |
| 569 | + Ok(()) | |
| 570 | + } | |
| 571 | + | |
| 572 | + fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> { | |
| 573 | + { | |
| 574 | + let self_ty = self_ty.clone(); | |
| 575 | + let tyname = type_name(&self_ty); | |
| 576 | + if let Some((path, _)) = &im.trait_ { | |
| 550 | 577 | let tr = path_name(path); |
| 551 | 578 | if im.items.is_empty() { |
| 552 | 579 | // A marker trait with no items. We do not model trait |
| @@ -579,11 +606,33 @@ impl Lowerer { | ||
| 579 | 606 | .insert((type_name(&src), tyname), name); |
| 580 | 607 | return Ok(()); |
| 581 | 608 | } |
| 582 | - return Err(format!( | |
| 583 | - "`impl {tr} for {tyname}`: only formatting traits \ | |
| 584 | - (Display, Debug, LowerHex, UpperHex, Binary, Octal), \ | |
| 585 | - `From`, and marker traits with no items are implemented" | |
| 586 | - )); | |
| 609 | + // Any other trait: its methods are emitted as procs on | |
| 610 | + // the type, named after the trait so two traits declaring | |
| 611 | + // the same method name do not collide. The *trait* is not | |
| 612 | + // modelled -- no dynamic dispatch, no bounds -- and a use | |
| 613 | + // that needs it is rejected where it appears. | |
| 614 | + if let Some(op) = operator_trait(&tr) { | |
| 615 | + self.op_impls.insert((tyname.clone(), op.to_string()), ()); | |
| 616 | + } | |
| 617 | + for it in &im.items { | |
| 618 | + let syn::ImplItem::Fn(m) = it else { | |
| 619 | + return Err(format!("unsupported item in `impl {tr}`")); | |
| 620 | + }; | |
| 621 | + let mname = m.sig.ident.to_string(); | |
| 622 | + let (mut params, ret) = self.signature(&m.sig)?; | |
| 623 | + let recv = if takes_self(&m.sig) { | |
| 624 | + params.insert(0, self_ty.clone()); | |
| 625 | + Some(self_ty.clone()) | |
| 626 | + } else { | |
| 627 | + None | |
| 628 | + }; | |
| 629 | + let nim = trait_method_name(&tyname, &tr, &mname); | |
| 630 | + self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?); | |
| 631 | + self.methods | |
| 632 | + .insert((tyname.clone(), mname.clone()), Sig { params, ret }); | |
| 633 | + self.statics.insert((tyname.clone(), mname), nim); | |
| 634 | + } | |
| 635 | + return Ok(()); | |
| 587 | 636 | } |
| 588 | 637 | for it in &im.items { |
| 589 | 638 | if let syn::ImplItem::Fn(m) = it { |
| @@ -592,14 +641,15 @@ impl Lowerer { | ||
| 592 | 641 | params.insert(0, self_ty.clone()); |
| 593 | 642 | } |
| 594 | 643 | let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; |
| 595 | - let head = self.head_of(&m.sig.ident.to_string(), &m.sig, recv.as_ref())?; | |
| 644 | + let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string()); | |
| 645 | + let head = self.head_of(&nim, &m.sig, recv.as_ref())?; | |
| 596 | 646 | self.forwards.push(head); |
| 597 | 647 | self.methods |
| 598 | 648 | .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret }); |
| 649 | + self.statics | |
| 650 | + .insert((tyname.clone(), m.sig.ident.to_string()), nim); | |
| 599 | 651 | } |
| 600 | 652 | } |
| 601 | - } | |
| 602 | - _ => {} | |
| 603 | 653 | } |
| 604 | 654 | Ok(()) |
| 605 | 655 | } |
| @@ -625,6 +675,29 @@ impl Lowerer { | ||
| 625 | 675 | |
| 626 | 676 | fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> { |
| 627 | 677 | match m { |
| 678 | + // Bare flags whose value is determined by the profile this project | |
| 679 | + // models: a normal (non-`--test`) debug build, not a docs build. | |
| 680 | + // Anything platform-specific stays rejected, since we would be | |
| 681 | + // picking a target on the user's behalf. | |
| 682 | + syn::Meta::Path(p) if p.is_ident("test") => Ok(false), | |
| 683 | + syn::Meta::Path(p) if p.is_ident("debug_assertions") => Ok(true), | |
| 684 | + syn::Meta::Path(p) if p.is_ident("docsrs") || p.is_ident("doc") => Ok(false), | |
| 685 | + // The generated Nim is compiled for the same machine, so the | |
| 686 | + // target's word size and endianness are known rather than | |
| 687 | + // guessed. This does mean the output is host-shaped: a crate that | |
| 688 | + // branches on pointer width has had that branch decided here. | |
| 689 | + syn::Meta::NameValue(nv) if nv.path.is_ident("target_pointer_width") => { | |
| 690 | + let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { | |
| 691 | + return Err("`target_pointer_width = ..` expects a string".into()); | |
| 692 | + }; | |
| 693 | + Ok(s.value() == (usize::BITS).to_string()) | |
| 694 | + } | |
| 695 | + syn::Meta::NameValue(nv) if nv.path.is_ident("target_endian") => { | |
| 696 | + let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { | |
| 697 | + return Err("`target_endian = ..` expects a string".into()); | |
| 698 | + }; | |
| 699 | + Ok(s.value() == if cfg!(target_endian = "big") { "big" } else { "little" }) | |
| 700 | + } | |
| 628 | 701 | syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => { |
| 629 | 702 | let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { |
| 630 | 703 | return Err("`feature = ..` expects a string".into()); |
| @@ -655,11 +728,28 @@ impl Lowerer { | ||
| 655 | 728 | } |
| 656 | 729 | } |
| 657 | 730 | |
| 658 | - /// Map a Rust type, expanding any `type` alias first. Every type in the | |
| 731 | + /// Map a Rust type, resolving `Self` and expanding any `type` alias. Every type in the | |
| 659 | 732 | /// lowering goes through here rather than calling `ty::map` directly, so |
| 660 | 733 | /// an alias cannot be missed in one position and honoured in another. |
| 661 | 734 | fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> { |
| 662 | - ty::map(&self.expand(t, 0)?) | |
| 735 | + let n = ty::map(&self.expand(t, 0)?)?; | |
| 736 | + Ok(self.subst_self(n)) | |
| 737 | + } | |
| 738 | + | |
| 739 | + /// `Self` inside an `impl` block names the type being implemented. | |
| 740 | + fn subst_self(&self, t: Nim) -> Nim { | |
| 741 | + let Some(me) = &self.self_ty else { return t }; | |
| 742 | + match t { | |
| 743 | + Nim::Named(n, _) if n == "Self" => me.clone(), | |
| 744 | + Nim::Seq(e) => Nim::Seq(Box::new(self.subst_self(*e))), | |
| 745 | + Nim::OpenArray(e) => Nim::OpenArray(Box::new(self.subst_self(*e))), | |
| 746 | + Nim::Array(n, e) => Nim::Array(n, Box::new(self.subst_self(*e))), | |
| 747 | + Nim::Var(e) => Nim::Var(Box::new(self.subst_self(*e))), | |
| 748 | + Nim::Named(n, a) => { | |
| 749 | + Nim::Named(n, a.into_iter().map(|x| self.subst_self(x)).collect()) | |
| 750 | + } | |
| 751 | + other => other, | |
| 752 | + } | |
| 663 | 753 | } |
| 664 | 754 | |
| 665 | 755 | fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> { |
| @@ -872,46 +962,32 @@ impl Lowerer { | ||
| 872 | 962 | } |
| 873 | 963 | Item::Const(c) => { |
| 874 | 964 | let t = self.map_ty(&c.ty)?.owned(); |
| 875 | - let v = self.expr(&c.expr)?; | |
| 965 | + // The annotation types the initialiser, exactly as it does for | |
| 966 | + // a `let`: `const MOD: u32 = 65521` is a u32 literal. | |
| 967 | + let v = self.expr_at(&c.expr, Some(&t))?; | |
| 876 | 968 | self.bind(&c.ident.to_string(), t.clone()); |
| 877 | - let line = format!("const {}*: {} = {}", ident(&c.ident.to_string()), t.render(), v.code); | |
| 969 | + // Only a top-level const is exported; `*` on a local is not | |
| 970 | + // Nim syntax. | |
| 971 | + let star = if self.indent == 0 { "*" } else { "" }; | |
| 972 | + let line = format!( | |
| 973 | + "const {}{}: {} = {}", | |
| 974 | + ident(&c.ident.to_string()), | |
| 975 | + star, | |
| 976 | + t.render(), | |
| 977 | + v.code | |
| 978 | + ); | |
| 878 | 979 | self.line(&line); |
| 879 | - self.blank(); | |
| 980 | + if self.indent == 0 { | |
| 981 | + self.blank(); | |
| 982 | + } | |
| 880 | 983 | Ok(()) |
| 881 | 984 | } |
| 882 | 985 | Item::Impl(im) => { |
| 883 | 986 | let self_ty = self.map_ty(&im.self_ty)?; |
| 884 | - if let Some((path, _)) = &im.trait_ { | |
| 885 | - let tr = path_name(path); | |
| 886 | - if im.items.is_empty() { | |
| 887 | - return Ok(()); | |
| 888 | - } | |
| 889 | - let syn::ImplItem::Fn(m) = &im.items[0] else { | |
| 890 | - return Err(format!("unsupported item in `impl {tr}`")); | |
| 891 | - }; | |
| 892 | - if is_fmt_trait(&tr) { | |
| 893 | - return self.fmt_impl(&tr, &self_ty, &m.sig, &m.block); | |
| 894 | - } | |
| 895 | - if tr == "From" { | |
| 896 | - let name = { | |
| 897 | - let (params, _) = self.signature(&m.sig)?; | |
| 898 | - let src = params.first().cloned().ok_or("`fn from` takes one argument")?; | |
| 899 | - self.from_impls[&(type_name(&src), type_name(&self_ty))].clone() | |
| 900 | - }; | |
| 901 | - return self.func_named(&name, &m.sig, &m.block, None); | |
| 902 | - } | |
| 903 | - return Err(format!("`impl {tr}` is not implemented")); | |
| 904 | - } | |
| 905 | - for it in &im.items { | |
| 906 | - match it { | |
| 907 | - syn::ImplItem::Fn(m) => { | |
| 908 | - let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; | |
| 909 | - self.func(&m.sig, &m.block, recv)?; | |
| 910 | - } | |
| 911 | - _ => return Err("only `fn` items are supported inside `impl`".into()), | |
| 912 | - } | |
| 913 | - } | |
| 914 | - Ok(()) | |
| 987 | + let outer = self.self_ty.replace(self_ty.clone()); | |
| 988 | + let r = self.impl_body(im, &self_ty); | |
| 989 | + self.self_ty = outer; | |
| 990 | + r | |
| 915 | 991 | } |
| 916 | 992 | // `use` and `extern crate` are resolution directives with no Nim |
| 917 | 993 | // analogue once everything is one module. |
| @@ -951,6 +1027,71 @@ impl Lowerer { | ||
| 951 | 1027 | } |
| 952 | 1028 | } |
| 953 | 1029 | |
| 1030 | + fn impl_body(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> { | |
| 1031 | + if let Some((path, _)) = &im.trait_ { | |
| 1032 | + let tr = path_name(path); | |
| 1033 | + if im.items.is_empty() { | |
| 1034 | + return Ok(()); | |
| 1035 | + } | |
| 1036 | + if is_fmt_trait(&tr) { | |
| 1037 | + let syn::ImplItem::Fn(m) = &im.items[0] else { | |
| 1038 | + return Err(format!("unsupported item in `impl {tr}`")); | |
| 1039 | + }; | |
| 1040 | + return self.fmt_impl(&tr, self_ty, &m.sig, &m.block); | |
| 1041 | + } | |
| 1042 | + if tr == "From" { | |
| 1043 | + let syn::ImplItem::Fn(m) = &im.items[0] else { | |
| 1044 | + return Err("`impl From` must contain `fn from`".into()); | |
| 1045 | + }; | |
| 1046 | + let name = { | |
| 1047 | + let (params, _) = self.signature(&m.sig)?; | |
| 1048 | + let src = params.first().cloned().ok_or("`fn from` takes one argument")?; | |
| 1049 | + self.from_impls[&(type_name(&src), type_name(self_ty))].clone() | |
| 1050 | + }; | |
| 1051 | + return self.func_named(&name, &m.sig, &m.block, None); | |
| 1052 | + } | |
| 1053 | + let tyname = type_name(self_ty); | |
| 1054 | + for it in &im.items { | |
| 1055 | + let syn::ImplItem::Fn(m) = it else { | |
| 1056 | + return Err(format!("unsupported item in `impl {tr}`")); | |
| 1057 | + }; | |
| 1058 | + let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; | |
| 1059 | + let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string()); | |
| 1060 | + self.func_named(&nim, &m.sig, &m.block, recv)?; | |
| 1061 | + } | |
| 1062 | + return Ok(()); | |
| 1063 | + } | |
| 1064 | + for it in &im.items { | |
| 1065 | + match it { | |
| 1066 | + syn::ImplItem::Fn(m) => { | |
| 1067 | + let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; | |
| 1068 | + let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string()); | |
| 1069 | + self.func_named(&nim, &m.sig, &m.block, recv)?; | |
| 1070 | + } | |
| 1071 | + _ => return Err("only `fn` items are supported inside `impl`".into()), | |
| 1072 | + } | |
| 1073 | + } | |
| 1074 | + Ok(()) | |
| 1075 | + } | |
| 1076 | + | |
| 1077 | + /// The type an operator impl declares for its right-hand operand. | |
| 1078 | + fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> { | |
| 1079 | + let n = type_name(t.as_ref()?); | |
| 1080 | + let sig = self.methods.get(&(n, op_method(op).to_string()))?; | |
| 1081 | + sig.params.get(1).cloned().map(|t| t.unvar()) | |
| 1082 | + } | |
| 1083 | + | |
| 1084 | + /// The proc implementing `op` for a user type, if there is one. | |
| 1085 | + fn op_proc(&self, t: &Option<Nim>, op: &str) -> Option<String> { | |
| 1086 | + let n = type_name(t.as_ref()?); | |
| 1087 | + let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0; | |
| 1088 | + if self.op_impls.contains_key(&(n.clone(), op.to_string())) { | |
| 1089 | + Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1)) | |
| 1090 | + } else { | |
| 1091 | + None | |
| 1092 | + } | |
| 1093 | + } | |
| 1094 | + | |
| 954 | 1095 | fn emit_enum(&mut self, def: &EnumDef) { |
| 955 | 1096 | let name = ident(&def.name); |
| 956 | 1097 | if def.simple { |
| @@ -1344,7 +1485,10 @@ impl Lowerer { | ||
| 1344 | 1485 | } |
| 1345 | 1486 | Ok(()) |
| 1346 | 1487 | } |
| 1347 | - Stmt::Item(i) => self.item(i), | |
| 1488 | + // A `const` declared inside a function body is local to it, and | |
| 1489 | + // must be emitted here rather than skipped as an already-emitted | |
| 1490 | + // top-level type. | |
| 1491 | + Stmt::Item(i) => self.item_inner(i), | |
| 1348 | 1492 | Stmt::Macro(m) => { |
| 1349 | 1493 | let line = self.macro_call(&m.mac)?; |
| 1350 | 1494 | self.line(&line); |
| @@ -1361,6 +1505,7 @@ impl Lowerer { | ||
| 1361 | 1505 | _ => return Err("only `let <ident>` bindings are supported".into()), |
| 1362 | 1506 | }, |
| 1363 | 1507 | Pat::Wild(_) => ("_".into(), false, None), |
| 1508 | + Pat::Tuple(t) => return self.local_tuple(l, t), | |
| 1364 | 1509 | _ => return Err("destructuring `let` is not implemented yet".into()), |
| 1365 | 1510 | }; |
| 1366 | 1511 | |
| @@ -1397,6 +1542,13 @@ impl Lowerer { | ||
| 1397 | 1542 | return self.assign_from(&init.expr, &target, Some(&t)); |
| 1398 | 1543 | } |
| 1399 | 1544 | |
| 1545 | + // `let it = xs.chunks_exact(k)` binds an iterator, not a value. | |
| 1546 | + if is_iterator_expr(&init.expr) { | |
| 1547 | + let it = self.resolve_iter(&init.expr)?; | |
| 1548 | + self.bind_alias(&name, Alias::Iterator(Box::new(it))); | |
| 1549 | + return Ok(()); | |
| 1550 | + } | |
| 1551 | + | |
| 1400 | 1552 | let v = self.expr_at(&init.expr, ann.as_ref())?; |
| 1401 | 1553 | |
| 1402 | 1554 | // `let s = &buf[..n]` binds a view of a place that is already in |
| @@ -1468,6 +1620,76 @@ impl Lowerer { | ||
| 1468 | 1620 | Ok(()) |
| 1469 | 1621 | } |
| 1470 | 1622 | |
| 1623 | + /// `let (a, b) = ..` — tuple destructuring. | |
| 1624 | + fn local_tuple(&mut self, l: &Local, t: &syn::PatTuple) -> Result<(), String> { | |
| 1625 | + let Some(init) = &l.init else { | |
| 1626 | + return Err("a destructuring `let` needs an initialiser".into()); | |
| 1627 | + }; | |
| 1628 | + let names: Vec<(String, bool)> = t | |
| 1629 | + .elems | |
| 1630 | + .iter() | |
| 1631 | + .map(|p| match p { | |
| 1632 | + Pat::Ident(i) => Ok((i.ident.to_string(), i.mutability.is_some())), | |
| 1633 | + Pat::Wild(_) => Ok(("_".to_string(), false)), | |
| 1634 | + _ => Err("only plain identifiers are supported in a destructuring `let`"), | |
| 1635 | + }) | |
| 1636 | + .collect::<Result<_, _>>()?; | |
| 1637 | + | |
| 1638 | + // `split_at` hands back two *views* of the same slice. Nim has no | |
| 1639 | + // tuple of views, and there is nothing to materialise anyway, so each | |
| 1640 | + // name becomes a window into the original. | |
| 1641 | + if let Expr::MethodCall(m) = &*init.expr { | |
| 1642 | + let mname = m.method.to_string(); | |
| 1643 | + if (mname == "split_at" || mname == "split_at_mut") | |
| 1644 | + && m.args.len() == 1 | |
| 1645 | + && names.len() == 2 | |
| 1646 | + { | |
| 1647 | + let (code, base, len, elem) = self.slice_parts(&m.receiver)?; | |
| 1648 | + let at = self.expr(&m.args[0])?; | |
| 1649 | + let cut = self.fresh("Cut"); | |
| 1650 | + self.line(&format!("let {}: int = int({})", cut, at.code)); | |
| 1651 | + self.bind_alias( | |
| 1652 | + &names[0].0, | |
| 1653 | + Alias::Window { | |
| 1654 | + code: code.clone(), | |
| 1655 | + off: base.clone(), | |
| 1656 | + len: cut.clone(), | |
| 1657 | + elem: elem.clone(), | |
| 1658 | + }, | |
| 1659 | + ); | |
| 1660 | + self.bind_alias( | |
| 1661 | + &names[1].0, | |
| 1662 | + Alias::Window { | |
| 1663 | + code, | |
| 1664 | + off: format!("({} + {})", base, cut), | |
| 1665 | + len: format!("({} - {})", len, cut), | |
| 1666 | + elem, | |
| 1667 | + }, | |
| 1668 | + ); | |
| 1669 | + return Ok(()); | |
| 1670 | + } | |
| 1671 | + } | |
| 1672 | + | |
| 1673 | + let v = self.expr(&init.expr)?; | |
| 1674 | + let tys = match &v.ty { | |
| 1675 | + Some(Nim::Tuple(ts)) if ts.len() == names.len() => ts.clone(), | |
| 1676 | + _ => { | |
| 1677 | + return Err(format!( | |
| 1678 | + "cannot destructure this into {} bindings: its type is not a \ | |
| 1679 | + tuple of that many elements", | |
| 1680 | + names.len() | |
| 1681 | + )) | |
| 1682 | + } | |
| 1683 | + }; | |
| 1684 | + let kw = if names.iter().any(|(_, m)| *m) { "var" } else { "let" }; | |
| 1685 | + let lhs: Vec<String> = names.iter().map(|(n, _)| ident(n)).collect(); | |
| 1686 | + self.line(&format!("{} ({}) = {}", kw, lhs.join(", "), v.code)); | |
| 1687 | + for ((n, _), t) in names.iter().zip(tys) { | |
| 1688 | + self.bind(n, t); | |
| 1689 | + } | |
| 1690 | + Ok(()) | |
| 1691 | + } | |
| 1692 | + | |
| 1471 | 1693 | /// Expressions that are statements in Rust and statements in Nim too |
| 1472 | 1694 | /// (control flow). Returns `None` when it emitted lines itself. |
| 1473 | 1695 | fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> { |
| @@ -1558,6 +1780,16 @@ impl Lowerer { | ||
| 1558 | 1780 | } |
| 1559 | 1781 | Expr::Binary(b) if is_compound(&b.op) => { |
| 1560 | 1782 | let lhs = self.expr(&b.left)?; |
| 1783 | + // A compound assignment on a user type goes to that type's own | |
| 1784 | + // `impl OpAssign`, not to Nim's built-in operator. | |
| 1785 | + if let Some(f) = self.op_proc(&lhs.ty, compound_symbol(&b.op)) { | |
| 1786 | + // The impl's own parameter type types the right operand, | |
| 1787 | + // so `b_vec *= 4` takes 4 at the width the impl declares. | |
| 1788 | + let want = self.op_param(&lhs.ty, compound_symbol(&b.op)); | |
| 1789 | + let rhs = self.expr_at(&b.right, want.as_ref())?; | |
| 1790 | + self.line(&format!("{}({}, {})", f, lhs.code, rhs.code)); | |
| 1791 | + return Ok(None); | |
| 1792 | + } | |
| 1561 | 1793 | // `i += 1` must widen the literal to `i`'s type, not to the |
| 1562 | 1794 | // i32 an unconstrained Rust literal would default to. |
| 1563 | 1795 | let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?; |
| @@ -1753,6 +1985,23 @@ impl Lowerer { | ||
| 1753 | 1985 | )), |
| 1754 | 1986 | } |
| 1755 | 1987 | } |
| 1988 | + Expr::Path(p) => { | |
| 1989 | + let n = path_name(&p.path); | |
| 1990 | + if let Some(Alias::Iterator(it)) = self.lookup_alias(&n) { | |
| 1991 | + return Ok((*it).clone()); | |
| 1992 | + } | |
| 1993 | + if let Some(Alias::Window { code, off, len, elem }) = self.lookup_alias(&n) { | |
| 1994 | + return Ok(Iter::Elems { code, off, len, elem, mutable: false }); | |
| 1995 | + } | |
| 1996 | + let v = self.expr(e)?; | |
| 1997 | + Ok(Iter::Elems { | |
| 1998 | + len: format!("{}.len", v.code), | |
| 1999 | + elem: elem_of(&v.ty), | |
| 2000 | + code: v.code, | |
| 2001 | + off: "0".into(), | |
| 2002 | + mutable: false, | |
| 2003 | + }) | |
| 2004 | + } | |
| 1756 | 2005 | other => { |
| 1757 | 2006 | // A `for` binding that is itself a window iterates that window, |
| 1758 | 2007 | // not the whole container it points into. |
| @@ -2262,6 +2511,14 @@ impl Lowerer { | ||
| 2262 | 2511 | format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len), |
| 2263 | 2512 | elem.map(|e| Nim::OpenArray(Box::new(e))), |
| 2264 | 2513 | ), |
| 2514 | + // An iterator is not a value here: it is consumed by a | |
| 2515 | + // `for`, or asked for its `.remainder()`. | |
| 2516 | + Alias::Iterator(_) => { | |
| 2517 | + return Err(format!( | |
| 2518 | + "`{name}` is an iterator; it can be iterated or asked \ | |
| 2519 | + for its `remainder()`, but not used as a value" | |
| 2520 | + )) | |
| 2521 | + } | |
| 2265 | 2522 | }); |
| 2266 | 2523 | } |
| 2267 | 2524 | if let Some(t) = self.lookup(&name) { |
| @@ -2458,7 +2715,21 @@ impl Lowerer { | ||
| 2458 | 2715 | Ok(Val::new(format!("[{}]", parts.join(", ")), t)) |
| 2459 | 2716 | } |
| 2460 | 2717 | Expr::Repeat(r) => { |
| 2461 | - let v = self.expr(&r.expr)?; | |
| 2718 | + // `[0; 4]` is an array in Rust. Nim distinguishes a fixed-size | |
| 2719 | + // array from a `seq`, so the expected type decides which, and | |
| 2720 | + // an array needs its elements written out. | |
| 2721 | + let want_elem = match expect { | |
| 2722 | + Some(Nim::Array(_, e)) | Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => { | |
| 2723 | + Some((**e).clone()) | |
| 2724 | + } | |
| 2725 | + _ => None, | |
| 2726 | + }; | |
| 2727 | + let v = self.expr_at(&r.expr, want_elem.as_ref())?; | |
| 2728 | + if let Some(Nim::Array(n, _)) = expect { | |
| 2729 | + let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect(); | |
| 2730 | + let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t))); | |
| 2731 | + return Ok(Val::new(format!("[{}]", elems.join(", ")), t)); | |
| 2732 | + } | |
| 2462 | 2733 | let n = self.expr(&r.len)?; |
| 2463 | 2734 | let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t))); |
| 2464 | 2735 | Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t)) |
| @@ -2619,6 +2890,19 @@ impl Lowerer { | ||
| 2619 | 2890 | l = self.expr_at(&b.left, r.ty.as_ref())?; |
| 2620 | 2891 | } |
| 2621 | 2892 | let r = std::mem::replace(&mut r, Val::untyped("")); |
| 2893 | + // A binary operator on a user type goes to that type's own impl. | |
| 2894 | + if let Some(f) = self.op_proc(&l.ty, binary_symbol(&b.op)) { | |
| 2895 | + let want = self.op_param(&l.ty, binary_symbol(&b.op)); | |
| 2896 | + let r = self.expr_at(&b.right, want.as_ref())?; | |
| 2897 | + let ret = self | |
| 2898 | + .methods | |
| 2899 | + .get(&( | |
| 2900 | + type_name(l.ty.as_ref().unwrap()), | |
| 2901 | + op_method(binary_symbol(&b.op)).to_string(), | |
| 2902 | + )) | |
| 2903 | + .map(|s| s.ret.clone()); | |
| 2904 | + return Ok(Val::new(format!("{}({}, {})", f, l.code, r.code), ret)); | |
| 2905 | + } | |
| 2622 | 2906 | let op = self.bin_op(&b.op, &l, &r)?; |
| 2623 | 2907 | let ty = match b.op { |
| 2624 | 2908 | BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_) |
| @@ -3107,6 +3391,20 @@ impl Lowerer { | ||
| 3107 | 3391 | } |
| 3108 | 3392 | } |
| 3109 | 3393 | |
| 3394 | + // `u32::from(b)`: `From` between primitives is lossless by definition | |
| 3395 | + // -- it is the widening direction only -- so a plain Nim conversion is | |
| 3396 | + // exact. (The truncating direction is `as`, which is `cast`.) | |
| 3397 | + if name == "from" && codes.len() == 1 { | |
| 3398 | + if let Some(q) = p.path.segments.iter().rev().nth(1) { | |
| 3399 | + if let Some(Nim::Prim(t)) = ty::prim(&q.ident.to_string()) { | |
| 3400 | + return Ok(Val::new( | |
| 3401 | + format!("{}({})", t, codes[0]), | |
| 3402 | + Some(Nim::Prim(t)), | |
| 3403 | + )); | |
| 3404 | + } | |
| 3405 | + } | |
| 3406 | + } | |
| 3407 | + | |
| 3110 | 3408 | // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a |
| 3111 | 3409 | // string view; no copy, no validation, same memory. |
| 3112 | 3410 | if name == "from_utf8_unchecked" && codes.len() == 1 { |
| @@ -3157,6 +3455,25 @@ impl Lowerer { | ||
| 3157 | 3455 | Some((*ret).clone()), |
| 3158 | 3456 | )); |
| 3159 | 3457 | } |
| 3458 | + // `Adler32::new()` / `Adler32::default()`: a method called through | |
| 3459 | + // its type rather than through a receiver. | |
| 3460 | + if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) { | |
| 3461 | + // `Self::new()` inside an `impl` names the type being implemented. | |
| 3462 | + let q = if q == "Self" { | |
| 3463 | + self.self_ty.as_ref().map(type_name).unwrap_or(q) | |
| 3464 | + } else { | |
| 3465 | + q | |
| 3466 | + }; | |
| 3467 | + if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) { | |
| 3468 | + let ret = sig.ret.clone(); | |
| 3469 | + let nim = self | |
| 3470 | + .statics | |
| 3471 | + .get(&(q.clone(), name.clone())) | |
| 3472 | + .cloned() | |
| 3473 | + .unwrap_or_else(|| ident(&name)); | |
| 3474 | + return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret))); | |
| 3475 | + } | |
| 3476 | + } | |
| 3160 | 3477 | let ret = target.as_ref().and_then(|k| self.fns.get(k)).map(|s| s.ret.clone()); |
| 3161 | 3478 | if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) { |
| 3162 | 3479 | return Err(format!( |
| @@ -3173,6 +3490,31 @@ impl Lowerer { | ||
| 3173 | 3490 | |
| 3174 | 3491 | fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> { |
| 3175 | 3492 | let name = m.method.to_string(); |
| 3493 | + // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield. | |
| 3494 | + if name == "remainder" && m.args.is_empty() { | |
| 3495 | + if let Expr::Path(p) = &*m.receiver { | |
| 3496 | + if let Some(Alias::Iterator(it)) = self.lookup_alias(&path_name(&p.path)) { | |
| 3497 | + if let Iter::Chunks { code, base, len, k, elem, .. } = &*it { | |
| 3498 | + let kept = format!("(({} div int({})) * int({}))", len, k, k); | |
| 3499 | + let mut v = Val::new( | |
| 3500 | + String::new(), | |
| 3501 | + elem.clone().map(|e| Nim::OpenArray(Box::new(e))), | |
| 3502 | + ); | |
| 3503 | + v.window = Some(Alias::Window { | |
| 3504 | + code: code.clone(), | |
| 3505 | + off: format!("({} + {})", base, kept), | |
| 3506 | + len: format!("({} - {})", len, kept), | |
| 3507 | + elem: elem.clone(), | |
| 3508 | + }); | |
| 3509 | + return Ok(v); | |
| 3510 | + } | |
| 3511 | + return Err( | |
| 3512 | + "`.remainder()` is only defined for a `chunks_exact` iterator".into(), | |
| 3513 | + ); | |
| 3514 | + } | |
| 3515 | + } | |
| 3516 | + return Err("`.remainder()` needs an iterator bound by `let`".into()); | |
| 3517 | + } | |
| 3176 | 3518 | if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) { |
| 3177 | 3519 | match name.as_str() { |
| 3178 | 3520 | "len" => { |
| @@ -3456,11 +3798,20 @@ impl Lowerer { | ||
| 3456 | 3798 | // A method defined in this file via `impl`, found by the |
| 3457 | 3799 | // receiver's type rather than by name alone. |
| 3458 | 3800 | let key = rt.as_ref().map(|t| (type_name(t), name.clone())); |
| 3459 | - let sig = key.and_then(|k| self.methods.get(&k)).map(|s| s.ret.clone()); | |
| 3801 | + let sig = key | |
| 3802 | + .as_ref() | |
| 3803 | + .and_then(|k| self.methods.get(k)) | |
| 3804 | + .map(|s| s.ret.clone()); | |
| 3460 | 3805 | if let Some(ret) = sig { |
| 3806 | + // Use the name the proc was actually emitted under: an | |
| 3807 | + // inherent method is qualified by its module, a trait | |
| 3808 | + // method by its trait. | |
| 3809 | + let nim = key | |
| 3810 | + .and_then(|k| self.statics.get(&k).cloned()) | |
| 3811 | + .unwrap_or_else(|| ident(&name)); | |
| 3461 | 3812 | let mut all = vec![recv.code.clone()]; |
| 3462 | 3813 | all.extend(args.iter().map(|a| a.code.clone())); |
| 3463 | - (format!("{}({})", ident(&name), all.join(", ")), Some(ret)) | |
| 3814 | + (format!("{}({})", nim, all.join(", ")), Some(ret)) | |
| 3464 | 3815 | } else { |
| 3465 | 3816 | return Err(format!( |
| 3466 | 3817 | "unsupported method `.{name}()`; it is neither defined in \ |
| @@ -3864,6 +4215,98 @@ fn type_name(t: &Nim) -> String { | ||
| 3864 | 4215 | } |
| 3865 | 4216 | } |
| 3866 | 4217 | |
| 4218 | +/// `(trait, operator)` for every operator trait we dispatch. | |
| 4219 | +const OPERATOR_TRAITS: &[(&str, &str)] = &[ | |
| 4220 | + ("Add", "+"), ("Sub", "-"), ("Mul", "*"), ("Div", "/"), ("Rem", "%"), | |
| 4221 | + ("BitAnd", "&"), ("BitOr", "|"), ("BitXor", "^"), ("Shl", "<<"), ("Shr", ">>"), | |
| 4222 | + ("AddAssign", "+="), ("SubAssign", "-="), ("MulAssign", "*="), ("DivAssign", "/="), | |
| 4223 | + ("RemAssign", "%="), ("BitAndAssign", "&="), ("BitOrAssign", "|="), | |
| 4224 | + ("BitXorAssign", "^="), ("ShlAssign", "<<="), ("ShrAssign", ">>="), | |
| 4225 | + ("Neg", "neg"), ("Not", "not"), | |
| 4226 | +]; | |
| 4227 | + | |
| 4228 | +/// `(operator, trait method name)`. | |
| 4229 | +const OP_METHOD: &[(&str, &str)] = &[ | |
| 4230 | + ("+", "add"), ("-", "sub"), ("*", "mul"), ("/", "div"), ("%", "rem"), | |
| 4231 | + ("&", "bitand"), ("|", "bitor"), ("^", "bitxor"), ("<<", "shl"), (">>", "shr"), | |
| 4232 | + ("+=", "add_assign"), ("-=", "sub_assign"), ("*=", "mul_assign"), | |
| 4233 | + ("/=", "div_assign"), ("%=", "rem_assign"), ("&=", "bitand_assign"), | |
| 4234 | + ("|=", "bitor_assign"), ("^=", "bitxor_assign"), ("<<=", "shl_assign"), | |
| 4235 | + (">>=", "shr_assign"), ("neg", "neg"), ("not", "not"), | |
| 4236 | +]; | |
| 4237 | + | |
| 4238 | +fn op_method(op: &str) -> &'static str { | |
| 4239 | + OP_METHOD.iter().find(|(o, _)| *o == op).map(|(_, m)| *m).unwrap_or("") | |
| 4240 | +} | |
| 4241 | + | |
| 4242 | +/// The operator symbol a compound assignment applies. | |
| 4243 | +fn compound_symbol(op: &BinOp) -> &'static str { | |
| 4244 | + match op { | |
| 4245 | + BinOp::AddAssign(_) => "+=", | |
| 4246 | + BinOp::SubAssign(_) => "-=", | |
| 4247 | + BinOp::MulAssign(_) => "*=", | |
| 4248 | + BinOp::DivAssign(_) => "/=", | |
| 4249 | + BinOp::RemAssign(_) => "%=", | |
| 4250 | + BinOp::BitAndAssign(_) => "&=", | |
| 4251 | + BinOp::BitOrAssign(_) => "|=", | |
| 4252 | + BinOp::BitXorAssign(_) => "^=", | |
| 4253 | + BinOp::ShlAssign(_) => "<<=", | |
| 4254 | + BinOp::ShrAssign(_) => ">>=", | |
| 4255 | + _ => "", | |
| 4256 | + } | |
| 4257 | +} | |
| 4258 | + | |
| 4259 | +fn binary_symbol(op: &BinOp) -> &'static str { | |
| 4260 | + match op { | |
| 4261 | + BinOp::Add(_) => "+", | |
| 4262 | + BinOp::Sub(_) => "-", | |
| 4263 | + BinOp::Mul(_) => "*", | |
| 4264 | + BinOp::Div(_) => "/", | |
| 4265 | + BinOp::Rem(_) => "%", | |
| 4266 | + BinOp::BitAnd(_) => "&", | |
| 4267 | + BinOp::BitOr(_) => "|", | |
| 4268 | + BinOp::BitXor(_) => "^", | |
| 4269 | + BinOp::Shl(_) => "<<", | |
| 4270 | + BinOp::Shr(_) => ">>", | |
| 4271 | + _ => "", | |
| 4272 | + } | |
| 4273 | +} | |
| 4274 | + | |
| 4275 | +/// The operator a trait overloads, if it is one of the operator traits. | |
| 4276 | +fn operator_trait(t: &str) -> Option<&'static str> { | |
| 4277 | + Some(match t { | |
| 4278 | + "Add" => "+", | |
| 4279 | + "Sub" => "-", | |
| 4280 | + "Mul" => "*", | |
| 4281 | + "Div" => "/", | |
| 4282 | + "Rem" => "%", | |
| 4283 | + "BitAnd" => "&", | |
| 4284 | + "BitOr" => "|", | |
| 4285 | + "BitXor" => "^", | |
| 4286 | + "Shl" => "<<", | |
| 4287 | + "Shr" => ">>", | |
| 4288 | + "AddAssign" => "+=", | |
| 4289 | + "SubAssign" => "-=", | |
| 4290 | + "MulAssign" => "*=", | |
| 4291 | + "DivAssign" => "/=", | |
| 4292 | + "RemAssign" => "%=", | |
| 4293 | + "BitAndAssign" => "&=", | |
| 4294 | + "BitOrAssign" => "|=", | |
| 4295 | + "BitXorAssign" => "^=", | |
| 4296 | + "ShlAssign" => "<<=", | |
| 4297 | + "ShrAssign" => ">>=", | |
| 4298 | + "Neg" => "neg", | |
| 4299 | + "Not" => "not", | |
| 4300 | + _ => return None, | |
| 4301 | + }) | |
| 4302 | +} | |
| 4303 | + | |
| 4304 | +/// The Nim proc name for a trait method, qualified by trait and type so that | |
| 4305 | +/// two traits declaring the same method name cannot collide. | |
| 4306 | +fn trait_method_name(ty: &str, tr: &str, m: &str) -> String { | |
| 4307 | + format!("rs{}_{}_{}", tr, ty, m) | |
| 4308 | +} | |
| 4309 | + | |
| 3867 | 4310 | fn is_fmt_trait(t: &str) -> bool { |
| 3868 | 4311 | matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal") |
| 3869 | 4312 | } |
| @@ -3880,6 +4323,19 @@ fn fmt_proc(t: &str) -> &'static str { | ||
| 3880 | 4323 | } |
| 3881 | 4324 | } |
| 3882 | 4325 | |
| 4326 | +/// Whether an expression is an iterator-producing chain rather than a value. | |
| 4327 | +fn is_iterator_expr(e: &Expr) -> bool { | |
| 4328 | + match e { | |
| 4329 | + Expr::MethodCall(m) => matches!( | |
| 4330 | + m.method.to_string().as_str(), | |
| 4331 | + "iter" | "iter_mut" | "into_iter" | "enumerate" | "zip" | "chunks_exact" | |
| 4332 | + | "chunks_exact_mut" | "windows" | |
| 4333 | + ), | |
| 4334 | + Expr::Paren(p) => is_iterator_expr(&p.expr), | |
| 4335 | + _ => false, | |
| 4336 | + } | |
| 4337 | +} | |
| 4338 | + | |
| 3883 | 4339 | /// Whether an expression denotes a place -- a variable, a field, or an index |
| 3884 | 4340 | /// or slice of one -- and so may be re-evaluated with no side effect. |
| 3885 | 4341 | fn is_pure_place(e: &Expr) -> bool { |
| @@ -110,6 +110,10 @@ enum Alias { | |||
| 110 | Value { code: String, ty: Option<Nim> }, | 110 | Value { code: String, ty: Option<Nim> }, |
| 111 | /// The name stands for a window: `code[off .. off + len - 1]`. | 111 | /// The name stands for a window: `code[off .. off + len - 1]`. |
| 112 | Window { code: String, off: String, len: String, elem: Option<Nim> }, | 112 | Window { code: String, off: String, len: String, elem: Option<Nim> }, |
| 113 | + /// The name stands for an iterator that has not been consumed yet, as in | ||
| 114 | + /// `let it = xs.chunks_exact(k);`. Rust's iterators are values; ours are | ||
| 115 | + /// resolved chains, so the chain is carried until a `for` consumes it. | ||
| 116 | + Iterator(Box<Iter>), | ||
| 113 | } | 117 | } |
| 114 | 118 | ||
| 115 | /// A lowered expression: its Nim text, and its type where we know it. | 119 | /// A lowered expression: its Nim text, and its type where we know it. |
| @@ -192,6 +196,8 @@ pub struct Lowerer { | |||
| 192 | fns: HashMap<(String, String), Sig>, | 196 | fns: HashMap<(String, String), Sig>, |
| 193 | /// Module being lowered: the file stem, or empty for the crate root. | 197 | /// Module being lowered: the file stem, or empty for the crate root. |
| 194 | cur_mod: String, | 198 | cur_mod: String, |
| 199 | + /// The type of the `impl` block being lowered, which `Self` names. | ||
| 200 | + self_ty: Option<Nim>, | ||
| 195 | /// `use` brings a name into scope from another module. Flattening loses | 201 | /// `use` brings a name into scope from another module. Flattening loses |
| 196 | /// the module structure, so the mapping is recorded and consulted when a | 202 | /// the module structure, so the mapping is recorded and consulted when a |
| 197 | /// bare call is resolved. | 203 | /// bare call is resolved. |
| @@ -211,6 +217,11 @@ pub struct Lowerer { | |||
| 211 | fmt_impls: HashMap<(String, String), ()>, | 217 | fmt_impls: HashMap<(String, String), ()>, |
| 212 | /// `(from, to)` conversions declared by `impl From<A> for B`. | 218 | /// `(from, to)` conversions declared by `impl From<A> for B`. |
| 213 | from_impls: HashMap<(String, String), String>, | 219 | from_impls: HashMap<(String, String), String>, |
| 220 | + /// Operator traits implemented for a type, so `a += b` on a user type can | ||
| 221 | + /// be dispatched to the impl rather than to Nim's built-in operator. | ||
| 222 | + op_impls: HashMap<(String, String), ()>, | ||
| 223 | + /// `(type, method) -> nim name`, for calls written as `Type::method(..)`. | ||
| 224 | + statics: HashMap<(String, String), String>, | ||
| 214 | /// Forward declarations, emitted between the type definitions and the | 225 | /// Forward declarations, emitted between the type definitions and the |
| 215 | /// bodies. Rust has no declaration-before-use rule and Nim does, so every | 226 | /// bodies. Rust has no declaration-before-use rule and Nim does, so every |
| 216 | /// proc is declared up front rather than the input being reordered -- | 227 | /// proc is declared up front rather than the input being reordered -- |
| @@ -252,6 +263,7 @@ impl Lowerer { | |||
| 252 | alias_scopes: vec![HashMap::new()], | 263 | alias_scopes: vec![HashMap::new()], |
| 253 | fns: HashMap::new(), | 264 | fns: HashMap::new(), |
| 254 | cur_mod: String::new(), | 265 | cur_mod: String::new(), |
| 266 | + self_ty: None, | ||
| 255 | use_map: HashMap::new(), | 267 | use_map: HashMap::new(), |
| 256 | structs: HashMap::new(), | 268 | structs: HashMap::new(), |
| 257 | enums: HashMap::new(), | 269 | enums: HashMap::new(), |
| @@ -259,6 +271,8 @@ impl Lowerer { | |||
| 259 | methods: HashMap::new(), | 271 | methods: HashMap::new(), |
| 260 | fmt_impls: HashMap::new(), | 272 | fmt_impls: HashMap::new(), |
| 261 | from_impls: HashMap::new(), | 273 | from_impls: HashMap::new(), |
| 274 | + op_impls: HashMap::new(), | ||
| 275 | + statics: HashMap::new(), | ||
| 262 | fmt_param: None, | 276 | fmt_param: None, |
| 263 | vec_expect: None, | 277 | vec_expect: None, |
| 264 | forwards: Vec::new(), | 278 | forwards: Vec::new(), |
| @@ -545,8 +559,21 @@ impl Lowerer { | |||
| 545 | } | 559 | } |
| 546 | Item::Impl(im) => { | 560 | Item::Impl(im) => { |
| 547 | let self_ty = self.map_ty(&im.self_ty)?; | 561 | let self_ty = self.map_ty(&im.self_ty)?; |
| 548 | - let tyname = type_name(&self_ty); | 562 | + let outer_self = self.self_ty.replace(self_ty.clone()); |
| 549 | - if let Some((path, _)) = &im.trait_ { | 563 | + let r = self.collect_impl(im, &self_ty); |
| 564 | + self.self_ty = outer_self; | ||
| 565 | + return r; | ||
| 566 | + } | ||
| 567 | + _ => {} | ||
| 568 | + } | ||
| 569 | + Ok(()) | ||
| 570 | + } | ||
| 571 | + | ||
| 572 | + fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> { | ||
| 573 | + { | ||
| 574 | + let self_ty = self_ty.clone(); | ||
| 575 | + let tyname = type_name(&self_ty); | ||
| 576 | + if let Some((path, _)) = &im.trait_ { | ||
| 550 | let tr = path_name(path); | 577 | let tr = path_name(path); |
| 551 | if im.items.is_empty() { | 578 | if im.items.is_empty() { |
| 552 | // A marker trait with no items. We do not model trait | 579 | // A marker trait with no items. We do not model trait |
| @@ -579,11 +606,33 @@ impl Lowerer { | |||
| 579 | .insert((type_name(&src), tyname), name); | 606 | .insert((type_name(&src), tyname), name); |
| 580 | return Ok(()); | 607 | return Ok(()); |
| 581 | } | 608 | } |
| 582 | - return Err(format!( | 609 | + // Any other trait: its methods are emitted as procs on |
| 583 | - "`impl {tr} for {tyname}`: only formatting traits \ | 610 | + // the type, named after the trait so two traits declaring |
| 584 | - (Display, Debug, LowerHex, UpperHex, Binary, Octal), \ | 611 | + // the same method name do not collide. The *trait* is not |
| 585 | - `From`, and marker traits with no items are implemented" | 612 | + // modelled -- no dynamic dispatch, no bounds -- and a use |
| 586 | - )); | 613 | + // that needs it is rejected where it appears. |
| 614 | + if let Some(op) = operator_trait(&tr) { | ||
| 615 | + self.op_impls.insert((tyname.clone(), op.to_string()), ()); | ||
| 616 | + } | ||
| 617 | + for it in &im.items { | ||
| 618 | + let syn::ImplItem::Fn(m) = it else { | ||
| 619 | + return Err(format!("unsupported item in `impl {tr}`")); | ||
| 620 | + }; | ||
| 621 | + let mname = m.sig.ident.to_string(); | ||
| 622 | + let (mut params, ret) = self.signature(&m.sig)?; | ||
| 623 | + let recv = if takes_self(&m.sig) { | ||
| 624 | + params.insert(0, self_ty.clone()); | ||
| 625 | + Some(self_ty.clone()) | ||
| 626 | + } else { | ||
| 627 | + None | ||
| 628 | + }; | ||
| 629 | + let nim = trait_method_name(&tyname, &tr, &mname); | ||
| 630 | + self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?); | ||
| 631 | + self.methods | ||
| 632 | + .insert((tyname.clone(), mname.clone()), Sig { params, ret }); | ||
| 633 | + self.statics.insert((tyname.clone(), mname), nim); | ||
| 634 | + } | ||
| 635 | + return Ok(()); | ||
| 587 | } | 636 | } |
| 588 | for it in &im.items { | 637 | for it in &im.items { |
| 589 | if let syn::ImplItem::Fn(m) = it { | 638 | if let syn::ImplItem::Fn(m) = it { |
| @@ -592,14 +641,15 @@ impl Lowerer { | |||
| 592 | params.insert(0, self_ty.clone()); | 641 | params.insert(0, self_ty.clone()); |
| 593 | } | 642 | } |
| 594 | let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; | 643 | let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; |
| 595 | - let head = self.head_of(&m.sig.ident.to_string(), &m.sig, recv.as_ref())?; | 644 | + let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string()); |
| 645 | + let head = self.head_of(&nim, &m.sig, recv.as_ref())?; | ||
| 596 | self.forwards.push(head); | 646 | self.forwards.push(head); |
| 597 | self.methods | 647 | self.methods |
| 598 | .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret }); | 648 | .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret }); |
| 649 | + self.statics | ||
| 650 | + .insert((tyname.clone(), m.sig.ident.to_string()), nim); | ||
| 599 | } | 651 | } |
| 600 | } | 652 | } |
| 601 | - } | ||
| 602 | - _ => {} | ||
| 603 | } | 653 | } |
| 604 | Ok(()) | 654 | Ok(()) |
| 605 | } | 655 | } |
| @@ -625,6 +675,29 @@ impl Lowerer { | |||
| 625 | 675 | ||
| 626 | fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> { | 676 | fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> { |
| 627 | match m { | 677 | match m { |
| 678 | + // Bare flags whose value is determined by the profile this project | ||
| 679 | + // models: a normal (non-`--test`) debug build, not a docs build. | ||
| 680 | + // Anything platform-specific stays rejected, since we would be | ||
| 681 | + // picking a target on the user's behalf. | ||
| 682 | + syn::Meta::Path(p) if p.is_ident("test") => Ok(false), | ||
| 683 | + syn::Meta::Path(p) if p.is_ident("debug_assertions") => Ok(true), | ||
| 684 | + syn::Meta::Path(p) if p.is_ident("docsrs") || p.is_ident("doc") => Ok(false), | ||
| 685 | + // The generated Nim is compiled for the same machine, so the | ||
| 686 | + // target's word size and endianness are known rather than | ||
| 687 | + // guessed. This does mean the output is host-shaped: a crate that | ||
| 688 | + // branches on pointer width has had that branch decided here. | ||
| 689 | + syn::Meta::NameValue(nv) if nv.path.is_ident("target_pointer_width") => { | ||
| 690 | + let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { | ||
| 691 | + return Err("`target_pointer_width = ..` expects a string".into()); | ||
| 692 | + }; | ||
| 693 | + Ok(s.value() == (usize::BITS).to_string()) | ||
| 694 | + } | ||
| 695 | + syn::Meta::NameValue(nv) if nv.path.is_ident("target_endian") => { | ||
| 696 | + let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { | ||
| 697 | + return Err("`target_endian = ..` expects a string".into()); | ||
| 698 | + }; | ||
| 699 | + Ok(s.value() == if cfg!(target_endian = "big") { "big" } else { "little" }) | ||
| 700 | + } | ||
| 628 | syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => { | 701 | syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => { |
| 629 | let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { | 702 | let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { |
| 630 | return Err("`feature = ..` expects a string".into()); | 703 | return Err("`feature = ..` expects a string".into()); |
| @@ -655,11 +728,28 @@ impl Lowerer { | |||
| 655 | } | 728 | } |
| 656 | } | 729 | } |
| 657 | 730 | ||
| 658 | - /// Map a Rust type, expanding any `type` alias first. Every type in the | 731 | + /// Map a Rust type, resolving `Self` and expanding any `type` alias. Every type in the |
| 659 | /// lowering goes through here rather than calling `ty::map` directly, so | 732 | /// lowering goes through here rather than calling `ty::map` directly, so |
| 660 | /// an alias cannot be missed in one position and honoured in another. | 733 | /// an alias cannot be missed in one position and honoured in another. |
| 661 | fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> { | 734 | fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> { |
| 662 | - ty::map(&self.expand(t, 0)?) | 735 | + let n = ty::map(&self.expand(t, 0)?)?; |
| 736 | + Ok(self.subst_self(n)) | ||
| 737 | + } | ||
| 738 | + | ||
| 739 | + /// `Self` inside an `impl` block names the type being implemented. | ||
| 740 | + fn subst_self(&self, t: Nim) -> Nim { | ||
| 741 | + let Some(me) = &self.self_ty else { return t }; | ||
| 742 | + match t { | ||
| 743 | + Nim::Named(n, _) if n == "Self" => me.clone(), | ||
| 744 | + Nim::Seq(e) => Nim::Seq(Box::new(self.subst_self(*e))), | ||
| 745 | + Nim::OpenArray(e) => Nim::OpenArray(Box::new(self.subst_self(*e))), | ||
| 746 | + Nim::Array(n, e) => Nim::Array(n, Box::new(self.subst_self(*e))), | ||
| 747 | + Nim::Var(e) => Nim::Var(Box::new(self.subst_self(*e))), | ||
| 748 | + Nim::Named(n, a) => { | ||
| 749 | + Nim::Named(n, a.into_iter().map(|x| self.subst_self(x)).collect()) | ||
| 750 | + } | ||
| 751 | + other => other, | ||
| 752 | + } | ||
| 663 | } | 753 | } |
| 664 | 754 | ||
| 665 | fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> { | 755 | fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> { |
| @@ -872,46 +962,32 @@ impl Lowerer { | |||
| 872 | } | 962 | } |
| 873 | Item::Const(c) => { | 963 | Item::Const(c) => { |
| 874 | let t = self.map_ty(&c.ty)?.owned(); | 964 | let t = self.map_ty(&c.ty)?.owned(); |
| 875 | - let v = self.expr(&c.expr)?; | 965 | + // The annotation types the initialiser, exactly as it does for |
| 966 | + // a `let`: `const MOD: u32 = 65521` is a u32 literal. | ||
| 967 | + let v = self.expr_at(&c.expr, Some(&t))?; | ||
| 876 | self.bind(&c.ident.to_string(), t.clone()); | 968 | self.bind(&c.ident.to_string(), t.clone()); |
| 877 | - let line = format!("const {}*: {} = {}", ident(&c.ident.to_string()), t.render(), v.code); | 969 | + // Only a top-level const is exported; `*` on a local is not |
| 970 | + // Nim syntax. | ||
| 971 | + let star = if self.indent == 0 { "*" } else { "" }; | ||
| 972 | + let line = format!( | ||
| 973 | + "const {}{}: {} = {}", | ||
| 974 | + ident(&c.ident.to_string()), | ||
| 975 | + star, | ||
| 976 | + t.render(), | ||
| 977 | + v.code | ||
| 978 | + ); | ||
| 878 | self.line(&line); | 979 | self.line(&line); |
| 879 | - self.blank(); | 980 | + if self.indent == 0 { |
| 981 | + self.blank(); | ||
| 982 | + } | ||
| 880 | Ok(()) | 983 | Ok(()) |
| 881 | } | 984 | } |
| 882 | Item::Impl(im) => { | 985 | Item::Impl(im) => { |
| 883 | let self_ty = self.map_ty(&im.self_ty)?; | 986 | let self_ty = self.map_ty(&im.self_ty)?; |
| 884 | - if let Some((path, _)) = &im.trait_ { | 987 | + let outer = self.self_ty.replace(self_ty.clone()); |
| 885 | - let tr = path_name(path); | 988 | + let r = self.impl_body(im, &self_ty); |
| 886 | - if im.items.is_empty() { | 989 | + self.self_ty = outer; |
| 887 | - return Ok(()); | 990 | + r |
| 888 | - } | ||
| 889 | - let syn::ImplItem::Fn(m) = &im.items[0] else { | ||
| 890 | - return Err(format!("unsupported item in `impl {tr}`")); | ||
| 891 | - }; | ||
| 892 | - if is_fmt_trait(&tr) { | ||
| 893 | - return self.fmt_impl(&tr, &self_ty, &m.sig, &m.block); | ||
| 894 | - } | ||
| 895 | - if tr == "From" { | ||
| 896 | - let name = { | ||
| 897 | - let (params, _) = self.signature(&m.sig)?; | ||
| 898 | - let src = params.first().cloned().ok_or("`fn from` takes one argument")?; | ||
| 899 | - self.from_impls[&(type_name(&src), type_name(&self_ty))].clone() | ||
| 900 | - }; | ||
| 901 | - return self.func_named(&name, &m.sig, &m.block, None); | ||
| 902 | - } | ||
| 903 | - return Err(format!("`impl {tr}` is not implemented")); | ||
| 904 | - } | ||
| 905 | - for it in &im.items { | ||
| 906 | - match it { | ||
| 907 | - syn::ImplItem::Fn(m) => { | ||
| 908 | - let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; | ||
| 909 | - self.func(&m.sig, &m.block, recv)?; | ||
| 910 | - } | ||
| 911 | - _ => return Err("only `fn` items are supported inside `impl`".into()), | ||
| 912 | - } | ||
| 913 | - } | ||
| 914 | - Ok(()) | ||
| 915 | } | 991 | } |
| 916 | // `use` and `extern crate` are resolution directives with no Nim | 992 | // `use` and `extern crate` are resolution directives with no Nim |
| 917 | // analogue once everything is one module. | 993 | // analogue once everything is one module. |
| @@ -951,6 +1027,71 @@ impl Lowerer { | |||
| 951 | } | 1027 | } |
| 952 | } | 1028 | } |
| 953 | 1029 | ||
| 1030 | + fn impl_body(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> { | ||
| 1031 | + if let Some((path, _)) = &im.trait_ { | ||
| 1032 | + let tr = path_name(path); | ||
| 1033 | + if im.items.is_empty() { | ||
| 1034 | + return Ok(()); | ||
| 1035 | + } | ||
| 1036 | + if is_fmt_trait(&tr) { | ||
| 1037 | + let syn::ImplItem::Fn(m) = &im.items[0] else { | ||
| 1038 | + return Err(format!("unsupported item in `impl {tr}`")); | ||
| 1039 | + }; | ||
| 1040 | + return self.fmt_impl(&tr, self_ty, &m.sig, &m.block); | ||
| 1041 | + } | ||
| 1042 | + if tr == "From" { | ||
| 1043 | + let syn::ImplItem::Fn(m) = &im.items[0] else { | ||
| 1044 | + return Err("`impl From` must contain `fn from`".into()); | ||
| 1045 | + }; | ||
| 1046 | + let name = { | ||
| 1047 | + let (params, _) = self.signature(&m.sig)?; | ||
| 1048 | + let src = params.first().cloned().ok_or("`fn from` takes one argument")?; | ||
| 1049 | + self.from_impls[&(type_name(&src), type_name(self_ty))].clone() | ||
| 1050 | + }; | ||
| 1051 | + return self.func_named(&name, &m.sig, &m.block, None); | ||
| 1052 | + } | ||
| 1053 | + let tyname = type_name(self_ty); | ||
| 1054 | + for it in &im.items { | ||
| 1055 | + let syn::ImplItem::Fn(m) = it else { | ||
| 1056 | + return Err(format!("unsupported item in `impl {tr}`")); | ||
| 1057 | + }; | ||
| 1058 | + let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; | ||
| 1059 | + let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string()); | ||
| 1060 | + self.func_named(&nim, &m.sig, &m.block, recv)?; | ||
| 1061 | + } | ||
| 1062 | + return Ok(()); | ||
| 1063 | + } | ||
| 1064 | + for it in &im.items { | ||
| 1065 | + match it { | ||
| 1066 | + syn::ImplItem::Fn(m) => { | ||
| 1067 | + let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; | ||
| 1068 | + let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string()); | ||
| 1069 | + self.func_named(&nim, &m.sig, &m.block, recv)?; | ||
| 1070 | + } | ||
| 1071 | + _ => return Err("only `fn` items are supported inside `impl`".into()), | ||
| 1072 | + } | ||
| 1073 | + } | ||
| 1074 | + Ok(()) | ||
| 1075 | + } | ||
| 1076 | + | ||
| 1077 | + /// The type an operator impl declares for its right-hand operand. | ||
| 1078 | + fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> { | ||
| 1079 | + let n = type_name(t.as_ref()?); | ||
| 1080 | + let sig = self.methods.get(&(n, op_method(op).to_string()))?; | ||
| 1081 | + sig.params.get(1).cloned().map(|t| t.unvar()) | ||
| 1082 | + } | ||
| 1083 | + | ||
| 1084 | + /// The proc implementing `op` for a user type, if there is one. | ||
| 1085 | + fn op_proc(&self, t: &Option<Nim>, op: &str) -> Option<String> { | ||
| 1086 | + let n = type_name(t.as_ref()?); | ||
| 1087 | + let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0; | ||
| 1088 | + if self.op_impls.contains_key(&(n.clone(), op.to_string())) { | ||
| 1089 | + Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1)) | ||
| 1090 | + } else { | ||
| 1091 | + None | ||
| 1092 | + } | ||
| 1093 | + } | ||
| 1094 | + | ||
| 954 | fn emit_enum(&mut self, def: &EnumDef) { | 1095 | fn emit_enum(&mut self, def: &EnumDef) { |
| 955 | let name = ident(&def.name); | 1096 | let name = ident(&def.name); |
| 956 | if def.simple { | 1097 | if def.simple { |
| @@ -1344,7 +1485,10 @@ impl Lowerer { | |||
| 1344 | } | 1485 | } |
| 1345 | Ok(()) | 1486 | Ok(()) |
| 1346 | } | 1487 | } |
| 1347 | - Stmt::Item(i) => self.item(i), | 1488 | + // A `const` declared inside a function body is local to it, and |
| 1489 | + // must be emitted here rather than skipped as an already-emitted | ||
| 1490 | + // top-level type. | ||
| 1491 | + Stmt::Item(i) => self.item_inner(i), | ||
| 1348 | Stmt::Macro(m) => { | 1492 | Stmt::Macro(m) => { |
| 1349 | let line = self.macro_call(&m.mac)?; | 1493 | let line = self.macro_call(&m.mac)?; |
| 1350 | self.line(&line); | 1494 | self.line(&line); |
| @@ -1361,6 +1505,7 @@ impl Lowerer { | |||
| 1361 | _ => return Err("only `let <ident>` bindings are supported".into()), | 1505 | _ => return Err("only `let <ident>` bindings are supported".into()), |
| 1362 | }, | 1506 | }, |
| 1363 | Pat::Wild(_) => ("_".into(), false, None), | 1507 | Pat::Wild(_) => ("_".into(), false, None), |
| 1508 | + Pat::Tuple(t) => return self.local_tuple(l, t), | ||
| 1364 | _ => return Err("destructuring `let` is not implemented yet".into()), | 1509 | _ => return Err("destructuring `let` is not implemented yet".into()), |
| 1365 | }; | 1510 | }; |
| 1366 | 1511 | ||
| @@ -1397,6 +1542,13 @@ impl Lowerer { | |||
| 1397 | return self.assign_from(&init.expr, &target, Some(&t)); | 1542 | return self.assign_from(&init.expr, &target, Some(&t)); |
| 1398 | } | 1543 | } |
| 1399 | 1544 | ||
| 1545 | + // `let it = xs.chunks_exact(k)` binds an iterator, not a value. | ||
| 1546 | + if is_iterator_expr(&init.expr) { | ||
| 1547 | + let it = self.resolve_iter(&init.expr)?; | ||
| 1548 | + self.bind_alias(&name, Alias::Iterator(Box::new(it))); | ||
| 1549 | + return Ok(()); | ||
| 1550 | + } | ||
| 1551 | + | ||
| 1400 | let v = self.expr_at(&init.expr, ann.as_ref())?; | 1552 | let v = self.expr_at(&init.expr, ann.as_ref())?; |
| 1401 | 1553 | ||
| 1402 | // `let s = &buf[..n]` binds a view of a place that is already in | 1554 | // `let s = &buf[..n]` binds a view of a place that is already in |
| @@ -1468,6 +1620,76 @@ impl Lowerer { | |||
| 1468 | Ok(()) | 1620 | Ok(()) |
| 1469 | } | 1621 | } |
| 1470 | 1622 | ||
| 1623 | + /// `let (a, b) = ..` — tuple destructuring. | ||
| 1624 | + fn local_tuple(&mut self, l: &Local, t: &syn::PatTuple) -> Result<(), String> { | ||
| 1625 | + let Some(init) = &l.init else { | ||
| 1626 | + return Err("a destructuring `let` needs an initialiser".into()); | ||
| 1627 | + }; | ||
| 1628 | + let names: Vec<(String, bool)> = t | ||
| 1629 | + .elems | ||
| 1630 | + .iter() | ||
| 1631 | + .map(|p| match p { | ||
| 1632 | + Pat::Ident(i) => Ok((i.ident.to_string(), i.mutability.is_some())), | ||
| 1633 | + Pat::Wild(_) => Ok(("_".to_string(), false)), | ||
| 1634 | + _ => Err("only plain identifiers are supported in a destructuring `let`"), | ||
| 1635 | + }) | ||
| 1636 | + .collect::<Result<_, _>>()?; | ||
| 1637 | + | ||
| 1638 | + // `split_at` hands back two *views* of the same slice. Nim has no | ||
| 1639 | + // tuple of views, and there is nothing to materialise anyway, so each | ||
| 1640 | + // name becomes a window into the original. | ||
| 1641 | + if let Expr::MethodCall(m) = &*init.expr { | ||
| 1642 | + let mname = m.method.to_string(); | ||
| 1643 | + if (mname == "split_at" || mname == "split_at_mut") | ||
| 1644 | + && m.args.len() == 1 | ||
| 1645 | + && names.len() == 2 | ||
| 1646 | + { | ||
| 1647 | + let (code, base, len, elem) = self.slice_parts(&m.receiver)?; | ||
| 1648 | + let at = self.expr(&m.args[0])?; | ||
| 1649 | + let cut = self.fresh("Cut"); | ||
| 1650 | + self.line(&format!("let {}: int = int({})", cut, at.code)); | ||
| 1651 | + self.bind_alias( | ||
| 1652 | + &names[0].0, | ||
| 1653 | + Alias::Window { | ||
| 1654 | + code: code.clone(), | ||
| 1655 | + off: base.clone(), | ||
| 1656 | + len: cut.clone(), | ||
| 1657 | + elem: elem.clone(), | ||
| 1658 | + }, | ||
| 1659 | + ); | ||
| 1660 | + self.bind_alias( | ||
| 1661 | + &names[1].0, | ||
| 1662 | + Alias::Window { | ||
| 1663 | + code, | ||
| 1664 | + off: format!("({} + {})", base, cut), | ||
| 1665 | + len: format!("({} - {})", len, cut), | ||
| 1666 | + elem, | ||
| 1667 | + }, | ||
| 1668 | + ); | ||
| 1669 | + return Ok(()); | ||
| 1670 | + } | ||
| 1671 | + } | ||
| 1672 | + | ||
| 1673 | + let v = self.expr(&init.expr)?; | ||
| 1674 | + let tys = match &v.ty { | ||
| 1675 | + Some(Nim::Tuple(ts)) if ts.len() == names.len() => ts.clone(), | ||
| 1676 | + _ => { | ||
| 1677 | + return Err(format!( | ||
| 1678 | + "cannot destructure this into {} bindings: its type is not a \ | ||
| 1679 | + tuple of that many elements", | ||
| 1680 | + names.len() | ||
| 1681 | + )) | ||
| 1682 | + } | ||
| 1683 | + }; | ||
| 1684 | + let kw = if names.iter().any(|(_, m)| *m) { "var" } else { "let" }; | ||
| 1685 | + let lhs: Vec<String> = names.iter().map(|(n, _)| ident(n)).collect(); | ||
| 1686 | + self.line(&format!("{} ({}) = {}", kw, lhs.join(", "), v.code)); | ||
| 1687 | + for ((n, _), t) in names.iter().zip(tys) { | ||
| 1688 | + self.bind(n, t); | ||
| 1689 | + } | ||
| 1690 | + Ok(()) | ||
| 1691 | + } | ||
| 1692 | + | ||
| 1471 | /// Expressions that are statements in Rust and statements in Nim too | 1693 | /// Expressions that are statements in Rust and statements in Nim too |
| 1472 | /// (control flow). Returns `None` when it emitted lines itself. | 1694 | /// (control flow). Returns `None` when it emitted lines itself. |
| 1473 | fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> { | 1695 | fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> { |
| @@ -1558,6 +1780,16 @@ impl Lowerer { | |||
| 1558 | } | 1780 | } |
| 1559 | Expr::Binary(b) if is_compound(&b.op) => { | 1781 | Expr::Binary(b) if is_compound(&b.op) => { |
| 1560 | let lhs = self.expr(&b.left)?; | 1782 | let lhs = self.expr(&b.left)?; |
| 1783 | + // A compound assignment on a user type goes to that type's own | ||
| 1784 | + // `impl OpAssign`, not to Nim's built-in operator. | ||
| 1785 | + if let Some(f) = self.op_proc(&lhs.ty, compound_symbol(&b.op)) { | ||
| 1786 | + // The impl's own parameter type types the right operand, | ||
| 1787 | + // so `b_vec *= 4` takes 4 at the width the impl declares. | ||
| 1788 | + let want = self.op_param(&lhs.ty, compound_symbol(&b.op)); | ||
| 1789 | + let rhs = self.expr_at(&b.right, want.as_ref())?; | ||
| 1790 | + self.line(&format!("{}({}, {})", f, lhs.code, rhs.code)); | ||
| 1791 | + return Ok(None); | ||
| 1792 | + } | ||
| 1561 | // `i += 1` must widen the literal to `i`'s type, not to the | 1793 | // `i += 1` must widen the literal to `i`'s type, not to the |
| 1562 | // i32 an unconstrained Rust literal would default to. | 1794 | // i32 an unconstrained Rust literal would default to. |
| 1563 | let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?; | 1795 | let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?; |
| @@ -1753,6 +1985,23 @@ impl Lowerer { | |||
| 1753 | )), | 1985 | )), |
| 1754 | } | 1986 | } |
| 1755 | } | 1987 | } |
| 1988 | + Expr::Path(p) => { | ||
| 1989 | + let n = path_name(&p.path); | ||
| 1990 | + if let Some(Alias::Iterator(it)) = self.lookup_alias(&n) { | ||
| 1991 | + return Ok((*it).clone()); | ||
| 1992 | + } | ||
| 1993 | + if let Some(Alias::Window { code, off, len, elem }) = self.lookup_alias(&n) { | ||
| 1994 | + return Ok(Iter::Elems { code, off, len, elem, mutable: false }); | ||
| 1995 | + } | ||
| 1996 | + let v = self.expr(e)?; | ||
| 1997 | + Ok(Iter::Elems { | ||
| 1998 | + len: format!("{}.len", v.code), | ||
| 1999 | + elem: elem_of(&v.ty), | ||
| 2000 | + code: v.code, | ||
| 2001 | + off: "0".into(), | ||
| 2002 | + mutable: false, | ||
| 2003 | + }) | ||
| 2004 | + } | ||
| 1756 | other => { | 2005 | other => { |
| 1757 | // A `for` binding that is itself a window iterates that window, | 2006 | // A `for` binding that is itself a window iterates that window, |
| 1758 | // not the whole container it points into. | 2007 | // not the whole container it points into. |
| @@ -2262,6 +2511,14 @@ impl Lowerer { | |||
| 2262 | format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len), | 2511 | format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len), |
| 2263 | elem.map(|e| Nim::OpenArray(Box::new(e))), | 2512 | elem.map(|e| Nim::OpenArray(Box::new(e))), |
| 2264 | ), | 2513 | ), |
| 2514 | + // An iterator is not a value here: it is consumed by a | ||
| 2515 | + // `for`, or asked for its `.remainder()`. | ||
| 2516 | + Alias::Iterator(_) => { | ||
| 2517 | + return Err(format!( | ||
| 2518 | + "`{name}` is an iterator; it can be iterated or asked \ | ||
| 2519 | + for its `remainder()`, but not used as a value" | ||
| 2520 | + )) | ||
| 2521 | + } | ||
| 2265 | }); | 2522 | }); |
| 2266 | } | 2523 | } |
| 2267 | if let Some(t) = self.lookup(&name) { | 2524 | if let Some(t) = self.lookup(&name) { |
| @@ -2458,7 +2715,21 @@ impl Lowerer { | |||
| 2458 | Ok(Val::new(format!("[{}]", parts.join(", ")), t)) | 2715 | Ok(Val::new(format!("[{}]", parts.join(", ")), t)) |
| 2459 | } | 2716 | } |
| 2460 | Expr::Repeat(r) => { | 2717 | Expr::Repeat(r) => { |
| 2461 | - let v = self.expr(&r.expr)?; | 2718 | + // `[0; 4]` is an array in Rust. Nim distinguishes a fixed-size |
| 2719 | + // array from a `seq`, so the expected type decides which, and | ||
| 2720 | + // an array needs its elements written out. | ||
| 2721 | + let want_elem = match expect { | ||
| 2722 | + Some(Nim::Array(_, e)) | Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => { | ||
| 2723 | + Some((**e).clone()) | ||
| 2724 | + } | ||
| 2725 | + _ => None, | ||
| 2726 | + }; | ||
| 2727 | + let v = self.expr_at(&r.expr, want_elem.as_ref())?; | ||
| 2728 | + if let Some(Nim::Array(n, _)) = expect { | ||
| 2729 | + let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect(); | ||
| 2730 | + let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t))); | ||
| 2731 | + return Ok(Val::new(format!("[{}]", elems.join(", ")), t)); | ||
| 2732 | + } | ||
| 2462 | let n = self.expr(&r.len)?; | 2733 | let n = self.expr(&r.len)?; |
| 2463 | let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t))); | 2734 | let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t))); |
| 2464 | Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t)) | 2735 | Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t)) |
| @@ -2619,6 +2890,19 @@ impl Lowerer { | |||
| 2619 | l = self.expr_at(&b.left, r.ty.as_ref())?; | 2890 | l = self.expr_at(&b.left, r.ty.as_ref())?; |
| 2620 | } | 2891 | } |
| 2621 | let r = std::mem::replace(&mut r, Val::untyped("")); | 2892 | let r = std::mem::replace(&mut r, Val::untyped("")); |
| 2893 | + // A binary operator on a user type goes to that type's own impl. | ||
| 2894 | + if let Some(f) = self.op_proc(&l.ty, binary_symbol(&b.op)) { | ||
| 2895 | + let want = self.op_param(&l.ty, binary_symbol(&b.op)); | ||
| 2896 | + let r = self.expr_at(&b.right, want.as_ref())?; | ||
| 2897 | + let ret = self | ||
| 2898 | + .methods | ||
| 2899 | + .get(&( | ||
| 2900 | + type_name(l.ty.as_ref().unwrap()), | ||
| 2901 | + op_method(binary_symbol(&b.op)).to_string(), | ||
| 2902 | + )) | ||
| 2903 | + .map(|s| s.ret.clone()); | ||
| 2904 | + return Ok(Val::new(format!("{}({}, {})", f, l.code, r.code), ret)); | ||
| 2905 | + } | ||
| 2622 | let op = self.bin_op(&b.op, &l, &r)?; | 2906 | let op = self.bin_op(&b.op, &l, &r)?; |
| 2623 | let ty = match b.op { | 2907 | let ty = match b.op { |
| 2624 | BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_) | 2908 | BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_) |
| @@ -3107,6 +3391,20 @@ impl Lowerer { | |||
| 3107 | } | 3391 | } |
| 3108 | } | 3392 | } |
| 3109 | 3393 | ||
| 3394 | + // `u32::from(b)`: `From` between primitives is lossless by definition | ||
| 3395 | + // -- it is the widening direction only -- so a plain Nim conversion is | ||
| 3396 | + // exact. (The truncating direction is `as`, which is `cast`.) | ||
| 3397 | + if name == "from" && codes.len() == 1 { | ||
| 3398 | + if let Some(q) = p.path.segments.iter().rev().nth(1) { | ||
| 3399 | + if let Some(Nim::Prim(t)) = ty::prim(&q.ident.to_string()) { | ||
| 3400 | + return Ok(Val::new( | ||
| 3401 | + format!("{}({})", t, codes[0]), | ||
| 3402 | + Some(Nim::Prim(t)), | ||
| 3403 | + )); | ||
| 3404 | + } | ||
| 3405 | + } | ||
| 3406 | + } | ||
| 3407 | + | ||
| 3110 | // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a | 3408 | // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a |
| 3111 | // string view; no copy, no validation, same memory. | 3409 | // string view; no copy, no validation, same memory. |
| 3112 | if name == "from_utf8_unchecked" && codes.len() == 1 { | 3410 | if name == "from_utf8_unchecked" && codes.len() == 1 { |
| @@ -3157,6 +3455,25 @@ impl Lowerer { | |||
| 3157 | Some((*ret).clone()), | 3455 | Some((*ret).clone()), |
| 3158 | )); | 3456 | )); |
| 3159 | } | 3457 | } |
| 3458 | + // `Adler32::new()` / `Adler32::default()`: a method called through | ||
| 3459 | + // its type rather than through a receiver. | ||
| 3460 | + if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) { | ||
| 3461 | + // `Self::new()` inside an `impl` names the type being implemented. | ||
| 3462 | + let q = if q == "Self" { | ||
| 3463 | + self.self_ty.as_ref().map(type_name).unwrap_or(q) | ||
| 3464 | + } else { | ||
| 3465 | + q | ||
| 3466 | + }; | ||
| 3467 | + if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) { | ||
| 3468 | + let ret = sig.ret.clone(); | ||
| 3469 | + let nim = self | ||
| 3470 | + .statics | ||
| 3471 | + .get(&(q.clone(), name.clone())) | ||
| 3472 | + .cloned() | ||
| 3473 | + .unwrap_or_else(|| ident(&name)); | ||
| 3474 | + return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret))); | ||
| 3475 | + } | ||
| 3476 | + } | ||
| 3160 | let ret = target.as_ref().and_then(|k| self.fns.get(k)).map(|s| s.ret.clone()); | 3477 | let ret = target.as_ref().and_then(|k| self.fns.get(k)).map(|s| s.ret.clone()); |
| 3161 | if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) { | 3478 | if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) { |
| 3162 | return Err(format!( | 3479 | return Err(format!( |
| @@ -3173,6 +3490,31 @@ impl Lowerer { | |||
| 3173 | 3490 | ||
| 3174 | fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> { | 3491 | fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> { |
| 3175 | let name = m.method.to_string(); | 3492 | let name = m.method.to_string(); |
| 3493 | + // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield. | ||
| 3494 | + if name == "remainder" && m.args.is_empty() { | ||
| 3495 | + if let Expr::Path(p) = &*m.receiver { | ||
| 3496 | + if let Some(Alias::Iterator(it)) = self.lookup_alias(&path_name(&p.path)) { | ||
| 3497 | + if let Iter::Chunks { code, base, len, k, elem, .. } = &*it { | ||
| 3498 | + let kept = format!("(({} div int({})) * int({}))", len, k, k); | ||
| 3499 | + let mut v = Val::new( | ||
| 3500 | + String::new(), | ||
| 3501 | + elem.clone().map(|e| Nim::OpenArray(Box::new(e))), | ||
| 3502 | + ); | ||
| 3503 | + v.window = Some(Alias::Window { | ||
| 3504 | + code: code.clone(), | ||
| 3505 | + off: format!("({} + {})", base, kept), | ||
| 3506 | + len: format!("({} - {})", len, kept), | ||
| 3507 | + elem: elem.clone(), | ||
| 3508 | + }); | ||
| 3509 | + return Ok(v); | ||
| 3510 | + } | ||
| 3511 | + return Err( | ||
| 3512 | + "`.remainder()` is only defined for a `chunks_exact` iterator".into(), | ||
| 3513 | + ); | ||
| 3514 | + } | ||
| 3515 | + } | ||
| 3516 | + return Err("`.remainder()` needs an iterator bound by `let`".into()); | ||
| 3517 | + } | ||
| 3176 | if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) { | 3518 | if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) { |
| 3177 | match name.as_str() { | 3519 | match name.as_str() { |
| 3178 | "len" => { | 3520 | "len" => { |
| @@ -3456,11 +3798,20 @@ impl Lowerer { | |||
| 3456 | // A method defined in this file via `impl`, found by the | 3798 | // A method defined in this file via `impl`, found by the |
| 3457 | // receiver's type rather than by name alone. | 3799 | // receiver's type rather than by name alone. |
| 3458 | let key = rt.as_ref().map(|t| (type_name(t), name.clone())); | 3800 | let key = rt.as_ref().map(|t| (type_name(t), name.clone())); |
| 3459 | - let sig = key.and_then(|k| self.methods.get(&k)).map(|s| s.ret.clone()); | 3801 | + let sig = key |
| 3802 | + .as_ref() | ||
| 3803 | + .and_then(|k| self.methods.get(k)) | ||
| 3804 | + .map(|s| s.ret.clone()); | ||
| 3460 | if let Some(ret) = sig { | 3805 | if let Some(ret) = sig { |
| 3806 | + // Use the name the proc was actually emitted under: an | ||
| 3807 | + // inherent method is qualified by its module, a trait | ||
| 3808 | + // method by its trait. | ||
| 3809 | + let nim = key | ||
| 3810 | + .and_then(|k| self.statics.get(&k).cloned()) | ||
| 3811 | + .unwrap_or_else(|| ident(&name)); | ||
| 3461 | let mut all = vec![recv.code.clone()]; | 3812 | let mut all = vec![recv.code.clone()]; |
| 3462 | all.extend(args.iter().map(|a| a.code.clone())); | 3813 | all.extend(args.iter().map(|a| a.code.clone())); |
| 3463 | - (format!("{}({})", ident(&name), all.join(", ")), Some(ret)) | 3814 | + (format!("{}({})", nim, all.join(", ")), Some(ret)) |
| 3464 | } else { | 3815 | } else { |
| 3465 | return Err(format!( | 3816 | return Err(format!( |
| 3466 | "unsupported method `.{name}()`; it is neither defined in \ | 3817 | "unsupported method `.{name}()`; it is neither defined in \ |
| @@ -3864,6 +4215,98 @@ fn type_name(t: &Nim) -> String { | |||
| 3864 | } | 4215 | } |
| 3865 | } | 4216 | } |
| 3866 | 4217 | ||
| 4218 | +/// `(trait, operator)` for every operator trait we dispatch. | ||
| 4219 | +const OPERATOR_TRAITS: &[(&str, &str)] = &[ | ||
| 4220 | + ("Add", "+"), ("Sub", "-"), ("Mul", "*"), ("Div", "/"), ("Rem", "%"), | ||
| 4221 | + ("BitAnd", "&"), ("BitOr", "|"), ("BitXor", "^"), ("Shl", "<<"), ("Shr", ">>"), | ||
| 4222 | + ("AddAssign", "+="), ("SubAssign", "-="), ("MulAssign", "*="), ("DivAssign", "/="), | ||
| 4223 | + ("RemAssign", "%="), ("BitAndAssign", "&="), ("BitOrAssign", "|="), | ||
| 4224 | + ("BitXorAssign", "^="), ("ShlAssign", "<<="), ("ShrAssign", ">>="), | ||
| 4225 | + ("Neg", "neg"), ("Not", "not"), | ||
| 4226 | +]; | ||
| 4227 | + | ||
| 4228 | +/// `(operator, trait method name)`. | ||
| 4229 | +const OP_METHOD: &[(&str, &str)] = &[ | ||
| 4230 | + ("+", "add"), ("-", "sub"), ("*", "mul"), ("/", "div"), ("%", "rem"), | ||
| 4231 | + ("&", "bitand"), ("|", "bitor"), ("^", "bitxor"), ("<<", "shl"), (">>", "shr"), | ||
| 4232 | + ("+=", "add_assign"), ("-=", "sub_assign"), ("*=", "mul_assign"), | ||
| 4233 | + ("/=", "div_assign"), ("%=", "rem_assign"), ("&=", "bitand_assign"), | ||
| 4234 | + ("|=", "bitor_assign"), ("^=", "bitxor_assign"), ("<<=", "shl_assign"), | ||
| 4235 | + (">>=", "shr_assign"), ("neg", "neg"), ("not", "not"), | ||
| 4236 | +]; | ||
| 4237 | + | ||
| 4238 | +fn op_method(op: &str) -> &'static str { | ||
| 4239 | + OP_METHOD.iter().find(|(o, _)| *o == op).map(|(_, m)| *m).unwrap_or("") | ||
| 4240 | +} | ||
| 4241 | + | ||
| 4242 | +/// The operator symbol a compound assignment applies. | ||
| 4243 | +fn compound_symbol(op: &BinOp) -> &'static str { | ||
| 4244 | + match op { | ||
| 4245 | + BinOp::AddAssign(_) => "+=", | ||
| 4246 | + BinOp::SubAssign(_) => "-=", | ||
| 4247 | + BinOp::MulAssign(_) => "*=", | ||
| 4248 | + BinOp::DivAssign(_) => "/=", | ||
| 4249 | + BinOp::RemAssign(_) => "%=", | ||
| 4250 | + BinOp::BitAndAssign(_) => "&=", | ||
| 4251 | + BinOp::BitOrAssign(_) => "|=", | ||
| 4252 | + BinOp::BitXorAssign(_) => "^=", | ||
| 4253 | + BinOp::ShlAssign(_) => "<<=", | ||
| 4254 | + BinOp::ShrAssign(_) => ">>=", | ||
| 4255 | + _ => "", | ||
| 4256 | + } | ||
| 4257 | +} | ||
| 4258 | + | ||
| 4259 | +fn binary_symbol(op: &BinOp) -> &'static str { | ||
| 4260 | + match op { | ||
| 4261 | + BinOp::Add(_) => "+", | ||
| 4262 | + BinOp::Sub(_) => "-", | ||
| 4263 | + BinOp::Mul(_) => "*", | ||
| 4264 | + BinOp::Div(_) => "/", | ||
| 4265 | + BinOp::Rem(_) => "%", | ||
| 4266 | + BinOp::BitAnd(_) => "&", | ||
| 4267 | + BinOp::BitOr(_) => "|", | ||
| 4268 | + BinOp::BitXor(_) => "^", | ||
| 4269 | + BinOp::Shl(_) => "<<", | ||
| 4270 | + BinOp::Shr(_) => ">>", | ||
| 4271 | + _ => "", | ||
| 4272 | + } | ||
| 4273 | +} | ||
| 4274 | + | ||
| 4275 | +/// The operator a trait overloads, if it is one of the operator traits. | ||
| 4276 | +fn operator_trait(t: &str) -> Option<&'static str> { | ||
| 4277 | + Some(match t { | ||
| 4278 | + "Add" => "+", | ||
| 4279 | + "Sub" => "-", | ||
| 4280 | + "Mul" => "*", | ||
| 4281 | + "Div" => "/", | ||
| 4282 | + "Rem" => "%", | ||
| 4283 | + "BitAnd" => "&", | ||
| 4284 | + "BitOr" => "|", | ||
| 4285 | + "BitXor" => "^", | ||
| 4286 | + "Shl" => "<<", | ||
| 4287 | + "Shr" => ">>", | ||
| 4288 | + "AddAssign" => "+=", | ||
| 4289 | + "SubAssign" => "-=", | ||
| 4290 | + "MulAssign" => "*=", | ||
| 4291 | + "DivAssign" => "/=", | ||
| 4292 | + "RemAssign" => "%=", | ||
| 4293 | + "BitAndAssign" => "&=", | ||
| 4294 | + "BitOrAssign" => "|=", | ||
| 4295 | + "BitXorAssign" => "^=", | ||
| 4296 | + "ShlAssign" => "<<=", | ||
| 4297 | + "ShrAssign" => ">>=", | ||
| 4298 | + "Neg" => "neg", | ||
| 4299 | + "Not" => "not", | ||
| 4300 | + _ => return None, | ||
| 4301 | + }) | ||
| 4302 | +} | ||
| 4303 | + | ||
| 4304 | +/// The Nim proc name for a trait method, qualified by trait and type so that | ||
| 4305 | +/// two traits declaring the same method name cannot collide. | ||
| 4306 | +fn trait_method_name(ty: &str, tr: &str, m: &str) -> String { | ||
| 4307 | + format!("rs{}_{}_{}", tr, ty, m) | ||
| 4308 | +} | ||
| 4309 | + | ||
| 3867 | fn is_fmt_trait(t: &str) -> bool { | 4310 | fn is_fmt_trait(t: &str) -> bool { |
| 3868 | matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal") | 4311 | matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal") |
| 3869 | } | 4312 | } |
| @@ -3880,6 +4323,19 @@ fn fmt_proc(t: &str) -> &'static str { | |||
| 3880 | } | 4323 | } |
| 3881 | } | 4324 | } |
| 3882 | 4325 | ||
| 4326 | +/// Whether an expression is an iterator-producing chain rather than a value. | ||
| 4327 | +fn is_iterator_expr(e: &Expr) -> bool { | ||
| 4328 | + match e { | ||
| 4329 | + Expr::MethodCall(m) => matches!( | ||
| 4330 | + m.method.to_string().as_str(), | ||
| 4331 | + "iter" | "iter_mut" | "into_iter" | "enumerate" | "zip" | "chunks_exact" | ||
| 4332 | + | "chunks_exact_mut" | "windows" | ||
| 4333 | + ), | ||
| 4334 | + Expr::Paren(p) => is_iterator_expr(&p.expr), | ||
| 4335 | + _ => false, | ||
| 4336 | + } | ||
| 4337 | +} | ||
| 4338 | + | ||
| 3883 | /// Whether an expression denotes a place -- a variable, a field, or an index | 4339 | /// Whether an expression denotes a place -- a variable, a field, or an index |
| 3884 | /// or slice of one -- and so may be re-evaluated with no side effect. | 4340 | /// or slice of one -- and so may be re-evaluated with no side effect. |
| 3885 | fn is_pure_place(e: &Expr) -> bool { | 4341 | fn is_pure_place(e: &Expr) -> bool { |
added
tests/cases/029-adler2-crate/algo.rs +155 -0 | new file mode 100644 | ||
| @@ -0,0 +1,155 @@ | ||
| 1 | +use crate::Adler32; | |
| 2 | +use std::ops::{AddAssign, MulAssign, RemAssign}; | |
| 3 | + | |
| 4 | +impl Adler32 { | |
| 5 | + pub(crate) fn compute(&mut self, bytes: &[u8]) { | |
| 6 | + // The basic algorithm is, for every byte: | |
| 7 | + // a = (a + byte) % MOD | |
| 8 | + // b = (b + a) % MOD | |
| 9 | + // where MOD = 65521. | |
| 10 | + // | |
| 11 | + // For efficiency, we can defer the `% MOD` operations as long as neither a nor b overflows: | |
| 12 | + // - Between calls to `write`, we ensure that a and b are always in range 0..MOD. | |
| 13 | + // - We use 32-bit arithmetic in this function. | |
| 14 | + // - Therefore, a and b must not increase by more than 2^32-MOD without performing a `% MOD` | |
| 15 | + // operation. | |
| 16 | + // | |
| 17 | + // According to Wikipedia, b is calculated as follows for non-incremental checksumming: | |
| 18 | + // b = n×D1 + (n−1)×D2 + (n−2)×D3 + ... + Dn + n*1 (mod 65521) | |
| 19 | + // Where n is the number of bytes and Di is the i-th Byte. We need to change this to account | |
| 20 | + // for the previous values of a and b, as well as treat every input Byte as being 255: | |
| 21 | + // b_inc = n×255 + (n-1)×255 + ... + 255 + n*65520 | |
| 22 | + // Or in other words: | |
| 23 | + // b_inc = n*65520 + n(n+1)/2*255 | |
| 24 | + // The max chunk size is thus the largest value of n so that b_inc <= 2^32-65521. | |
| 25 | + // 2^32-65521 = n*65520 + n(n+1)/2*255 | |
| 26 | + // Plugging this into an equation solver since I can't math gives n = 5552.18..., so 5552. | |
| 27 | + // | |
| 28 | + // On top of the optimization outlined above, the algorithm can also be parallelized with a | |
| 29 | + // bit more work: | |
| 30 | + // | |
| 31 | + // Note that b is a linear combination of a vector of input bytes (D1, ..., Dn). | |
| 32 | + // | |
| 33 | + // If we fix some value k<N and rewrite indices 1, ..., N as | |
| 34 | + // | |
| 35 | + // 1_1, 1_2, ..., 1_k, 2_1, ..., 2_k, ..., (N/k)_k, | |
| 36 | + // | |
| 37 | + // then we can express a and b in terms of sums of smaller sequences kb and ka: | |
| 38 | + // | |
| 39 | + // ka(j) := D1_j + D2_j + ... + D(N/k)_j where j <= k | |
| 40 | + // kb(j) := (N/k)*D1_j + (N/k-1)*D2_j + ... + D(N/k)_j where j <= k | |
| 41 | + // | |
| 42 | + // a = ka(1) + ka(2) + ... + ka(k) + 1 | |
| 43 | + // b = k*(kb(1) + kb(2) + ... + kb(k)) - 1*ka(2) - ... - (k-1)*ka(k) + N | |
| 44 | + // | |
| 45 | + // We use this insight to unroll the main loop and process k=4 bytes at a time. | |
| 46 | + // The resulting code is highly amenable to SIMD acceleration, although the immediate speedups | |
| 47 | + // stem from increased pipeline parallelism rather than auto-vectorization. | |
| 48 | + // | |
| 49 | + // This technique is described in-depth (here:)[https://software.intel.com/content/www/us/\ | |
| 50 | + // en/develop/articles/fast-computation-of-fletcher-checksums.html] | |
| 51 | + | |
| 52 | + const MOD: u32 = 65521; | |
| 53 | + const CHUNK_SIZE: usize = 5552 * 4; | |
| 54 | + | |
| 55 | + let mut a = u32::from(self.a); | |
| 56 | + let mut b = u32::from(self.b); | |
| 57 | + let mut a_vec = U32X4([0; 4]); | |
| 58 | + let mut b_vec = a_vec; | |
| 59 | + | |
| 60 | + let (bytes, remainder) = bytes.split_at(bytes.len() - bytes.len() % 4); | |
| 61 | + | |
| 62 | + // iterate over 4 bytes at a time | |
| 63 | + let chunk_iter = bytes.chunks_exact(CHUNK_SIZE); | |
| 64 | + let remainder_chunk = chunk_iter.remainder(); | |
| 65 | + for chunk in chunk_iter { | |
| 66 | + for byte_vec in chunk.chunks_exact(4) { | |
| 67 | + let val = U32X4::from(byte_vec); | |
| 68 | + a_vec += val; | |
| 69 | + b_vec += a_vec; | |
| 70 | + } | |
| 71 | + | |
| 72 | + b += CHUNK_SIZE as u32 * a; | |
| 73 | + a_vec %= MOD; | |
| 74 | + b_vec %= MOD; | |
| 75 | + b %= MOD; | |
| 76 | + } | |
| 77 | + // special-case the final chunk because it may be shorter than the rest | |
| 78 | + for byte_vec in remainder_chunk.chunks_exact(4) { | |
| 79 | + let val = U32X4::from(byte_vec); | |
| 80 | + a_vec += val; | |
| 81 | + b_vec += a_vec; | |
| 82 | + } | |
| 83 | + b += remainder_chunk.len() as u32 * a; | |
| 84 | + a_vec %= MOD; | |
| 85 | + b_vec %= MOD; | |
| 86 | + b %= MOD; | |
| 87 | + | |
| 88 | + // combine the sub-sum results into the main sum | |
| 89 | + b_vec *= 4; | |
| 90 | + b_vec.0[1] += MOD - a_vec.0[1]; | |
| 91 | + b_vec.0[2] += (MOD - a_vec.0[2]) * 2; | |
| 92 | + b_vec.0[3] += (MOD - a_vec.0[3]) * 3; | |
| 93 | + for &av in a_vec.0.iter() { | |
| 94 | + a += av; | |
| 95 | + } | |
| 96 | + for &bv in b_vec.0.iter() { | |
| 97 | + b += bv; | |
| 98 | + } | |
| 99 | + | |
| 100 | + // iterate over the remaining few bytes in serial | |
| 101 | + for &byte in remainder.iter() { | |
| 102 | + a += u32::from(byte); | |
| 103 | + b += a; | |
| 104 | + } | |
| 105 | + | |
| 106 | + self.a = (a % MOD) as u16; | |
| 107 | + self.b = (b % MOD) as u16; | |
| 108 | + } | |
| 109 | +} | |
| 110 | + | |
| 111 | +#[derive(Copy, Clone)] | |
| 112 | +struct U32X4([u32; 4]); | |
| 113 | + | |
| 114 | +impl U32X4 { | |
| 115 | + #[inline] | |
| 116 | + fn from(bytes: &[u8]) -> Self { | |
| 117 | + U32X4([ | |
| 118 | + u32::from(bytes[0]), | |
| 119 | + u32::from(bytes[1]), | |
| 120 | + u32::from(bytes[2]), | |
| 121 | + u32::from(bytes[3]), | |
| 122 | + ]) | |
| 123 | + } | |
| 124 | +} | |
| 125 | + | |
| 126 | +impl AddAssign<Self> for U32X4 { | |
| 127 | + #[inline] | |
| 128 | + fn add_assign(&mut self, other: Self) { | |
| 129 | + // Implement this in a primitive manner to help out the compiler a bit. | |
| 130 | + self.0[0] += other.0[0]; | |
| 131 | + self.0[1] += other.0[1]; | |
| 132 | + self.0[2] += other.0[2]; | |
| 133 | + self.0[3] += other.0[3]; | |
| 134 | + } | |
| 135 | +} | |
| 136 | + | |
| 137 | +impl RemAssign<u32> for U32X4 { | |
| 138 | + #[inline] | |
| 139 | + fn rem_assign(&mut self, quotient: u32) { | |
| 140 | + self.0[0] %= quotient; | |
| 141 | + self.0[1] %= quotient; | |
| 142 | + self.0[2] %= quotient; | |
| 143 | + self.0[3] %= quotient; | |
| 144 | + } | |
| 145 | +} | |
| 146 | + | |
| 147 | +impl MulAssign<u32> for U32X4 { | |
| 148 | + #[inline] | |
| 149 | + fn mul_assign(&mut self, rhs: u32) { | |
| 150 | + self.0[0] *= rhs; | |
| 151 | + self.0[1] *= rhs; | |
| 152 | + self.0[2] *= rhs; | |
| 153 | + self.0[3] *= rhs; | |
| 154 | + } | |
| 155 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,155 @@ | |||
| 1 | +use crate::Adler32; | ||
| 2 | +use std::ops::{AddAssign, MulAssign, RemAssign}; | ||
| 3 | + | ||
| 4 | +impl Adler32 { | ||
| 5 | + pub(crate) fn compute(&mut self, bytes: &[u8]) { | ||
| 6 | + // The basic algorithm is, for every byte: | ||
| 7 | + // a = (a + byte) % MOD | ||
| 8 | + // b = (b + a) % MOD | ||
| 9 | + // where MOD = 65521. | ||
| 10 | + // | ||
| 11 | + // For efficiency, we can defer the `% MOD` operations as long as neither a nor b overflows: | ||
| 12 | + // - Between calls to `write`, we ensure that a and b are always in range 0..MOD. | ||
| 13 | + // - We use 32-bit arithmetic in this function. | ||
| 14 | + // - Therefore, a and b must not increase by more than 2^32-MOD without performing a `% MOD` | ||
| 15 | + // operation. | ||
| 16 | + // | ||
| 17 | + // According to Wikipedia, b is calculated as follows for non-incremental checksumming: | ||
| 18 | + // b = n×D1 + (n−1)×D2 + (n−2)×D3 + ... + Dn + n*1 (mod 65521) | ||
| 19 | + // Where n is the number of bytes and Di is the i-th Byte. We need to change this to account | ||
| 20 | + // for the previous values of a and b, as well as treat every input Byte as being 255: | ||
| 21 | + // b_inc = n×255 + (n-1)×255 + ... + 255 + n*65520 | ||
| 22 | + // Or in other words: | ||
| 23 | + // b_inc = n*65520 + n(n+1)/2*255 | ||
| 24 | + // The max chunk size is thus the largest value of n so that b_inc <= 2^32-65521. | ||
| 25 | + // 2^32-65521 = n*65520 + n(n+1)/2*255 | ||
| 26 | + // Plugging this into an equation solver since I can't math gives n = 5552.18..., so 5552. | ||
| 27 | + // | ||
| 28 | + // On top of the optimization outlined above, the algorithm can also be parallelized with a | ||
| 29 | + // bit more work: | ||
| 30 | + // | ||
| 31 | + // Note that b is a linear combination of a vector of input bytes (D1, ..., Dn). | ||
| 32 | + // | ||
| 33 | + // If we fix some value k<N and rewrite indices 1, ..., N as | ||
| 34 | + // | ||
| 35 | + // 1_1, 1_2, ..., 1_k, 2_1, ..., 2_k, ..., (N/k)_k, | ||
| 36 | + // | ||
| 37 | + // then we can express a and b in terms of sums of smaller sequences kb and ka: | ||
| 38 | + // | ||
| 39 | + // ka(j) := D1_j + D2_j + ... + D(N/k)_j where j <= k | ||
| 40 | + // kb(j) := (N/k)*D1_j + (N/k-1)*D2_j + ... + D(N/k)_j where j <= k | ||
| 41 | + // | ||
| 42 | + // a = ka(1) + ka(2) + ... + ka(k) + 1 | ||
| 43 | + // b = k*(kb(1) + kb(2) + ... + kb(k)) - 1*ka(2) - ... - (k-1)*ka(k) + N | ||
| 44 | + // | ||
| 45 | + // We use this insight to unroll the main loop and process k=4 bytes at a time. | ||
| 46 | + // The resulting code is highly amenable to SIMD acceleration, although the immediate speedups | ||
| 47 | + // stem from increased pipeline parallelism rather than auto-vectorization. | ||
| 48 | + // | ||
| 49 | + // This technique is described in-depth (here:)[https://software.intel.com/content/www/us/\ | ||
| 50 | + // en/develop/articles/fast-computation-of-fletcher-checksums.html] | ||
| 51 | + | ||
| 52 | + const MOD: u32 = 65521; | ||
| 53 | + const CHUNK_SIZE: usize = 5552 * 4; | ||
| 54 | + | ||
| 55 | + let mut a = u32::from(self.a); | ||
| 56 | + let mut b = u32::from(self.b); | ||
| 57 | + let mut a_vec = U32X4([0; 4]); | ||
| 58 | + let mut b_vec = a_vec; | ||
| 59 | + | ||
| 60 | + let (bytes, remainder) = bytes.split_at(bytes.len() - bytes.len() % 4); | ||
| 61 | + | ||
| 62 | + // iterate over 4 bytes at a time | ||
| 63 | + let chunk_iter = bytes.chunks_exact(CHUNK_SIZE); | ||
| 64 | + let remainder_chunk = chunk_iter.remainder(); | ||
| 65 | + for chunk in chunk_iter { | ||
| 66 | + for byte_vec in chunk.chunks_exact(4) { | ||
| 67 | + let val = U32X4::from(byte_vec); | ||
| 68 | + a_vec += val; | ||
| 69 | + b_vec += a_vec; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + b += CHUNK_SIZE as u32 * a; | ||
| 73 | + a_vec %= MOD; | ||
| 74 | + b_vec %= MOD; | ||
| 75 | + b %= MOD; | ||
| 76 | + } | ||
| 77 | + // special-case the final chunk because it may be shorter than the rest | ||
| 78 | + for byte_vec in remainder_chunk.chunks_exact(4) { | ||
| 79 | + let val = U32X4::from(byte_vec); | ||
| 80 | + a_vec += val; | ||
| 81 | + b_vec += a_vec; | ||
| 82 | + } | ||
| 83 | + b += remainder_chunk.len() as u32 * a; | ||
| 84 | + a_vec %= MOD; | ||
| 85 | + b_vec %= MOD; | ||
| 86 | + b %= MOD; | ||
| 87 | + | ||
| 88 | + // combine the sub-sum results into the main sum | ||
| 89 | + b_vec *= 4; | ||
| 90 | + b_vec.0[1] += MOD - a_vec.0[1]; | ||
| 91 | + b_vec.0[2] += (MOD - a_vec.0[2]) * 2; | ||
| 92 | + b_vec.0[3] += (MOD - a_vec.0[3]) * 3; | ||
| 93 | + for &av in a_vec.0.iter() { | ||
| 94 | + a += av; | ||
| 95 | + } | ||
| 96 | + for &bv in b_vec.0.iter() { | ||
| 97 | + b += bv; | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + // iterate over the remaining few bytes in serial | ||
| 101 | + for &byte in remainder.iter() { | ||
| 102 | + a += u32::from(byte); | ||
| 103 | + b += a; | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + self.a = (a % MOD) as u16; | ||
| 107 | + self.b = (b % MOD) as u16; | ||
| 108 | + } | ||
| 109 | +} | ||
| 110 | + | ||
| 111 | +#[derive(Copy, Clone)] | ||
| 112 | +struct U32X4([u32; 4]); | ||
| 113 | + | ||
| 114 | +impl U32X4 { | ||
| 115 | + #[inline] | ||
| 116 | + fn from(bytes: &[u8]) -> Self { | ||
| 117 | + U32X4([ | ||
| 118 | + u32::from(bytes[0]), | ||
| 119 | + u32::from(bytes[1]), | ||
| 120 | + u32::from(bytes[2]), | ||
| 121 | + u32::from(bytes[3]), | ||
| 122 | + ]) | ||
| 123 | + } | ||
| 124 | +} | ||
| 125 | + | ||
| 126 | +impl AddAssign<Self> for U32X4 { | ||
| 127 | + #[inline] | ||
| 128 | + fn add_assign(&mut self, other: Self) { | ||
| 129 | + // Implement this in a primitive manner to help out the compiler a bit. | ||
| 130 | + self.0[0] += other.0[0]; | ||
| 131 | + self.0[1] += other.0[1]; | ||
| 132 | + self.0[2] += other.0[2]; | ||
| 133 | + self.0[3] += other.0[3]; | ||
| 134 | + } | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +impl RemAssign<u32> for U32X4 { | ||
| 138 | + #[inline] | ||
| 139 | + fn rem_assign(&mut self, quotient: u32) { | ||
| 140 | + self.0[0] %= quotient; | ||
| 141 | + self.0[1] %= quotient; | ||
| 142 | + self.0[2] %= quotient; | ||
| 143 | + self.0[3] %= quotient; | ||
| 144 | + } | ||
| 145 | +} | ||
| 146 | + | ||
| 147 | +impl MulAssign<u32> for U32X4 { | ||
| 148 | + #[inline] | ||
| 149 | + fn mul_assign(&mut self, rhs: u32) { | ||
| 150 | + self.0[0] *= rhs; | ||
| 151 | + self.0[1] *= rhs; | ||
| 152 | + self.0[2] *= rhs; | ||
| 153 | + self.0[3] *= rhs; | ||
| 154 | + } | ||
| 155 | +} | ||
added
tests/cases/029-adler2-crate/main.rs +148 -0 | new file mode 100644 | ||
| @@ -0,0 +1,148 @@ | ||
| 1 | +//@ args: run | |
| 2 | +// adler2 2.0.1. `algo.rs` is the crate's own file, byte-for-byte. | |
| 3 | +// This root carries `lib.rs`'s items (its `BufRead` reader needs std I/O | |
| 4 | +// and is left out) plus a driver, since the runner needs a `main`. | |
| 5 | + | |
| 6 | +mod algo; | |
| 7 | + | |
| 8 | +use core::hash::Hasher; | |
| 9 | + | |
| 10 | +#[derive(Debug, Copy, Clone)] | |
| 11 | +pub struct Adler32 { | |
| 12 | + a: u16, | |
| 13 | + b: u16, | |
| 14 | +} | |
| 15 | + | |
| 16 | +impl Adler32 { | |
| 17 | + /// Creates a new Adler-32 instance with default state. | |
| 18 | + #[inline] | |
| 19 | + pub fn new() -> Self { | |
| 20 | + Self::default() | |
| 21 | + } | |
| 22 | + | |
| 23 | + /// Creates an `Adler32` instance from a precomputed Adler-32 checksum. | |
| 24 | + /// | |
| 25 | + /// This allows resuming checksum calculation without having to keep the `Adler32` instance | |
| 26 | + /// around. | |
| 27 | + /// | |
| 28 | + /// # Example | |
| 29 | + /// | |
| 30 | + /// ``` | |
| 31 | + /// # use adler2::Adler32; | |
| 32 | + /// let parts = [ | |
| 33 | + /// "rust", | |
| 34 | + /// "acean", | |
| 35 | + /// ]; | |
| 36 | + /// let whole = adler2::adler32_slice(b"rustacean"); | |
| 37 | + /// | |
| 38 | + /// let mut sum = Adler32::new(); | |
| 39 | + /// sum.write_slice(parts[0].as_bytes()); | |
| 40 | + /// let partial = sum.checksum(); | |
| 41 | + /// | |
| 42 | + /// // ...later | |
| 43 | + /// | |
| 44 | + /// let mut sum = Adler32::from_checksum(partial); | |
| 45 | + /// sum.write_slice(parts[1].as_bytes()); | |
| 46 | + /// assert_eq!(sum.checksum(), whole); | |
| 47 | + /// ``` | |
| 48 | + #[inline] | |
| 49 | + pub const fn from_checksum(sum: u32) -> Self { | |
| 50 | + Adler32 { | |
| 51 | + a: sum as u16, | |
| 52 | + b: (sum >> 16) as u16, | |
| 53 | + } | |
| 54 | + } | |
| 55 | + | |
| 56 | + /// Returns the calculated checksum at this point in time. | |
| 57 | + #[inline] | |
| 58 | + pub fn checksum(&self) -> u32 { | |
| 59 | + (u32::from(self.b) << 16) | u32::from(self.a) | |
| 60 | + } | |
| 61 | + | |
| 62 | + /// Adds `bytes` to the checksum calculation. | |
| 63 | + /// | |
| 64 | + /// If efficiency matters, this should be called with Byte slices that contain at least a few | |
| 65 | + /// thousand Bytes. | |
| 66 | + pub fn write_slice(&mut self, bytes: &[u8]) { | |
| 67 | + self.compute(bytes); | |
| 68 | + } | |
| 69 | +} | |
| 70 | + | |
| 71 | +impl Default for Adler32 { | |
| 72 | + #[inline] | |
| 73 | + fn default() -> Self { | |
| 74 | + Adler32 { a: 1, b: 0 } | |
| 75 | + } | |
| 76 | +} | |
| 77 | + | |
| 78 | +impl Hasher for Adler32 { | |
| 79 | + #[inline] | |
| 80 | + fn finish(&self) -> u64 { | |
| 81 | + u64::from(self.checksum()) | |
| 82 | + } | |
| 83 | + | |
| 84 | + fn write(&mut self, bytes: &[u8]) { | |
| 85 | + self.write_slice(bytes); | |
| 86 | + } | |
| 87 | +} | |
| 88 | + | |
| 89 | +/// Calculates the Adler-32 checksum of a byte slice. | |
| 90 | +/// | |
| 91 | +/// This is a convenience function around the [`Adler32`] type. | |
| 92 | +/// | |
| 93 | +/// [`Adler32`]: struct.Adler32.html | |
| 94 | +pub fn adler32_slice(data: &[u8]) -> u32 { | |
| 95 | + let mut h = Adler32::new(); | |
| 96 | + h.write_slice(data); | |
| 97 | + h.checksum() | |
| 98 | +} | |
| 99 | + | |
| 100 | +fn main() { | |
| 101 | + // Known vectors: the empty input, "Wikipedia", and simple patterns. | |
| 102 | + println!("{:08x}", adler32_slice(b"")); | |
| 103 | + println!("{:08x}", adler32_slice(b"Wikipedia")); | |
| 104 | + println!("{:08x}", adler32_slice(b"a")); | |
| 105 | + println!("{:08x}", adler32_slice(b"abc")); | |
| 106 | + | |
| 107 | + // Every single byte. | |
| 108 | + let mut i: u32 = 0; | |
| 109 | + while i < 256 { | |
| 110 | + let one: [u8; 1] = [i as u8]; | |
| 111 | + print!("{:08x} ", adler32_slice(&one)); | |
| 112 | + i += 1; | |
| 113 | + } | |
| 114 | + println!(""); | |
| 115 | + | |
| 116 | + // Lengths across the 4-byte unrolling boundary and well past it, so the | |
| 117 | + // chunked path, the remainder path and the serial tail are all exercised. | |
| 118 | + let mut n: usize = 0; | |
| 119 | + while n <= 600 { | |
| 120 | + let mut buf: Vec<u8> = vec![0u8; n]; | |
| 121 | + let mut j: usize = 0; | |
| 122 | + while j < n { | |
| 123 | + buf[j] = ((j * 31 + 7) % 256) as u8; | |
| 124 | + j += 1; | |
| 125 | + } | |
| 126 | + print!("{:08x} ", adler32_slice(&buf)); | |
| 127 | + n += 1; | |
| 128 | + } | |
| 129 | + println!(""); | |
| 130 | + | |
| 131 | + // Incremental writes must equal one write of the concatenation. | |
| 132 | + let mut data: Vec<u8> = vec![0u8; 1000]; | |
| 133 | + let mut k: usize = 0; | |
| 134 | + while k < 1000 { | |
| 135 | + data[k] = ((k * 97 + 13) % 256) as u8; | |
| 136 | + k += 1; | |
| 137 | + } | |
| 138 | + let mut split: usize = 0; | |
| 139 | + while split <= 1000 { | |
| 140 | + let mut h = Adler32::new(); | |
| 141 | + h.write_slice(&data[..split]); | |
| 142 | + h.write_slice(&data[split..]); | |
| 143 | + print!("{:08x} ", h.checksum()); | |
| 144 | + split += 7; | |
| 145 | + } | |
| 146 | + println!(""); | |
| 147 | + println!("{:08x}", adler32_slice(&data)); | |
| 148 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,148 @@ | |||
| 1 | +//@ args: run | ||
| 2 | +// adler2 2.0.1. `algo.rs` is the crate's own file, byte-for-byte. | ||
| 3 | +// This root carries `lib.rs`'s items (its `BufRead` reader needs std I/O | ||
| 4 | +// and is left out) plus a driver, since the runner needs a `main`. | ||
| 5 | + | ||
| 6 | +mod algo; | ||
| 7 | + | ||
| 8 | +use core::hash::Hasher; | ||
| 9 | + | ||
| 10 | +#[derive(Debug, Copy, Clone)] | ||
| 11 | +pub struct Adler32 { | ||
| 12 | + a: u16, | ||
| 13 | + b: u16, | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | +impl Adler32 { | ||
| 17 | + /// Creates a new Adler-32 instance with default state. | ||
| 18 | + #[inline] | ||
| 19 | + pub fn new() -> Self { | ||
| 20 | + Self::default() | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + /// Creates an `Adler32` instance from a precomputed Adler-32 checksum. | ||
| 24 | + /// | ||
| 25 | + /// This allows resuming checksum calculation without having to keep the `Adler32` instance | ||
| 26 | + /// around. | ||
| 27 | + /// | ||
| 28 | + /// # Example | ||
| 29 | + /// | ||
| 30 | + /// ``` | ||
| 31 | + /// # use adler2::Adler32; | ||
| 32 | + /// let parts = [ | ||
| 33 | + /// "rust", | ||
| 34 | + /// "acean", | ||
| 35 | + /// ]; | ||
| 36 | + /// let whole = adler2::adler32_slice(b"rustacean"); | ||
| 37 | + /// | ||
| 38 | + /// let mut sum = Adler32::new(); | ||
| 39 | + /// sum.write_slice(parts[0].as_bytes()); | ||
| 40 | + /// let partial = sum.checksum(); | ||
| 41 | + /// | ||
| 42 | + /// // ...later | ||
| 43 | + /// | ||
| 44 | + /// let mut sum = Adler32::from_checksum(partial); | ||
| 45 | + /// sum.write_slice(parts[1].as_bytes()); | ||
| 46 | + /// assert_eq!(sum.checksum(), whole); | ||
| 47 | + /// ``` | ||
| 48 | + #[inline] | ||
| 49 | + pub const fn from_checksum(sum: u32) -> Self { | ||
| 50 | + Adler32 { | ||
| 51 | + a: sum as u16, | ||
| 52 | + b: (sum >> 16) as u16, | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + /// Returns the calculated checksum at this point in time. | ||
| 57 | + #[inline] | ||
| 58 | + pub fn checksum(&self) -> u32 { | ||
| 59 | + (u32::from(self.b) << 16) | u32::from(self.a) | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + /// Adds `bytes` to the checksum calculation. | ||
| 63 | + /// | ||
| 64 | + /// If efficiency matters, this should be called with Byte slices that contain at least a few | ||
| 65 | + /// thousand Bytes. | ||
| 66 | + pub fn write_slice(&mut self, bytes: &[u8]) { | ||
| 67 | + self.compute(bytes); | ||
| 68 | + } | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +impl Default for Adler32 { | ||
| 72 | + #[inline] | ||
| 73 | + fn default() -> Self { | ||
| 74 | + Adler32 { a: 1, b: 0 } | ||
| 75 | + } | ||
| 76 | +} | ||
| 77 | + | ||
| 78 | +impl Hasher for Adler32 { | ||
| 79 | + #[inline] | ||
| 80 | + fn finish(&self) -> u64 { | ||
| 81 | + u64::from(self.checksum()) | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + fn write(&mut self, bytes: &[u8]) { | ||
| 85 | + self.write_slice(bytes); | ||
| 86 | + } | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +/// Calculates the Adler-32 checksum of a byte slice. | ||
| 90 | +/// | ||
| 91 | +/// This is a convenience function around the [`Adler32`] type. | ||
| 92 | +/// | ||
| 93 | +/// [`Adler32`]: struct.Adler32.html | ||
| 94 | +pub fn adler32_slice(data: &[u8]) -> u32 { | ||
| 95 | + let mut h = Adler32::new(); | ||
| 96 | + h.write_slice(data); | ||
| 97 | + h.checksum() | ||
| 98 | +} | ||
| 99 | + | ||
| 100 | +fn main() { | ||
| 101 | + // Known vectors: the empty input, "Wikipedia", and simple patterns. | ||
| 102 | + println!("{:08x}", adler32_slice(b"")); | ||
| 103 | + println!("{:08x}", adler32_slice(b"Wikipedia")); | ||
| 104 | + println!("{:08x}", adler32_slice(b"a")); | ||
| 105 | + println!("{:08x}", adler32_slice(b"abc")); | ||
| 106 | + | ||
| 107 | + // Every single byte. | ||
| 108 | + let mut i: u32 = 0; | ||
| 109 | + while i < 256 { | ||
| 110 | + let one: [u8; 1] = [i as u8]; | ||
| 111 | + print!("{:08x} ", adler32_slice(&one)); | ||
| 112 | + i += 1; | ||
| 113 | + } | ||
| 114 | + println!(""); | ||
| 115 | + | ||
| 116 | + // Lengths across the 4-byte unrolling boundary and well past it, so the | ||
| 117 | + // chunked path, the remainder path and the serial tail are all exercised. | ||
| 118 | + let mut n: usize = 0; | ||
| 119 | + while n <= 600 { | ||
| 120 | + let mut buf: Vec<u8> = vec![0u8; n]; | ||
| 121 | + let mut j: usize = 0; | ||
| 122 | + while j < n { | ||
| 123 | + buf[j] = ((j * 31 + 7) % 256) as u8; | ||
| 124 | + j += 1; | ||
| 125 | + } | ||
| 126 | + print!("{:08x} ", adler32_slice(&buf)); | ||
| 127 | + n += 1; | ||
| 128 | + } | ||
| 129 | + println!(""); | ||
| 130 | + | ||
| 131 | + // Incremental writes must equal one write of the concatenation. | ||
| 132 | + let mut data: Vec<u8> = vec![0u8; 1000]; | ||
| 133 | + let mut k: usize = 0; | ||
| 134 | + while k < 1000 { | ||
| 135 | + data[k] = ((k * 97 + 13) % 256) as u8; | ||
| 136 | + k += 1; | ||
| 137 | + } | ||
| 138 | + let mut split: usize = 0; | ||
| 139 | + while split <= 1000 { | ||
| 140 | + let mut h = Adler32::new(); | ||
| 141 | + h.write_slice(&data[..split]); | ||
| 142 | + h.write_slice(&data[split..]); | ||
| 143 | + print!("{:08x} ", h.checksum()); | ||
| 144 | + split += 7; | ||
| 145 | + } | ||
| 146 | + println!(""); | ||
| 147 | + println!("{:08x}", adler32_slice(&data)); | ||
| 148 | +} | ||