nandi/rustnimpublic Fork 0
5fdd5be
Commits
Clone
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

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>
nandithebull committed 2026-09-18T21:08:48-07:00 Browse files
5fdd5be parent: 99b8376
modified DESIGN.md +48 -1
@@ -291,10 +291,57 @@ runner) rather than a wrong answer.
291291 modules declaring the same type name would collide. Relatedly, a crate's
292292 own `type Result<T>` is told apart from the builtin `Result<T, E>` by
293293 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
295301 value. Rust's consumes the `Vec` without copying. Observably the same from
296302 the caller, but it is a copy where Rust has none.
297303
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+
298345 ## Proof of byte-identity for `base16ct`
299346
300347 [`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's291 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>` by292 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 owned294+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 from301 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: exhaustive347 [`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.
7676 `cargo test --test proof` runs it — 151,463 cases, 9.1 MB of output, compared
7777 byte for byte.
7878
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+
7992 ## Tests
8093
8194 ```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, compared76 `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 ## Tests92 ## Tests
80 93
81 ```bash94 ```bash
modified src/lower.rs +506 -50
@@ -110,6 +110,10 @@ enum Alias {
110110 Value { code: String, ty: Option<Nim> },
111111 /// The name stands for a window: `code[off .. off + len - 1]`.
112112 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>),
113117 }
114118
115119 /// A lowered expression: its Nim text, and its type where we know it.
@@ -192,6 +196,8 @@ pub struct Lowerer {
192196 fns: HashMap<(String, String), Sig>,
193197 /// Module being lowered: the file stem, or empty for the crate root.
194198 cur_mod: String,
199+ /// The type of the `impl` block being lowered, which `Self` names.
200+ self_ty: Option<Nim>,
195201 /// `use` brings a name into scope from another module. Flattening loses
196202 /// the module structure, so the mapping is recorded and consulted when a
197203 /// bare call is resolved.
@@ -211,6 +217,11 @@ pub struct Lowerer {
211217 fmt_impls: HashMap<(String, String), ()>,
212218 /// `(from, to)` conversions declared by `impl From<A> for B`.
213219 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>,
214225 /// Forward declarations, emitted between the type definitions and the
215226 /// bodies. Rust has no declaration-before-use rule and Nim does, so every
216227 /// proc is declared up front rather than the input being reordered --
@@ -252,6 +263,7 @@ impl Lowerer {
252263 alias_scopes: vec![HashMap::new()],
253264 fns: HashMap::new(),
254265 cur_mod: String::new(),
266+ self_ty: None,
255267 use_map: HashMap::new(),
256268 structs: HashMap::new(),
257269 enums: HashMap::new(),
@@ -259,6 +271,8 @@ impl Lowerer {
259271 methods: HashMap::new(),
260272 fmt_impls: HashMap::new(),
261273 from_impls: HashMap::new(),
274+ op_impls: HashMap::new(),
275+ statics: HashMap::new(),
262276 fmt_param: None,
263277 vec_expect: None,
264278 forwards: Vec::new(),
@@ -545,8 +559,21 @@ impl Lowerer {
545559 }
546560 Item::Impl(im) => {
547561 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_ {
550577 let tr = path_name(path);
551578 if im.items.is_empty() {
552579 // A marker trait with no items. We do not model trait
@@ -579,11 +606,33 @@ impl Lowerer {
579606 .insert((type_name(&src), tyname), name);
580607 return Ok(());
581608 }
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(());
587636 }
588637 for it in &im.items {
589638 if let syn::ImplItem::Fn(m) = it {
@@ -592,14 +641,15 @@ impl Lowerer {
592641 params.insert(0, self_ty.clone());
593642 }
594643 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())?;
596646 self.forwards.push(head);
597647 self.methods
598648 .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret });
649+ self.statics
650+ .insert((tyname.clone(), m.sig.ident.to_string()), nim);
599651 }
600652 }
601- }
602- _ => {}
603653 }
604654 Ok(())
605655 }
@@ -625,6 +675,29 @@ impl Lowerer {
625675
626676 fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
627677 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+ }
628701 syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
629702 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
630703 return Err("`feature = ..` expects a string".into());
@@ -655,11 +728,28 @@ impl Lowerer {
655728 }
656729 }
657730
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
659732 /// lowering goes through here rather than calling `ty::map` directly, so
660733 /// an alias cannot be missed in one position and honoured in another.
661734 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+ }
663753 }
664754
665755 fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
@@ -872,46 +962,32 @@ impl Lowerer {
872962 }
873963 Item::Const(c) => {
874964 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))?;
876968 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+ );
878979 self.line(&line);
879- self.blank();
980+ if self.indent == 0 {
981+ self.blank();
982+ }
880983 Ok(())
881984 }
882985 Item::Impl(im) => {
883986 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
915991 }
916992 // `use` and `extern crate` are resolution directives with no Nim
917993 // analogue once everything is one module.
@@ -951,6 +1027,71 @@ impl Lowerer {
9511027 }
9521028 }
9531029
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+
9541095 fn emit_enum(&mut self, def: &EnumDef) {
9551096 let name = ident(&def.name);
9561097 if def.simple {
@@ -1344,7 +1485,10 @@ impl Lowerer {
13441485 }
13451486 Ok(())
13461487 }
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),
13481492 Stmt::Macro(m) => {
13491493 let line = self.macro_call(&m.mac)?;
13501494 self.line(&line);
@@ -1361,6 +1505,7 @@ impl Lowerer {
13611505 _ => return Err("only `let <ident>` bindings are supported".into()),
13621506 },
13631507 Pat::Wild(_) => ("_".into(), false, None),
1508+ Pat::Tuple(t) => return self.local_tuple(l, t),
13641509 _ => return Err("destructuring `let` is not implemented yet".into()),
13651510 };
13661511
@@ -1397,6 +1542,13 @@ impl Lowerer {
13971542 return self.assign_from(&init.expr, &target, Some(&t));
13981543 }
13991544
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+
14001552 let v = self.expr_at(&init.expr, ann.as_ref())?;
14011553
14021554 // `let s = &buf[..n]` binds a view of a place that is already in
@@ -1468,6 +1620,76 @@ impl Lowerer {
14681620 Ok(())
14691621 }
14701622
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+
14711693 /// Expressions that are statements in Rust and statements in Nim too
14721694 /// (control flow). Returns `None` when it emitted lines itself.
14731695 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
@@ -1558,6 +1780,16 @@ impl Lowerer {
15581780 }
15591781 Expr::Binary(b) if is_compound(&b.op) => {
15601782 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+ }
15611793 // `i += 1` must widen the literal to `i`'s type, not to the
15621794 // i32 an unconstrained Rust literal would default to.
15631795 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
@@ -1753,6 +1985,23 @@ impl Lowerer {
17531985 )),
17541986 }
17551987 }
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+ }
17562005 other => {
17572006 // A `for` binding that is itself a window iterates that window,
17582007 // not the whole container it points into.
@@ -2262,6 +2511,14 @@ impl Lowerer {
22622511 format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
22632512 elem.map(|e| Nim::OpenArray(Box::new(e))),
22642513 ),
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+ }
22652522 });
22662523 }
22672524 if let Some(t) = self.lookup(&name) {
@@ -2458,7 +2715,21 @@ impl Lowerer {
24582715 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
24592716 }
24602717 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+ }
24622733 let n = self.expr(&r.len)?;
24632734 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
24642735 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
@@ -2619,6 +2890,19 @@ impl Lowerer {
26192890 l = self.expr_at(&b.left, r.ty.as_ref())?;
26202891 }
26212892 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+ }
26222906 let op = self.bin_op(&b.op, &l, &r)?;
26232907 let ty = match b.op {
26242908 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
@@ -3107,6 +3391,20 @@ impl Lowerer {
31073391 }
31083392 }
31093393
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+
31103408 // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
31113409 // string view; no copy, no validation, same memory.
31123410 if name == "from_utf8_unchecked" && codes.len() == 1 {
@@ -3157,6 +3455,25 @@ impl Lowerer {
31573455 Some((*ret).clone()),
31583456 ));
31593457 }
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+ }
31603477 let ret = target.as_ref().and_then(|k| self.fns.get(k)).map(|s| s.ret.clone());
31613478 if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {
31623479 return Err(format!(
@@ -3173,6 +3490,31 @@ impl Lowerer {
31733490
31743491 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
31753492 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+ }
31763518 if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
31773519 match name.as_str() {
31783520 "len" => {
@@ -3456,11 +3798,20 @@ impl Lowerer {
34563798 // A method defined in this file via `impl`, found by the
34573799 // receiver's type rather than by name alone.
34583800 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());
34603805 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));
34613812 let mut all = vec![recv.code.clone()];
34623813 all.extend(args.iter().map(|a| a.code.clone()));
3463- (format!("{}({})", ident(&name), all.join(", ")), Some(ret))
3814+ (format!("{}({})", nim, all.join(", ")), Some(ret))
34643815 } else {
34653816 return Err(format!(
34663817 "unsupported method `.{name}()`; it is neither defined in \
@@ -3864,6 +4215,98 @@ fn type_name(t: &Nim) -> String {
38644215 }
38654216 }
38664217
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+
38674310 fn is_fmt_trait(t: &str) -> bool {
38684311 matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
38694312 }
@@ -3880,6 +4323,19 @@ fn fmt_proc(t: &str) -> &'static str {
38804323 }
38814324 }
38824325
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+
38834339 /// Whether an expression denotes a place -- a variable, a field, or an index
38844340 /// or slice of one -- and so may be re-evaluated with no side effect.
38854341 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 loses201 /// `use` brings a name into scope from another module. Flattening loses
196 /// the module structure, so the mapping is recorded and consulted when a202 /// 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 the225 /// Forward declarations, emitted between the type definitions and the
215 /// bodies. Rust has no declaration-before-use rule and Nim does, so every226 /// 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 trait579 // 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.methods647 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 the731+ /// 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, so732 /// 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 Nim992 // `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 in1554 // `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 too1693 /// 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 the1793 // `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 a3408 // `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 the3798 // 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 index4339 /// 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+}