nandi/rustnimpublic Fork 0
04eb29f2f77152e460b50f58935cb88305227b43
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.

lower.rs · 5579 lines · 236.7 KBRust Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1//! Rust AST -> Nim source.
2//!
3//! The governing rule is in DESIGN.md and it shapes every function here:
4//! anything whose Rust semantics cannot be reproduced exactly in Nim returns
5//! `Err` with a reason. Nothing is emitted on a guess. Where a construct maps
6//! one-to-one (signed `shr`, unsigned wrapping, truncating `div`/`mod`) the
7//! mapping is direct and there is a comment saying why that is safe.
8
9use crate::fmt;
10use crate::ty::{self, Nim};
11use std::collections::HashMap;
12use syn::{
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago13 BinOp, Expr, FnArg, GenericArgument, Item, Lit, Local, Pat, ReturnType, Stmt, UnOp,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday14};
15
16// --------------------------------------------------------------- vocabulary
17
18/// Nim keywords. Rust code may legally use any of these as an identifier.
19const NIM_KEYWORDS: &[&str] = &[
20 "addr", "and", "as", "asm", "bind", "block", "break", "case", "cast",
21 "concept", "const", "continue", "converter", "defer", "discard", "distinct",
22 "div", "do", "elif", "else", "end", "enum", "except", "export", "finally",
23 "for", "from", "func", "if", "import", "in", "include", "interface", "is",
24 "isnot", "iterator", "let", "macro", "method", "mixin", "mod", "nil", "not",
25 "notin", "object", "of", "or", "out", "proc", "ptr", "raise", "ref",
26 "return", "shl", "shr", "static", "template", "try", "tuple", "type",
27 "using", "var", "when", "while", "xor", "result", "echo",
28];
29
30fn ident(name: &str) -> String {
31 if NIM_KEYWORDS.contains(&name) {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago32 return format!("{name}_r");
33 }
34 // Nim identifiers may not begin with an underscore, and may not contain
35 // two in a row. Rust uses both freely (`_unused`, `__private`).
36 let mut out = String::new();
37 let mut last_us = false;
38 for (i, c) in name.chars().enumerate() {
39 if c == '_' {
40 if i == 0 {
41 out.push('u');
42 out.push('_');
43 last_us = true;
44 continue;
45 }
46 if last_us {
47 continue;
48 }
49 last_us = true;
50 out.push('_');
51 } else {
52 last_us = false;
53 out.push(c);
54 }
55 }
56 if out.ends_with('_') {
57 out.push('x');
58 }
59 out
60}
61
62/// A `for`-loop source, resolved from a chain of iterator adaptors.
63///
64/// Rust's slice iterators are lazy and compose; Nim's `for` is over one
65/// sequence. So a chain is resolved into this shape and then emitted as a
66/// single index loop, with each binding becoming an *lvalue* into the original
67/// container. That is what makes `*dst = v` through `iter_mut()` write back to
68/// the caller's slice rather than to a copy.
69#[derive(Clone, Debug)]
70enum Iter {
71 /// `a..b` / `a..=b`.
72 Range { lo: String, hi: String, closed: bool, ty: Option<Nim> },
73 /// `for x in a`, `a.iter()`, `a.iter_mut()`. `off`/`len` let the same
74 /// shape cover a subslice view. `mutable` only affects whether the binding
75 /// may be assigned through.
76 Elems { code: String, off: String, len: String, elem: Option<Nim>, mutable: bool },
77 /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of
78 /// `k` elements starting at `k * i`.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago79 Chunks { code: String, base: String, len: String, k: String, elem: Option<Nim>, mutable: bool },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago80 /// `a.windows(k)`: like `Chunks` but advancing one element at a time.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago81 Windows { code: String, base: String, len: String, k: String, elem: Option<Nim> },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago82 /// `.enumerate()` — the index is the first half of the pair.
83 Enumerate(Box<Iter>),
84 /// `.zip(other)` — stops at the shorter, as Rust's does.
85 Zip(Box<Iter>, Box<Iter>),
86}
87
88impl Iter {
89 /// The number of iterations, as a Nim expression in terms of the loop's
90 /// own containers.
91 fn len(&self) -> String {
92 match self {
93 Iter::Range { lo, hi, closed, .. } => {
94 let n = format!("(int({hi}) - int({lo}))");
95 if *closed { format!("({n} + 1)") } else { n }
96 }
97 Iter::Elems { len, .. } => len.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago98 Iter::Chunks { k, len, .. } => format!("({} div int({}))", len, k),
99 Iter::Windows { len, k, .. } => format!("(max(0, {} - int({}) + 1))", len, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago100 Iter::Enumerate(i) => i.len(),
101 Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),
102 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday103 }
104}
105
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago106/// How a `for`-loop pattern name refers back into the container it came from.
107#[derive(Clone, Debug)]
108enum Alias {
109 /// The name stands for this Nim lvalue expression.
110 Value { code: String, ty: Option<Nim> },
111 /// The name stands for a window: `code[off .. off + len - 1]`.
112 Window { code: String, off: String, len: String, elem: Option<Nim> },
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago113 /// 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>),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago117}
118
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday119/// A lowered expression: its Nim text, and its type where we know it.
120///
121/// The type is not decoration. Nim needs it to pick `div` over `/`, to size a
122/// `cast`, and to annotate every binding so that Nim's own type checker
123/// catches a mistake in this file rather than letting it through as output
124/// that runs and is wrong.
125#[derive(Clone, Debug)]
126struct Val {
127 code: String,
128 ty: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago129 /// Set when the value *is* a slice view rather than a Nim value: binding
130 /// it introduces an alias, not a copy.
131 window: Option<Alias>,
132 /// For `get`/`get_mut`: the condition under which the `Option` is `Some`,
133 /// carried until an `ok_or`/`?` or an `unwrap` consumes it. Nim's view
134 /// types cannot live inside an object, so an `Option` of a view has no
135 /// runtime representation -- it is tracked here instead.
136 guard: Option<String>,
137 /// The error an `ok_or` attached to that guard.
138 guard_err: Option<String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday139}
140
141impl Val {
142 fn new(code: impl Into<String>, ty: Option<Nim>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago143 Val { code: code.into(), ty, window: None, guard: None, guard_err: None }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday144 }
145 fn untyped(code: impl Into<String>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago146 Val::new(code, None)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday147 }
148}
149
150struct Sig {
151 params: Vec<Nim>,
152 ret: Nim,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago153 /// Type parameters this signature is generic in, so a call site can bind
154 /// them from its argument types.
155 generics: Vec<String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday156}
157
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago158/// One variant of a Rust enum.
159#[derive(Clone)]
160struct Variant {
161 name: String,
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 20h ago162 /// `Error = 1` — Nim enums take explicit ordinals too, so the value is
163 /// preserved rather than the variant being renumbered.
164 discriminant: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago165 /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
166 /// `f0`, `f1`, ...; every field is prefixed with the variant name because
167 /// Nim requires the branches of a variant object to have distinct fields.
168 fields: Vec<(String, Nim)>,
169}
170
171#[derive(Clone)]
172struct EnumDef {
173 name: String,
174 /// True when every variant is a unit variant, which Nim represents as a
175 /// plain `enum` rather than an object variant.
176 simple: bool,
177 variants: Vec<Variant>,
178}
179
180impl EnumDef {
181 fn kind_ident(&self, v: &str) -> String {
182 format!("k{}{}", self.name, v)
183 }
184 fn ctor_ident(&self, v: &str) -> String {
185 format!("{}{}", self.name, v)
186 }
187 fn get(&self, v: &str) -> Option<&Variant> {
188 self.variants.iter().find(|x| x.name == v)
189 }
190}
191
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday192pub struct Lowerer {
193 out: String,
194 indent: usize,
195 scopes: Vec<HashMap<String, Nim>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago196 /// Names introduced by a `for` pattern that stand for an lvalue or a
197 /// window into a container, rather than for a variable of their own.
198 alias_scopes: Vec<HashMap<String, Alias>>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago199 /// `(module, name) -> signature`. Rust keeps `lower::decode` and
200 /// `mixed::decode` apart by module; flattening into one Nim module would
201 /// merge them, so the module is part of the key and of the emitted name.
202 fns: HashMap<(String, String), Sig>,
203 /// Module being lowered: the file stem, or empty for the crate root.
204 cur_mod: String,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago205 /// The type of the `impl` block being lowered, which `Self` names.
206 self_ty: Option<Nim>,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago207 /// Type parameters of the enclosing `impl`, which its methods share.
208 impl_generics: Vec<String>,
209 /// Type parameters of the proc being lowered, impl's included.
210 fn_generics: Vec<String>,
211 /// Type parameters declared by each generic struct or enum.
212 type_generics: HashMap<String, Vec<String>>,
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago213 /// `(type, name) -> type` for `type Item = ..;` inside an `impl`. Rust
214 /// writes those as `Self::Item`, which has to resolve before any
215 /// signature mentioning it is mapped.
216 assoc: HashMap<(String, String), Nim>,
217 /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`.
218 assoc_consts: HashMap<(String, String), (String, Nim)>,
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 20h ago219 /// Symbols declared by an `extern "C"` block.
220 foreign: std::collections::HashSet<String>,
221 /// Const-qualified C pointer aliases already declared.
222 const_ptrs: std::collections::HashSet<String>,
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 20h ago223 /// `macro_rules!` definitions we can expand, and the reasons for those we
224 /// cannot, so a call site can say *why* rather than "unknown macro".
225 mrules: HashMap<String, crate::mrules::MacroDef>,
226 mrules_bad: HashMap<String, String>,
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 20h ago227 /// Types declared by a `bitflags!` invocation.
228 bitflags: std::collections::HashSet<String>,
229 /// `(type, flag) -> nim const name`.
230 flag_consts: HashMap<(String, String), String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago231 /// `use` brings a name into scope from another module. Flattening loses
232 /// the module structure, so the mapping is recorded and consulted when a
233 /// bare call is resolved.
234 use_map: HashMap<String, String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday235 /// struct name -> (field, type)
236 structs: HashMap<String, Vec<(String, Nim)>>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago237 enums: HashMap<String, EnumDef>,
238 /// variant name -> enums declaring it. A variant named by more than one
239 /// enum must be written qualified, or it is rejected as ambiguous.
240 variant_owner: HashMap<String, Vec<String>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago241 /// `(receiver type, method) -> signature`. Keyed by type because two
242 /// types may define the same method name, and Nim tells them apart by
243 /// overload resolution on the first parameter.
244 methods: HashMap<(String, String), Sig>,
245 /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}`
246 /// on a user type can be checked rather than assumed.
247 fmt_impls: HashMap<(String, String), ()>,
248 /// `(from, to)` conversions declared by `impl From<A> for B`.
249 from_impls: HashMap<(String, String), String>,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago250 /// Operator traits implemented for a type, so `a += b` on a user type can
251 /// be dispatched to the impl rather than to Nim's built-in operator.
252 op_impls: HashMap<(String, String), ()>,
253 /// `(type, method) -> nim name`, for calls written as `Type::method(..)`.
254 statics: HashMap<(String, String), String>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago255 /// Forward declarations, emitted between the type definitions and the
256 /// bodies. Rust has no declaration-before-use rule and Nim does, so every
257 /// proc is declared up front rather than the input being reordered --
258 /// which would not work for mutual recursion anyway.
259 forwards: Vec<String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago260 /// Element type a `vec![..]` should build, from the binding's annotation.
261 vec_expect: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago262 /// While lowering a formatting impl: the `Formatter` parameter's name.
263 /// Writes through it produce the proc's string result.
264 fmt_param: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago265 /// `type X<T> = ...`, expanded before any type is mapped.
266 aliases: HashMap<String, (Vec<String>, syn::Type)>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago267 /// Module names supplied as separate input files. A `mod x;` naming one
268 /// of these is satisfied by that file having been passed in.
269 pub modules: Vec<String>,
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 22h ago270 /// How many items were actually translated. If this is zero the input
271 /// produced nothing but the prelude, and reporting success for that is
272 /// the precise failure this project exists to avoid -- see `findings/`.
273 emitted: usize,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago274 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
275 /// evaluated against these exactly as rustc would, so an item that is
276 /// dropped here is genuinely not part of the program being compiled.
277 pub features: Vec<String>,
278 dropped_by_cfg: usize,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday279 /// Return type of the proc being lowered, so `return e` and a trailing
280 /// expression can type their literals the way Rust's inference would.
281 ret: Option<Nim>,
282 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
283 /// statement must assign their value to.
284 target: Option<(String, Option<Nim>)>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago285 /// Set while lowering a `while` condition, which Nim re-evaluates each
286 /// iteration and so cannot have statements hoisted out of it.
287 in_loop_cond: bool,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday288 tmp: usize,
289}
290
291impl Lowerer {
292 pub fn new() -> Self {
293 Lowerer {
294 out: String::new(),
295 indent: 0,
296 scopes: vec![HashMap::new()],
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago297 alias_scopes: vec![HashMap::new()],
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday298 fns: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago299 cur_mod: String::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago300 self_ty: None,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago301 impl_generics: Vec::new(),
302 fn_generics: Vec::new(),
303 type_generics: HashMap::new(),
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago304 assoc: HashMap::new(),
305 assoc_consts: HashMap::new(),
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 20h ago306 foreign: std::collections::HashSet::new(),
307 const_ptrs: std::collections::HashSet::new(),
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 20h ago308 mrules: HashMap::new(),
309 mrules_bad: HashMap::new(),
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 20h ago310 bitflags: std::collections::HashSet::new(),
311 flag_consts: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago312 use_map: HashMap::new(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday313 structs: HashMap::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago314 enums: HashMap::new(),
315 variant_owner: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago316 methods: HashMap::new(),
317 fmt_impls: HashMap::new(),
318 from_impls: HashMap::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago319 op_impls: HashMap::new(),
320 statics: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago321 fmt_param: None,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago322 vec_expect: None,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago323 forwards: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago324 aliases: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago325 modules: Vec::new(),
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 22h ago326 emitted: 0,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago327 features: Vec::new(),
328 dropped_by_cfg: 0,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday329 ret: None,
330 target: None,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago331 in_loop_cond: false,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday332 tmp: 0,
333 }
334 }
335
336 // ------------------------------------------------------------ emission
337
338 fn line(&mut self, s: &str) {
339 for _ in 0..self.indent {
340 self.out.push_str(" ");
341 }
342 self.out.push_str(s);
343 self.out.push('\n');
344 }
345
346 fn blank(&mut self) {
347 self.out.push('\n');
348 }
349
350 fn fresh(&mut self, hint: &str) -> String {
351 self.tmp += 1;
352 format!("rsTmp{}{}", hint, self.tmp)
353 }
354
355 // --------------------------------------------------------------- scope
356
357 fn push_scope(&mut self) {
358 self.scopes.push(HashMap::new());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago359 self.alias_scopes.push(HashMap::new());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday360 }
361 fn pop_scope(&mut self) {
362 self.scopes.pop();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago363 self.alias_scopes.pop();
364 }
365 fn bind_alias(&mut self, name: &str, a: Alias) {
366 self.alias_scopes
367 .last_mut()
368 .unwrap()
369 .insert(name.to_string(), a);
370 }
371 fn lookup_alias(&self, name: &str) -> Option<Alias> {
372 self.alias_scopes
373 .iter()
374 .rev()
375 .find_map(|s| s.get(name).cloned())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday376 }
377 fn bind(&mut self, name: &str, t: Nim) {
378 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
379 }
380 fn lookup(&self, name: &str) -> Option<Nim> {
381 self.scopes.iter().rev().find_map(|s| s.get(name).cloned())
382 }
383
384 // ---------------------------------------------------------------- file
385
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago386 pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday387 self.out.push_str(include_str!("prelude.nim"));
388 self.blank();
389
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago390 // Pass 0: type aliases. A signature in one file may use an alias
391 // declared in another, and inputs are given in whatever order suits
392 // the caller, so aliases are registered before anything is mapped.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago393 for (m, f) in files {
394 self.cur_mod = m.clone();
395 for item in &f.items {
396 self.collect_aliases(item)?;
397 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago398 }
399
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday400 // Pass 1: signatures and struct shapes, so that a call can be typed
401 // regardless of declaration order (Rust has no forward declarations).
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago402 for (m, f) in files {
403 self.cur_mod = m.clone();
404 for item in &f.items {
405 self.collect(item)?;
406 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday407 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago408 // Pass 2: type definitions, which every signature may mention.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago409 for (m, f) in files {
410 self.cur_mod = m.clone();
411 for item in &f.items {
412 self.item_types(item)?;
413 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago414 }
415
416 // Pass 3: forward declarations. Rust imposes no declaration order and
417 // Nim does, so everything is declared before any body is emitted;
418 // reordering the input would not handle mutual recursion anyway.
419 if !self.forwards.is_empty() {
420 for f in self.forwards.clone() {
421 self.line(&f);
422 }
423 self.blank();
424 }
425
426 // Pass 4: bodies.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago427 for (m, f) in files {
428 self.cur_mod = m.clone();
429 for item in &f.items {
430 self.item(item)?;
431 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday432 }
433
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 22h ago434 // An input that translates to nothing is a failure, however plausible
435 // the output file looks. The prelude alone is not a translation.
436 if self.emitted == 0 {
437 return Err(format!(
438 "nothing was translated: the input has no items this lowering \
439 emits{}. Writing a file containing only the prelude would \
440 report success for work that was not done",
441 if self.dropped_by_cfg > 0 {
442 format!(
443 " ({} item(s) were dropped by `#[cfg]`; enable them with \
444 `--cfg feature=<name>`)",
445 self.dropped_by_cfg
446 )
447 } else {
448 String::new()
449 }
450 ));
451 }
452
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago453 if self.fns.contains_key(&(String::new(), "main".to_string())) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday454 self.blank();
455 self.line("when isMainModule:");
456 self.indent += 1;
457 self.line("try:");
458 self.line(" main()");
459 // Rust's panic exits 101 with a message on stderr. Nim's Defects
460 // exit 1. Mapping them here is what keeps the differential runner's
461 // exit-status comparison meaningful for panicking programs.
462 self.line("except RustPanic as e:");
463 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
464 self.line(" quit(101)");
465 self.line("except Defect as e:");
466 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
467 self.line(" quit(101)");
468 self.indent -= 1;
469 }
470 Ok(std::mem::take(&mut self.out))
471 }
472
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago473 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
474 if !self.cfg_keeps(item_attrs(item))? {
475 return Ok(());
476 }
477 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago478 Item::Use(u) => self.collect_use(&u.tree, &[]),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago479 Item::Type(t) => {
480 let params: Vec<String> = t
481 .generics
482 .params
483 .iter()
484 .filter_map(|g| match g {
485 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
486 _ => None,
487 })
488 .collect();
489 self.aliases
490 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
491 }
492 Item::Mod(m) if m.content.is_some() => {
493 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
494 for i in &items {
495 self.collect_aliases(i)?;
496 }
497 }
498 _ => {}
499 }
500 Ok(())
501 }
502
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago503 /// Record what a `use` brings into scope, as `name -> module`.
504 fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) {
505 use syn::UseTree;
506 match t {
507 UseTree::Path(p) => {
508 let mut pre = prefix.to_vec();
509 pre.push(p.ident.to_string());
510 self.collect_use(&p.tree, &pre);
511 }
512 UseTree::Group(g) => {
513 for t in &g.items {
514 self.collect_use(t, prefix);
515 }
516 }
517 UseTree::Name(n) => {
518 let m = module_of(prefix);
519 self.use_map.insert(n.ident.to_string(), m);
520 }
521 UseTree::Rename(r) => {
522 let m = module_of(prefix);
523 self.use_map.insert(r.rename.to_string(), m);
524 }
525 // A glob brings in an unknown set of names; resolution falls back
526 // to the current module and the root, as it would without it.
527 UseTree::Glob(_) => {}
528 }
529 }
530
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday531 fn collect(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago532 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
533 // silently would change what the program does; picking a feature set
534 // on the user's behalf would be a guess. So it is reported, except on
535 // items that carry no runtime meaning here anyway.
536 if !self.cfg_keeps(item_attrs(item))? {
537 self.dropped_by_cfg += 1;
538 return Ok(());
539 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday540 match item {
541 Item::Fn(f) => {
542 let (params, ret) = self.signature(&f.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago543 let gen_names = Self::generics_of(&f.sig.generics);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago544 let name = f.sig.ident.to_string();
545 let nim = self.fn_name(&self.cur_mod, &name);
546 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
547 self.fns
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago548 .insert((self.cur_mod.clone(), name), Sig { params, ret, generics: gen_names.clone() });
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday549 }
550 Item::Struct(s) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago551 if s.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
552 return Err(format!(
553 "`struct {}` has a const generic parameter, which Nim has \
554 no equivalent for",
555 s.ident
556 ));
557 }
558 let g = Self::generics_of(&s.generics);
559 // The parameters must be in scope while the field types are
560 // mapped, so that `T` resolves to itself rather than to an
561 // unknown named type.
562 self.type_generics.insert(s.ident.to_string(), g);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday563 let mut fields = Vec::new();
564 for (i, f) in s.fields.iter().enumerate() {
565 let name = match &f.ident {
566 Some(id) => id.to_string(),
567 None => format!("f{i}"), // tuple struct
568 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago569 // A field of `&[T]` / `&str` type is a borrow, and Nim's
570 // view types allow it as an object field, so it stays a
571 // view rather than being copied into a `seq`.
572 let t = self.map_ty(&f.ty)?;
573 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
574 fields.push((name, t));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday575 }
576 self.structs.insert(s.ident.to_string(), fields);
577 }
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 20h ago578 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => {
579 self.collect_bitflags(&m.mac)?;
580 }
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 20h ago581 // A `macro_rules!` definition is recorded, not emitted: it is
582 // expanded at each call site, where the types its body needs are
583 // known. See `src/mrules.rs` for why that beats translating it
584 // into a Nim template.
585 Item::Macro(m) if path_name(&m.mac.path) == "macro_rules" => {
586 if let Some(name) = &m.ident {
587 match crate::mrules::parse(m.mac.tokens.clone()) {
588 Ok(def) => {
589 self.mrules.insert(name.to_string(), def);
590 }
591 Err(why) => {
592 // Recorded as unusable rather than silently absent:
593 // a call site gets this reason instead of "unknown
594 // macro".
595 self.mrules_bad.insert(name.to_string(), why);
596 }
597 }
598 }
599 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 20h ago600 Item::ForeignMod(f) => {
601 for it in &f.items {
602 if let syn::ForeignItem::Fn(fi) = it {
603 let (params, ret) = self.signature(&fi.sig)?;
604 self.fns.insert(
605 (self.cur_mod.clone(), fi.sig.ident.to_string()),
606 Sig { params, ret, generics: Vec::new() },
607 );
608 }
609 }
610 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago611 Item::Mod(m) if m.content.is_some() => {
612 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
613 for i in &items {
614 self.collect(i)?;
615 }
616 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago617 Item::Type(t) => {
618 let params: Vec<String> = t
619 .generics
620 .params
621 .iter()
622 .filter_map(|g| match g {
623 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
624 _ => None,
625 })
626 .collect();
627 self.aliases
628 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
629 }
630 Item::Enum(e) => {
631 let name = e.ident.to_string();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago632 if e.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
633 return Err(format!(
634 "`enum {name}` has a const generic parameter, which Nim \
635 has no equivalent for"
636 ));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago637 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago638 self.type_generics
639 .insert(name.clone(), Self::generics_of(&e.generics));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago640 let mut variants = Vec::new();
641 for v in &e.variants {
642 let vname = v.ident.to_string();
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 20h ago643 let discriminant = match &v.discriminant {
644 Some((_, e)) => Some(self.expr(e)?.code),
645 None => None,
646 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago647 let mut fields = Vec::new();
648 for (i, f) in v.fields.iter().enumerate() {
649 // Nim requires the branches of a variant object to have
650 // distinct field names, so each is prefixed.
651 let fname = match &f.ident {
652 Some(id) => format!("{vname}_{id}"),
653 None => format!("{vname}_f{i}"),
654 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago655 let t = self.map_ty(&f.ty)?;
656 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
657 fields.push((fname, t));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago658 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 20h ago659 variants.push(Variant { name: vname, discriminant, fields });
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago660 }
661 let simple = variants.iter().all(|v| v.fields.is_empty());
662 for v in &variants {
663 self.variant_owner
664 .entry(v.name.clone())
665 .or_default()
666 .push(name.clone());
667 }
668 self.enums.insert(
669 name.clone(),
670 EnumDef { name, simple, variants },
671 );
672 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday673 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago674 let outer_g =
675 std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago676 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago677 let outer_self = self.self_ty.replace(self_ty.clone());
678 let r = self.collect_impl(im, &self_ty);
679 self.self_ty = outer_self;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago680 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago681 return r;
682 }
683 _ => {}
684 }
685 Ok(())
686 }
687
688 fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
689 {
690 let self_ty = self_ty.clone();
691 let tyname = type_name(&self_ty);
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago692 // Associated types first: a signature in the same block may name
693 // one, and it has to resolve by the time that signature is mapped.
694 for it in &im.items {
695 if let syn::ImplItem::Type(t) = it {
696 let v = self.map_ty(&t.ty)?;
697 self.assoc.insert((tyname.clone(), t.ident.to_string()), v);
698 }
699 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago700 if let Some((path, _)) = &im.trait_ {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago701 let tr = path_name(path);
702 if im.items.is_empty() {
703 // A marker trait with no items. We do not model trait
704 // resolution at all, so it generates nothing; any use
705 // that actually needed the trait (a `dyn`, a bound) is
706 // rejected where it appears.
707 return Ok(());
708 }
709 if is_fmt_trait(&tr) {
710 self.forwards.push(format!(
711 "proc {}*(self: {}): string",
712 fmt_proc(&tr),
713 self_ty.render()
714 ));
715 self.fmt_impls.insert((tyname, tr), ());
716 return Ok(());
717 }
718 if tr == "From" {
719 let syn::ImplItem::Fn(m) = &im.items[0] else {
720 return Err("`impl From` must contain `fn from`".into());
721 };
722 let (params, _) = self.signature(&m.sig)?;
723 let src = params
724 .first()
725 .ok_or("`fn from` takes one argument")?
726 .clone();
727 let name = format!("rsFrom{}{}", tyname, type_name(&src));
728 self.forwards.push(self.head_of(&name, &m.sig, None)?);
729 self.from_impls
730 .insert((type_name(&src), tyname), name);
731 return Ok(());
732 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago733 // Any other trait: its methods are emitted as procs on
734 // the type, named after the trait so two traits declaring
735 // the same method name do not collide. The *trait* is not
736 // modelled -- no dynamic dispatch, no bounds -- and a use
737 // that needs it is rejected where it appears.
738 if let Some(op) = operator_trait(&tr) {
739 self.op_impls.insert((tyname.clone(), op.to_string()), ());
740 }
741 for it in &im.items {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago742 // Already recorded above; a const is emitted with the
743 // bodies.
744 if matches!(it, syn::ImplItem::Type(_) | syn::ImplItem::Const(_)) {
745 continue;
746 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago747 let syn::ImplItem::Fn(m) = it else {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago748 return Err(format!(
749 "unsupported item in `impl {tr}`: only `fn`, \
750 `type` and `const` are implemented"
751 ));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago752 };
753 let mname = m.sig.ident.to_string();
754 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago755 let mut gen_names = self.impl_generics.clone();
756 gen_names.extend(Self::generics_of(&m.sig.generics));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago757 let recv = if takes_self(&m.sig) {
758 params.insert(0, self_ty.clone());
759 Some(self_ty.clone())
760 } else {
761 None
762 };
763 let nim = trait_method_name(&tyname, &tr, &mname);
764 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
765 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago766 .insert((tyname.clone(), mname.clone()), Sig { params, ret, generics: gen_names.clone() });
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago767 self.statics.insert((tyname.clone(), mname), nim);
768 }
769 return Ok(());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago770 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday771 for it in &im.items {
772 if let syn::ImplItem::Fn(m) = it {
773 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago774 let mut gen_names = self.impl_generics.clone();
775 gen_names.extend(Self::generics_of(&m.sig.generics));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday776 if takes_self(&m.sig) {
777 params.insert(0, self_ty.clone());
778 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago779 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago780 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
781 let head = self.head_of(&nim, &m.sig, recv.as_ref())?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago782 self.forwards.push(head);
783 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago784 .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret, generics: gen_names.clone() });
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago785 self.statics
786 .insert((tyname.clone(), m.sig.ident.to_string()), nim);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday787 }
788 }
789 }
790 Ok(())
791 }
792
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago793 /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
794 ///
795 /// This is evaluation, not approximation: rustc does the same thing, and
796 /// an item whose predicate is false is not part of the compiled program.
797 /// A predicate that cannot be evaluated is reported rather than assumed.
798 fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
799 for a in attrs {
800 if a.path().is_ident("cfg") {
801 let pred: syn::Meta = a
802 .parse_args()
803 .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
804 if !self.cfg_eval(&pred)? {
805 return Ok(false);
806 }
807 }
808 }
809 Ok(true)
810 }
811
812 fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
813 match m {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago814 // Bare flags whose value is determined by the profile this project
815 // models: a normal (non-`--test`) debug build, not a docs build.
816 // Anything platform-specific stays rejected, since we would be
817 // picking a target on the user's behalf.
818 syn::Meta::Path(p) if p.is_ident("test") => Ok(false),
819 syn::Meta::Path(p) if p.is_ident("debug_assertions") => Ok(true),
820 syn::Meta::Path(p) if p.is_ident("docsrs") || p.is_ident("doc") => Ok(false),
Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 21h ago821 syn::Meta::Path(p) if p.is_ident("doctest") || p.is_ident("miri") => Ok(false),
822 // Host facts. The generated Nim is compiled for this machine, so
823 // these are known rather than chosen. See DESIGN.md item 10: it
824 // does make the output host-shaped.
825 syn::Meta::Path(p) if p.is_ident("unix") => Ok(cfg!(unix)),
826 syn::Meta::Path(p) if p.is_ident("windows") => Ok(cfg!(windows)),
827 syn::Meta::NameValue(nv)
828 if nv.path.is_ident("target_os")
829 || nv.path.is_ident("target_arch")
830 || nv.path.is_ident("target_family")
831 || nv.path.is_ident("target_vendor") =>
832 {
833 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
834 return Err("this `cfg` key expects a string".into());
835 };
836 let key = nv.path.get_ident().map(|i| i.to_string()).unwrap_or_default();
837 Ok(s.value()
838 == match key.as_str() {
839 "target_os" => std::env::consts::OS,
840 "target_arch" => std::env::consts::ARCH,
841 "target_family" => std::env::consts::FAMILY,
842 _ => "unknown",
843 })
844 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago845 // The generated Nim is compiled for the same machine, so the
846 // target's word size and endianness are known rather than
847 // guessed. This does mean the output is host-shaped: a crate that
848 // branches on pointer width has had that branch decided here.
849 syn::Meta::NameValue(nv) if nv.path.is_ident("target_pointer_width") => {
850 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
851 return Err("`target_pointer_width = ..` expects a string".into());
852 };
853 Ok(s.value() == (usize::BITS).to_string())
854 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 20h ago855 // Every integer width and pointer-sized atomic exists on the
856 // targets Nim builds for here; like the other host facts this is
857 // read off the machine rather than chosen.
858 syn::Meta::NameValue(nv) if nv.path.is_ident("target_has_atomic") => {
859 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
860 return Err("`target_has_atomic = ..` expects a string".into());
861 };
862 Ok(matches!(
863 s.value().as_str(),
864 "8" | "16" | "32" | "64" | "ptr"
865 ))
866 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago867 syn::Meta::NameValue(nv) if nv.path.is_ident("target_endian") => {
868 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
869 return Err("`target_endian = ..` expects a string".into());
870 };
871 Ok(s.value() == if cfg!(target_endian = "big") { "big" } else { "little" })
872 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago873 syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
874 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
875 return Err("`feature = ..` expects a string".into());
876 };
877 Ok(self.features.iter().any(|f| *f == s.value()))
878 }
879 syn::Meta::List(l) if l.path.is_ident("not") => {
880 let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
881 Ok(!self.cfg_eval(&inner)?)
882 }
883 syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
884 let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
885 .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
886 .map_err(|e| e.to_string())?;
887 let all = l.path.is_ident("all");
888 let mut acc = all;
889 for i in &items {
890 let v = self.cfg_eval(i)?;
891 acc = if all { acc && v } else { acc || v };
892 }
893 Ok(acc)
894 }
895 other => Err(format!(
Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 21h ago896 "`#[cfg({})]` is not a predicate rustnim can evaluate. \
897 Features (`--cfg feature=..`), host facts (`unix`, `windows`, \
898 `target_os`, `target_arch`, `target_family`, \
899 `target_pointer_width`, `target_endian`), `doc`/`doctest`/\
900 `miri`, and `not`/`all`/`any` over those are. A custom or \
901 build-script `cfg` has no value we could know",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago902 quote_meta(other)
903 )),
904 }
905 }
906
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago907 /// Map a Rust type, resolving `Self` and expanding any `type` alias. Every type in the
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago908 /// lowering goes through here rather than calling `ty::map` directly, so
909 /// an alias cannot be missed in one position and honoured in another.
910 fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago911 // `Self::Item` names an associated type of the enclosing `impl`.
912 if let syn::Type::Path(p) = t {
913 let segs: Vec<String> =
914 p.path.segments.iter().map(|s| s.ident.to_string()).collect();
915 if segs.len() == 2 {
916 let owner = if segs[0] == "Self" {
917 self.self_ty.as_ref().map(type_name)
918 } else {
919 Some(segs[0].clone())
920 };
921 if let Some(o) = owner {
922 if let Some(a) = self.assoc.get(&(o, segs[1].clone())) {
923 return Ok(a.clone());
924 }
925 }
926 }
927 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago928 let n = ty::map(&self.expand(t, 0)?)?;
929 Ok(self.subst_self(n))
930 }
931
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago932 /// Substitute a generic type's parameters with the arguments the use site
933 /// supplies: a field of `Holder<T>` read through a `Holder<i32>` is `i32`.
934 fn subst_type_args(&self, name: &str, used_as: &Nim, field: Nim) -> Nim {
935 let Some(params) = self.type_generics.get(name) else { return field };
936 if params.is_empty() {
937 return field;
938 }
939 let Nim::Named(n, args) = used_as else { return field };
940 if n != name || args.len() != params.len() {
941 return field;
942 }
943 let map: HashMap<String, Nim> =
944 params.iter().cloned().zip(args.iter().cloned()).collect();
945 Self::subst(&field, &map)
946 }
947
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago948 /// `Self` inside an `impl` block names the type being implemented.
949 fn subst_self(&self, t: Nim) -> Nim {
950 let Some(me) = &self.self_ty else { return t };
951 match t {
952 Nim::Named(n, _) if n == "Self" => me.clone(),
953 Nim::Seq(e) => Nim::Seq(Box::new(self.subst_self(*e))),
954 Nim::OpenArray(e) => Nim::OpenArray(Box::new(self.subst_self(*e))),
955 Nim::Array(n, e) => Nim::Array(n, Box::new(self.subst_self(*e))),
956 Nim::Var(e) => Nim::Var(Box::new(self.subst_self(*e))),
957 Nim::Named(n, a) => {
958 Nim::Named(n, a.into_iter().map(|x| self.subst_self(x)).collect())
959 }
960 other => other,
961 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago962 }
963
964 fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
965 if depth > 16 {
966 return Err("type alias expansion did not terminate; is it cyclic?".into());
967 }
968 let syn::Type::Path(p) = t else { return Ok(t.clone()) };
969 // Only an unqualified name can be one of this file's aliases.
970 // `fmt::Result` and `core::result::Result` are different types that
971 // merely end in the same segment.
972 if p.path.segments.len() != 1 {
973 return Ok(t.clone());
974 }
975 let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
976 let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
977 return Ok(t.clone());
978 };
979 let args: Vec<syn::Type> = match &seg.arguments {
980 syn::PathArguments::AngleBracketed(a) => a
981 .args
982 .iter()
983 .filter_map(|g| match g {
984 GenericArgument::Type(t) => Some(t.clone()),
985 _ => None,
986 })
987 .collect(),
988 _ => vec![],
989 };
990 if args.len() != params.len() {
991 // Flattening several files into one module can bring a crate's own
992 // alias (`type Result<T> = Result<T, Error>`) into scope at a site
993 // that meant the builtin (`Result<T, E>`). Rust kept them apart by
994 // module; here they are told apart by arity, and a use that fits
995 // neither is left for `ty::map` to report.
996 return Ok(t.clone());
997 }
998 self.expand(&substitute(target, params, &args), depth + 1)
999 }
1000
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1001 /// The type parameters a generic item declares.
1002 ///
1003 /// Trait bounds and `where` clauses are dropped. Nim instantiates a
1004 /// generic structurally: an operation the bound would have permitted
1005 /// either exists for the instantiated type or is a compile error at the
1006 /// instantiation site. So dropping a bound cannot make an accepted
1007 /// program mean something different — it only makes rustnim accept some
1008 /// programs rustc would have rejected, which does not matter when the
1009 /// input is known-good Rust.
1010 fn generics_of(g: &syn::Generics) -> Vec<String> {
1011 g.params
1012 .iter()
1013 .filter_map(|p| match p {
1014 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
1015 _ => None,
1016 })
1017 .collect()
1018 }
1019
1020 /// Bind a signature's type parameters by matching its declared parameter
1021 /// types against the actual argument types, then substitute into `ret`.
1022 ///
1023 /// This is the small amount of inference a call site needs: Nim will
1024 /// resolve the instantiation itself, but the *binding* still has to be
1025 /// annotated with a concrete type, and `T` is not one.
1026 fn instantiate(sig: &Sig, args: &[Option<Nim>]) -> Nim {
1027 if sig.generics.is_empty() {
1028 return sig.ret.clone();
1029 }
1030 let mut bound: HashMap<String, Nim> = HashMap::new();
1031 for (decl, actual) in sig.params.iter().zip(args) {
1032 if let Some(a) = actual {
1033 Self::unify(decl, a, &sig.generics, &mut bound);
1034 }
1035 }
1036 Self::subst(&sig.ret, &bound)
1037 }
1038
1039 fn unify(decl: &Nim, actual: &Nim, params: &[String], out: &mut HashMap<String, Nim>) {
1040 match (decl, actual) {
1041 (Nim::Named(n, da), _) if params.iter().any(|p| p == n) && da.is_empty() => {
1042 out.entry(n.clone()).or_insert_with(|| actual.clone());
1043 }
1044 (Nim::Named(_, da), Nim::Named(_, aa)) if da.len() == aa.len() => {
1045 for (d, a) in da.iter().zip(aa) {
1046 Self::unify(d, a, params, out);
1047 }
1048 }
1049 (Nim::Seq(d), Nim::Seq(a))
1050 | (Nim::OpenArray(d), Nim::OpenArray(a))
1051 | (Nim::Seq(d), Nim::OpenArray(a))
1052 | (Nim::OpenArray(d), Nim::Seq(a))
1053 | (Nim::Var(d), Nim::Var(a))
1054 | (Nim::Array(_, d), Nim::Array(_, a)) => Self::unify(d, a, params, out),
1055 (Nim::Var(d), a) => Self::unify(d, a, params, out),
1056 (d, Nim::Var(a)) => Self::unify(d, a, params, out),
1057 (Nim::Tuple(d), Nim::Tuple(a)) if d.len() == a.len() => {
1058 for (d, a) in d.iter().zip(a) {
1059 Self::unify(d, a, params, out);
1060 }
1061 }
1062 _ => {}
1063 }
1064 }
1065
1066 fn subst(t: &Nim, m: &HashMap<String, Nim>) -> Nim {
1067 match t {
1068 Nim::Named(n, a) if a.is_empty() => m.get(n).cloned().unwrap_or_else(|| t.clone()),
1069 Nim::Named(n, a) => {
1070 Nim::Named(n.clone(), a.iter().map(|x| Self::subst(x, m)).collect())
1071 }
1072 Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))),
1073 Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))),
1074 Nim::Array(n, e) => Nim::Array(*n, Box::new(Self::subst(e, m))),
1075 Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))),
1076 Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()),
1077 other => other.clone(),
1078 }
1079 }
1080
1081 /// Whether a type mentions a type parameter that is in scope here. Such a
1082 /// type cannot be used as a Nim annotation at an instantiation site: Nim
1083 /// infers it, and writing `T` would name something that is not bound.
1084 fn mentions_type_param(&self, t: &Nim) -> bool {
1085 match t {
1086 Nim::Named(n, a) => {
1087 self.fn_generics.iter().any(|g| g == n)
1088 || a.iter().any(|x| self.mentions_type_param(x))
1089 }
1090 Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) => {
1091 self.mentions_type_param(e)
1092 }
1093 Nim::Tuple(ts) => ts.iter().any(|x| self.mentions_type_param(x)),
1094 Nim::Proc(a, r) => {
1095 a.iter().any(|x| self.mentions_type_param(x)) || self.mentions_type_param(r)
1096 }
1097 _ => false,
1098 }
1099 }
1100
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 20h ago1101 /// The Nim name of a `log::Level` written as a path.
1102 fn log_level_of(&mut self, e: &Expr) -> Result<String, String> {
1103 let Expr::Path(p) = e else {
1104 return Err("a log level must be written as `Level::Info`".into());
1105 };
1106 let last = path_name(&p.path);
1107 Ok(match last.as_str() {
1108 "Error" => "rsLvlError",
1109 "Warn" => "rsLvlWarn",
1110 "Info" => "rsLvlInfo",
1111 "Debug" => "rsLvlDebug",
1112 "Trace" => "rsLvlTrace",
1113 other => return Err(format!("`Level::{other}` is not a log level")),
1114 }
1115 .to_string())
1116 }
1117
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1118 /// `[T, U]`, or empty.
1119 fn gen_list(params: &[String]) -> String {
1120 if params.is_empty() {
1121 String::new()
1122 } else {
1123 format!("[{}]", params.join(", "))
1124 }
1125 }
1126
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago1127 /// The Nim name for a function, qualified by its module.
1128 fn fn_name(&self, module: &str, name: &str) -> String {
1129 if module.is_empty() {
1130 ident(name)
1131 } else {
1132 format!("{}_{}", module, ident(name))
1133 }
1134 }
1135
1136 /// Resolve a call path to the module and name it refers to: an explicit
1137 /// `mixed::decode`, then the current module, then the crate root.
1138 fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> {
1139 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1140 let last = segs.last()?.clone();
1141 if segs.len() >= 2 {
1142 let q = &segs[segs.len() - 2];
1143 if self.fns.contains_key(&(q.clone(), last.clone())) {
1144 return Some((q.clone(), last));
1145 }
1146 }
1147 let imported = self.use_map.get(&last).cloned();
1148 for m in [Some(self.cur_mod.clone()), imported, Some(String::new())]
1149 .into_iter()
1150 .flatten()
1151 {
1152 if self.fns.contains_key(&(m.clone(), last.clone())) {
1153 return Some((m, last));
1154 }
1155 }
1156 None
1157 }
1158
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago1159 /// The Nim `proc` head for a Rust signature, used both for the forward
1160 /// declaration and for the definition, so the two cannot drift apart.
1161 fn head_of(
1162 &self,
1163 name: &str,
1164 sig: &syn::Signature,
1165 recv: Option<&Nim>,
1166 ) -> Result<String, String> {
1167 let (ptys, ret) = self.signature(sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1168 // A method inside `impl<T> Foo<T>` is generic in the impl's
1169 // parameters as well as its own.
1170 let mut params = self.impl_generics.clone();
1171 for g in Self::generics_of(&sig.generics) {
1172 if !params.contains(&g) {
1173 params.push(g);
1174 }
1175 }
1176 let gens = Self::gen_list(&params);
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago1177 let mut parts = Vec::new();
1178 if let Some(self_ty) = recv {
1179 let mutable = matches!(
1180 sig.inputs.first(),
1181 Some(FnArg::Receiver(r))
1182 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1183 );
1184 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1185 parts.push(format!("self: {}", t.render()));
1186 }
1187 let typed: Vec<&syn::PatType> = sig
1188 .inputs
1189 .iter()
1190 .filter_map(|a| match a {
1191 FnArg::Typed(t) => Some(t),
1192 _ => None,
1193 })
1194 .collect();
1195 for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
1196 let pname = match &*p.pat {
1197 Pat::Ident(id) => id.ident.to_string(),
1198 Pat::Wild(_) => format!("unused{}", parts.len()),
1199 _ => return Err("only plain identifier parameters are supported".into()),
1200 };
1201 let _ = i;
1202 parts.push(format!("{}: {}", ident(&pname), t.render()));
1203 }
1204 Ok(if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1205 format!("proc {}*{}({})", ident(name), gens, parts.join(", "))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago1206 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1207 format!(
1208 "proc {}*{}({}): {}",
1209 ident(name),
1210 gens,
1211 parts.join(", "),
1212 ret.render()
1213 )
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago1214 })
1215 }
1216
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1217 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago1218 // `unsafe fn` marks a contract for callers; it does not change what
1219 // the body means, so it lowers like any other proc.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1220 if sig.asyncness.is_some() {
1221 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
1222 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1223 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1224 // they disappear. Type parameters become Nim generic parameters.
1225 // Const parameters have no Nim equivalent and are still rejected.
1226 if sig.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1227 return Err(format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1228 "`fn {}` has a const generic parameter, which Nim has no \
1229 equivalent for",
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1230 sig.ident
1231 ));
1232 }
1233 let mut params = Vec::new();
1234 for a in &sig.inputs {
1235 if let FnArg::Typed(t) = a {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1236 params.push(self.map_ty(&t.ty)?);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1237 }
1238 }
1239 let ret = match &sig.output {
1240 ReturnType::Default => Nim::Unit,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago1241 // A returned `&[T]` is a borrow of the caller's buffer, so it
1242 // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
1243 // a `seq`, which `owned()` would do to both.
1244 ReturnType::Type(_, t) => {
1245 let n = self.map_ty(t)?;
1246 if returns_borrow(t) { n } else { n.owned() }
1247 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1248 };
1249 Ok((params, ret))
1250 }
1251
1252 // --------------------------------------------------------------- items
1253
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago1254 /// Emit the type definitions only: they must precede every signature.
1255 fn item_types(&mut self, item: &Item) -> Result<(), String> {
1256 if !self.cfg_keeps(item_attrs(item))? {
1257 return Ok(());
1258 }
1259 match item {
1260 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 20h ago1261 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.item_inner(item),
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 20h ago1262 Item::ForeignMod(_) => self.item_inner(item),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago1263 Item::Mod(m) if m.content.is_some() => {
1264 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1265 for i in &items {
1266 self.item_types(i)?;
1267 }
1268 Ok(())
1269 }
1270 _ => Ok(()),
1271 }
1272 }
1273
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1274 fn item(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1275 if !self.cfg_keeps(item_attrs(item))? {
1276 return Ok(());
1277 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago1278 // Types were emitted in their own pass.
1279 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
1280 return Ok(());
1281 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 20h ago1282 if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags")
1283 || matches!(item, Item::ForeignMod(_))
1284 {
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 20h ago1285 return Ok(()); // emitted with the types
1286 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago1287 self.item_inner(item)
1288 }
1289
1290 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 22h ago1291 if !matches!(item, Item::Use(_) | Item::ExternCrate(_) | Item::Mod(_) | Item::Type(_)) {
1292 self.emitted += 1;
1293 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1294 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago1295 Item::Fn(f) => {
1296 let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string());
1297 self.func_named(&nim, &f.sig, &f.block, None)
1298 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1299 Item::Struct(s) => {
1300 let name = s.ident.to_string();
1301 let fields = self.structs[&name].clone();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1302 let g = Self::gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));
1303 self.line(&format!("type {}*{} = object", ident(&name), g));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1304 self.indent += 1;
1305 if fields.is_empty() {
1306 self.line("discard");
1307 }
1308 for (fname, fty) in &fields {
1309 self.line(&format!("{}*: {}", ident(fname), fty.render()));
1310 }
1311 self.indent -= 1;
1312 self.blank();
1313 Ok(())
1314 }
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 20h ago1315 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.emit_bitflags(&m.mac),
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 20h ago1316 Item::Macro(m) if path_name(&m.mac.path) == "macro_rules" => Ok(()),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1317 Item::Type(_) => Ok(()), // expanded at every use site
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago1318 Item::Trait(t) => {
1319 // We do not model trait resolution, so a declaration generates
1320 // nothing and a use that needed it is rejected where it
1321 // appears. A *default body*, though, is code: dropping it
1322 // would silently remove a method the impls inherit.
1323 for it in &t.items {
1324 if let syn::TraitItem::Fn(f) = it {
1325 if f.default.is_some() {
1326 return Err(format!(
1327 "`trait {}` gives `{}` a default body; trait \
1328 resolution is not modelled, so that body has no \
1329 impl to be emitted into and dropping it would \
1330 remove code",
1331 t.ident, f.sig.ident
1332 ));
1333 }
1334 }
1335 }
1336 Ok(())
1337 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1338 Item::Enum(e) => {
1339 let def = self.enums[&e.ident.to_string()].clone();
1340 self.emit_enum(&def);
1341 Ok(())
1342 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1343 Item::Const(c) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1344 let t = self.map_ty(&c.ty)?.owned();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago1345 // The annotation types the initialiser, exactly as it does for
1346 // a `let`: `const MOD: u32 = 65521` is a u32 literal.
1347 let v = self.expr_at(&c.expr, Some(&t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1348 self.bind(&c.ident.to_string(), t.clone());
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago1349 // Only a top-level const is exported; `*` on a local is not
1350 // Nim syntax.
1351 let star = if self.indent == 0 { "*" } else { "" };
1352 let line = format!(
1353 "const {}{}: {} = {}",
1354 ident(&c.ident.to_string()),
1355 star,
1356 t.render(),
1357 v.code
1358 );
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1359 self.line(&line);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago1360 if self.indent == 0 {
1361 self.blank();
1362 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1363 Ok(())
1364 }
1365 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1366 let outer_g =
1367 std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1368 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago1369 let outer = self.self_ty.replace(self_ty.clone());
1370 let r = self.impl_body(im, &self_ty);
1371 self.self_ty = outer;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1372 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago1373 r
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1374 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1375 // `use` and `extern crate` are resolution directives with no Nim
1376 // analogue once everything is one module.
1377 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 20h ago1378 Item::ForeignMod(f) => self.foreign_mod(f),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1379 Item::Mod(m) if m.content.is_some() => {
1380 // An inline `mod` is flattened; Nim has no nested modules in a
1381 // single file.
1382 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1383 for i in &items {
1384 self.item(i)?;
1385 }
1386 Ok(())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1387 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago1388 Item::Mod(m) => {
1389 // Satisfied if that file was passed in too; everything is one
1390 // Nim module, so the declaration itself emits nothing.
1391 if self.modules.iter().any(|x| *x == m.ident.to_string()) {
1392 return Ok(());
1393 }
1394 Err(format!(
1395 "`mod {};` refers to another file that was not passed to \
1396 rustnim; add it to the input list",
1397 m.ident
1398 ))
1399 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday1400 other => Err(format!("unsupported item: {}", item_kind(other))),
1401 }
1402 }
1403
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1404 /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
1405 fn none_of(&self, expect: Option<&Nim>) -> String {
1406 match expect {
1407 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
1408 format!("rsNone[{}]()", a[0].render())
1409 }
1410 _ => "rsNone()".to_string(),
1411 }
1412 }
1413
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago1414 fn impl_body(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
1415 if let Some((path, _)) = &im.trait_ {
1416 let tr = path_name(path);
1417 if im.items.is_empty() {
1418 return Ok(());
1419 }
1420 if is_fmt_trait(&tr) {
1421 let syn::ImplItem::Fn(m) = &im.items[0] else {
1422 return Err(format!("unsupported item in `impl {tr}`"));
1423 };
1424 return self.fmt_impl(&tr, self_ty, &m.sig, &m.block);
1425 }
1426 if tr == "From" {
1427 let syn::ImplItem::Fn(m) = &im.items[0] else {
1428 return Err("`impl From` must contain `fn from`".into());
1429 };
1430 let name = {
1431 let (params, _) = self.signature(&m.sig)?;
1432 let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
1433 self.from_impls[&(type_name(&src), type_name(self_ty))].clone()
1434 };
1435 return self.func_named(&name, &m.sig, &m.block, None);
1436 }
1437 let tyname = type_name(self_ty);
1438 for it in &im.items {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago1439 if let syn::ImplItem::Const(c) = it {
1440 self.assoc_const(&tyname, c)?;
1441 continue;
1442 }
1443 if matches!(it, syn::ImplItem::Type(_)) {
1444 continue; // a type binding emits nothing
1445 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago1446 let syn::ImplItem::Fn(m) = it else {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago1447 return Err(format!(
1448 "unsupported item in `impl {tr}`: only `fn`, `type` and \
1449 `const` are implemented"
1450 ));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago1451 };
1452 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1453 let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());
1454 self.func_named(&nim, &m.sig, &m.block, recv)?;
1455 }
1456 return Ok(());
1457 }
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago1458 let tyname = type_name(self_ty);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago1459 for it in &im.items {
1460 match it {
1461 syn::ImplItem::Fn(m) => {
1462 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1463 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
1464 self.func_named(&nim, &m.sig, &m.block, recv)?;
1465 }
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago1466 syn::ImplItem::Type(_) => {}
1467 syn::ImplItem::Const(c) => self.assoc_const(&tyname, c)?,
1468 _ => {
1469 return Err("only `fn`, `type` and `const` items are supported \
1470 inside `impl`"
1471 .into())
1472 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago1473 }
1474 }
1475 Ok(())
1476 }
1477
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 20h ago1478 /// An `extern "C" { .. }` block: declarations of symbols someone else
1479 /// defines. Nim's `importc` is the same statement, and both are bound by
1480 /// the C ABI, so the two declarations describe one symbol rather than one
1481 /// being a translation of the other.
1482 fn foreign_mod(&mut self, f: &syn::ItemForeignMod) -> Result<(), String> {
1483 let abi = f
1484 .abi
1485 .name
1486 .as_ref()
1487 .map(|s| s.value())
1488 .unwrap_or_else(|| "C".into());
1489 if abi != "C" {
1490 return Err(format!(
1491 "`extern \"{abi}\"` is not the C ABI; only that one has a Nim \
1492 equivalent"
1493 ));
1494 }
1495 // A const-qualified C pointer needs a type whose C spelling carries
1496 // the `const`; Nim has no such type built in, so one is declared per
1497 // element type actually used.
1498 let mut needed: Vec<Nim> = Vec::new();
1499 for it in &f.items {
1500 if let syn::ForeignItem::Fn(fi) = it {
1501 let (params, ret) = self.signature(&fi.sig)?;
1502 for t in params.iter().chain(std::iter::once(&ret)) {
1503 if let Nim::ConstPtr(inner) = t {
1504 if !matches!(&**inner, Nim::Prim(p) if p == "void")
1505 && !needed.contains(t)
1506 {
1507 needed.push(t.clone());
1508 }
1509 }
1510 }
1511 }
1512 }
1513 for t in &needed {
1514 let Nim::ConstPtr(inner) = t else { continue };
1515 let alias = ty::const_ptr_alias(inner);
1516 if !self.const_ptrs.insert(alias.clone()) {
1517 continue;
1518 }
1519 let c = ty::c_spelling(inner).ok_or_else(|| {
1520 format!(
1521 "`*const {}` has no C spelling we know, so a const-qualified \
1522 declaration cannot be emitted for it",
1523 inner.render()
1524 )
1525 })?;
1526 self.line(&format!(
1527 "type {}* {{.importc: \"const {} *\", nodecl.}} = distinct pointer",
1528 alias, c
1529 ));
1530 }
1531
1532 for it in &f.items {
1533 match it {
1534 syn::ForeignItem::Fn(fi) => {
1535 if fi.sig.variadic.is_some() {
1536 return Err(format!(
1537 "`{}` is variadic; Nim needs `varargs` with a fixed \
1538 calling shape, which this does not give us",
1539 fi.sig.ident
1540 ));
1541 }
1542 let name = fi.sig.ident.to_string();
1543 let head = self.head_of(&name, &fi.sig, None)?;
1544 // `importc` names the C symbol, so the Nim name may differ
1545 // from it without changing what is linked.
1546 self.line(&format!(
1547 "{} {{.importc: \"{}\", cdecl.}}",
1548 head, name
1549 ));
1550 let (params, ret) = self.signature(&fi.sig)?;
1551 self.fns.insert(
1552 (self.cur_mod.clone(), name.clone()),
1553 Sig { params, ret, generics: Vec::new() },
1554 );
1555 self.foreign.insert(name);
1556 }
1557 syn::ForeignItem::Static(st) => {
1558 let t = self.map_ty(&st.ty)?;
1559 let name = st.ident.to_string();
1560 self.line(&format!(
1561 "var {}* {{.importc: \"{}\".}}: {}",
1562 ident(&name),
1563 name,
1564 t.render()
1565 ));
1566 self.bind(&name, t);
1567 }
1568 syn::ForeignItem::Type(_) => {
1569 // An opaque C type: Nim spells it as a distinct object.
1570 continue;
1571 }
1572 _ => return Err("unsupported item in an `extern` block".into()),
1573 }
1574 }
1575 self.blank();
1576 Ok(())
1577 }
1578
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago1579 /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant
1580 /// namespace, so it becomes a module-level const named for both.
1581 fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> {
1582 let t = self.map_ty(&c.ty)?.owned();
1583 let v = self.expr_at(&c.expr, Some(&t))?;
1584 let name = format!("{}_{}", tyname, c.ident);
1585 self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));
1586 self.blank();
1587 self.assoc_consts
1588 .insert((tyname.to_string(), c.ident.to_string()), (ident(&name), t));
1589 Ok(())
1590 }
1591
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago1592 /// The type an operator impl declares for its right-hand operand.
1593 fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> {
1594 let n = type_name(t.as_ref()?);
1595 let sig = self.methods.get(&(n, op_method(op).to_string()))?;
1596 sig.params.get(1).cloned().map(|t| t.unvar())
1597 }
1598
1599 /// The proc implementing `op` for a user type, if there is one.
1600 fn op_proc(&self, t: &Option<Nim>, op: &str) -> Option<String> {
1601 let n = type_name(t.as_ref()?);
1602 let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0;
1603 if self.op_impls.contains_key(&(n.clone(), op.to_string())) {
1604 Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1))
1605 } else {
1606 None
1607 }
1608 }
1609
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 20h ago1610 /// Register a `bitflags!` type's operations so call sites resolve.
1611 fn collect_bitflags(&mut self, mac: &syn::Macro) -> Result<(), String> {
1612 let input: crate::macros::BitflagsInput = mac
1613 .parse_body()
1614 .map_err(|e| format!("`bitflags!`: {e}"))?;
1615 for def in &input.0 {
1616 let name = def.name.to_string();
1617 let repr = self.map_ty(&def.repr)?;
1618 if !repr.is_integer() {
1619 return Err(format!("`bitflags! {name}` needs an integer representation"));
1620 }
1621 let me = Nim::Named(name.clone(), vec![]);
1622 let b = Nim::Prim("bool".into());
1623 self.structs
1624 .insert(name.clone(), vec![("bitsField".into(), repr.clone())]);
1625 self.type_generics.insert(name.clone(), Vec::new());
1626
1627 let mut m = |n: &str, params: Vec<Nim>, ret: Nim, nim: String| {
1628 self.methods.insert(
1629 (name.clone(), n.to_string()),
1630 Sig { params, ret, generics: Vec::new() },
1631 );
1632 self.statics.insert((name.clone(), n.to_string()), nim);
1633 };
1634 let s1 = vec![me.clone()];
1635 let s2 = vec![me.clone(), me.clone()];
1636 let vs2 = vec![Nim::Var(Box::new(me.clone())), me.clone()];
1637 m("bits", s1.clone(), repr.clone(), format!("{name}_bits"));
1638 m("is_empty", s1.clone(), b.clone(), format!("{name}_is_empty"));
1639 m("is_all", s1.clone(), b.clone(), format!("{name}_is_all"));
1640 m("contains", s2.clone(), b.clone(), format!("{name}_contains"));
1641 m("intersects", s2.clone(), b.clone(), format!("{name}_intersects"));
1642 for (rust, nim) in [
1643 ("union", "union"),
1644 ("intersection", "intersection"),
1645 ("difference", "difference"),
1646 ("symmetric_difference", "symmetric_difference"),
1647 ] {
1648 m(rust, s2.clone(), me.clone(), format!("{name}_{nim}"));
1649 }
1650 for n in ["insert", "remove", "toggle"] {
1651 m(n, vs2.clone(), Nim::Unit, format!("{name}_{n}"));
1652 }
1653 m(
1654 "set",
1655 vec![Nim::Var(Box::new(me.clone())), me.clone(), b.clone()],
1656 Nim::Unit,
1657 format!("{name}_set"),
1658 );
1659 m("empty", vec![], me.clone(), format!("{name}_empty"));
1660 m("all", vec![], me.clone(), format!("{name}_all"));
1661 m(
1662 "from_bits",
1663 vec![repr.clone()],
1664 Nim::Named("Option".into(), vec![me.clone()]),
1665 format!("{name}_from_bits"),
1666 );
1667 m(
1668 "from_bits_truncate",
1669 vec![repr.clone()],
1670 me.clone(),
1671 format!("{name}_from_bits_truncate"),
1672 );
1673 m("complement", s1.clone(), me.clone(), format!("{name}_complement"));
1674
1675 // The operator forms, routed through the same dispatch that a
1676 // hand-written `impl BitOr` would use.
1677 for (op, trait_name, method) in [
1678 ("|", "BitOr", "bitor"),
1679 ("&", "BitAnd", "bitand"),
1680 ("^", "BitXor", "bitxor"),
1681 ("-", "Sub", "sub"),
1682 ("not", "Not", "not"),
1683 ] {
1684 self.op_impls.insert((name.clone(), op.to_string()), ());
1685 let params = if op == "not" { s1.clone() } else { s2.clone() };
1686 self.methods.insert(
1687 (name.clone(), method.to_string()),
1688 Sig { params, ret: me.clone(), generics: Vec::new() },
1689 );
1690 let _ = trait_name;
1691 }
1692 self.bitflags.insert(name);
1693 }
1694 Ok(())
1695 }
1696
1697 /// Emit the Nim for a `bitflags!` type. See `src/macros.rs` for why this
1698 /// is lowered directly rather than by expanding the macro.
1699 fn emit_bitflags(&mut self, mac: &syn::Macro) -> Result<(), String> {
1700 let input: crate::macros::BitflagsInput = mac
1701 .parse_body()
1702 .map_err(|e| format!("`bitflags!`: {e}"))?;
1703 for def in &input.0 {
1704 let name = def.name.to_string();
1705 let repr = self.map_ty(&def.repr)?;
1706 let r = repr.render();
1707
1708 self.line(&format!("type {name}* = object"));
1709 self.line(&format!(" bitsField*: {r}"));
1710 self.blank();
1711 self.line(&format!("proc {name}_bits*(x: {name}): {r} = x.bitsField"));
1712
1713 // The constants. A flag's value may name earlier flags, as
1714 // `const ALL = Self::READ.bits() | ..` does, so they are emitted
1715 // in order and each is in scope for the next.
1716 self.push_scope();
1717 self.bind_static_type(&name, &repr);
1718 for (fname, value) in &def.flags {
1719 let v = self.expr_at(value, Some(&repr))?;
1720 self.line(&format!(
1721 "const {}{}* = {}(bitsField: {})",
1722 name, fname, name, v.code
1723 ));
1724 self.flag_consts
1725 .insert((name.clone(), fname.to_string()), format!("{name}{fname}"));
1726 }
1727 self.pop_scope();
1728
1729 let all: Vec<String> = def
1730 .flags
1731 .iter()
1732 .map(|(f, _)| format!("{name}{f}.bitsField"))
1733 .collect();
1734 let all_bits = if all.is_empty() {
1735 format!("{}(0)", r)
1736 } else {
1737 all.join(" or ")
1738 };
1739 self.blank();
1740 self.line(&format!("const {name}AllBits: {r} = {all_bits}"));
1741 self.blank();
1742
1743 for l in [
1744 format!("proc {name}_empty*(): {name} = {name}(bitsField: {r}(0))"),
1745 format!("proc {name}_all*(): {name} = {name}(bitsField: {name}AllBits)"),
1746 format!("proc {name}_is_empty*(x: {name}): bool = x.bitsField == {r}(0)"),
1747 format!("proc {name}_is_all*(x: {name}): bool = (x.bitsField and {name}AllBits) == {name}AllBits"),
1748 format!("proc {name}_contains*(a, b: {name}): bool = (a.bitsField and b.bitsField) == b.bitsField"),
1749 format!("proc {name}_intersects*(a, b: {name}): bool = (a.bitsField and b.bitsField) != {r}(0)"),
1750 format!("proc {name}_union*(a, b: {name}): {name} = {name}(bitsField: a.bitsField or b.bitsField)"),
1751 format!("proc {name}_intersection*(a, b: {name}): {name} = {name}(bitsField: a.bitsField and b.bitsField)"),
1752 format!("proc {name}_difference*(a, b: {name}): {name} = {name}(bitsField: a.bitsField and (not b.bitsField))"),
1753 format!("proc {name}_symmetric_difference*(a, b: {name}): {name} = {name}(bitsField: a.bitsField xor b.bitsField)"),
1754 // `!x` complements and then masks to `all()`, which is what
1755 // bitflags does and not what a plain `not` would give.
1756 format!("proc {name}_complement*(x: {name}): {name} = {name}(bitsField: (not x.bitsField) and {name}AllBits)"),
1757 format!("proc {name}_from_bits_truncate*(b: {r}): {name} = {name}(bitsField: b and {name}AllBits)"),
1758 format!("proc {name}_from_bits*(b: {r}): Option[{name}] ="),
1759 format!(" if (b and (not {name}AllBits)) != {r}(0): rsNone[{name}]() else: rsSome({name}(bitsField: b))"),
1760 format!("proc {name}_insert*(x: var {name}, o: {name}) = x.bitsField = x.bitsField or o.bitsField"),
1761 format!("proc {name}_remove*(x: var {name}, o: {name}) = x.bitsField = x.bitsField and (not o.bitsField)"),
1762 format!("proc {name}_toggle*(x: var {name}, o: {name}) = x.bitsField = x.bitsField xor o.bitsField"),
1763 format!("proc {name}_set*(x: var {name}, o: {name}, on: bool) ="),
1764 format!(" if on: {name}_insert(x, o) else: {name}_remove(x, o)"),
1765 format!("proc rsBitOr_{name}_bitor*(a, b: {name}): {name} = {name}_union(a, b)"),
1766 format!("proc rsBitAnd_{name}_bitand*(a, b: {name}): {name} = {name}_intersection(a, b)"),
1767 format!("proc rsBitXor_{name}_bitxor*(a, b: {name}): {name} = {name}_symmetric_difference(a, b)"),
1768 format!("proc rsSub_{name}_sub*(a, b: {name}): {name} = {name}_difference(a, b)"),
1769 format!("proc rsNot_{name}_not*(a: {name}): {name} = {name}_complement(a)"),
1770 ] {
1771 self.line(&l);
1772 }
1773
1774 // Debug prints the set flag names, or `0x0` when empty -- again
1775 // matching the crate rather than a guess.
1776 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1777 self.line(&format!(" result = \"{name}(\""));
1778 self.line(" var first = true");
1779 for (fname, _) in &def.flags {
1780 self.line(&format!(
1781 " if (x.bitsField and {name}{f}.bitsField) == {name}{f}.bitsField and {name}{f}.bitsField != {r}(0):",
1782 f = fname
1783 ));
1784 self.line(" if not first: result.add(\" | \")");
1785 self.line(&format!(" result.add(\"{fname}\")"));
1786 self.line(" first = false");
1787 }
1788 self.line(" if first: result.add(\"0x0\")");
1789 self.line(" result.add(\")\")");
1790 self.blank();
1791 }
1792 Ok(())
1793 }
1794
1795 fn bind_static_type(&mut self, _name: &str, _repr: &Nim) {}
1796
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1797 fn emit_enum(&mut self, def: &EnumDef) {
1798 let name = ident(&def.name);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1799 let g = Self::gen_list(
1800 self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]),
1801 );
1802 if def.simple && !g.is_empty() {
1803 // A Nim `enum` cannot take parameters; an all-unit generic enum
1804 // has no payload to be generic in anyway, so this would be a
1805 // parameter that never appears.
1806 // Fall through to the object-variant form instead.
1807 }
1808 if def.simple && g.is_empty() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1809 // Every variant is a unit variant, so a plain Nim enum is an exact
1810 // fit: it compares, orders and `case`-checks like Rust's.
1811 self.line(&format!("type {name}* = enum"));
1812 self.indent += 1;
1813 for v in &def.variants {
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 20h ago1814 match &v.discriminant {
1815 Some(d) => self.line(&format!("{} = {}", ident(&v.name), d)),
1816 None => self.line(&ident(&v.name)),
1817 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1818 }
1819 self.indent -= 1;
1820 self.blank();
1821 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1822 self.indent += 1;
1823 self.line("case x");
1824 for v in &def.variants {
1825 self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
1826 }
1827 self.indent -= 1;
1828 self.blank();
1829 return;
1830 }
1831
1832 // A data-carrying enum is a Nim object variant: one discriminant enum
1833 // plus a branch per variant. This is the same shape the prelude uses
1834 // for `Option` and `Result`.
1835 self.line("type");
1836 self.indent += 1;
1837 self.line(&format!("{}Kind* = enum", name));
1838 self.indent += 1;
1839 for v in &def.variants {
1840 self.line(&def.kind_ident(&v.name));
1841 }
1842 self.indent -= 1;
1843 self.blank();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1844 self.line(&format!("{}*{} = object", name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1845 self.indent += 1;
1846 self.line(&format!("case kind*: {}Kind", name));
1847 for v in &def.variants {
1848 if v.fields.is_empty() {
1849 self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
1850 } else {
1851 self.line(&format!("of {}:", def.kind_ident(&v.name)));
1852 self.indent += 1;
1853 for (f, t) in &v.fields {
1854 self.line(&format!("{}*: {}", ident(f), t.render()));
1855 }
1856 self.indent -= 1;
1857 }
1858 }
1859 self.indent -= 2;
1860 self.blank();
1861
1862 for v in &def.variants {
1863 let args: Vec<String> = v
1864 .fields
1865 .iter()
1866 .enumerate()
1867 .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
1868 .collect();
1869 let inits: Vec<String> = v
1870 .fields
1871 .iter()
1872 .enumerate()
1873 .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
1874 .collect();
1875 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
1876 all.extend(inits);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1877 let ret = format!("{}{}", name, g);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1878 self.line(&format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1879 "proc {}*{}({}): {} = {}({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1880 def.ctor_ident(&v.name),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1881 g,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1882 args.join(", "),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1883 ret,
1884 ret,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1885 all.join(", ")
1886 ));
1887 }
1888 self.blank();
1889
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1890 self.line(&format!("proc rsDebug*{}(x: {}{}): string =", g, name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1891 self.indent += 1;
1892 self.line("case x.kind");
1893 for v in &def.variants {
1894 if v.fields.is_empty() {
1895 self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
1896 } else {
1897 let parts: Vec<String> = v
1898 .fields
1899 .iter()
1900 .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
1901 .collect();
1902 self.line(&format!(
1903 "of {}: \"{}(\" & {} & \")\"",
1904 def.kind_ident(&v.name),
1905 v.name,
1906 parts.join(" & \", \" & ")
1907 ));
1908 }
1909 }
1910 self.indent -= 1;
1911 self.blank();
1912 }
1913
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago1914 /// The concrete type an enum variant constructs, and the `[T]` list to
1915 /// spell at the constructor when the enum is generic.
1916 fn variant_type(
1917 &self,
1918 def: &EnumDef,
1919 expect: Option<&Nim>,
1920 ) -> Result<(Nim, String), String> {
1921 let params = self.type_generics.get(&def.name).cloned().unwrap_or_default();
1922 if params.is_empty() {
1923 return Ok((Nim::Named(def.name.clone(), vec![]), String::new()));
1924 }
1925 match expect {
1926 Some(Nim::Named(n, a)) if *n == def.name && a.len() == params.len() => Ok((
1927 Nim::Named(def.name.clone(), a.clone()),
1928 format!("[{}]", a.iter().map(|t| t.render()).collect::<Vec<_>>().join(", ")),
1929 )),
1930 _ => Err(format!(
1931 "`{}` is a variant of a generic enum, and its type parameters \
1932 cannot be inferred here; annotate the binding or the return type",
1933 def.name
1934 )),
1935 }
1936 }
1937
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago1938 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
1939 /// to the enum that declares it.
1940 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
1941 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1942 let last = segs.last()?.clone();
1943 if segs.len() >= 2 {
1944 if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
1945 if def.get(&last).is_some() {
1946 return Some((def.clone(), last));
1947 }
1948 }
1949 }
1950 // Unqualified: only unambiguous if exactly one enum declares it.
1951 match self.variant_owner.get(&last) {
1952 Some(owners) if owners.len() == 1 => {
1953 let def = self.enums.get(&owners[0])?;
1954 Some((def.clone(), last))
1955 }
1956 _ => None,
1957 }
1958 }
1959
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago1960 /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
1961 ///
1962 /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
1963 /// observable result of `{}` is exactly the bytes written. So the method
1964 /// becomes `proc rsDisplay(self: T): string` and every write through the
1965 /// formatter produces that string. A `fmt` body that does anything else
1966 /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
1967 /// because those affect the output and this model does not carry them.
1968 /// The window an expression names, if it names one.
1969 fn window_of(&self, e: &Expr) -> Option<Alias> {
1970 match e {
1971 Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
1972 Some(a @ Alias::Window { .. }) => Some(a),
1973 _ => None,
1974 },
1975 Expr::Reference(r) => self.window_of(&r.expr),
1976 Expr::Paren(p) => self.window_of(&p.expr),
1977 Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
1978 _ => None,
1979 }
1980 }
1981
1982 /// Whether an expression is the `Formatter` parameter of the formatting
1983 /// impl currently being lowered.
1984 fn is_fmt_param(&self, e: &Expr) -> bool {
1985 let Some(f) = &self.fmt_param else { return false };
1986 match e {
1987 Expr::Path(p) => path_name(&p.path) == *f,
1988 Expr::Reference(r) => self.is_fmt_param(&r.expr),
1989 Expr::Paren(p) => self.is_fmt_param(&p.expr),
1990 _ => false,
1991 }
1992 }
1993
1994 fn fmt_impl(
1995 &mut self,
1996 tr: &str,
1997 self_ty: &Nim,
1998 sig: &syn::Signature,
1999 body: &syn::Block,
2000 ) -> Result<(), String> {
2001 let proc_name = fmt_proc(tr);
2002 // The formatter is the parameter after `self`.
2003 let f = sig
2004 .inputs
2005 .iter()
2006 .filter_map(|a| match a {
2007 FnArg::Typed(t) => match &*t.pat {
2008 Pat::Ident(i) => Some(i.ident.to_string()),
2009 _ => None,
2010 },
2011 _ => None,
2012 })
2013 .next()
2014 .ok_or("`fn fmt` needs a `Formatter` parameter")?;
2015
2016 self.push_scope();
2017 self.bind("self", self_ty.clone());
2018 let saved = self.fmt_param.replace(f);
2019 let outer_ret = self.ret.replace(Nim::Prim("string".into()));
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago2020 // No assignment target: a formatter write *appends*, because a `fmt`
2021 // body may write repeatedly -- `UpperHex` writes once per byte in a
2022 // loop -- and assigning would keep only the last one.
2023 let outer_target = self.target.take();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2024
2025 self.line(&format!(
2026 "proc {}*(self: {}): string =",
2027 proc_name,
2028 self_ty.render()
2029 ));
2030 self.indent += 1;
2031 let before = self.out.len();
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago2032 let tail = self.block_body(body)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2033 self.emit_tail(tail);
2034 if self.out.len() == before {
2035 self.line("discard");
2036 }
2037 self.indent -= 1;
2038
2039 self.target = outer_target;
2040 self.ret = outer_ret;
2041 self.fmt_param = saved;
2042 self.pop_scope();
2043 self.blank();
2044 Ok(())
2045 }
2046
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2047 fn func(
2048 &mut self,
2049 sig: &syn::Signature,
2050 body: &syn::Block,
2051 recv: Option<Nim>,
2052 ) -> Result<(), String> {
2053 let name = sig.ident.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2054 self.func_named(&name.clone(), sig, body, recv)
2055 }
2056
2057 fn func_named(
2058 &mut self,
2059 name: &str,
2060 sig: &syn::Signature,
2061 body: &syn::Block,
2062 recv: Option<Nim>,
2063 ) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2064 let (ptys, ret) = self.signature(sig)?;
2065
2066 self.push_scope();
2067 let mut rendered: Vec<String> = Vec::new();
2068
2069 if let Some(self_ty) = recv {
2070 // `&mut self` and `mut self` both mean the body may mutate the
2071 // receiver; only the former is observable by the caller, and a Nim
2072 // `var` parameter is the faithful spelling of that.
2073 let mutable = matches!(
2074 sig.inputs.first(),
2075 Some(FnArg::Receiver(r))
2076 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
2077 );
2078 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
2079 rendered.push(format!("self: {}", t.render()));
2080 self.bind("self", self_ty);
2081 }
2082
2083 let typed: Vec<&syn::PatType> = sig
2084 .inputs
2085 .iter()
2086 .filter_map(|a| match a {
2087 FnArg::Typed(t) => Some(t),
2088 _ => None,
2089 })
2090 .collect();
2091 for (p, t) in typed.iter().zip(ptys.iter()) {
2092 let pname = match &*p.pat {
2093 Pat::Ident(i) => i.ident.to_string(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2094 // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
2095 // still needs a name for it.
2096 Pat::Wild(_) => format!("unused{}", rendered.len()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2097 _ => return Err("only plain identifier parameters are supported".into()),
2098 };
2099 rendered.push(format!("{}: {}", ident(&pname), t.render()));
2100 // Inside the body a `var T` parameter is used exactly like a `T`.
2101 self.bind(&pname, t.clone().owned());
2102 }
2103
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago2104 let mut gparams = self.impl_generics.clone();
2105 for g in Self::generics_of(&sig.generics) {
2106 if !gparams.contains(&g) {
2107 gparams.push(g);
2108 }
2109 }
2110 let gens = Self::gen_list(&gparams);
2111 let outer_fg = std::mem::replace(&mut self.fn_generics, gparams.clone());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2112 let head = if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago2113 format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", "))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2114 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago2115 format!(
2116 "proc {}*{}({}): {} =",
2117 ident(name),
2118 gens,
2119 rendered.join(", "),
2120 ret.render()
2121 )
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2122 };
2123 self.line(&head);
2124 self.indent += 1;
2125 let outer_ret = self.ret.replace(ret.clone());
2126
2127 // A Rust fn's trailing expression is its return value. Naming Nim's
2128 // implicit `result` as the target makes that true whether the tail is
2129 // a plain expression or an `if`/`match` with statement arms.
2130 let outer_target = if ret == Nim::Unit {
2131 self.target.take()
2132 } else {
2133 self.target.replace(("result".to_string(), Some(ret.clone())))
2134 };
2135 let before = self.out.len();
2136 let tail = self.block_body_at(body, Some(&ret))?;
2137 self.target = outer_target;
2138 match tail {
2139 Some(v) if ret != Nim::Unit => {
2140 let code = v.code.clone();
2141 self.line(&format!("result = {code}"));
2142 }
2143 Some(v) => {
2144 // A trailing expression in a `()`-returning fn is evaluated for
2145 // its effect; Nim requires an explicit discard.
2146 let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2147 if needs_discard && !v.code.is_empty() {
2148 let code = v.code.clone();
2149 self.line(&format!("discard {code}"));
2150 }
2151 }
2152 None => {}
2153 }
2154 if self.out.len() == before {
2155 self.line("discard");
2156 }
2157
2158 self.indent -= 1;
2159 self.ret = outer_ret;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago2160 self.fn_generics = outer_fg;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2161 self.pop_scope();
2162 self.blank();
2163 Ok(())
2164 }
2165
2166 // ---------------------------------------------------------- statements
2167
2168 /// Lower a block's statements. Returns the block's trailing expression,
2169 /// if it has one, *without* emitting it — the caller decides whether that
2170 /// value is a return value, a binding, or discarded.
2171 fn block_body(&mut self, b: &syn::Block) -> Result<Option<Val>, String> {
2172 self.block_body_at(b, None)
2173 }
2174
2175 fn block_body_at(
2176 &mut self,
2177 b: &syn::Block,
2178 expect: Option<&Nim>,
2179 ) -> Result<Option<Val>, String> {
2180 // An assignment target belongs to *this* block's trailing expression
2181 // only. A non-final `if` is a statement and must not assign anything.
2182 let target = self.target.take();
2183 let n = b.stmts.len();
2184 let mut tail = None;
2185 for (i, st) in b.stmts.iter().enumerate() {
2186 let last = i + 1 == n;
2187 match st {
2188 Stmt::Expr(e, None) if last && expressible(e) => {
2189 tail = Some(self.expr_at(e, expect)?)
2190 }
2191 Stmt::Expr(e, None) if last => {
2192 // A trailing `if`/`match` with statement arms, or a loop.
2193 // Lower it as statements; if this block's value is wanted,
2194 // each arm assigns it.
2195 match &target {
2196 Some((t, ty)) => {
2197 let (t, ty) = (t.clone(), ty.clone());
2198 self.assign_from(e, &t, ty.as_ref())?;
2199 }
2200 None => self.stmt(st)?,
2201 }
2202 }
2203 _ => self.stmt(st)?,
2204 }
2205 }
2206 self.target = target;
2207 Ok(tail)
2208 }
2209
2210 /// Lower a block in statement position (loop bodies, `if` arms).
2211 fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> {
2212 self.push_scope();
2213 self.indent += 1;
2214 let before = self.out.len();
2215 let want = self.target.clone().and_then(|(_, t)| t);
2216 let tail = self.block_body_at(b, want.as_ref())?;
2217 self.emit_tail(tail);
2218 if self.out.len() == before {
2219 self.line("discard");
2220 }
2221 self.indent -= 1;
2222 self.pop_scope();
2223 Ok(())
2224 }
2225
2226 fn stmt(&mut self, s: &Stmt) -> Result<(), String> {
2227 match s {
2228 Stmt::Local(l) => self.local(l),
2229 Stmt::Expr(e, _) => {
2230 let v = self.expr_stmt(e)?;
2231 if let Some(v) = v {
2232 // A bare expression with a value must be discarded in Nim.
2233 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2234 let code = v.code.clone();
2235 if needs {
2236 self.line(&format!("discard {code}"));
2237 } else if !code.is_empty() {
2238 self.line(&code);
2239 }
2240 }
2241 Ok(())
2242 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago2243 // A `const` declared inside a function body is local to it, and
2244 // must be emitted here rather than skipped as an already-emitted
2245 // top-level type.
2246 Stmt::Item(i) => self.item_inner(i),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2247 Stmt::Macro(m) => {
2248 let line = self.macro_call(&m.mac)?;
2249 self.line(&line);
2250 Ok(())
2251 }
2252 }
2253 }
2254
2255 fn local(&mut self, l: &Local) -> Result<(), String> {
2256 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
2257 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
2258 Pat::Type(t) => match &*t.pat {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago2259 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), Some(self.map_ty(&t.ty)?)),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2260 _ => return Err("only `let <ident>` bindings are supported".into()),
2261 },
2262 Pat::Wild(_) => ("_".into(), false, None),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago2263 Pat::Tuple(t) => return self.local_tuple(l, t),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2264 _ => return Err("destructuring `let` is not implemented yet".into()),
2265 };
2266
2267 let Some(init) = &l.init else {
2268 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
2269 // not. Rust's own rules make reading it before assignment illegal,
2270 // so the two agree on every program rustc accepts.
2271 let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
2272 let t = t.owned();
2273 self.line(&format!("var {}: {}", ident(&name), t.render()));
2274 self.bind(&name, t);
2275 return Ok(());
2276 };
2277 if init.diverge.is_some() {
2278 return Err("`let ... else` is not implemented yet".into());
2279 }
2280
2281 if !expressible(&init.expr) && name != "_" {
2282 // The initialiser is an `if`/`match` whose arms are statements.
2283 // Declare first, then let each arm assign into the binding.
2284 let t = ann
2285 .clone()
2286 .ok_or_else(|| {
2287 format!(
2288 "`let {name} = match/if ...` needs a type annotation: \
2289 its arms are statements, so the binding must be \
2290 declared before they run"
2291 )
2292 })?
2293 .owned();
2294 self.line(&format!("var {}: {}", ident(&name), t.render()));
2295 self.bind(&name, t.clone());
2296 let target = ident(&name);
2297 return self.assign_from(&init.expr, &target, Some(&t));
2298 }
2299
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago2300 // `let it = xs.chunks_exact(k)` binds an iterator, not a value.
2301 if is_iterator_expr(&init.expr) {
2302 let it = self.resolve_iter(&init.expr)?;
2303 self.bind_alias(&name, Alias::Iterator(Box::new(it)));
2304 return Ok(());
2305 }
2306
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2307 let v = self.expr_at(&init.expr, ann.as_ref())?;
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 22h ago2308
2309 // `let s = &buf[..n]` binds a view of a place that is already in
2310 // scope. Nim's borrow checker will not let a `let` borrow out of a
2311 // local, and there is nothing to materialise anyway -- a view is a
2312 // reference. Binding it as an alias substitutes the same expression at
2313 // each use, which re-evaluates nothing because the initialiser is a
2314 // place expression with no side effects.
2315 if v.window.is_none()
2316 && matches!(v.ty, Some(Nim::OpenArray(_)))
2317 && is_pure_place(&init.expr)
2318 {
2319 let t = v.ty.clone().unwrap();
2320 let elem = match &t {
2321 Nim::OpenArray(e) => Some((**e).clone()),
2322 _ => None,
2323 };
2324 self.bind_alias(&name, Alias::Value { code: v.code.clone(), ty: Some(t) });
2325 let _ = elem;
2326 return Ok(());
2327 }
2328
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2329 if let Some(w) = v.window.clone() {
2330 // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
2331 // view into the caller's buffer. Copying it into a `seq` would
2332 // still print the right bytes but would stop writes reaching the
2333 // caller, so it is bound as an alias.
2334 if v.guard.is_some() && v.guard_err.is_some() {
2335 return Err(format!(
2336 "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
2337 which Nim cannot represent; apply `?` or `unwrap()` to it \
2338 in the same expression"
2339 ));
2340 }
2341 self.bind_alias(&name, w);
2342 return Ok(());
2343 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago2344 // A `let` binding a borrow keeps the view: `let res = encode(..)?`
2345 // names the caller's buffer, and copying it into a `seq` would still
2346 // print the right bytes while silently breaking the aliasing.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2347 let t = match (ann, &v.ty) {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago2348 (Some(a), _) => a.unvar(),
2349 (None, Some(t)) => t.clone().unvar(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2350 (None, None) => {
2351 return Err(format!(
2352 "cannot infer the type of `let {name}`; annotate it — \
2353 guessing here would change integer width, and with it the \
2354 meaning of any arithmetic on `{name}`"
2355 ))
2356 }
2357 };
2358
2359 if name == "_" {
2360 let code = v.code.clone();
2361 self.line(&format!("discard {code}"));
2362 return Ok(());
2363 }
2364 // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing
2365 // works in both, so a re-`let` of the same name needs no rename.
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 22h ago2366 //
2367 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:
2368 // Rust may write through it, and Nim only accepts a `var` where a
2369 // `var` parameter is wanted, so the binding has to be one.
2370 let mutable = mutable || is_mut_borrow(&init.expr);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2371 let kw = if mutable { "var" } else { "let" };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago2372 // Inside a generic proc the binding's type may mention a parameter Nim
2373 // will infer; naming it in an annotation would not resolve.
2374 let line = if self.mentions_type_param(&t) {
2375 format!("{} {} = {}", kw, ident(&name), v.code)
2376 } else {
2377 format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code)
2378 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2379 self.line(&line);
2380 self.bind(&name, t);
2381 Ok(())
2382 }
2383
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago2384 /// `let (a, b) = ..` — tuple destructuring.
2385 fn local_tuple(&mut self, l: &Local, t: &syn::PatTuple) -> Result<(), String> {
2386 let Some(init) = &l.init else {
2387 return Err("a destructuring `let` needs an initialiser".into());
2388 };
2389 let names: Vec<(String, bool)> = t
2390 .elems
2391 .iter()
2392 .map(|p| match p {
2393 Pat::Ident(i) => Ok((i.ident.to_string(), i.mutability.is_some())),
2394 Pat::Wild(_) => Ok(("_".to_string(), false)),
2395 _ => Err("only plain identifiers are supported in a destructuring `let`"),
2396 })
2397 .collect::<Result<_, _>>()?;
2398
2399 // `split_at` hands back two *views* of the same slice. Nim has no
2400 // tuple of views, and there is nothing to materialise anyway, so each
2401 // name becomes a window into the original.
2402 if let Expr::MethodCall(m) = &*init.expr {
2403 let mname = m.method.to_string();
2404 if (mname == "split_at" || mname == "split_at_mut")
2405 && m.args.len() == 1
2406 && names.len() == 2
2407 {
2408 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
2409 let at = self.expr(&m.args[0])?;
2410 let cut = self.fresh("Cut");
2411 self.line(&format!("let {}: int = int({})", cut, at.code));
2412 self.bind_alias(
2413 &names[0].0,
2414 Alias::Window {
2415 code: code.clone(),
2416 off: base.clone(),
2417 len: cut.clone(),
2418 elem: elem.clone(),
2419 },
2420 );
2421 self.bind_alias(
2422 &names[1].0,
2423 Alias::Window {
2424 code,
2425 off: format!("({} + {})", base, cut),
2426 len: format!("({} - {})", len, cut),
2427 elem,
2428 },
2429 );
2430 return Ok(());
2431 }
2432 }
2433
2434 let v = self.expr(&init.expr)?;
2435 let tys = match &v.ty {
2436 Some(Nim::Tuple(ts)) if ts.len() == names.len() => ts.clone(),
2437 _ => {
2438 return Err(format!(
2439 "cannot destructure this into {} bindings: its type is not a \
2440 tuple of that many elements",
2441 names.len()
2442 ))
2443 }
2444 };
2445 let kw = if names.iter().any(|(_, m)| *m) { "var" } else { "let" };
2446 let lhs: Vec<String> = names.iter().map(|(n, _)| ident(n)).collect();
2447 self.line(&format!("{} ({}) = {}", kw, lhs.join(", "), v.code));
2448 for ((n, _), t) in names.iter().zip(tys) {
2449 self.bind(n, t);
2450 }
2451 Ok(())
2452 }
2453
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2454 /// Expressions that are statements in Rust and statements in Nim too
2455 /// (control flow). Returns `None` when it emitted lines itself.
2456 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
2457 match e {
2458 Expr::If(_) => {
2459 self.if_stmt(e)?;
2460 Ok(None)
2461 }
2462 Expr::While(w) => {
2463 if w.label.is_some() {
2464 return Err("loop labels are not implemented yet".into());
2465 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago2466 self.in_loop_cond = true;
2467 let c = self.expr(&w.cond);
2468 self.in_loop_cond = false;
2469 let c = c?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2470 self.line(&format!("while {}:", c.code));
2471 let saved = self.target.take();
2472 self.nested_block(&w.body)?;
2473 self.target = saved;
2474 Ok(None)
2475 }
2476 Expr::Loop(l) => {
2477 if l.label.is_some() {
2478 return Err("loop labels are not implemented yet".into());
2479 }
2480 self.line("while true:");
2481 let saved = self.target.take();
2482 self.nested_block(&l.body)?;
2483 self.target = saved;
2484 Ok(None)
2485 }
2486 Expr::ForLoop(f) => {
2487 self.for_loop(f)?;
2488 Ok(None)
2489 }
2490 Expr::Block(b) => {
2491 if b.label.is_some() {
2492 return Err("block labels are not implemented yet".into());
2493 }
2494 self.line("block:");
2495 self.nested_block(&b.block)?;
2496 Ok(None)
2497 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2498 Expr::Unsafe(u) => {
2499 // Transparent in statement position too, for the same reason.
2500 self.nested_block_flat(&u.block)?;
2501 Ok(None)
2502 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2503 Expr::Match(_) => {
2504 self.match_stmt(e)?;
2505 Ok(None)
2506 }
2507 Expr::Return(r) => {
2508 match &r.expr {
2509 Some(e) => {
2510 let want = self.ret.clone();
2511 let v = self.expr_at(e, want.as_ref())?;
2512 self.line(&format!("return {}", v.code));
2513 }
2514 None => self.line("return"),
2515 }
2516 Ok(None)
2517 }
2518 Expr::Break(b) => {
2519 if b.expr.is_some() || b.label.is_some() {
2520 return Err("`break` with a value or a label is not implemented yet".into());
2521 }
2522 self.line("break");
2523 Ok(None)
2524 }
2525 Expr::Continue(c) => {
2526 if c.label.is_some() {
2527 return Err("labelled `continue` is not implemented yet".into());
2528 }
2529 self.line("continue");
2530 Ok(None)
2531 }
2532 Expr::Assign(a) => {
2533 let lhs = self.expr(&a.left)?;
2534 if !expressible(&a.right) {
2535 let target = lhs.code.clone();
2536 return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None);
2537 }
2538 let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?;
2539 self.line(&format!("{} = {}", lhs.code, rhs.code));
2540 Ok(None)
2541 }
2542 Expr::Binary(b) if is_compound(&b.op) => {
2543 let lhs = self.expr(&b.left)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago2544 // A compound assignment on a user type goes to that type's own
2545 // `impl OpAssign`, not to Nim's built-in operator.
2546 if let Some(f) = self.op_proc(&lhs.ty, compound_symbol(&b.op)) {
2547 // The impl's own parameter type types the right operand,
2548 // so `b_vec *= 4` takes 4 at the width the impl declares.
2549 let want = self.op_param(&lhs.ty, compound_symbol(&b.op));
2550 let rhs = self.expr_at(&b.right, want.as_ref())?;
2551 self.line(&format!("{}({}, {})", f, lhs.code, rhs.code));
2552 return Ok(None);
2553 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2554 // `i += 1` must widen the literal to `i`'s type, not to the
2555 // i32 an unconstrained Rust literal would default to.
2556 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
2557 let op = self.bin_op(&b.op, &lhs, &rhs)?;
2558 // Nim has no `shl=` etc., and `+=` on a `let` is illegal in
2559 // both languages, so the expanded form is always correct.
2560 self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code));
2561 Ok(None)
2562 }
2563 Expr::Macro(m) => {
2564 let line = self.macro_call(&m.mac)?;
2565 self.line(&line);
2566 Ok(None)
2567 }
2568 _ => Ok(Some(self.expr(e)?)),
2569 }
2570 }
2571
2572 /// Lower `e` in statement position, assigning each arm's value to
2573 /// `target`. This is how Rust's expression-oriented `if`/`match` survive
2574 /// the trip when their arms are too big for a Nim `if`-expression.
2575 fn assign_from(
2576 &mut self,
2577 e: &Expr,
2578 target: &str,
2579 expect: Option<&Nim>,
2580 ) -> Result<(), String> {
2581 let saved = self.target.replace((target.to_string(), expect.cloned()));
2582 let r = match e {
2583 Expr::If(_) => self.if_stmt(e),
2584 Expr::Match(_) => self.match_stmt(e),
2585 other => {
2586 let v = self.expr_at(other, expect)?;
2587 self.line(&format!("{} = {}", target, v.code));
2588 Ok(())
2589 }
2590 };
2591 self.target = saved;
2592 r
2593 }
2594
2595 /// Emit a block's value into the active assignment target, if there is
2596 /// one, or discard it if there is not.
2597 fn emit_tail(&mut self, v: Option<Val>) {
2598 let Some(v) = v else { return };
2599 match self.target.clone() {
2600 Some((t, _)) => {
2601 let code = v.code.clone();
2602 self.line(&format!("{t} = {code}"));
2603 }
2604 None => {
2605 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2606 let code = v.code.clone();
2607 if needs {
2608 self.line(&format!("discard {code}"));
2609 } else if !code.is_empty() {
2610 self.line(&code);
2611 }
2612 }
2613 }
2614 }
2615
2616 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
2617 let Expr::If(i) = e else { unreachable!() };
2618 if let Expr::Let(_) = &*i.cond {
2619 return Err("`if let` is not implemented yet".into());
2620 }
2621 let c = self.expr(&i.cond)?;
2622 self.line(&format!("if {}:", c.code));
2623 self.nested_block(&i.then_branch)?;
2624 match &i.else_branch {
2625 None => {}
2626 Some((_, els)) => match &**els {
2627 Expr::If(_) => {
2628 // Nim needs `elif`; splice the nested `if` in as one.
2629 let mark = self.out.len();
2630 self.if_stmt(els)?;
2631 let tail = self.out.split_off(mark);
2632 let indent = " ".repeat(self.indent);
2633 self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1));
2634 }
2635 Expr::Block(b) => {
2636 self.line("else:");
2637 self.nested_block(&b.block)?;
2638 }
2639 _ => return Err("unsupported `else` form".into()),
2640 },
2641 }
2642 Ok(())
2643 }
2644
2645 fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> {
2646 if f.label.is_some() {
2647 return Err("loop labels are not implemented yet".into());
2648 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2649 let it = self.resolve_iter(&f.expr)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2650
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2651 // One index loop drives the whole chain. Rust's adaptors are lazy and
2652 // compose; resolving them to an index and binding each name to an
2653 // lvalue reproduces that without materialising anything.
2654 let i = self.fresh("Idx");
2655 self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
2656 self.indent += 1;
2657 self.push_scope();
2658 let before = self.out.len();
2659
2660 self.bind_pattern(&f.pat, &it, &i)?;
2661
2662 let saved = self.target.take();
2663 if let Some(v) = self.block_body(&f.body)? {
2664 let code = v.code.clone();
2665 self.line(&format!("discard {code}"));
2666 }
2667 self.target = saved;
2668 if self.out.len() == before {
2669 self.line("discard");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2670 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2671 self.pop_scope();
2672 self.indent -= 1;
2673 Ok(())
2674 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2675
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2676 /// Resolve a chain of iterator adaptors into a single `Iter`.
2677 ///
2678 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
2679 /// `filter`, `take_while` and friends are rejected rather than partially
2680 /// honoured: silently dropping an adaptor would change which elements the
2681 /// loop visits.
2682 fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
2683 match e {
2684 Expr::Reference(r) => self.resolve_iter(&r.expr),
2685 Expr::Paren(p) => self.resolve_iter(&p.expr),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2686 Expr::Range(r) => {
2687 let lo = match &r.start {
2688 Some(e) => self.expr(e)?,
2689 None => return Err("a `for` over `..n` needs a start bound".into()),
2690 };
2691 let hi = match &r.end {
2692 Some(e) => self.expr(e)?,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2693 None => {
2694 return Err("a `for` over an unbounded range would not terminate".into())
2695 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2696 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2697 let ty = lo.ty.clone().or(hi.ty.clone());
2698 Ok(Iter::Range {
2699 lo: lo.code,
2700 hi: hi.code,
2701 closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
2702 ty,
2703 })
2704 }
2705 Expr::MethodCall(m) => {
2706 let name = m.method.to_string();
2707 match name.as_str() {
2708 "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
2709 let mut it = self.resolve_iter(&m.receiver)?;
2710 if name == "iter_mut" {
2711 if let Iter::Elems { mutable, .. } = &mut it {
2712 *mutable = true;
2713 }
2714 }
2715 Ok(it)
2716 }
2717 "enumerate" if m.args.is_empty() => {
2718 Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
2719 }
2720 "zip" if m.args.len() == 1 => {
2721 let a = self.resolve_iter(&m.receiver)?;
2722 let b = self.resolve_iter(&m.args[0])?;
2723 Ok(Iter::Zip(Box::new(a), Box::new(b)))
2724 }
2725 "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2726 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2727 let k = self.expr(&m.args[0])?;
2728 Ok(Iter::Chunks {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2729 code,
2730 base,
2731 len,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2732 k: k.code,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2733 elem,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2734 mutable: name.ends_with("_mut"),
2735 })
2736 }
2737 "windows" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2738 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2739 let k = self.expr(&m.args[0])?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2740 Ok(Iter::Windows { code, base, len, k: k.code, elem })
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2741 }
2742 other => Err(format!(
2743 "iterator adaptor `.{other}()` is not implemented; it has \
2744 no index-loop equivalent here, and dropping it would \
2745 change which elements the loop visits"
2746 )),
2747 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2748 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago2749 Expr::Path(p) => {
2750 let n = path_name(&p.path);
2751 if let Some(Alias::Iterator(it)) = self.lookup_alias(&n) {
2752 return Ok((*it).clone());
2753 }
2754 if let Some(Alias::Window { code, off, len, elem }) = self.lookup_alias(&n) {
2755 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
2756 }
2757 let v = self.expr(e)?;
2758 Ok(Iter::Elems {
2759 len: format!("{}.len", v.code),
2760 elem: elem_of(&v.ty),
2761 code: v.code,
2762 off: "0".into(),
2763 mutable: false,
2764 })
2765 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2766 other => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2767 // A `for` binding that is itself a window iterates that window,
2768 // not the whole container it points into.
2769 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2770 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2771 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2772 let v = self.expr(other)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2773 Ok(Iter::Elems {
2774 len: format!("{}.len", v.code),
2775 elem: elem_of(&v.ty),
2776 code: v.code,
2777 off: "0".into(),
2778 mutable: false,
2779 })
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2780 }
2781 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2782 }
2783
2784 /// Bind a `for` pattern against a resolved iterator at index `i`.
2785 fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
2786 match (p, it) {
2787 (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
2788 self.bind_pattern(&t.elems[0], a, i)?;
2789 self.bind_pattern(&t.elems[1], b, i)
2790 }
2791 (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
2792 if let Pat::Ident(id) = &t.elems[0] {
2793 let n = id.ident.to_string();
2794 // Rust's `enumerate` counts in `usize`.
2795 self.line(&format!("let {}: uint = uint({})", ident(&n), i));
2796 self.bind(&n, Nim::Prim("uint".into()));
2797 }
2798 self.bind_pattern(&t.elems[1], inner, i)
2799 }
2800 (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
2801 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
2802 ),
2803 (Pat::Wild(_), _) => Ok(()),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2804 // `for &byte in xs` — the `&` destructures the reference, which in
2805 // Nim is already the value.
2806 (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i),
2807 (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2808 (Pat::Ident(id), _) => {
2809 let name = id.ident.to_string();
2810 match it {
2811 Iter::Range { lo, ty, .. } => {
2812 let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
2813 // The loop counts from zero; the range's own start is
2814 // added back so the binding has Rust's value and type.
2815 self.line(&format!(
2816 "let {}: {} = {}({}) + {}",
2817 ident(&name),
2818 t.render(),
2819 t.render(),
2820 i,
2821 lo
2822 ));
2823 self.bind(&name, t);
2824 Ok(())
2825 }
2826 Iter::Elems { code, off, elem, mutable, .. } => {
2827 let access = if off == "0" {
2828 format!("{}[{}]", code, i)
2829 } else {
2830 format!("{}[{} + {}]", code, off, i)
2831 };
2832 if *mutable {
2833 // An alias, not a copy: assigning through the
2834 // binding must reach the original element.
2835 self.bind_alias(
2836 &name,
2837 Alias::Value { code: access, ty: elem.clone() },
2838 );
2839 } else {
2840 let t = elem
2841 .clone()
2842 .ok_or("cannot infer the element type of this `for`")?;
2843 self.line(&format!(
2844 "let {}: {} = {}",
2845 ident(&name),
2846 t.render(),
2847 access
2848 ));
2849 self.bind(&name, t);
2850 }
2851 Ok(())
2852 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2853 Iter::Chunks { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2854 self.bind_alias(
2855 &name,
2856 Alias::Window {
2857 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2858 off: format!("({} + {} * int({}))", base, i, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2859 len: format!("int({})", k),
2860 elem: elem.clone(),
2861 },
2862 );
2863 Ok(())
2864 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2865 Iter::Windows { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2866 self.bind_alias(
2867 &name,
2868 Alias::Window {
2869 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago2870 off: format!("({} + {})", base, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago2871 len: format!("int({})", k),
2872 elem: elem.clone(),
2873 },
2874 );
2875 Ok(())
2876 }
2877 // Handled above: a zip or enumerate needs a tuple pattern,
2878 // and binding one name to the pair is not supported.
2879 Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
2880 }
2881 }
2882 _ => Err("unsupported `for` pattern".into()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2883 }
2884 }
2885
2886 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
2887 let Expr::Match(m) = e else { unreachable!() };
2888 let scrut = self.expr(&m.expr)?;
2889 let t = scrut
2890 .ty
2891 .clone()
2892 .ok_or("cannot infer the type of a `match` scrutinee")?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago2893 let name = self.fresh("Match");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2894 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago2895
2896 // A `match` whose arms neither bind nor guard is a Nim `case`, which
2897 // is exhaustiveness-checked the way Rust's is. Anything richer becomes
2898 // an if/elif chain, because Nim's `case` cannot destructure.
2899 let plain = m.arms.iter().all(|a| {
2900 !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
2901 });
2902 if plain {
2903 self.match_case(m, &name, &t)
2904 } else {
2905 self.match_chain(m, &name, &t)
2906 }
2907 }
2908
2909 fn match_case(
2910 &mut self,
2911 m: &syn::ExprMatch,
2912 name: &str,
2913 t: &Nim,
2914 ) -> Result<(), String> {
2915 // A variant object is discriminated by its `kind` field.
2916 let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
2917 self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" }));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2918
2919 let mut saw_wild = false;
2920 for arm in &m.arms {
2921 match &arm.pat {
2922 Pat::Wild(_) => {
2923 saw_wild = true;
2924 self.line("else:");
2925 }
2926 p => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago2927 let labels = self.pat_labels(p, Some(t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2928 self.line(&format!("of {}:", labels.join(", ")));
2929 }
2930 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago2931 self.arm_body(&arm.body)?;
2932 }
2933 if !saw_wild && !self.case_is_total(t, m) {
2934 // Rust checked exhaustiveness already, but Nim cannot always see
2935 // it -- an integer `case` needs every value covered -- so make the
2936 // unreachable arm explicit rather than leave a compile error.
2937 self.line("else:");
2938 self.line(" rsPanic(\"unreachable match arm\")");
2939 }
2940 Ok(())
2941 }
2942
2943 /// Whether a Nim `case` over this type is already total, in which case
2944 /// adding an `else` would be a compile error rather than a safety net.
2945 fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
2946 let Nim::Named(n, _) = t else { return false };
2947 let Some(def) = self.enums.get(n) else { return false };
2948 def.variants.len() == m.arms.len()
2949 }
2950
2951 /// The if/elif form, for arms that bind or destructure.
2952 fn match_chain(
2953 &mut self,
2954 m: &syn::ExprMatch,
2955 name: &str,
2956 t: &Nim,
2957 ) -> Result<(), String> {
2958 let mut first = true;
2959 let mut closed = false;
2960 for arm in &m.arms {
2961 let (pat, guard) = match &arm.pat {
2962 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
2963 p => (p, None),
2964 };
2965 if guard.is_some() && binds(pat) {
2966 return Err("a `match` guard on a binding pattern is not \
2967 implemented yet"
2968 .into());
2969 }
2970 let test = self.pat_test(pat, name, t)?;
2971 let test = match (test, guard) {
2972 (Some(t), Some(g)) => {
2973 let g = self.expr(g)?;
2974 Some(format!("({}) and ({})", t, g.code))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2975 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago2976 (None, Some(g)) => Some(self.expr(g)?.code),
2977 (t, None) => t,
2978 };
2979 match test {
2980 Some(test) => {
2981 self.line(&format!(
2982 "{} {}:",
2983 if first { "if" } else { "elif" },
2984 test
2985 ));
2986 first = false;
2987 }
2988 None => {
2989 // An irrefutable pattern: everything left falls here.
2990 if first {
2991 self.line("block:");
2992 } else {
2993 self.line("else:");
2994 }
2995 closed = true;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday2996 }
2997 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago2998 self.indent += 1;
2999 self.push_scope();
3000 let before = self.out.len();
3001 self.pat_bind(pat, name, t)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3002 self.indent -= 1;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3003 self.arm_body_at(&arm.body, before)?;
3004 self.pop_scope();
3005 if closed {
3006 break;
3007 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3008 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3009 if !closed {
3010 // Rust proved this unreachable; Nim cannot see that, and leaving
3011 // the chain open would silently fall through instead.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3012 self.line("else:");
3013 self.line(" rsPanic(\"unreachable match arm\")");
3014 }
3015 Ok(())
3016 }
3017
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3018 /// The condition that selects this arm, or `None` if it always matches.
3019 fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
3020 Ok(match p {
3021 Pat::Wild(_) => None,
3022 Pat::Ident(i) if i.subpat.is_none() => None,
3023 Pat::Or(o) => {
3024 let mut parts = Vec::new();
3025 for c in &o.cases {
3026 match self.pat_test(c, name, t)? {
3027 Some(x) => parts.push(x),
3028 None => return Ok(None),
3029 }
3030 }
3031 Some(format!("({})", parts.join(" or ")))
3032 }
3033 Pat::Lit(_) | Pat::Range(_) => {
3034 let labels = self.pat_labels(p, Some(t))?;
3035 Some(match p {
3036 Pat::Range(_) => format!("({} in {})", name, labels[0]),
3037 _ => format!("({} == {})", name, labels[0]),
3038 })
3039 }
3040 Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
3041 Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
3042 Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
3043 Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
3044 Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
3045 _ => return Err("unsupported `match` pattern".into()),
3046 })
3047 }
3048
3049 /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
3050 fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
3051 let last = path_name(path);
3052 match last.as_str() {
3053 "Ok" => return Ok(format!("{name}.ok")),
3054 "Err" => return Ok(format!("(not {name}.ok)")),
3055 "Some" => return Ok(format!("{name}.has")),
3056 "None" => return Ok(format!("(not {name}.has)")),
3057 _ => {}
3058 }
3059 let Some((def, v)) = self.resolve_variant(path) else {
3060 return Err(format!(
3061 "`{last}` in a pattern is not a known enum variant; if it names \
3062 an enum declared in another module, that is not implemented yet"
3063 ));
3064 };
3065 if let Nim::Named(n, _) = t {
3066 if *n != def.name {
3067 return Err(format!(
3068 "pattern `{}::{}` does not match the scrutinee type `{}`",
3069 def.name, v, n
3070 ));
3071 }
3072 }
3073 Ok(if def.simple {
3074 format!("({} == {}.{})", name, ident(&def.name), ident(&v))
3075 } else {
3076 format!("({}.kind == {})", name, def.kind_ident(&v))
3077 })
3078 }
3079
3080 /// Emit the `let`s that a pattern's bindings introduce.
3081 fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
3082 match p {
3083 Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
3084 Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
3085 Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
3086 Pat::Ident(i) if i.subpat.is_none() => {
3087 let b = i.ident.to_string();
3088 self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
3089 self.bind(&b, t.clone());
3090 Ok(())
3091 }
3092 Pat::TupleStruct(ts) => {
3093 let fields = self.variant_fields(&ts.path, t)?;
3094 for (i, sub) in ts.elems.iter().enumerate() {
3095 let Some((fname, fty)) = fields.get(i) else {
3096 return Err(format!(
3097 "pattern binds {} field(s) but the variant has {}",
3098 ts.elems.len(),
3099 fields.len()
3100 ));
3101 };
3102 let access = format!("{}.{}", name, ident(fname));
3103 self.pat_bind(sub, &access, fty)?;
3104 }
3105 Ok(())
3106 }
3107 Pat::Struct(st) => {
3108 let fields = self.variant_fields(&st.path, t)?;
3109 for f in &st.fields {
3110 let syn::Member::Named(m) = &f.member else {
3111 return Err("unsupported struct pattern field".into());
3112 };
3113 let m = m.to_string();
3114 let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
3115 return Err(format!("unknown field `{m}` in pattern"));
3116 };
3117 let access = format!("{}.{}", name, ident(fname));
3118 self.pat_bind(&f.pat, &access, fty)?;
3119 }
3120 Ok(())
3121 }
3122 _ => Err("unsupported `match` pattern".into()),
3123 }
3124 }
3125
3126 /// The payload fields a variant pattern destructures.
3127 fn variant_fields(
3128 &self,
3129 path: &syn::Path,
3130 t: &Nim,
3131 ) -> Result<Vec<(String, Nim)>, String> {
3132 let last = path_name(path);
3133 // `Ok`/`Err`/`Some` read the prelude's own field names.
3134 if let Nim::Named(n, a) = t {
3135 match (n.as_str(), last.as_str()) {
3136 ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
3137 ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
3138 ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
3139 _ => {}
3140 }
3141 }
3142 let Some((def, v)) = self.resolve_variant(path) else {
3143 return Err(format!("`{last}` is not a known enum variant"));
3144 };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago3145 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
3146 // The variant's payload is declared in the enum's own parameters; the
3147 // scrutinee says what they are here.
3148 Ok(fields
3149 .into_iter()
3150 .map(|(n, ft)| (n, self.subst_type_args(&def.name, t, ft)))
3151 .collect())
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3152 }
3153
3154 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
3155 self.indent += 1;
3156 let before = self.out.len();
3157 self.indent -= 1;
3158 self.arm_body_at(body, before)
3159 }
3160
3161 fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
3162 match body {
3163 Expr::Block(b) => self.nested_block(&b.block)?,
3164 other => {
3165 self.indent += 1;
3166 // An arm's value is the `match`'s value, so it is typed by
3167 // whatever the `match` is being assigned to -- without which
3168 // an `Ok(..)` arm has no way to know its `Result<T, E>`.
3169 let want = self.target.clone().and_then(|(_, t)| t);
3170 let v = match (want, expressible(other)) {
3171 (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
3172 _ => self.expr_stmt(other)?,
3173 };
3174 self.emit_tail(v);
3175 self.indent -= 1;
3176 }
3177 }
3178 if self.out.len() == before {
3179 self.indent += 1;
3180 self.line("discard");
3181 self.indent -= 1;
3182 }
3183 Ok(())
3184 }
3185
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3186 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
3187 match p {
3188 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
3189 Pat::Or(o) => {
3190 let mut out = Vec::new();
3191 for p in &o.cases {
3192 out.extend(self.pat_labels(p, expect)?);
3193 }
3194 Ok(out)
3195 }
3196 Pat::Range(r) => {
3197 let lo = r.start.as_ref().ok_or("open-ended range pattern")?;
3198 let hi = r.end.as_ref().ok_or("open-ended range pattern")?;
3199 let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?);
3200 let op = match r.limits {
3201 syn::RangeLimits::HalfOpen(_) => "..<",
3202 syn::RangeLimits::Closed(_) => "..",
3203 };
3204 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
3205 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3206 Pat::Path(pp) => {
3207 if let Some((def, v)) = self.resolve_variant(&pp.path) {
3208 return Ok(vec![if def.simple {
3209 format!("{}.{}", ident(&def.name), ident(&v))
3210 } else {
3211 def.kind_ident(&v)
3212 }]);
3213 }
3214 Ok(vec![ident(&path_name(&pp.path))])
3215 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3216 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3217 alternatives, enum variants and `_` are implemented"
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3218 .into()),
3219 }
3220 }
3221
3222 // --------------------------------------------------------- expressions
3223
3224 fn expr(&mut self, e: &Expr) -> Result<Val, String> {
3225 self.expr_at(e, None)
3226 }
3227
3228 /// Lower `e`, with the type the surrounding code expects of it.
3229 ///
3230 /// Rust infers an unsuffixed integer literal's type from its context and
3231 /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the
3232 /// expected type down to the literal is what makes `let x: u8 = 255` and
3233 /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the
3234 /// widths silently diverge, which is exactly the class of bug this
3235 /// project refuses to ship.
3236 fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result<Val, String> {
3237 match e {
3238 Expr::Lit(l) => self.lit_at(&l.lit, expect),
3239 Expr::Path(p) => {
3240 let name = path_name(&p.path);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3241 if name == "None" {
3242 return Ok(Val::new(self.none_of(expect), expect.cloned()));
3243 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 20h ago3244 // `log::Level` and `log::LevelFilter` come from the facade
3245 // shim, under names no crate can collide with.
3246 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3247 if (q == "Level" || q == "LevelFilter") && !self.enums.contains_key(&q) {
3248 let pre = if q == "Level" { "rsLvl" } else { "rsFlt" };
3249 let t = if q == "Level" { "RsLogLevel" } else { "RsLogFilter" };
3250 if q == "LevelFilter" && name == "Off" {
3251 return Ok(Val::new("rsFltOff", Some(Nim::Prim(t.into()))));
3252 }
3253 if matches!(name.as_str(), "Error" | "Warn" | "Info" | "Debug" | "Trace") {
3254 return Ok(Val::new(
3255 format!("{pre}{name}"),
3256 Some(Nim::Prim(t.into())),
3257 ));
3258 }
3259 }
3260 }
3261
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 20h ago3262 // `Perms::READ`: a constant of a `bitflags!` type.
3263 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3264 let q = if q == "Self" {
3265 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3266 } else {
3267 q
3268 };
3269 if let Some(c) = self.flag_consts.get(&(q.clone(), name.clone())) {
3270 return Ok(Val::new(c.clone(), Some(Nim::Named(q, vec![]))));
3271 }
3272 }
3273
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago3274 // `Grid::BORDER`: a `const` declared inside an `impl`.
3275 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3276 let q = if q == "Self" {
3277 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3278 } else {
3279 q
3280 };
3281 if let Some((nim, t)) = self.assoc_consts.get(&(q, name.clone())) {
3282 return Ok(Val::new(nim.clone(), Some(t.clone())));
3283 }
3284 }
3285
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago3286 // `i32::MAX` and friends: an associated const on a primitive.
3287 if matches!(name.as_str(), "MAX" | "MIN") {
3288 if let Some(q) = p.path.segments.iter().rev().nth(1) {
3289 if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) {
3290 if t.is_integer() {
3291 let f = if name == "MAX" { "high" } else { "low" };
3292 return Ok(Val::new(
3293 format!("{}({})", f, t.render()),
3294 Some(t),
3295 ));
3296 }
3297 }
3298 }
3299 }
3300
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago3301 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
3302 // declared here. In Nim that is a constructor call.
3303 if p.path.segments.len() > 1 {
3304 let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
3305 if let Ok(Nim::Prim(n)) = ty::map(&ty) {
3306 if n == "FmtError" {
3307 return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
3308 }
3309 }
3310 }
3311 if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
3312 return Ok(Val::new(
3313 format!("{}()", ident(&name)),
3314 Some(Nim::Named(name.clone(), vec![])),
3315 ));
3316 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3317 // A unit enum variant used as a value: `Error::InvalidLength`.
3318 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago3319 let (ty, targs) = self.variant_type(&def, expect)?;
3320 return Ok(if def.simple && targs.is_empty() {
3321 Val::new(format!("{}.{}", ident(&def.name), ident(&v)), Some(ty))
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3322 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago3323 // A unit variant of a generic enum has no argument to
3324 // infer the parameters from, so they are written out.
3325 Val::new(
3326 format!("{}{}()", def.ctor_ident(&v), targs),
3327 Some(ty),
3328 )
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3329 });
3330 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago3331 // A `for` binding that stands for an element of the container
3332 // it came from: using it must read (and assigning through it
3333 // must write) that element, not a copy.
3334 if let Some(a) = self.lookup_alias(&name) {
3335 return Ok(match a {
3336 Alias::Value { code, ty } => Val::new(code, ty),
3337 // A window *is* a slice; as a value it is the view it
3338 // denotes, which is what Rust's `&[T]` means too.
3339 Alias::Window { code, off, len, elem } => Val::new(
3340 format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
3341 elem.map(|e| Nim::OpenArray(Box::new(e))),
3342 ),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago3343 // An iterator is not a value here: it is consumed by a
3344 // `for`, or asked for its `.remainder()`.
3345 Alias::Iterator(_) => {
3346 return Err(format!(
3347 "`{name}` is an iterator; it can be iterated or asked \
3348 for its `remainder()`, but not used as a value"
3349 ))
3350 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago3351 });
3352 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3353 if let Some(t) = self.lookup(&name) {
3354 return Ok(Val::new(ident(&name), Some(t)));
3355 }
3356 // A top-level function used as a value, e.g. passed to a
3357 // parameter of `impl Fn(..)` type.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago3358 if let Some(k) = self.resolve_fn(&p.path) {
3359 let sig = &self.fns[&k];
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3360 let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone()));
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago3361 return Ok(Val::new(self.fn_name(&k.0, &k.1), Some(t)));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3362 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3363 Ok(Val::new(ident(&name), None))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3364 }
3365 Expr::Paren(p) => {
3366 let v = self.expr_at(&p.expr, expect)?;
3367 Ok(Val::new(format!("({})", v.code), v.ty))
3368 }
3369 Expr::Group(g) => self.expr_at(&g.expr, expect),
3370 // `&x` is a value in Nim; `&mut x` in an argument position binds to
3371 // a `var` parameter, which is also just `x` at the call site.
3372 Expr::Reference(r) => self.expr_at(&r.expr, expect),
3373 Expr::Unary(u) => self.unary(u, expect),
3374 Expr::Binary(b) => self.binary(b, expect),
3375 Expr::Cast(c) => self.cast(c),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago3376 Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
3377 let Expr::Range(r) = &*i.index else { unreachable!() };
3378 let base = self.expr(&i.expr)?;
3379 let lo = match &r.start {
3380 Some(e) => format!("int({})", self.expr(e)?.code),
3381 None => "0".into(),
3382 };
3383 // Nim's `toOpenArray` takes an inclusive upper bound.
3384 let hi = match (&r.end, r.limits) {
3385 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
3386 format!("int({}) - 1", self.expr(e)?.code)
3387 }
3388 (Some(e), syn::RangeLimits::Closed(_)) => {
3389 format!("int({})", self.expr(e)?.code)
3390 }
3391 (None, _) => format!("{}.len - 1", base.code),
3392 };
3393 let elem = elem_of(&base.ty)
3394 .ok_or("cannot infer the element type of this slice")?;
3395 Ok(Val::new(
3396 format!("{}.toOpenArray({}, {})", base.code, lo, hi),
3397 Some(Nim::OpenArray(Box::new(elem))),
3398 ))
3399 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3400 Expr::Index(i) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago3401 if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
3402 let idx = self.expr(&i.index)?;
3403 return Ok(Val::new(
3404 format!("{}[{} + int({})]", code, off, idx.code),
3405 elem,
3406 ));
3407 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3408 let base = self.expr(&i.expr)?;
3409 let idx = self.expr(&i.index)?;
3410 // Rust indexes with usize; Nim wants an `int`, and a `uint`
3411 // index is a type error there rather than a silent conversion.
3412 let idx_code = match &idx.ty {
3413 Some(t) if t.is_unsigned() => format!("int({})", idx.code),
3414 _ => idx.code.clone(),
3415 };
3416 let elem = match base.ty.clone() {
3417 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
3418 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
3419 _ => None,
3420 };
3421 Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem))
3422 }
3423 Expr::Field(f) => {
3424 let base = self.expr(&f.base)?;
3425 let name = match &f.member {
3426 syn::Member::Named(n) => n.to_string(),
3427 syn::Member::Unnamed(i) => format!("f{}", i.index),
3428 };
3429 let t = match &base.ty {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago3430 Some(bt @ Nim::Named(s, _)) => self
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3431 .structs
3432 .get(s)
3433 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago3434 .map(|(_, t)| self.subst_type_args(s, bt, t.clone())),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3435 _ => None,
3436 };
3437 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
3438 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago3439 // `unsafe` is a permission marker, not a semantic change: it does
3440 // not alter what the enclosed operations mean. So the block is
3441 // transparent here, and each operation inside still goes through
3442 // the ordinary lowering -- and is still rejected if it has no
3443 // faithful mapping.
3444 Expr::Unsafe(u) => match single_expr(&u.block) {
3445 Some(e) => self.expr_at(e, expect),
3446 None => Err("an `unsafe` block used as a value must be a single \
3447 expression"
3448 .into()),
3449 },
3450 Expr::Closure(c) => self.closure(c, expect),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3451 Expr::Try(t) => self.try_op(t),
3452 Expr::Call(c) => self.call(c, expect),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago3453 Expr::MethodCall(m) => self.method(m, expect),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago3454 Expr::Macro(m) if path_name(&m.mac.path) == "vec" => {
3455 // `vec![..]`'s elements take their type from the annotation on
3456 // the binding, exactly as Rust's would.
3457 let want = match expect {
3458 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()),
3459 _ => None,
3460 };
3461 let saved = std::mem::replace(&mut self.vec_expect, want.clone());
3462 let code = self.macro_call(&m.mac);
3463 self.vec_expect = saved;
3464 let code = code?;
3465 let ty = match want {
3466 Some(e) => Some(Nim::Seq(Box::new(e))),
3467 None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))),
3468 };
3469 Ok(Val::new(code, ty))
3470 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3471 Expr::Macro(m) => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago3472 let is_write = matches!(path_name(&m.mac.path).as_str(), "write" | "writeln");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3473 let code = self.macro_call(&m.mac)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago3474 // A formatter write is a statement that appends, not a value.
3475 let ty = if is_write { Some(Nim::Unit) } else { None };
3476 Ok(Val::new(code, ty))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3477 }
3478 Expr::Struct(s) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3479 if s.rest.is_some() {
3480 return Err("struct update syntax `..rest` is not implemented yet".into());
3481 }
3482 // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
3483 // which is constructed positionally in Nim.
3484 if let Some((def, v)) = self.resolve_variant(&s.path) {
3485 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
3486 let mut args = vec![String::new(); fields.len()];
3487 for f in &s.fields {
3488 let syn::Member::Named(m) = &f.member else {
3489 return Err("unsupported enum variant field".into());
3490 };
3491 let want = format!("{}_{}", v, m);
3492 let i = fields
3493 .iter()
3494 .position(|(n, _)| *n == want)
3495 .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
3496 args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
3497 }
3498 if let Some(i) = args.iter().position(|a| a.is_empty()) {
3499 return Err(format!(
3500 "`{}::{}` is missing field `{}`",
3501 def.name, v, fields[i].0
3502 ));
3503 }
3504 return Ok(Val::new(
3505 format!("{}({})", def.ctor_ident(&v), args.join(", ")),
3506 Some(Nim::Named(def.name.clone(), vec![])),
3507 ));
3508 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago3509 // `Self { .. }` inside an `impl` names the type being
3510 // implemented, and its fields are that type's fields.
3511 let name = match path_name(&s.path).as_str() {
3512 "Self" => self
3513 .self_ty
3514 .as_ref()
3515 .map(type_name)
3516 .ok_or("`Self` outside an `impl` block")?,
3517 other => other.to_string(),
3518 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3519 let mut parts = Vec::new();
3520 for f in &s.fields {
3521 let fname = match &f.member {
3522 syn::Member::Named(n) => n.to_string(),
3523 syn::Member::Unnamed(i) => format!("f{}", i.index),
3524 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3525 let want = self
3526 .structs
3527 .get(&name)
3528 .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
3529 .map(|(_, t)| t.clone());
3530 let v = self.expr_at(&f.expr, want.as_ref())?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3531 parts.push(format!("{}: {}", ident(&fname), v.code));
3532 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago3533 // Nim cannot infer an object's generic parameters from a
3534 // constructor's field values, so they are written out.
3535 let gp = self.type_generics.get(&name).cloned().unwrap_or_default();
3536 let ty = if gp.is_empty() {
3537 Nim::Named(name.clone(), vec![])
3538 } else {
3539 match expect {
3540 Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => {
3541 Nim::Named(name.clone(), a.clone())
3542 }
3543 _ => {
3544 return Err(format!(
3545 "`{name} {{ .. }}` is generic, and Nim cannot infer \
3546 its parameters from the field values; annotate the \
3547 binding or the return type"
3548 ))
3549 }
3550 }
3551 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3552 Ok(Val::new(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago3553 format!("{}({})", ty.render(), parts.join(", ")),
3554 Some(ty),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3555 ))
3556 }
3557 Expr::Array(a) => {
3558 let mut parts = Vec::new();
3559 let mut elem = match expect {
3560 Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => {
3561 Some((**t).clone())
3562 }
3563 _ => None,
3564 };
3565 for e in &a.elems {
3566 let want = elem.clone();
3567 let v = self.expr_at(e, want.as_ref())?;
3568 elem = elem.or(v.ty.clone());
3569 parts.push(v.code);
3570 }
3571 let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t)));
3572 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
3573 }
3574 Expr::Repeat(r) => {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago3575 // `[0; 4]` is an array in Rust. Nim distinguishes a fixed-size
3576 // array from a `seq`, so the expected type decides which, and
3577 // an array needs its elements written out.
3578 let want_elem = match expect {
3579 Some(Nim::Array(_, e)) | Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => {
3580 Some((**e).clone())
3581 }
3582 _ => None,
3583 };
3584 let v = self.expr_at(&r.expr, want_elem.as_ref())?;
3585 if let Some(Nim::Array(n, _)) = expect {
3586 let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect();
3587 let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t)));
3588 return Ok(Val::new(format!("[{}]", elems.join(", ")), t));
3589 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3590 let n = self.expr(&r.len)?;
3591 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
3592 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
3593 }
3594 Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))),
3595 Expr::Tuple(t) => {
3596 let mut parts = Vec::new();
3597 let mut tys = Vec::new();
3598 for e in &t.elems {
3599 let v = self.expr(e)?;
3600 tys.push(v.ty.clone());
3601 parts.push(v.code);
3602 }
3603 let ty = tys
3604 .iter()
3605 .cloned()
3606 .collect::<Option<Vec<_>>>()
3607 .map(Nim::Tuple);
3608 Ok(Val::new(format!("({})", parts.join(", ")), ty))
3609 }
3610 // `if` and `match` are expressions in both languages, but only
3611 // when every arm is itself a single expression.
3612 Expr::If(i) => self.if_expr(i, expect),
3613 Expr::Block(b) if b.block.stmts.len() == 1 => {
3614 if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() {
3615 self.expr_at(e, expect)
3616 } else {
3617 Err("block expression with statements in value position is not implemented yet".into())
3618 }
3619 }
3620 other => Err(format!(
3621 "unsupported expression in value position: {}",
3622 expr_kind(other)
3623 )),
3624 }
3625 }
3626
3627 fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> {
3628 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
3629 return Err(
3630 "an `if` used as a value must have an `else` and single-expression arms".into(),
3631 );
3632 };
3633 let c = self.expr(&i.cond)?;
3634 let t = self.expr_at(then, expect)?;
3635 let want = expect.cloned().or_else(|| t.ty.clone());
3636 let e = match &**els {
3637 Expr::Block(b) => match single_expr(&b.block) {
3638 Some(x) => self.expr_at(x, want.as_ref())?,
3639 None => return Err("an `if` used as a value must have single-expression arms".into()),
3640 },
3641 other => self.expr_at(other, want.as_ref())?,
3642 };
3643 let ty = t.ty.clone().or(e.ty.clone());
3644 Ok(Val::new(
3645 format!("(if {}: {} else: {})", c.code, t.code, e.code),
3646 ty,
3647 ))
3648 }
3649
3650 fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result<Val, String> {
3651 match l {
3652 Lit::Int(i) => {
3653 let suffix = i.suffix();
3654 if let Some(why) = ty::rejected(suffix) {
3655 return Err(format!("integer literal `{}`: {}", i, why));
3656 }
3657 let digits = i.base10_digits().to_string();
3658 // Rust's default for an unconstrained integer literal is i32.
3659 // Nim's is `int` (64-bit). Making the width explicit is what
3660 // keeps overflow behaviour the same on both sides.
3661 let t = if suffix.is_empty() {
3662 match expect {
3663 Some(t) if t.is_integer() => t.clone(),
3664 // Rust's fallback for an otherwise-unconstrained
3665 // integer literal.
3666 _ => Nim::Prim("int32".into()),
3667 }
3668 } else {
3669 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
3670 };
3671 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
3672 }
3673 Lit::Float(f) => {
3674 let t = match f.suffix() {
3675 "" => match expect {
3676 Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()),
3677 _ => Nim::Prim("float64".into()),
3678 },
3679 "f64" => Nim::Prim("float64".into()),
3680 "f32" => Nim::Prim("float32".into()),
3681 s => return Err(format!("unknown float suffix `{s}`")),
3682 };
3683 let d = f.base10_digits();
3684 let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") };
3685 Ok(Val::new(d, Some(t)))
3686 }
3687 Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))),
3688 Lit::Str(s) => Ok(Val::new(
3689 fmt::nim_str(&s.value()),
3690 Some(Nim::Prim("string".into())),
3691 )),
3692 Lit::Char(c) => Ok(Val::new(
3693 format!("Rune({})", c.value() as u32),
3694 Some(Nim::Prim("Rune".into())),
3695 )),
3696 Lit::Byte(b) => Ok(Val::new(
3697 format!("{}'u8", b.value()),
3698 Some(Nim::Prim("uint8".into())),
3699 )),
3700 Lit::ByteStr(b) => {
3701 let bytes: Vec<String> = b.value().iter().map(|x| format!("{x}'u8")).collect();
3702 Ok(Val::new(
3703 format!("@[{}]", bytes.join(", ")),
3704 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
3705 ))
3706 }
3707 other => Err(format!("unsupported literal: {other:?}")),
3708 }
3709 }
3710
3711 fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result<Val, String> {
3712 // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow
3713 // the positive half of the range before the negation runs. Folding the
3714 // sign into the literal keeps `i8::MIN` and friends expressible.
3715 if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) {
3716 if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) {
3717 let v = self.lit_at(&l.lit, expect)?;
3718 return Ok(Val::new(format!("-{}", v.code), v.ty));
3719 }
3720 }
3721 let v = self.expr_at(&u.expr, expect)?;
3722 match u.op {
3723 UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)),
3724 // Rust's `!` is logical on bool and bitwise-complement on integers.
3725 // Nim spells those `not` and `not` as well, so one mapping covers
3726 // both — but only because Nim overloads `not` the same way.
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 20h ago3727 UnOp::Not(_) => {
3728 if let Some(f) = self.op_proc(&v.ty, "not") {
3729 return Ok(Val::new(format!("{}({})", f, v.code), v.ty));
3730 }
3731 Ok(Val::new(format!("(not {})", v.code), v.ty))
3732 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3733 UnOp::Deref(_) => Ok(v),
3734 _ => Err("unsupported unary operator".into()),
3735 }
3736 }
3737
3738 fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result<Val, String> {
3739 // A comparison's operands are unrelated to the `bool` it produces, so
3740 // the outer expectation is not passed through to them.
3741 let down = match b.op {
3742 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3743 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None,
3744 _ => expect,
3745 };
3746 let mut l = self.expr_at(&b.left, down)?;
3747 // Rust unifies the two operand types; propagating whichever side is
3748 // known to the other reproduces that, and disagreement then surfaces
3749 // as a Nim type error rather than as a silent width change.
3750 let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?;
3751 if l.ty.is_none() && r.ty.is_some() {
3752 l = self.expr_at(&b.left, r.ty.as_ref())?;
3753 }
3754 let r = std::mem::replace(&mut r, Val::untyped(""));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago3755 // A binary operator on a user type goes to that type's own impl.
3756 if let Some(f) = self.op_proc(&l.ty, binary_symbol(&b.op)) {
3757 let want = self.op_param(&l.ty, binary_symbol(&b.op));
3758 let r = self.expr_at(&b.right, want.as_ref())?;
3759 let ret = self
3760 .methods
3761 .get(&(
3762 type_name(l.ty.as_ref().unwrap()),
3763 op_method(binary_symbol(&b.op)).to_string(),
3764 ))
3765 .map(|s| s.ret.clone());
3766 return Ok(Val::new(format!("{}({}, {})", f, l.code, r.code), ret));
3767 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3768 let op = self.bin_op(&b.op, &l, &r)?;
3769 let ty = match b.op {
3770 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3771 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())),
3772 // Rust's shift takes its result type from the *left* operand, and
3773 // the right may be a different width entirely.
3774 BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(),
3775 _ => l.ty.clone().or(r.ty.clone()),
3776 };
3777 Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty))
3778 }
3779
3780 fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> {
3781 Ok(match op {
3782 BinOp::Add(_) | BinOp::AddAssign(_) => "+",
3783 BinOp::Sub(_) | BinOp::SubAssign(_) => "-",
3784 BinOp::Mul(_) | BinOp::MulAssign(_) => "*",
3785 BinOp::Div(_) | BinOp::DivAssign(_) => {
3786 // Nim spells integer division `div`. Both languages truncate
3787 // toward zero, so once the right operator is chosen the
3788 // semantics match, including for negative operands.
3789 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3790 "cannot tell integer from float division here; annotate the operands",
3791 )?;
3792 if t.is_integer() { "div" } else { "/" }
3793 }
3794 BinOp::Rem(_) | BinOp::RemAssign(_) => {
3795 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3796 "cannot tell integer from float remainder here; annotate the operands",
3797 )?;
3798 if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) }
3799 }
3800 BinOp::And(_) => "and",
3801 BinOp::Or(_) => "or",
3802 // Nim's `and`/`or`/`xor` are bitwise on integers and logical on
3803 // bools, exactly as Rust's `&`/`|`/`^` are.
3804 BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and",
3805 BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or",
3806 BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor",
3807 // Settled empirically: Nim's `shr` on a signed integer is
3808 // arithmetic, matching Rust. See DESIGN.md.
3809 BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl",
3810 BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr",
3811 BinOp::Eq(_) => "==",
3812 BinOp::Ne(_) => "!=",
3813 BinOp::Lt(_) => "<",
3814 BinOp::Le(_) => "<=",
3815 BinOp::Gt(_) => ">",
3816 BinOp::Ge(_) => ">=",
3817 other => return Err(format!("unsupported binary operator {other:?}")),
3818 })
3819 }
3820
3821 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
3822 let v = self.expr(&c.expr)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3823 let to = self.map_ty(&c.ty)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3824 let from = v.ty.clone().ok_or_else(|| {
3825 format!(
3826 "cannot lower `as {}`: the source type is unknown, and `as` \
3827 truncates, so the source width decides the result",
3828 to.render()
3829 )
3830 })?;
3831
3832 let code = match (&from, &to) {
3833 (f, t) if f.is_integer() && t.is_integer() => {
3834 // Rust's `as` between integers is a pure bit-width truncation
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 22h ago3835 // or sign-extension, never a range check. `cast` says exactly
3836 // that. (Nim's `T(x)` turns out to truncate here as well --
3837 // see DESIGN.md item 5 -- but `cast` is the spelling that
3838 // means it rather than the one that happens to agree.)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3839 format!("cast[{}]({})", t.render(), v.code)
3840 }
3841 (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => {
3842 format!("{}({})", p, v.code)
3843 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 20h ago3844 // The facade's level enums carry their Rust discriminants, so
3845 // `Level::Info as usize` is the ordinal.
3846 (Nim::Prim(p), t)
3847 if t.is_integer() && (p == "RsLogLevel" || p == "RsLogFilter") =>
3848 {
3849 format!("{}(ord({}))", t.render(), v.code)
3850 }
3851 // A C-like enum's `as` yields its discriminant, which is its
3852 // ordinal in Nim.
3853 (Nim::Named(n, _), t) if t.is_integer() && self.enums.get(n).is_some_and(|d| d.simple) => {
3854 format!("{}(ord({}))", t.render(), v.code)
3855 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3856 (Nim::Prim(b), t) if b == "bool" && t.is_integer() => {
3857 format!("{}(ord({}))", t.render(), v.code)
3858 }
3859 (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => {
3860 format!("cast[{}](int32({}))", t.render(), v.code)
3861 }
3862 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
3863 format!("Rune(int32({}))", v.code)
3864 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 20h ago3865 // Pointer-to-pointer, and integer-to-pointer, are reinterpretations
3866 // in both languages.
3867 (Nim::Ptr(_) | Nim::ConstPtr(_), Nim::Ptr(_) | Nim::ConstPtr(_)) => {
3868 format!("cast[{}]({})", to.render(), v.code)
3869 }
3870 (f, Nim::Ptr(_) | Nim::ConstPtr(_)) if f.is_integer() => {
3871 format!("cast[{}]({})", to.render(), v.code)
3872 }
3873 (Nim::Ptr(_) | Nim::ConstPtr(_), t) if t.is_integer() => {
3874 format!("cast[{}]({})", t.render(), v.code)
3875 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday3876 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
3877 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
3878 // Rust saturates float->int casts; Nim rounds and range-errors.
3879 // Not the same operation, so it is refused rather than mapped.
3880 return Err(format!(
3881 "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \
3882 no faithful mapping is implemented",
3883 t.render()
3884 ));
3885 }
3886 (f, t) => {
3887 return Err(format!(
3888 "unsupported cast from `{}` to `{}`",
3889 f.render(),
3890 t.render()
3891 ))
3892 }
3893 };
3894 Ok(Val::new(code, Some(to)))
3895 }
3896
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago3897 /// Rust's `?`: return early on the error branch, otherwise yield the value.
3898 ///
3899 /// The early return is statements, not an expression, so they are emitted
3900 /// ahead of the line being built. Every caller lowers its sub-expressions
3901 /// before emitting its own line, which is what makes that ordering hold.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago3902 /// The container, start offset, length and element type an expression
3903 /// denotes as a slice. A window alias contributes its own offset, so
3904 /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight
3905 /// into the original buffer rather than through a rebuilt view.
3906 fn slice_parts(
3907 &mut self,
3908 e: &Expr,
3909 ) -> Result<(String, String, String, Option<Nim>), String> {
3910 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) {
3911 return Ok((code, off, len, elem));
3912 }
3913 let v = self.expr(e)?;
3914 let len = format!("{}.len", v.code);
3915 Ok((v.code, "0".to_string(), len, elem_of(&v.ty)))
3916 }
3917
3918 /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline.
3919 fn map_closure(
3920 &mut self,
3921 what: &str,
3922 recv: &Val,
3923 kind: &str,
3924 targs: &[Nim],
3925 c: &syn::ExprClosure,
3926 ) -> Result<Val, String> {
3927 if c.capture.is_some() {
3928 return Err("a `move` closure captures by value; Nim's closures \
3929 capture by reference, and the two are not the same"
3930 .into());
3931 }
3932 if c.inputs.len() != 1 {
3933 return Err(format!("`.{what}()` takes a one-argument closure"));
3934 }
3935 let pname = match &c.inputs[0] {
3936 Pat::Ident(i) => i.ident.to_string(),
3937 Pat::Wild(_) => "unused0".into(),
3938 _ => return Err("only plain identifier closure parameters are supported".into()),
3939 };
3940
3941 let is_opt = kind == "Option";
3942 let tmp = self.fresh("Map");
3943 let recv_ty = Nim::Named(kind.to_string(), targs.to_vec());
3944 self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code));
3945
3946 let body = match &*c.body {
3947 Expr::Block(b) => single_expr(&b.block)
3948 .ok_or("a closure body with statements is not implemented yet")?,
3949 other => other,
3950 };
3951 self.push_scope();
3952 // The parameter names the payload itself, so a view stays a view.
3953 self.bind_alias(
3954 &pname,
3955 Alias::Value {
3956 code: format!("{}.val", tmp),
3957 ty: Some(targs[0].clone()),
3958 },
3959 );
3960 let v = self.expr(body)?;
3961 self.pop_scope();
3962
3963 let inner = v
3964 .ty
3965 .clone()
3966 .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?;
3967 // `and_then`'s closure already returns the wrapped type; `map`'s does
3968 // not and has to be re-wrapped.
3969 let (test, some_branch, none_branch, out_ty) = if is_opt {
3970 let out = if what == "map" {
3971 Nim::Named("Option".into(), vec![inner.clone()])
3972 } else {
3973 inner.clone()
3974 };
3975 let body_code = if what == "map" {
3976 format!("rsSome[{}]({})", inner.render(), v.code)
3977 } else {
3978 v.code.clone()
3979 };
3980 (
3981 format!("{}.has", tmp),
3982 body_code,
3983 format!("rsNone[{}]()", elem_arg(&out).render()),
3984 out,
3985 )
3986 } else {
3987 let e = targs[1].clone();
3988 let out = if what == "map" {
3989 Nim::Named("Result".into(), vec![inner.clone(), e.clone()])
3990 } else {
3991 inner.clone()
3992 };
3993 let ok_ty = elem_arg(&out);
3994 let body_code = if what == "map" {
3995 format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code)
3996 } else {
3997 v.code.clone()
3998 };
3999 (
4000 format!("{}.ok", tmp),
4001 body_code,
4002 format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp),
4003 out,
4004 )
4005 };
4006 Ok(Val::new(
4007 format!("(if {}: {} else: {})", test, some_branch, none_branch),
4008 Some(out_ty),
4009 ))
4010 }
4011
4012 /// `|x| x + 1` -> a Nim anonymous proc.
4013 ///
4014 /// Nim's closures capture by reference, as Rust's non-`move` closures do.
4015 /// A `move` closure captures by value, which is a different thing, so it
4016 /// is rejected rather than lowered to the same construct.
4017 fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> {
4018 if c.capture.is_some() {
4019 return Err("a `move` closure captures by value; Nim's closures \
4020 capture by reference, and the two are not the same"
4021 .into());
4022 }
4023 let want: Option<&Vec<Nim>> = match expect {
4024 Some(Nim::Proc(a, _)) => Some(a),
4025 _ => None,
4026 };
4027
4028 self.push_scope();
4029 let mut parts = Vec::new();
4030 let mut ptys = Vec::new();
4031 for (i, p) in c.inputs.iter().enumerate() {
4032 let (name, ann) = match p {
4033 Pat::Ident(id) => (id.ident.to_string(), None),
4034 Pat::Type(t) => match &*t.pat {
4035 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
4036 _ => return Err("only plain identifier closure parameters are supported".into()),
4037 },
4038 Pat::Wild(_) => (format!("unused{i}"), None),
4039 _ => return Err("only plain identifier closure parameters are supported".into()),
4040 };
4041 let t = ann
4042 .or_else(|| want.and_then(|w| w.get(i).cloned()))
4043 .ok_or_else(|| {
4044 format!(
4045 "cannot infer the type of closure parameter `{name}`; \
4046 annotate it"
4047 )
4048 })?;
4049 parts.push(format!("{}: {}", ident(&name), t.render()));
4050 self.bind(&name, t.clone());
4051 ptys.push(t);
4052 }
4053
4054 let ret_ann = match &c.output {
4055 ReturnType::Default => None,
4056 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
4057 };
4058 let body = match &*c.body {
4059 Expr::Block(b) => single_expr(&b.block)
4060 .ok_or("a closure body with statements is not implemented yet")?,
4061 other => other,
4062 };
4063 let v = self.expr_at(body, ret_ann.as_ref())?;
4064 self.pop_scope();
4065
4066 let ret = ret_ann
4067 .or_else(|| v.ty.clone())
4068 .ok_or("cannot infer a closure's return type; annotate it")?;
4069 Ok(Val::new(
4070 format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code),
4071 Some(Nim::Proc(ptys, Box::new(ret))),
4072 ))
4073 }
4074
4075 /// Lower a block's statements at the current indentation, without opening
4076 /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope
4077 /// of its own in the generated code.
4078 fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> {
4079 self.push_scope();
4080 let tail = self.block_body(b)?;
4081 self.emit_tail(tail);
4082 self.pop_scope();
4083 Ok(())
4084 }
4085
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4086 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
4087 if self.in_loop_cond {
4088 return Err("`?` in a loop condition is not implemented yet: the \
4089 early-return it expands to would be evaluated once, \
4090 before the loop, rather than on each iteration"
4091 .into());
4092 }
4093 let v = self.expr(&t.expr)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4094 if self.fmt_param.is_some() {
4095 // Writing into a string cannot fail, so `?` on a formatter write
4096 // is a no-op. `?` on anything else can fail, and `format!` panics
4097 // when a formatting impl returns an error -- so that is what the
4098 // error branch does here, with std's own message.
4099 if v.ty.as_ref() == Some(&Nim::Unit) {
4100 return Ok(v);
4101 }
4102 if let Some(Nim::Named(n, a)) = v.ty.clone() {
4103 if n == "Result" && a.len() == 2 {
4104 let tmp = self.fresh("Fmt");
4105 self.line(&format!(
4106 "let {}: {} = {}",
4107 tmp,
4108 Nim::Named(n, a.clone()).render(),
4109 v.code
4110 ));
4111 self.line(&format!("if not {}.ok:", tmp));
4112 self.line(
4113 " rsPanic(\"a formatting trait implementation returned an error\")",
4114 );
4115 return Ok(Val::new(format!("{}.val", tmp), Some(a[0].clone())));
4116 }
4117 }
4118 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4119 if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
4120 // An `Option`/`Result` of a view: the check is emitted here and the
4121 // view itself survives as an alias, since it has no value form.
4122 let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
4123 let err = v.guard_err.clone().ok_or(
4124 "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
4125 )?;
4126 let Nim::Named(n, ra) = &ret else {
4127 return Err(format!("`?` in a function returning `{}`", ret.render()));
4128 };
4129 if n != "Result" || ra.len() != 2 {
4130 return Err(format!("`?` in a function returning `{}`", ret.render()));
4131 }
4132 self.line(&format!("if not {}:", guard));
4133 self.line(&format!(
4134 " return rsErr[{}, {}]({})",
4135 ra[0].render(),
4136 ra[1].render(),
4137 err
4138 ));
4139 let mut out = Val::new(String::new(), None);
4140 out.window = Some(w);
4141 return Ok(out);
4142 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4143 let vt = v.ty.clone().ok_or(
4144 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
4145 )?;
4146 let ret = self
4147 .ret
4148 .clone()
4149 .ok_or("`?` outside a function with a return type")?;
4150 let tmp = self.fresh("Try");
4151 self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
4152
4153 match (&vt, &ret) {
4154 (Nim::Named(a, ai), Nim::Named(b, bi))
4155 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
4156 {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4157 // Rust inserts a `From::from` on the error here. Where the
4158 // types differ we call the crate's own `impl From`; we never
4159 // assume the conversion is the identity.
4160 let err = if ai[1] == bi[1] {
4161 format!("{}.err", tmp)
4162 } else {
4163 let key = (type_name(&ai[1]), type_name(&bi[1]));
4164 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
4165 format!(
4166 "`?` needs `From<{}> for {}` to convert the error, and \
4167 no such `impl` is in scope; assuming the conversion is \
4168 the identity would be a guess",
4169 key.0, key.1
4170 )
4171 })?;
4172 format!("{}({}.err)", f, tmp)
4173 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4174 self.line(&format!("if not {}.ok:", tmp));
4175 self.line(&format!(
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4176 " return rsErr[{}, {}]({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4177 bi[0].render(),
4178 bi[1].render(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4179 err
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4180 ));
4181 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
4182 }
4183 (Nim::Named(a, ai), Nim::Named(b, bi))
4184 if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
4185 {
4186 self.line(&format!("if not {}.has:", tmp));
4187 self.line(&format!(" return rsNone[{}]()", bi[0].render()));
4188 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
4189 }
4190 _ => Err(format!(
4191 "`?` on `{}` in a function returning `{}` is not a supported \
4192 combination",
4193 vt.render(),
4194 ret.render()
4195 )),
4196 }
4197 }
4198
4199 fn call(&mut self, c: &syn::ExprCall, expect: Option<&Nim>) -> Result<Val, String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4200 let Expr::Path(p) = &*c.func else {
4201 return Err("only calls to named functions are supported".into());
4202 };
4203 let name = path_name(&p.path);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4204 let target = self.resolve_fn(&p.path);
4205 let ptys: Vec<Nim> = target
4206 .as_ref()
4207 .and_then(|k| self.fns.get(k))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4208 .map(|s| s.params.clone())
4209 .unwrap_or_default();
4210 let mut args = Vec::new();
4211 for (i, a) in c.args.iter().enumerate() {
4212 let want = ptys.get(i).cloned();
4213 args.push(self.expr_at(a, want.as_ref())?);
4214 }
4215 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
4216
4217 // Constructors from the prelude.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4218 // `Ok`/`Err` must name the *whole* Result type, not just the half
4219 // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
4220 match name.as_str() {
4221 "Some" => {
4222 let inner = match expect {
4223 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
4224 _ => {
4225 return Err("`Some(..)` needs a known `Option<T>` type here; \
4226 annotate the binding or the return type"
4227 .into())
4228 }
4229 };
4230 return Ok(Val::new(
4231 format!("rsSome[{}]({})", inner, codes.join(", ")),
4232 expect.cloned(),
4233 ));
4234 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4235 "Ok" if self.fmt_param.is_some()
4236 && matches!(c.args.first(), Some(Expr::Tuple(t)) if t.elems.is_empty()) =>
4237 {
4238 // `Ok(())` ends a `fmt` body: nothing more is written.
4239 return Ok(Val::new(String::new(), Some(Nim::Unit)));
4240 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4241 "Ok" | "Err" => {
4242 let (t, e) = match expect {
4243 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
4244 (a[0].render(), a[1].render())
4245 }
4246 _ => {
4247 return Err(format!(
4248 "`{name}(..)` needs a known `Result<T, E>` type here; \
4249 annotate the binding or the return type"
4250 ))
4251 }
4252 };
4253 let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
4254 let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
4255 return Ok(Val::new(
4256 format!("{}[{}, {}]({})", ctor, t, e, arg),
4257 expect.cloned(),
4258 ));
4259 }
4260 _ => {}
4261 }
4262
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4263 // A tuple struct applied to arguments: `HexDisplay(bytes)`. Nim's
4264 // object constructor names its fields even when Rust's does not.
4265 if let Some(fields) = self.structs.get(&name).cloned() {
4266 if fields.len() == c.args.len() {
4267 let mut parts = Vec::new();
4268 for (i, a) in c.args.iter().enumerate() {
4269 let v = self.expr_at(a, Some(&fields[i].1))?;
4270 parts.push(format!("{}: {}", ident(&fields[i].0), v.code));
4271 }
4272 return Ok(Val::new(
4273 format!("{}({})", ident(&name), parts.join(", ")),
4274 Some(Nim::Named(name.clone(), vec![])),
4275 ));
4276 }
4277 }
4278
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 20h ago4279 // `log::set_max_level` / `log::max_level`.
4280 if name == "set_max_level" && codes.len() == 1 {
4281 return Ok(Val::new(
4282 format!("rsLogMaxLevel = int({})", codes[0]),
4283 Some(Nim::Unit),
4284 ));
4285 }
4286 if name == "max_level" && codes.is_empty() {
4287 return Ok(Val::new(
4288 "RsLogFilter(rsLogMaxLevel)",
4289 Some(Nim::Prim("RsLogFilter".into())),
4290 ));
4291 }
4292
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago4293 // `Spacing::from(d)`: a `From` impl called through its target type.
4294 // Rust picks the impl by the argument's type, and so do we -- Nim
4295 // cannot overload on return type, so each impl has its own proc name.
4296 if name == "from" && codes.len() == 1 {
4297 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
4298 let q = if q == "Self" {
4299 self.self_ty.as_ref().map(type_name).unwrap_or(q)
4300 } else {
4301 q
4302 };
4303 if let Some(src) = args[0].ty.as_ref().map(type_name) {
4304 if let Some(f) = self.from_impls.get(&(src, q.clone())).cloned() {
4305 return Ok(Val::new(
4306 format!("{}({})", f, codes[0]),
4307 Some(Nim::Named(q, vec![])),
4308 ));
4309 }
4310 }
4311 }
4312 }
4313
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago4314 // `u32::from(b)`: `From` between primitives is lossless by definition
4315 // -- it is the widening direction only -- so a plain Nim conversion is
4316 // exact. (The truncating direction is `as`, which is `cast`.)
4317 if name == "from" && codes.len() == 1 {
4318 if let Some(q) = p.path.segments.iter().rev().nth(1) {
4319 if let Some(Nim::Prim(t)) = ty::prim(&q.ident.to_string()) {
4320 return Ok(Val::new(
4321 format!("{}({})", t, codes[0]),
4322 Some(Nim::Prim(t)),
4323 ));
4324 }
4325 }
4326 }
4327
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4328 // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
4329 // string view; no copy, no validation, same memory.
4330 if name == "from_utf8_unchecked" && codes.len() == 1 {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4331 // `String::from_utf8_unchecked(v)` takes ownership and yields an
4332 // owned `String`; `str::from_utf8_unchecked(b)` borrows and yields
4333 // a view. Same name, different operations -- the qualifier says
4334 // which, and an unqualified call is ambiguous.
4335 let q = p
4336 .path
4337 .segments
4338 .iter()
4339 .rev()
4340 .nth(1)
4341 .map(|s| s.ident.to_string());
4342 return match q.as_deref() {
4343 Some("String") => Ok(Val::new(
4344 format!("rsStringOf({})", codes[0]),
4345 Some(Nim::Prim("string".into())),
4346 )),
4347 Some("str") => Ok(Val::new(
4348 format!("rsStrView({})", codes[0]),
4349 Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))),
4350 )),
4351 _ => Err(
4352 "`from_utf8_unchecked` must be written as `str::..` (a \
4353 borrowed view) or `String::..` (an owned string); the two \
4354 are different operations"
4355 .into(),
4356 ),
4357 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4358 }
4359
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4360 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
4361 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago4362 let (ty, _) = self.variant_type(&def, expect)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4363 return Ok(Val::new(
4364 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago4365 Some(ty),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4366 ));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4367 }
4368
4369 // A bare path that names a primitive type is Rust's tuple-struct-like
4370 // conversion, e.g. `String::from(..)`; handled by the method path.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4371 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
4372 // is invoked.
4373 if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
4374 return Ok(Val::new(
4375 format!("{}({})", ident(&name), codes.join(", ")),
4376 Some((*ret).clone()),
4377 ));
4378 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago4379 // `Adler32::new()` / `Adler32::default()`: a method called through
4380 // its type rather than through a receiver.
4381 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
4382 // `Self::new()` inside an `impl` names the type being implemented.
4383 let q = if q == "Self" {
4384 self.self_ty.as_ref().map(type_name).unwrap_or(q)
4385 } else {
4386 q
4387 };
4388 if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) {
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 20h ago4389 // Re-lower the arguments with the declared parameter types, so
4390 // a literal takes the width the signature asks for.
4391 let declared = sig.params.clone();
4392 let mut args = args.clone();
4393 let mut codes = codes.clone();
4394 for (i, a) in c.args.iter().enumerate() {
4395 if let Some(want) = declared.get(i) {
4396 let want = want.clone().unvar();
4397 args[i] = self.expr_at(a, Some(&want))?;
4398 codes[i] = args[i].code.clone();
4399 }
4400 }
4401 let sig = &self.methods[&(q.clone(), name.clone())];
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago4402 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
4403 let ret = Self::instantiate(sig, &arg_tys);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago4404 let nim = self
4405 .statics
4406 .get(&(q.clone(), name.clone()))
4407 .cloned()
4408 .unwrap_or_else(|| ident(&name));
4409 return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));
4410 }
4411 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago4412 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
4413 let ret = target
4414 .as_ref()
4415 .and_then(|k| self.fns.get(k))
4416 .map(|sig| Self::instantiate(sig, &arg_tys));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4417 if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4418 return Err(format!(
4419 "call to unknown function `{name}`; only functions defined in \
4420 this file and the supported standard-library subset can be lowered"
4421 ));
4422 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4423 let nim = match &target {
4424 Some((m, n)) => self.fn_name(m, n),
4425 None => ident(&name),
4426 };
4427 Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4428 }
4429
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4430 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4431 let name = m.method.to_string();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago4432 // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield.
4433 if name == "remainder" && m.args.is_empty() {
4434 if let Expr::Path(p) = &*m.receiver {
4435 if let Some(Alias::Iterator(it)) = self.lookup_alias(&path_name(&p.path)) {
4436 if let Iter::Chunks { code, base, len, k, elem, .. } = &*it {
4437 let kept = format!("(({} div int({})) * int({}))", len, k, k);
4438 let mut v = Val::new(
4439 String::new(),
4440 elem.clone().map(|e| Nim::OpenArray(Box::new(e))),
4441 );
4442 v.window = Some(Alias::Window {
4443 code: code.clone(),
4444 off: format!("({} + {})", base, kept),
4445 len: format!("({} - {})", len, kept),
4446 elem: elem.clone(),
4447 });
4448 return Ok(v);
4449 }
4450 return Err(
4451 "`.remainder()` is only defined for a `chunks_exact` iterator".into(),
4452 );
4453 }
4454 }
4455 return Err("`.remainder()` needs an iterator bound by `let`".into());
4456 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4457 if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
4458 match name.as_str() {
4459 "len" => {
4460 return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
4461 }
4462 "is_empty" => {
4463 return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
4464 }
4465 other => {
4466 return Err(format!(
4467 "`.{other}()` on a slice window from `chunks_exact`/\
4468 `windows` is not implemented; only indexing and \
4469 `len()` are"
4470 ))
4471 }
4472 }
4473 }
4474 let recv = self.expr(&m.receiver)?;
4475 let rt0 = recv.ty.clone();
4476
4477// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
4478 // way to put a view in an object, so instead of materialising an
4479 // Option the view and its validity condition travel together until
4480 // an `ok_or`/`?`/`unwrap` resolves them.
4481 if matches!(name.as_str(), "get" | "get_mut")
4482 && matches!(m.args.first(), Some(Expr::Range(_)))
4483 {
4484 let Some(Expr::Range(r)) = m.args.first() else { unreachable!() };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4485 let (code, base, blen, belem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4486 let lo = match &r.start {
4487 Some(e) => format!("int({})", self.expr(e)?.code),
4488 None => "0".into(),
4489 };
4490 let len = match (&r.end, r.limits) {
4491 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
4492 format!("(int({}) - {})", self.expr(e)?.code, lo)
4493 }
4494 (Some(e), syn::RangeLimits::Closed(_)) => {
4495 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
4496 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4497 (None, _) => format!("({} - {})", blen, lo),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4498 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4499 // Hoisted, so the bounds are computed once -- as Rust computes
4500 // them once -- and cannot be re-evaluated later in a scope where
4501 // the names they mention have been shadowed by a loop pattern.
4502 let off_t = self.fresh("Off");
4503 let len_t = self.fresh("Len");
4504 self.line(&format!("let {}: int = {} + {}", off_t, base, lo));
4505 self.line(&format!("let {}: int = {}", len_t, len));
4506 let elem = belem
4507 .or_else(|| elem_of(&rt0))
4508 .ok_or("cannot infer the element type of this slice")?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4509 let mut v = Val::new(
4510 String::new(),
4511 Some(Nim::Named(
4512 "Option".into(),
4513 vec![Nim::OpenArray(Box::new(elem.clone()))],
4514 )),
4515 );
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4516 v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4517 v.window = Some(Alias::Window {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4518 code,
4519 off: off_t,
4520 len: len_t,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4521 elem: Some(elem),
4522 });
4523 return Ok(v);
4524 }
4525
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4526 // `.map`/`.and_then` over an `Option`/`Result` take a closure whose
4527 // parameter type comes from the receiver, so they are handled before
4528 // the arguments are lowered. The closure is expanded inline, with its
4529 // parameter aliased to the payload: that keeps the whole thing an
4530 // expression and avoids handing a view to a generic proc.
4531 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
4532 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
4533 (recv.ty.clone(), &m.args[0])
4534 {
4535 if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2)
4536 {
4537 return self.map_closure(&name, &recv, &kind, &targs, c);
4538 }
4539 }
4540 }
4541
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4542 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
4543 // own type; `v.push(e)` takes the element type.
4544 let arg_want = match (name.as_str(), &recv.ty) {
4545 ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()),
4546 (_, t) => t.clone(),
4547 };
4548 let mut args = Vec::new();
4549 for a in &m.args {
4550 args.push(self.expr_at(a, arg_want.as_ref())?);
4551 }
4552 let a0 = args.first().map(|a| a.code.clone());
4553 let rt = recv.ty.clone();
4554
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 20h ago4555 // A method the input defines wins over our model of the standard
4556 // library: `is_empty` on a `bitflags!` type is that type's, not the
4557 // sequence one. Rust resolves inherent methods the same way.
4558 if let Some(t) = &rt {
4559 let key = (type_name(t), name.clone());
4560 if self.methods.contains_key(&key) {
4561 let declared = self.methods[&key].params.clone();
4562 let skip = usize::from(declared.len() == m.args.len() + 1);
4563 for (i, a) in m.args.iter().enumerate() {
4564 if let Some(want) = declared.get(i + skip) {
4565 let want = want.clone().unvar();
4566 args[i] = self.expr_at(a, Some(&want))?;
4567 }
4568 }
4569 let mut arg_tys: Vec<Option<Nim>> = vec![rt.clone()];
4570 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
4571 let ret = Self::instantiate(&self.methods[&key], &arg_tys);
4572 let nim = self
4573 .statics
4574 .get(&key)
4575 .cloned()
4576 .unwrap_or_else(|| ident(&name));
4577 let mut all = vec![recv.code.clone()];
4578 all.extend(args.iter().map(|a| a.code.clone()));
4579 return Ok(Val::new(format!("{}({})", nim, all.join(", ")), Some(ret)));
4580 }
4581 }
4582
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4583 let (code, ty) = match name.as_str() {
4584 // Rust's `len()` is `usize`; Nim's is `int`. The conversion is
4585 // explicit so that a `usize` binding type-checks on the Nim side.
4586 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
4587 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
4588 "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)),
4589 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
4590 | "into_iter" => (recv.code.clone(), rt.clone()),
4591 "unwrap" | "expect" => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4592 // Expanded inline rather than called as a generic proc: when
4593 // the payload is a view, Nim can only borrow from a path
4594 // expression, which a proc body containing the panic is not.
4595 let (kind, inner) = match &rt {
4596 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
4597 ("Option", a[0].clone())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4598 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4599 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
4600 ("Result", a[0].clone())
4601 }
4602 _ => {
4603 return Err(format!(
4604 "`.{name}()` needs a known `Option`/`Result` receiver type"
4605 ))
4606 }
4607 };
4608 if self.in_loop_cond {
4609 return Err(format!(
4610 "`.{name}()` in a loop condition is not implemented yet: the \
4611 check it expands to would run once, before the loop"
4612 ));
4613 }
4614 let tmp = self.fresh("Unwrap");
4615 let rty = rt.clone().unwrap();
4616 self.line(&format!("let {}: {} = {}", tmp, rty.render(), recv.code));
4617 let (test, msg) = if kind == "Option" {
4618 (format!("{}.has", tmp), "called `Option::unwrap()` on a `None` value")
4619 } else {
4620 (format!("{}.ok", tmp), "called `Result::unwrap()` on an `Err` value")
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4621 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4622 let msg = if name == "expect" {
4623 args.first().map(|a| a.code.clone()).unwrap_or_else(|| fmt::nim_str(msg))
4624 } else {
4625 fmt::nim_str(msg)
4626 };
4627 self.line(&format!("if not {}:", test));
4628 self.line(&format!(" rsPanic({})", msg));
4629 // If the payload is a view, hand back an alias rather than a
4630 // value: Nim will not let a `let` borrow out of a local, and a
4631 // view is a reference anyway, so there is nothing to bind.
4632 // `{tmp}.val` is a plain field access, so substituting it at
4633 // each use re-evaluates nothing.
4634 if matches!(inner, Nim::OpenArray(_)) {
4635 let mut v = Val::new(format!("{}.val", tmp), Some(inner.clone()));
4636 v.window = Some(Alias::Value {
4637 code: format!("{}.val", tmp),
4638 ty: Some(inner),
4639 });
4640 return Ok(v);
4641 }
4642 (format!("{}.val", tmp), Some(inner))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4643 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4644 "ok_or" if recv.guard.is_some() => {
4645 let e = args.first().ok_or("`ok_or` takes one argument")?;
4646 let ety = e.ty.clone();
4647 let mut v = recv.clone();
4648 v.guard_err = Some(e.code.clone());
4649 v.ty = match (&recv.ty, ety) {
4650 (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
4651 Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
4652 }
4653 _ => None,
4654 };
4655 return Ok(v);
4656 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago4657 "ok_or" => {
4658 let inner = match &rt {
4659 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
4660 _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
4661 };
4662 let e = args.first().ok_or("`ok_or` takes one argument")?;
4663 let ety = e
4664 .ty
4665 .clone()
4666 .ok_or("`ok_or` needs a known error type for its argument")?;
4667 (
4668 format!(
4669 "rsOkOr[{}, {}]({}, {})",
4670 inner.render(),
4671 ety.render(),
4672 recv.code,
4673 e.code
4674 ),
4675 Some(Nim::Named("Result".into(), vec![inner, ety])),
4676 )
4677 }
4678 "unwrap_or" => {
4679 let inner = match &rt {
4680 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
4681 Some(a[0].clone())
4682 }
4683 _ => None,
4684 };
4685 (
4686 format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
4687 inner,
4688 )
4689 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4690 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
4691 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
4692 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
4693 "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))),
4694
4695 // Settled empirically: Nim's fixed-width *unsigned* arithmetic
4696 // wraps silently, matching Rust's `wrapping_*`. For *signed* types
4697 // Nim raises OverflowDefect, so the operation is routed through
4698 // the unsigned view of the same width, which is what Rust's
4699 // wrapping_* is defined to compute.
4700 "wrapping_add" | "wrapping_sub" | "wrapping_mul" => {
4701 let op = match name.as_str() {
4702 "wrapping_add" => "+",
4703 "wrapping_sub" => "-",
4704 _ => "*",
4705 };
4706 let t = rt.clone().ok_or_else(|| {
4707 format!("`{name}` needs a known receiver type to pick the wrapping width")
4708 })?;
4709 if !t.is_integer() {
4710 return Err(format!("`{name}` on a non-integer type"));
4711 }
4712 let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?;
4713 if t.is_unsigned() {
4714 (format!("({} {} {})", recv.code, op, arg), Some(t))
4715 } else {
4716 let u = unsigned_peer(&t)?;
4717 (
4718 format!(
4719 "cast[{}](cast[{}]({}) {} cast[{}]({}))",
4720 t.render(), u, recv.code, op, u, arg
4721 ),
4722 Some(t),
4723 )
4724 }
4725 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4726 // Inside a formatting impl, a write through the `Formatter` *is*
4727 // the value the proc returns, so it lowers to the string written.
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4728 "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => {
4729 let a = args.first().ok_or("`write_str` takes one argument")?;
4730 // A `&str` argument is a character view, not a Nim string.
4731 let text = match &a.ty {
4732 Some(Nim::Prim(p)) if p == "string" => a.code.clone(),
4733 _ => format!("rsDisplay({})", a.code),
4734 };
4735 (format!("result.add({})", text), Some(Nim::Unit))
4736 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago4737 "saturating_add" | "saturating_sub" | "saturating_mul" | "checked_add"
4738 | "checked_sub" | "checked_mul" => {
4739 let t = rt
4740 .clone()
4741 .filter(|t| t.is_integer())
4742 .ok_or_else(|| format!("`{name}` needs a known integer receiver"))?;
4743 let arg = args
4744 .first()
4745 .ok_or_else(|| format!("`{name}` takes one argument"))?;
4746 let f = match name.as_str() {
4747 "saturating_add" => "rsSatAdd",
4748 "saturating_sub" => "rsSatSub",
4749 "saturating_mul" => "rsSatMul",
4750 "checked_add" => "rsChkAdd",
4751 "checked_sub" => "rsChkSub",
4752 _ => "rsChkMul",
4753 };
4754 let out = if name.starts_with("checked") {
4755 Nim::Named("Option".into(), vec![t])
4756 } else {
4757 t
4758 };
4759 (format!("{}({}, {})", f, recv.code, arg.code), Some(out))
4760 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 20h ago4761 // `as_ptr` hands a C function the address of the first element,
4762 // which is what Rust's does. An empty slice has no first element
4763 // in either language, and reading through the pointer would be
4764 // undefined in both.
4765 "as_ptr" | "as_mut_ptr" => {
4766 let elem = elem_of(&rt)
4767 .ok_or("`as_ptr` needs a known element type")?;
4768 (
4769 format!(
4770 "(if {r}.len == 0: nil else: cast[ptr {e}](addr {r}[0]))",
4771 r = recv.code,
4772 e = elem.render()
4773 ),
4774 Some(Nim::Ptr(Box::new(elem))),
4775 )
4776 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4777 "abs" => (format!("abs({})", recv.code), rt.clone()),
4778 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4779 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4780 "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))),
4781 "as_bytes" | "into_bytes" => (
4782 format!("rsBytes({})", recv.code),
4783 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
4784 ),
4785
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4786 "into" => {
4787 // `.into()` resolves through the `impl From` declarations, and
4788 // needs the target type to pick one.
4789 let from = rt
4790 .clone()
4791 .ok_or("`.into()` needs a known receiver type")?;
4792 let to = expect
4793 .ok_or("`.into()` needs a known target type; annotate the binding")?;
4794 let key = (type_name(&from), type_name(to));
4795 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
4796 format!(
4797 "no `impl From<{}> for {}` in this file, so `.into()` has \
4798 no conversion to call",
4799 key.0, key.1
4800 )
4801 })?;
4802 (format!("{}({})", f, recv.code), Some(to.clone()))
4803 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4804 _ => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4805 // A method defined in this file via `impl`, found by the
4806 // receiver's type rather than by name alone.
4807 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 21h ago4808 // Re-lower the arguments with the declared parameter types:
4809 // a method's own signature says what width its literals are,
4810 // which the receiver's type does not.
4811 let declared: Option<Vec<Nim>> = key
4812 .as_ref()
4813 .and_then(|k| self.methods.get(k))
4814 .map(|s| s.params.clone());
4815 if let Some(d) = &declared {
4816 // params[0] is the receiver for a method with `self`.
4817 let skip = usize::from(d.len() == m.args.len() + 1);
4818 for (i, a) in m.args.iter().enumerate() {
4819 if let Some(want) = d.get(i + skip) {
4820 let want = want.clone().unvar();
4821 args[i] = self.expr_at(a, Some(&want))?;
4822 }
4823 }
4824 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago4825 let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()];
4826 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago4827 let sig = key
4828 .as_ref()
4829 .and_then(|k| self.methods.get(k))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 21h ago4830 .map(|s| Self::instantiate(s, &arg_tys));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4831 if let Some(ret) = sig {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago4832 // Use the name the proc was actually emitted under: an
4833 // inherent method is qualified by its module, a trait
4834 // method by its trait.
4835 let nim = key
4836 .and_then(|k| self.statics.get(&k).cloned())
4837 .unwrap_or_else(|| ident(&name));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4838 let mut all = vec![recv.code.clone()];
4839 all.extend(args.iter().map(|a| a.code.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago4840 (format!("{}({})", nim, all.join(", ")), Some(ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4841 } else {
4842 return Err(format!(
4843 "unsupported method `.{name}()`; it is neither defined in \
4844 this file nor part of the standard-library subset that \
4845 has a verified Nim equivalent"
4846 ));
4847 }
4848 }
4849 };
4850 Ok(Val::new(code, ty))
4851 }
4852
4853 // -------------------------------------------------------------- macros
4854
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago4855 /// The element type of a `vec![..]`, from its first element.
4856 fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> {
4857 let body = mac.tokens.to_string();
4858 if body.trim().is_empty() {
4859 return Ok(None);
4860 }
4861 let first: Option<Expr> = if body.contains(';') {
4862 // The whole body must be consumed or the parse fails, so the
4863 // length is parsed too even though only the element is wanted.
4864 mac.parse_body_with(|input: syn::parse::ParseStream| {
4865 let v: Expr = input.parse()?;
4866 input.parse::<syn::Token![;]>()?;
4867 let _len: Expr = input.parse()?;
4868 Ok(v)
4869 })
4870 .ok()
4871 } else {
4872 mac.parse_body_with(
4873 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4874 )
4875 .ok()
4876 .and_then(|p| p.into_iter().next())
4877 };
4878 match first {
4879 Some(e) => Ok(self.expr(&e)?.ty),
4880 None => Ok(None),
4881 }
4882 }
4883
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4884 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
4885 let name = path_name(&mac.path);
4886 match name.as_str() {
4887 "println" | "print" | "eprintln" | "eprint" => {
4888 let s = self.format_args(mac)?;
4889 let nl = name.ends_with("ln");
4890 Ok(match (name.starts_with('e'), nl) {
4891 (false, true) => format!("echo {s}"),
4892 (false, false) => format!("stdout.write({s})"),
4893 (true, true) => format!("stderr.writeLine({s})"),
4894 (true, false) => format!("stderr.write({s})"),
4895 })
4896 }
4897 "format" => self.format_args(mac),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4898 "write" | "writeln" => {
4899 // `write!(f, "..", ..)` inside a formatting impl: the first
4900 // argument is the sink, the rest is an ordinary format call.
4901 let args: Vec<Expr> = mac
4902 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
4903 .map_err(|e| format!("write!: {e}"))?
4904 .into_iter()
4905 .collect();
4906 let sink = args.first().ok_or("`write!` needs a sink")?;
4907 if !self.is_fmt_param(sink) {
4908 return Err("`write!` to anything but the `Formatter` of the \
4909 enclosing formatting impl is not implemented"
4910 .into());
4911 }
4912 let s = self.format_pieces(&args[1..])?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4913 let s = if name == "writeln" {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4914 format!("({} & \"\\n\")", s)
4915 } else {
4916 s
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4917 };
4918 Ok(format!("result.add({})", s))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago4919 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4920 "panic" => {
4921 let s = self.format_args(mac)?;
4922 Ok(format!("rsPanic({s})"))
4923 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4924 // `debug_assert*` fires in debug builds, which is the profile
4925 // this project models, so it lowers the same as `assert*`.
4926 "assert" | "debug_assert" => {
4927 let args: Vec<Expr> = mac
4928 .parse_body_with(
4929 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4930 )
4931 .map_err(|e| format!("{name}!: {e}"))?
4932 .into_iter()
4933 .collect();
4934 let cond = args.first().ok_or("`assert!` needs a condition")?;
4935 let v = self.expr(cond)?;
4936 let msg = if args.len() > 1 {
4937 self.format_pieces(&args[1..])?
4938 } else {
4939 fmt::nim_str("assertion failed")
4940 };
4941 Ok(format!("(if not ({}): rsPanic({}))", v.code, msg))
4942 }
4943 "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => {
4944 let args: Vec<Expr> = mac
4945 .parse_body_with(
4946 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4947 )
4948 .map_err(|e| format!("{name}!: {e}"))?
4949 .into_iter()
4950 .collect();
4951 if args.len() < 2 {
4952 return Err(format!("`{name}!` takes two operands"));
4953 }
4954 let a = self.expr(&args[0])?;
4955 let b = self.expr_at(&args[1], a.ty.as_ref())?;
4956 let ne = name.ends_with("_ne");
4957 let op = if ne { "!=" } else { "==" };
4958 // Rust's message shows both sides; reproducing it keeps a
4959 // failing assertion as informative as the original.
4960 let label = if ne { "assertion failed: `(left != right)`" } else { "assertion failed: `(left == right)`" };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4961 Ok(format!(
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago4962 "(if not (({}) {} ({})): rsPanic({} & \"\\n left: \" & rsDebug({}) & \"\\n right: \" & rsDebug({})))",
4963 a.code, op, b.code, fmt::nim_str(label), a.code, b.code
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday4964 ))
4965 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 20h ago4966 // The `log` facade. See `src/prelude.nim` for why these are
4967 // lowered directly rather than expanded. The enabled check wraps
4968 // the whole thing because Rust does not evaluate a log record's
4969 // arguments when the level is disabled.
4970 "error" | "warn" | "info" | "debug" | "trace" | "log" => {
4971 let args: Vec<Expr> = mac
4972 .parse_body_with(
4973 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4974 )
4975 .map_err(|e| format!("`{name}!`: {e}"))?
4976 .into_iter()
4977 .collect();
4978 let (level, rest) = if name == "log" {
4979 let first = args.first().ok_or("`log!` needs a level")?;
4980 (self.log_level_of(first)?, &args[1..])
4981 } else {
4982 (
4983 match name.as_str() {
4984 "error" => "rsLvlError",
4985 "warn" => "rsLvlWarn",
4986 "info" => "rsLvlInfo",
4987 "debug" => "rsLvlDebug",
4988 _ => "rsLvlTrace",
4989 }
4990 .to_string(),
4991 &args[..],
4992 )
4993 };
4994 let msg = self.format_pieces(rest)?;
4995 let target = fmt::nim_str(&self.cur_mod.clone());
4996 Ok(format!(
4997 "(if rsLogEnabled({lvl}): rsLog({lvl}, {target}, {msg}))",
4998 lvl = level
4999 ))
5000 }
5001 "log_enabled" => {
5002 let e: Expr = mac
5003 .parse_body()
5004 .map_err(|e| format!("`log_enabled!`: {e}"))?;
5005 let l = self.log_level_of(&e)?;
5006 Ok(format!("rsLogEnabled({l})"))
5007 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5008 "vec" => {
5009 let body = mac.tokens.to_string();
5010 if body.trim().is_empty() {
5011 return Ok("@[]".into());
5012 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago5013 // `vec![elem; n]` is the repeat form, not a list. The macro
5014 // body has no brackets, so it is parsed directly.
5015 if body.contains(';') {
5016 let (v, n) = mac
5017 .parse_body_with(|input: syn::parse::ParseStream| {
5018 let v: Expr = input.parse()?;
5019 input.parse::<syn::Token![;]>()?;
5020 let n: Expr = input.parse()?;
5021 Ok((v, n))
5022 })
5023 .map_err(|e| format!("vec![elem; n]: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago5024 let want = self.vec_expect.clone();
5025 let v = self.expr_at(&v, want.as_ref())?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago5026 let n = self.expr(&n)?;
5027 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
5028 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5029 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
5030 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
5031 .map_err(|e| format!("vec!: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago5032 let want = self.vec_expect.clone();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5033 let mut parts = Vec::new();
5034 for e in &elems {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago5035 parts.push(self.expr_at(e, want.as_ref())?.code);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5036 }
5037 Ok(format!("@[{}]", parts.join(", ")))
5038 }
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 20h ago5039 other => {
5040 if let Some(why) = self.mrules_bad.get(other) {
5041 return Err(format!("`{other}!` cannot be expanded: {why}"));
5042 }
5043 if let Some(def) = self.mrules.get(other).cloned() {
5044 let expanded = def
5045 .expand(mac.tokens.clone())
5046 .map_err(|e| format!("expanding `{other}!`: {e}"))?;
5047 // The expansion is ordinary Rust, lowered in a context
5048 // where its types are known.
5049 let e: Expr = syn::parse2(expanded.clone()).map_err(|_| {
5050 format!(
5051 "`{other}!` expands to something that is not an \
5052 expression: `{}`",
5053 expanded
5054 )
5055 })?;
5056 return Ok(self.expr(&e)?.code);
5057 }
5058 Err(format!(
5059 "unsupported macro `{other}!`; a macro whose expansion is not \
5060 known cannot be lowered faithfully"
5061 ))
5062 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5063 }
5064 }
5065
5066 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
5067 fn format_args(&mut self, mac: &syn::Macro) -> Result<String, String> {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago5068 let args: Vec<Expr> = mac
5069 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
5070 .map_err(|e| format!("format arguments: {e}"))?
5071 .into_iter()
5072 .collect();
5073 self.format_pieces(&args)
5074 }
5075
5076 /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
5077 fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
5078 let Some(Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. })) = args.first() else {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5079 if args.is_empty() {
5080 return Ok("\"\"".into());
5081 }
5082 return Err("the first argument must be a literal format string".into());
5083 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago5084 let rest: Vec<&Expr> = args[1..].iter().collect();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5085
5086 let pieces = fmt::parse(&s.value())?;
5087 let mut parts: Vec<String> = Vec::new();
5088 let mut next = 0usize;
5089 let mut used = vec![false; rest.len()];
5090 for p in &pieces {
5091 match p {
5092 fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)),
5093 fmt::Piece::Arg { r#ref, spec } => {
5094 let v = match r#ref {
5095 fmt::Ref::Next => {
5096 let e = rest.get(next).ok_or("too few arguments for format string")?;
5097 used[next] = true;
5098 next += 1;
5099 self.expr(e)?
5100 }
5101 fmt::Ref::Index(i) => {
5102 let e = rest.get(*i).ok_or("format index out of range")?;
5103 used[*i] = true;
5104 self.expr(e)?
5105 }
5106 fmt::Ref::Named(n) => {
5107 let t = self.lookup(n).ok_or_else(|| {
5108 format!("`{{{n}}}` captures `{n}`, which is not in scope")
5109 })?;
5110 Val::new(ident(n), Some(t))
5111 }
5112 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 22h ago5113 let integer = v.ty.as_ref().is_some_and(|t| t.is_integer());
5114 if spec.radix.is_some() && !integer && v.ty.is_none() {
5115 return Err(
5116 "a radix format (`{:x}`, `{:b}`, ...) needs a known \
5117 argument type: on an integer it formats the bit \
5118 pattern, on anything else it calls that type's own \
5119 impl"
5120 .into(),
5121 );
5122 }
5123 parts.push(fmt::render_arg(&v.code, spec, integer));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5124 }
5125 }
5126 }
5127 // Rust rejects an argument that no `{}` consumes; so do we, rather
5128 // than dropping it from the output.
5129 if let Some(i) = used.iter().position(|u| !u) {
5130 return Err(format!(
5131 "argument {} is never used by the format string",
5132 i + 1
5133 ));
5134 }
5135 Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") })
5136 }
5137}
5138
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago5139/// Whether a pattern introduces a binding.
5140fn binds(p: &Pat) -> bool {
5141 match p {
5142 Pat::Ident(_) => true,
5143 Pat::Guard(g) => binds(&g.pat),
5144 Pat::Paren(x) => binds(&x.pat),
5145 Pat::Reference(r) => binds(&r.pat),
5146 Pat::Or(o) => o.cases.iter().any(binds),
5147 Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
5148 Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
5149 _ => false,
5150 }
5151}
5152
5153/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
5154fn destructures(p: &Pat) -> bool {
5155 matches!(
5156 p,
5157 Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
5158 ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
5159 || matches!(p, Pat::Paren(x) if destructures(&x.pat))
5160 || matches!(p, Pat::Reference(r) if destructures(&r.pat))
5161}
5162
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5163/// Whether an expression has a direct Nim expression form.
5164///
5165/// Nim's `if` is an expression only when every arm is a single expression, and
5166/// its `case` is never one here. Anything else has to be lowered as statements
5167/// that assign into a target.
5168fn expressible(e: &Expr) -> bool {
5169 match e {
5170 Expr::If(i) => {
5171 let Some(then) = single_expr(&i.then_branch) else { return false };
5172 if !expressible(then) {
5173 return false;
5174 }
5175 match &i.else_branch {
5176 None => false,
5177 Some((_, els)) => match &**els {
5178 Expr::Block(b) => single_expr(&b.block).is_some_and(expressible),
5179 other => expressible(other),
5180 },
5181 }
5182 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 20h ago5183 // `unsafe { .. }` is transparent, so it is an expression exactly when
5184 // its block is one.
5185 Expr::Unsafe(u) => single_expr(&u.block).is_some_and(expressible),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5186 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
5187 _ => true,
5188 }
5189}
5190
5191/// The single expression a block consists of, if that is all it is. An `if`
5192/// can only be lowered as a Nim `if`-expression when both arms are this shape.
5193fn single_expr(b: &syn::Block) -> Option<&Expr> {
5194 match (b.stmts.len(), b.stmts.first()) {
5195 (1, Some(Stmt::Expr(e, None))) => Some(e),
5196 _ => None,
5197 }
5198}
5199
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago5200/// Substitute `params[i] -> args[i]` through a type. Enough of the type
5201/// grammar is covered to expand the aliases we accept; anything else is left
5202/// alone and will be reported by `ty::map` if it is unsupported.
5203fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
5204 use syn::Type;
5205 match t {
5206 Type::Path(p) => {
5207 if p.qself.is_none() && p.path.segments.len() == 1 {
5208 let seg = &p.path.segments[0];
5209 if seg.arguments.is_empty() {
5210 let name = seg.ident.to_string();
5211 if let Some(i) = params.iter().position(|x| *x == name) {
5212 return args[i].clone();
5213 }
5214 }
5215 }
5216 let mut p = p.clone();
5217 for seg in &mut p.path.segments {
5218 if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
5219 for g in &mut a.args {
5220 if let syn::GenericArgument::Type(t) = g {
5221 *t = substitute(t, params, args);
5222 }
5223 }
5224 }
5225 }
5226 Type::Path(p)
5227 }
5228 Type::Reference(r) => {
5229 let mut r = r.clone();
5230 r.elem = Box::new(substitute(&r.elem, params, args));
5231 Type::Reference(r)
5232 }
5233 Type::Slice(sl) => {
5234 let mut sl = sl.clone();
5235 sl.elem = Box::new(substitute(&sl.elem, params, args));
5236 Type::Slice(sl)
5237 }
5238 Type::Array(a) => {
5239 let mut a = a.clone();
5240 a.elem = Box::new(substitute(&a.elem, params, args));
5241 Type::Array(a)
5242 }
5243 Type::Tuple(tp) => {
5244 let mut tp = tp.clone();
5245 tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
5246 Type::Tuple(tp)
5247 }
5248 Type::Paren(p) => substitute(&p.elem, params, args),
5249 Type::Group(g) => substitute(&g.elem, params, args),
5250 other => other.clone(),
5251 }
5252}
5253
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5254// --------------------------------------------------------------- utilities
5255
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago5256/// Whether a return type is a borrow of one of the arguments, which Nim
5257/// models with a view rather than with an owned copy.
5258fn returns_borrow(t: &syn::Type) -> bool {
5259 match t {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago5260 syn::Type::Reference(r) => match &*r.elem {
5261 syn::Type::Slice(_) => true,
5262 // `&str` is a borrow of someone else's bytes too, and returning it
5263 // means returning a view, not an owned string.
5264 syn::Type::Path(p) => p.path.is_ident("str"),
5265 _ => false,
5266 },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago5267 syn::Type::Paren(p) => returns_borrow(&p.elem),
5268 syn::Type::Group(g) => returns_borrow(&g.elem),
5269 _ => false,
5270 }
5271}
5272
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 23h ago5273/// The module a `use` prefix names. `crate`, `self` and `super` all resolve
5274/// to the crate root, which is where a flattened module's items live unless
5275/// they came from one of the extra input files.
5276fn module_of(prefix: &[String]) -> String {
5277 match prefix.last() {
5278 Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(),
5279 _ => String::new(),
5280 }
5281}
5282
5283/// The first type argument of an `Option[T]` / `Result[T, E]`.
5284fn elem_arg(t: &Nim) -> Nim {
5285 match t {
5286 Nim::Named(_, a) if !a.is_empty() => a[0].clone(),
5287 other => other.clone(),
5288 }
5289}
5290
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago5291/// The element type of a sequence-like Nim type.
5292fn elem_of(t: &Option<Nim>) -> Option<Nim> {
5293 match t {
5294 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
5295 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
5296 _ => None,
5297 }
5298}
5299
5300/// The short name a Nim type is known by, for keying method tables.
5301fn type_name(t: &Nim) -> String {
5302 match t {
5303 Nim::Named(n, _) => n.clone(),
5304 Nim::Prim(p) => p.clone(),
5305 other => other.render(),
5306 }
5307}
5308
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago5309/// `(trait, operator)` for every operator trait we dispatch.
5310const OPERATOR_TRAITS: &[(&str, &str)] = &[
5311 ("Add", "+"), ("Sub", "-"), ("Mul", "*"), ("Div", "/"), ("Rem", "%"),
5312 ("BitAnd", "&"), ("BitOr", "|"), ("BitXor", "^"), ("Shl", "<<"), ("Shr", ">>"),
5313 ("AddAssign", "+="), ("SubAssign", "-="), ("MulAssign", "*="), ("DivAssign", "/="),
5314 ("RemAssign", "%="), ("BitAndAssign", "&="), ("BitOrAssign", "|="),
5315 ("BitXorAssign", "^="), ("ShlAssign", "<<="), ("ShrAssign", ">>="),
5316 ("Neg", "neg"), ("Not", "not"),
5317];
5318
5319/// `(operator, trait method name)`.
5320const OP_METHOD: &[(&str, &str)] = &[
5321 ("+", "add"), ("-", "sub"), ("*", "mul"), ("/", "div"), ("%", "rem"),
5322 ("&", "bitand"), ("|", "bitor"), ("^", "bitxor"), ("<<", "shl"), (">>", "shr"),
5323 ("+=", "add_assign"), ("-=", "sub_assign"), ("*=", "mul_assign"),
5324 ("/=", "div_assign"), ("%=", "rem_assign"), ("&=", "bitand_assign"),
5325 ("|=", "bitor_assign"), ("^=", "bitxor_assign"), ("<<=", "shl_assign"),
5326 (">>=", "shr_assign"), ("neg", "neg"), ("not", "not"),
5327];
5328
5329fn op_method(op: &str) -> &'static str {
5330 OP_METHOD.iter().find(|(o, _)| *o == op).map(|(_, m)| *m).unwrap_or("")
5331}
5332
5333/// The operator symbol a compound assignment applies.
5334fn compound_symbol(op: &BinOp) -> &'static str {
5335 match op {
5336 BinOp::AddAssign(_) => "+=",
5337 BinOp::SubAssign(_) => "-=",
5338 BinOp::MulAssign(_) => "*=",
5339 BinOp::DivAssign(_) => "/=",
5340 BinOp::RemAssign(_) => "%=",
5341 BinOp::BitAndAssign(_) => "&=",
5342 BinOp::BitOrAssign(_) => "|=",
5343 BinOp::BitXorAssign(_) => "^=",
5344 BinOp::ShlAssign(_) => "<<=",
5345 BinOp::ShrAssign(_) => ">>=",
5346 _ => "",
5347 }
5348}
5349
5350fn binary_symbol(op: &BinOp) -> &'static str {
5351 match op {
5352 BinOp::Add(_) => "+",
5353 BinOp::Sub(_) => "-",
5354 BinOp::Mul(_) => "*",
5355 BinOp::Div(_) => "/",
5356 BinOp::Rem(_) => "%",
5357 BinOp::BitAnd(_) => "&",
5358 BinOp::BitOr(_) => "|",
5359 BinOp::BitXor(_) => "^",
5360 BinOp::Shl(_) => "<<",
5361 BinOp::Shr(_) => ">>",
5362 _ => "",
5363 }
5364}
5365
5366/// The operator a trait overloads, if it is one of the operator traits.
5367fn operator_trait(t: &str) -> Option<&'static str> {
5368 Some(match t {
5369 "Add" => "+",
5370 "Sub" => "-",
5371 "Mul" => "*",
5372 "Div" => "/",
5373 "Rem" => "%",
5374 "BitAnd" => "&",
5375 "BitOr" => "|",
5376 "BitXor" => "^",
5377 "Shl" => "<<",
5378 "Shr" => ">>",
5379 "AddAssign" => "+=",
5380 "SubAssign" => "-=",
5381 "MulAssign" => "*=",
5382 "DivAssign" => "/=",
5383 "RemAssign" => "%=",
5384 "BitAndAssign" => "&=",
5385 "BitOrAssign" => "|=",
5386 "BitXorAssign" => "^=",
5387 "ShlAssign" => "<<=",
5388 "ShrAssign" => ">>=",
5389 "Neg" => "neg",
5390 "Not" => "not",
5391 _ => return None,
5392 })
5393}
5394
5395/// The Nim proc name for a trait method, qualified by trait and type so that
5396/// two traits declaring the same method name cannot collide.
5397fn trait_method_name(ty: &str, tr: &str, m: &str) -> String {
5398 format!("rs{}_{}_{}", tr, ty, m)
5399}
5400
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 23h ago5401fn is_fmt_trait(t: &str) -> bool {
5402 matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
5403}
5404
5405/// The prelude proc a formatting trait's output is produced by.
5406fn fmt_proc(t: &str) -> &'static str {
5407 match t {
5408 "Display" => "rsDisplay",
5409 "Debug" => "rsDebug",
5410 "LowerHex" => "rsLowerHex",
5411 "UpperHex" => "rsUpperHex",
5412 "Binary" => "rsBinary",
5413 _ => "rsOctal",
5414 }
5415}
5416
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 22h ago5417/// Whether an expression is an iterator-producing chain rather than a value.
5418fn is_iterator_expr(e: &Expr) -> bool {
5419 match e {
5420 Expr::MethodCall(m) => matches!(
5421 m.method.to_string().as_str(),
5422 "iter" | "iter_mut" | "into_iter" | "enumerate" | "zip" | "chunks_exact"
5423 | "chunks_exact_mut" | "windows"
5424 ),
5425 Expr::Paren(p) => is_iterator_expr(&p.expr),
5426 _ => false,
5427 }
5428}
5429
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 22h ago5430/// Whether an expression denotes a place -- a variable, a field, or an index
5431/// or slice of one -- and so may be re-evaluated with no side effect.
5432fn is_pure_place(e: &Expr) -> bool {
5433 match e {
5434 Expr::Path(_) => true,
5435 Expr::Field(f) => is_pure_place(&f.base),
5436 Expr::Index(i) => {
5437 is_pure_place(&i.expr)
5438 && match &*i.index {
5439 Expr::Range(r) => {
5440 r.start.as_deref().map_or(true, is_pure_place)
5441 && r.end.as_deref().map_or(true, is_pure_place)
5442 }
5443 other => is_pure_place(other),
5444 }
5445 }
5446 Expr::Lit(_) => true,
5447 Expr::Reference(r) => is_pure_place(&r.expr),
5448 Expr::Paren(p) => is_pure_place(&p.expr),
5449 Expr::Group(g) => is_pure_place(&g.expr),
5450 // Arithmetic on places is still side-effect free, so a bound like
5451 // `..want - 1` does not stop the binding being an alias.
5452 Expr::Binary(b) if !is_compound(&b.op) => {
5453 is_pure_place(&b.left) && is_pure_place(&b.right)
5454 }
5455 Expr::Unary(u) => is_pure_place(&u.expr),
5456 Expr::Cast(c) => is_pure_place(&c.expr),
5457 _ => false,
5458 }
5459}
5460
5461/// Whether an expression is a `&mut` borrow, directly or through parens.
5462fn is_mut_borrow(e: &Expr) -> bool {
5463 match e {
5464 Expr::Reference(r) => r.mutability.is_some(),
5465 Expr::Paren(p) => is_mut_borrow(&p.expr),
5466 Expr::Group(g) => is_mut_borrow(&g.expr),
5467 _ => false,
5468 }
5469}
5470
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5471fn takes_self(sig: &syn::Signature) -> bool {
5472 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
5473}
5474
5475fn path_name(p: &syn::Path) -> String {
5476 p.segments
5477 .last()
5478 .map(|s| s.ident.to_string())
5479 .unwrap_or_default()
5480}
5481
5482fn is_compound(op: &BinOp) -> bool {
5483 matches!(
5484 op,
5485 BinOp::AddAssign(_)
5486 | BinOp::SubAssign(_)
5487 | BinOp::MulAssign(_)
5488 | BinOp::DivAssign(_)
5489 | BinOp::RemAssign(_)
5490 | BinOp::BitAndAssign(_)
5491 | BinOp::BitOrAssign(_)
5492 | BinOp::BitXorAssign(_)
5493 | BinOp::ShlAssign(_)
5494 | BinOp::ShrAssign(_)
5495 )
5496}
5497
5498/// The Nim literal suffix for an integer type (`5'i32`).
5499fn nim_suffix(t: &Nim) -> Result<&'static str, String> {
5500 let Nim::Prim(p) = t else {
5501 return Err("not a primitive integer".into());
5502 };
5503 Ok(match p.as_str() {
5504 "int8" => "i8",
5505 "int16" => "i16",
5506 "int32" => "i32",
5507 "int64" => "i64",
5508 "int" => "i",
5509 "uint8" => "u8",
5510 "uint16" => "u16",
5511 "uint32" => "u32",
5512 "uint64" => "u64",
5513 "uint" => "u",
5514 other => return Err(format!("no Nim literal suffix for `{other}`")),
5515 })
5516}
5517
5518/// The unsigned integer type of the same width, used to spell `wrapping_*`.
5519fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
5520 let Nim::Prim(p) = t else {
5521 return Err("not a primitive integer".into());
5522 };
5523 Ok(match p.as_str() {
5524 "int8" => "uint8",
5525 "int16" => "uint16",
5526 "int32" => "uint32",
5527 "int64" => "uint64",
5528 "int" => "uint",
5529 other => return Err(format!("`{other}` has no unsigned peer")),
5530 })
5531}
5532
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 23h ago5533fn quote_meta(m: &syn::Meta) -> String {
5534 match m {
5535 syn::Meta::Path(p) => path_name(p),
5536 syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
5537 syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
5538 }
5539}
5540
5541fn item_attrs(i: &Item) -> &[syn::Attribute] {
5542 match i {
5543 Item::Fn(f) => &f.attrs,
5544 Item::Struct(s) => &s.attrs,
5545 Item::Enum(e) => &e.attrs,
5546 Item::Impl(x) => &x.attrs,
5547 Item::Const(c) => &c.attrs,
5548 Item::Type(t) => &t.attrs,
5549 Item::Mod(m) => &m.attrs,
5550 Item::Use(u) => &u.attrs,
5551 Item::ExternCrate(e) => &e.attrs,
5552 Item::Static(s) => &s.attrs,
5553 _ => &[],
5554 }
5555}
5556
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull yesterday5557fn item_kind(i: &Item) -> &'static str {
5558 match i {
5559 Item::Trait(_) => "`trait`",
5560 Item::Static(_) => "`static`",
5561 Item::Macro(_) => "macro definition",
5562 Item::Union(_) => "`union`",
5563 _ => "item",
5564 }
5565}
5566
5567fn expr_kind(e: &Expr) -> &'static str {
5568 match e {
5569 Expr::Async(_) => "`async` block",
5570 Expr::Await(_) => "`.await`",
5571 Expr::Try(_) => "`?`",
5572 Expr::Range(_) => "range",
5573 Expr::Match(_) => "`match` (only statement position is implemented)",
5574 Expr::Let(_) => "`let` expression",
5575 Expr::Unsafe(_) => "`unsafe` block",
5576 Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)",
5577 _ => "expression",
5578 }
5579}