Skip to main content

zsh/ported/modules/
example.rs

1//! `zsh/example` module — port of `Src/Modules/example.c`.
2//!
3//! Top-level declaration order matches C source line-by-line:
4//!   - `static zlong intparam;`                     c:35
5//!   - `static char *strparam;`                     c:36
6//!   - `static char **arrparam;`                    c:37
7//!   - `bin_example(nam, args, ops, func)`          c:42
8//!   - `cond_p_len(a, id)`                          c:80
9//!   - `cond_i_ex(a, id)`                           c:95
10//!   - `math_sum(name, argc, argv, id)`             c:104
11//!   - `math_length(name, arg, id)`                 c:133
12//!   - `ex_wrapper(prog, w, name)`                  c:145
13//!   - `static struct builtin bintab[]`             c:164
14//!   - `static struct conddef cotab[]`              c:168
15//!   - `static struct paramdef patab[]`             c:173
16//!   - `static struct mathfunc mftab[]`             c:179
17//!   - `static struct funcwrap wrapper[]`           c:184
18//!   - `static struct features module_features`     c:188
19//!   - `setup_(m)`                                   c:198
20//!   - `features_(m, features)`                      c:207
21//!   - `enables_(m, enables)`                        c:215
22//!   - `boot_(m)`                                    c:222
23//!   - `cleanup_(m)`                                 c:235
24//!   - `finish_(m)`                                  c:243
25
26#![allow(non_camel_case_types)]
27#![allow(non_upper_case_globals)]
28#![allow(non_snake_case)]
29
30use std::io::Write;
31use std::sync::atomic::{AtomicI64, Ordering};
32use std::sync::{Mutex, OnceLock};
33
34use crate::ported::compat::output64;
35use crate::ported::cond::{cond_str, cond_val};
36use crate::ported::math::{mnumber, MN_FLOAT, MN_INTEGER};
37use crate::ported::string::dyncat;
38use crate::ported::zsh_h::{features, module, options, MAX_OPS, OPT_ISSET};
39
40// =====================================================================
41// /* parameters */                                                  c:33
42// =====================================================================
43
44/// Port of `static zlong intparam;` from `Src/Modules/example.c:35`.
45/// Bound to the `exint` integer paramdef at c:175.
46pub static intparam: AtomicI64 = AtomicI64::new(0); // c:35
47
48/// Port of `static char *strparam;` from `Src/Modules/example.c:36`.
49/// Bound to the `exstr` string paramdef at c:176. `None` mirrors C's
50/// initial NULL which `bin_example` prints as the empty string at c:63.
51pub static strparam: Mutex<Option<String>> = Mutex::new(None); // c:36
52
53/// Port of `static char **arrparam;` from `Src/Modules/example.c:37`.
54/// Bound to the `exarr` array paramdef at c:174. `None` mirrors C's
55/// initial NULL.
56pub static arrparam: Mutex<Option<Vec<String>>> = Mutex::new(None); // c:37
57
58// =====================================================================
59// bin_example(char *nam, char **args, Options ops, UNUSED(int func))  c:42
60// =====================================================================
61
62/// Port of `bin_example(char *nam, char **args, Options ops, UNUSED(int func))` from `Src/Modules/example.c:42`.
63///
64/// C signature mirrored verbatim:
65/// ```c
66/// static int
67/// bin_example(char *nam, char **args, Options ops, UNUSED(int func))
68/// ```
69#[allow(unused_variables)]
70pub fn bin_example(nam: &str, args: &[String], ops: &options, func: i32) -> i32 {
71    // c:42
72    let mut stdout = std::io::stdout().lock();
73    // c:44 — `unsigned char c;`
74    let mut c: u8;
75    // c:45 — `char **oargs = args, **p = arrparam;`
76    let oargs = args; // c:45
77                      // `p` walks `arrparam` below; lock acquired in the print block.
78                      // c:46 — `long i = 0;`
79    let mut i: i64 = 0; // c:46
80
81    let _ = write!(stdout, "Options: "); // c:48
82                                         // c:49-51 — `for (c = 32; ++c < 128;) if (OPT_ISSET(ops,c)) putchar(c);`
83    c = 32; // c:49
84    loop {
85        c += 1;
86        if c >= 128 {
87            break;
88        }
89        if OPT_ISSET(ops, c) {
90            // c:50
91            let _ = write!(stdout, "{}", c as char); // c:51
92        }
93    }
94    let _ = write!(stdout, "\nArguments:"); // c:52
95                                            // c:53-56 — `for (; *args; i++, args++) { putchar(' '); fputs(*args, stdout); }`
96    for a in args {
97        let _ = write!(stdout, " "); // c:54
98        let _ = write!(stdout, "{}", a); // c:55
99        i += 1; // c:53
100    }
101    let _ = writeln!(stdout, "\nName: {}", nam); // c:57
102                                                 // c:58-62 — `#ifdef ZSH_64_BIT_TYPE` branch is taken on every
103                                                 // modern platform; port that branch.
104    let _ = writeln!(
105        stdout,
106        "\nInteger Parameter: {}",
107        output64(intparam.load(Ordering::Relaxed))
108    ); // c:59
109    {
110        let sp = strparam.lock().unwrap();
111        let _ = writeln!(stdout, "String Parameter: {}", sp.as_deref().unwrap_or(""));
112        // c:63
113    }
114    let _ = write!(stdout, "Array Parameter:"); // c:64
115    {
116        let p = arrparam.lock().unwrap();
117        if let Some(arr) = p.as_ref() {
118            // c:65 if (p)
119            for s in arr {
120                // c:66 while (*p)
121                let _ = write!(stdout, " {}", s); // c:66
122            }
123        }
124    }
125    let _ = writeln!(stdout); // c:67
126
127    intparam.store(i, Ordering::Relaxed); // c:69 intparam = i;
128                                          // c:70 — zsfree(strparam);
129                                          // c:71 — strparam = ztrdup(*oargs ? *oargs : "");
130    let new_sp = if let Some(first) = oargs.first() {
131        first.clone()
132    } else {
133        String::new()
134    };
135    *strparam.lock().unwrap() = Some(new_sp); // c:71
136                                              // c:72-74 — if (arrparam) freearray(arrparam); arrparam = zarrdup(oargs);
137    let new_ap: Vec<String> = oargs.to_vec(); // c:80
138    *arrparam.lock().unwrap() = Some(new_ap); // c:80
139
140    0 // c:80
141}
142
143// =====================================================================
144// cond_p_len(char **a, UNUSED(int id))                               c:80
145// =====================================================================
146
147/// Port of `cond_p_len(char **a, UNUSED(int id))` from `Src/Modules/example.c:80`.
148#[allow(unused_variables)]
149pub fn cond_p_len(a: &[String], id: i32) -> i32 {
150    // c:80
151    // c:80 — `char *s1 = cond_str(a, 0, 0);`
152    let s1: String = cond_str(a, 0, false); // c:82
153    if a.len() > 1 {
154        // c:84 if (a[1])
155        let v: i64 = cond_val(a, 1); // c:85 zlong v = cond_val(a, 1);
156                                     // c:87 — `return strlen(s1) == v;`
157        if (s1.len() as i64) == v {
158            1
159        } else {
160            0
161        }
162    } else {
163        // c:95
164        // c:95 — `return !s1[0];`
165        if s1.is_empty() {
166            1
167        } else {
168            0
169        }
170    }
171}
172
173// =====================================================================
174// cond_i_ex(char **a, UNUSED(int id))                                c:95
175// =====================================================================
176
177/// Port of `cond_i_ex(char **a, UNUSED(int id))` from `Src/Modules/example.c:95`.
178#[allow(unused_variables)]
179pub fn cond_i_ex(a: &[String], id: i32) -> i32 {
180    // c:95
181    // c:95 — `char *s1 = cond_str(a, 0, 0), *s2 = cond_str(a, 1, 0);`
182    let s1: String = cond_str(a, 0, false); // c:104
183    let s2: String = cond_str(a, 1, false); // c:104
184                                            // c:104 — `return !strcmp("example", dyncat(s1, s2));`
185    if dyncat(&s1, &s2) == "example" {
186        1
187    } else {
188        0
189    } // c:104
190}
191
192// =====================================================================
193// math_sum(UNUSED(char *name), int argc, mnumber *argv, UNUSED(int id))  c:104
194// =====================================================================
195
196/// Port of `math_sum(UNUSED(char *name), int argc, mnumber *argv, UNUSED(int id))` from `Src/Modules/example.c:104`.
197#[allow(unused_variables)]
198pub fn math_sum(name: &str, argc: i32, argv: &[mnumber], id: i32) -> mnumber {
199    // c:104
200    // c:104 — `mnumber ret;`
201    let mut ret = mnumber {
202        l: 0,
203        d: 0.0,
204        type_: MN_INTEGER,
205    };
206    // c:107 — `int f = 0;`
207    let mut f: i32 = 0;
208    // c:109 — `ret.u.l = 0;`
209    ret.l = 0;
210    let mut argc = argc;
211    let mut p: usize = 0; // emulates argv++ pointer walk (c:124)
212    while {
213        // c:110 while (argc--)
214        let go = argc > 0;
215        argc -= 1;
216        go
217    } {
218        if argv[p].type_ == MN_INTEGER {
219            // c:111
220            if f != 0 {
221                // c:112
222                ret.d += argv[p].l as f64; // c:113
223            } else {
224                ret.l += argv[p].l; // c:115
225            }
226        } else {
227            // c:116
228            if f != 0 {
229                // c:117
230                ret.d += argv[p].d; // c:118
231            } else {
232                // c:119
233                ret.d = (ret.l as f64) + argv[p].d; // c:120
234                f = 1; // c:121
235            }
236        }
237        p += 1; // c:124 argv++
238    }
239    // c:133 — `ret.type = (f ? MN_FLOAT : MN_INTEGER);`
240    ret.type_ = if f != 0 { MN_FLOAT } else { MN_INTEGER };
241    ret // c:133
242}
243
244// =====================================================================
245// math_length(UNUSED(char *name), char *arg, UNUSED(int id))         c:133
246// =====================================================================
247
248/// Port of `math_length(UNUSED(char *name), char *arg, UNUSED(int id))` from `Src/Modules/example.c:133`.
249#[allow(unused_variables)]
250pub fn math_length(name: &str, arg: &str, id: i32) -> mnumber {
251    // c:133
252    // c:133 — `mnumber ret;`
253    // c:137 — `ret.type = MN_INTEGER;`
254    // c:138 — `ret.u.l = strlen(arg);`
255    mnumber {
256        type_: MN_INTEGER,
257        l: arg.len() as i64,
258        d: 0.0,
259    }
260}
261
262// =====================================================================
263// ex_wrapper(Eprog prog, FuncWrap w, char *name)                     c:145
264// =====================================================================
265
266/// Port of `ex_wrapper(Eprog prog, FuncWrap w, char *name)` from `Src/Modules/example.c:145`.
267///
268/// `Eprog` and `FuncWrap` are `Box<eprog>` / `Box<funcwrap>` per
269/// zsh.h:774 / 522. Pointer-shape preserved as `*const eprog` /
270/// `*const funcwrap` since the C body never derefs `prog`/`w` beyond
271/// passing them to `runshfunc`.
272/// WARNING: param names don't match C — Rust=(prog, name) vs C=(prog, w, name)
273pub fn ex_wrapper(
274    prog: *const crate::ported::zsh_h::eprog, // c:145
275    w: *const crate::ported::zsh_h::funcwrap,
276    name: &str,
277) -> i32 {
278    // c:147 — `if (strncmp(name, "example", 7)) return 1;`
279    if !name.starts_with("example") {
280        return 1; // c:148
281    }
282    // c:149-156 — else branch:
283    //   int ogd = opts[GLOBDOTS];
284    //   opts[GLOBDOTS] = 1;
285    //   runshfunc(prog, w, name);
286    //   opts[GLOBDOTS] = ogd;
287    //   return 0;
288    // The opts/runshfunc machinery is the legacy tree-walker funcwrap
289    // dispatcher that fusevm replaces; static-link path is never
290    // invoked through addwrapper, so the body stays a no-op besides
291    // the prefix-match contract.
292    let _ = (prog, w);
293    0 // c:156
294}
295
296// =====================================================================
297// static struct builtin bintab[]                                     c:164
298// static struct conddef cotab[]                                      c:168
299// static struct paramdef patab[]                                     c:173
300// static struct mathfunc mftab[]                                     c:179
301// static struct funcwrap wrapper[]                                   c:184
302// static struct features module_features                             c:188
303//
304// These dispatch tables are consumed by the C module loader
305// (`featuresarray` + `handlefeatures` + `addwrapper`). The
306// static-link / fusevm path doesn't go through that registry; the
307// dispatcher in `src/extensions/` invokes `bin_example` etc.
308// directly. Tables omitted from the Rust port until the module-loader
309// port lands.
310// =====================================================================
311
312// =====================================================================
313// setup_(UNUSED(Module m))                                           c:198
314// =====================================================================
315
316/// Port of `setup_(UNUSED(Module m))` from `Src/Modules/example.c:198`.
317#[allow(unused_variables)]
318pub fn setup_(m: *const module) -> i32 {
319    let mut stdout = std::io::stdout().lock();
320    let _ = writeln!(stdout, "The example module has now been set up."); // c:207
321    let _ = stdout.flush(); // c:207
322    0 // c:207
323}
324
325// =====================================================================
326// features_(Module m, char ***features)                              c:207
327// =====================================================================
328
329/// Port of `features_(UNUSED(Module m), UNUSED(char ***features))` from `Src/Modules/example.c:207`.
330/// C body: `*features = featuresarray(m, &module_features); return 0;`
331pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 {
332    *features = featuresarray(m, module_features());
333    0 // c:215
334}
335
336// =====================================================================
337// enables_(Module m, int **enables)                                  c:215
338// =====================================================================
339
340/// Port of `enables_(UNUSED(Module m), UNUSED(int **enables))` from `Src/Modules/example.c:215`.
341/// C body: `return handlefeatures(m, &module_features, enables);`
342pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
343    handlefeatures(m, module_features(), enables) // c:222
344}
345
346// =====================================================================
347// boot_(Module m)                                                    c:222
348// =====================================================================
349
350/// Port of `boot_(UNUSED(Module m))` from `Src/Modules/example.c:222`.
351/// C body sets the demo paramdef-bound statics then calls
352/// `addwrapper(m, wrapper)`.
353pub fn boot_(m: *const module) -> i32 {
354    intparam.store(42, Ordering::Relaxed); // c:222
355    *strparam.lock().unwrap() = Some("example".to_string()); // c:225
356    *arrparam.lock().unwrap() = Some(vec![
357        // c:226-228
358        "example".to_string(), // c:227
359        "array".to_string(),   // c:228
360    ]);
361    // c:230 — addwrapper(m, wrapper); registers ex_wrapper into the
362    // global wrappers linked list (Src/module.c:577). zshrs's fusevm
363    // bytecode doesn't run through C's wrapper-dispatch chain, so the
364    // registration is a no-op until the wrapper machinery has a Rust
365    // equivalent.
366    let _ = m;
367    0
368}
369
370// =====================================================================
371// cleanup_(Module m)                                                 c:235
372// =====================================================================
373
374/// Port of `cleanup_(UNUSED(Module m))` from `Src/Modules/example.c:235`.
375/// C body: `deletewrapper(m, wrapper); return setfeatureenables(m, &module_features, NULL);`
376pub fn cleanup_(m: *const module) -> i32 {
377    // c:243 — deletewrapper(m, wrapper); paired with c:230 addwrapper,
378    // no-op until the wrapper machinery has a Rust equivalent.
379    setfeatureenables(m, module_features(), None) // c:243
380}
381
382// =====================================================================
383// finish_(UNUSED(Module m))                                          c:243
384// =====================================================================
385
386/// Port of `finish_(UNUSED(Module m))` from `Src/Modules/example.c:243`.
387#[allow(unused_variables)]
388pub fn finish_(m: *const module) -> i32 {
389    let mut stdout = std::io::stdout().lock();
390    let _ = writeln!(
391        stdout,
392        "Thank you for using the example module.  Have a nice day."
393    ); // c:245
394    let _ = stdout.flush(); // c:246
395    0 // c:247
396}
397
398// =====================================================================
399// External ported + tables — `static struct funcwrap wrapper[]` (c:184),
400// `static struct features module_features` (c:188), and
401// `featuresarray`/`handlefeatures`/`setfeatureenables`/`addwrapper`/
402// `deletewrapper` from `Src/module.c`.
403// =====================================================================
404
405// `bintab` — port of `static struct builtin bintab[]` (example.c:182).
406
407// `cotab` — port of `static struct conddef cotab[]` (example.c).
408// `CONDDEF("ex", CONDF_INFIX|CONDF_ADDED, …)` + `CONDDEF("len", 0, …)`.
409
410// `mftab` — port of `static struct mathfunc mftab[]` (example.c).
411
412// `patab` — port of `static struct paramdef patab[]` (example.c).
413
414// `module_features` — port of `static struct features module_features`
415// from example.c:188.
416
417static MODULE_FEATURES: OnceLock<Mutex<features>> = OnceLock::new();
418
419/// `static struct mathfunc mftab[]` (example.c:179). Initialized inline
420/// by `setfeatureenables` via `MFTAB.get_or_init` (single consumer); the
421/// `MFF_ADDED` bit setmathfuncs keeps on each row lives here, as it does
422/// on C's static table.
423static MFTAB: OnceLock<Mutex<Vec<crate::ported::zsh_h::mathfunc>>> = OnceLock::new();
424
425/// `static struct conddef cotab[]` (example.c:168-171). File-static in
426/// C, and the per-entry `CONDF_ADDED` bit setconddefs (Src/module.c:754)
427/// keeps on each row has to survive across calls, so it lives here.
428static COTAB: OnceLock<Mutex<Vec<crate::ported::zsh_h::conddef>>> = OnceLock::new();
429
430/// `static struct paramdef patab[]` (example.c:173-177). `d->pm` is the
431/// per-parameter enable bit setparamdefs (Src/module.c:1169) keeps.
432static PATAB: OnceLock<Mutex<Vec<crate::ported::zsh_h::paramdef>>> = OnceLock::new();
433
434// Local stubs for the per-module entry points. C uses generic
435// `featuresarray`/`handlefeatures`/`setfeatureenables` (module.c:
436// 3275/3370/3445) but those take `Builtin` + `Features` pointer
437// fields the Rust port doesn't carry. The hardcoded descriptor
438// list mirrors the C bintab/conddefs/mathfuncs/paramdefs.
439// WARNING: NOT IN EXAMPLE.C — Rust-only module-framework shim.
440// C uses generic featuresarray/handlefeatures/setfeatureenables from
441// Src/module.c:3275/3370/3445 with C-side Builtin/Features pointers;
442// Rust per-module shims hardcode the bintab/conddefs/mathfuncs/paramdefs.
443fn featuresarray(_m: *const module, _f: &Mutex<features>) -> Vec<String> {
444    vec![
445        "b:example".to_string(),
446        // c:169 — `CONDDEF("ex", CONDF_INFIX, …)`; featuresarray
447        // names an infix condition `C:` (Src/module.c:3299).
448        "C:ex".to_string(),
449        "c:len".to_string(),
450        "f:length".to_string(),
451        "f:sum".to_string(),
452        "p:exarr".to_string(),
453        "p:exint".to_string(),
454        "p:exstr".to_string(),
455    ]
456}
457
458// WARNING: NOT IN EXAMPLE.C — Rust-only module-framework shim.
459// C uses generic featuresarray/handlefeatures/setfeatureenables from
460// Src/module.c:3275/3370/3445 with C-side Builtin/Features pointers;
461// Rust per-module shims hardcode the bintab/conddefs/mathfuncs/paramdefs.
462fn handlefeatures(m: *const module, f: &Mutex<features>, enables: &mut Option<Vec<i32>>) -> i32 {
463    // c:3394-3395 — `if (!enables || *enables) return
464    // setfeatureenables(m, f, enables ? *enables : NULL);`
465    if let Some(e) = enables.as_ref() {
466        let e = e.clone();
467        return setfeatureenables(m, f, Some(&e));
468    }
469    // c:3396 — `*enables = getfeatureenables(m, f);` read from the
470    // name-keyed ADDED ledger in src/ported/module.rs (this module ships
471    // no `Features` descriptor tables for the bit to live on; see
472    // MODULE_FEATURE_ENABLES there).
473    crate::ported::module::handlefeatures("zsh/example", &featuresarray(m, f), enables)
474}
475
476// WARNING: NOT IN EXAMPLE.C — Rust-only module-framework shim.
477// C uses generic featuresarray/handlefeatures/setfeatureenables from
478// Src/module.c:3275/3370/3445 with C-side Builtin/Features pointers;
479// Rust per-module shims hardcode the bintab/conddefs/mathfuncs/paramdefs.
480fn setfeatureenables(m: *const module, f: &Mutex<features>, e: Option<&[i32]>) -> i32 {
481    // c:3354-3382 — walk bn/cd/mf/pd in that order, each block taking
482    // its slice of the positional enables bitmap; `e == NULL` disables
483    // everything. The b:/C:/c:/p: ADDED bits live in the name-keyed
484    // ledger (module.rs MODULE_FEATURE_ENABLES).
485    let mut ret = crate::ported::module::setfeatureenables("zsh/example", &featuresarray(m, f), e);
486    // c:3368-3372 — `if (f->mf_size) { if (setmathfuncs(m->node.nam,
487    // f->mf_list, f->mf_size, e)) ret = 1; … }`. mftab sits after the
488    // one bintab row (c:164) and the two cotab rows (c:168), so its
489    // slice of `e` starts after them. setmathfuncs (Src/module.c:1374)
490    // is what puts `length` / `sum` into the global `mathfuncs` list.
491    let (bn_size, cd_size, mf_size) = {
492        let g = f.lock().unwrap();
493        (g.bn_size as usize, g.cd_size as usize, g.mf_size as usize)
494    };
495    // `e` sliced per block, the `if (e) e += f->xx_size;` advance of
496    // c:3361/3366/3371; `e == NULL` stays NULL for every block.
497    let block = |start: usize, len: usize| -> Option<Vec<i32>> {
498        e.map(|a| (0..len).map(|i| a.get(start + i).copied().unwrap_or(0)).collect())
499    };
500    // c:3363-3367 — `if (f->cd_size) { if (setconddefs(m->node.nam,
501    // f->cd_list, f->cd_size, e)) ret = 1; … }`. This is what replaces the
502    // `zmodload -ac zsh/example ex` autoload stub with the real
503    // `cond_i_ex` handler, so the getconddef retry (Src/module.c:669-685)
504    // finds a module-less definition after the load.
505    let cd_e = block(bn_size, cd_size);
506    let cotab_mutex = COTAB.get_or_init(|| {
507        Mutex::new(vec![
508            crate::ported::zsh_h::CONDDEF("ex", crate::ported::module::CONDF_INFIX, cond_i_ex, 0, 0, 0), // c:169
509            crate::ported::zsh_h::CONDDEF("len", 0, cond_p_len, 1, 2, 0), // c:170
510        ])
511    });
512    {
513        let mut cotab = cotab_mutex.lock().unwrap();
514        if crate::ported::module::setconddefs("zsh/example", &mut cotab, cd_e.as_deref()) != 0 {
515            ret = 1; // c:3365
516        }
517    }
518    let mf_e: Option<Vec<i32>> = e.map(|a| {
519        (0..mf_size)
520            .map(|i| a.get(bn_size + cd_size + i).copied().unwrap_or(0))
521            .collect()
522    });
523    // `static struct mathfunc mftab[]` — example.c:179-182.
524    let tab_mutex = MFTAB.get_or_init(|| {
525        Mutex::new(vec![
526            crate::ported::zsh_h::STRMATHFUNC("length", math_length, 0), // c:180
527            crate::ported::zsh_h::NUMMATHFUNC("sum", math_sum, 1, -1, 0), // c:181
528        ])
529    });
530    {
531        let mut tab = tab_mutex.lock().unwrap();
532        if crate::ported::module::setmathfuncs("zsh/example", &mut tab, mf_e.as_deref()) != 0 {
533            ret = 1; // c:3370
534        }
535    }
536    // c:3373-3378 — `if (f->pd_size) { if (setparamdefs(m->node.nam,
537    // f->pd_list, f->pd_size, e)) ret = 1; }`. addparamdef (c:1060) is what
538    // replaces a `zmodload -ap zsh/example exint` autoload stub, so
539    // loadparamnode (Src/params.c:544-566) finds the parameter defined.
540    let pd_size = f.lock().unwrap().pd_size as usize;
541    let pd_e = block(bn_size + cd_size + mf_size, pd_size);
542    let patab_mutex = PATAB.get_or_init(|| {
543        Mutex::new(vec![
544            // C passes `&arrparam` / `&intparam` / `&strparam` as `var`;
545            // addparamdef's `pm->u.data = d->var` (Src/module.c:1079-1080)
546            // has no Rust slot yet, so `var` is 0.
547            crate::ported::zsh_h::ARRPARAMDEF("exarr", 0), // c:174
548            crate::ported::zsh_h::INTPARAMDEF("exint", 0), // c:175
549            crate::ported::zsh_h::STRPARAMDEF("exstr", 0), // c:176
550        ])
551    });
552    {
553        let mut patab = patab_mutex.lock().unwrap();
554        if crate::ported::module::setparamdefs("zsh/example", &mut patab, pd_e.as_deref()) != 0 {
555            ret = 1; // c:3377
556        }
557    }
558    ret // c:3382
559}
560
561// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
562// ─── RUST-ONLY ACCESSORS ───
563//
564// Singleton accessor ported for `OnceLock<Mutex<T>>` / `OnceLock<
565// RwLock<T>>` globals declared above. C zsh uses direct global
566// access; Rust needs these wrappers because `OnceLock::get_or_init`
567// is the only way to lazily construct shared state. These ported sit
568// here so the body of this file reads in C source order without
569// the accessor wrappers interleaved between real port ported.
570// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
571
572// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
573// ─── RUST-ONLY ACCESSORS ───
574//
575// Singleton accessor ported for `OnceLock<Mutex<T>>` / `OnceLock<
576// RwLock<T>>` globals declared above. C zsh uses direct global
577// access; Rust needs these wrappers because `OnceLock::get_or_init`
578// is the only way to lazily construct shared state. These ported sit
579// here so the body of this file reads in C source order without
580// the accessor wrappers interleaved between real port ported.
581// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
582
583// WARNING: NOT IN EXAMPLE.C — Rust-only module-framework shim.
584// C uses generic featuresarray/handlefeatures/setfeatureenables from
585// Src/module.c:3275/3370/3445 with C-side Builtin/Features pointers;
586// Rust per-module shims hardcode the bintab/conddefs/mathfuncs/paramdefs.
587fn module_features() -> &'static Mutex<features> {
588    MODULE_FEATURES.get_or_init(|| {
589        Mutex::new(features {
590            bn_list: None,
591            bn_size: 1,
592            cd_list: None,
593            cd_size: 2,
594            mf_list: None,
595            mf_size: 2,
596            pd_list: None,
597            pd_size: 3,
598            n_abstract: 0,
599        })
600    })
601}
602
603#[cfg(test)]
604mod tests {
605    use super::*;
606
607    fn empty_ops() -> options {
608        options {
609            ind: [0u8; MAX_OPS],
610            args: Vec::new(),
611            argscount: 0,
612            argsalloc: 0,
613        }
614    }
615    fn s(x: &str) -> String {
616        x.to_string()
617    }
618
619    /// Serialises tests that mutate `intparam` / `strparam` / `arrparam`
620    /// (paramdef bindings at `Src/Modules/example.c:208-218`). The C
621    /// source declares those as file-scope statics that `boot_` and
622    /// `bin_example` overwrite; zsh is single-threaded so the C source
623    /// is safe. cargo's parallel test runner can fire `boot_populates_demo_params`
624    /// and `bin_example_returns_zero_and_assigns_state` concurrently —
625    /// each then reads the values the other just wrote and fails. The
626    /// lock restores the single-writer assumption for the test phase.
627    static EXAMPLE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
628
629    /// Port of `boot_(UNUSED(Module m))` from `Src/Modules/example.c:222`.
630    /// Verifies `boot_()` populates the three paramdef-bound statics
631    /// per c:224-228: intparam=42, strparam="example",
632    /// arrparam=["example","array"].
633    #[test]
634    fn boot_populates_demo_params() {
635        let _g = crate::test_util::global_state_lock();
636        let _g = EXAMPLE_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
637        boot_(std::ptr::null());
638        assert_eq!(intparam.load(Ordering::SeqCst), 42);
639        assert_eq!(strparam.lock().unwrap().as_deref(), Some("example"));
640        let arr = arrparam.lock().unwrap();
641        let arr = arr.as_ref().expect("arrparam must be Some after boot_");
642        assert_eq!(arr.len(), 2);
643        assert_eq!(arr[0], "example");
644        assert_eq!(arr[1], "array");
645    }
646
647    /// Verifies `cond_p_len`'s two arities — c:84/89.
648    #[test]
649    fn cond_p_len_arities() {
650        let _g = crate::test_util::global_state_lock();
651        assert_eq!(cond_p_len(&[s("hello"), s("5")], 0), 1);
652        assert_eq!(cond_p_len(&[s("hello"), s("4")], 0), 0);
653        assert_eq!(cond_p_len(&[s("")], 0), 1);
654        assert_eq!(cond_p_len(&[s("x")], 0), 0);
655    }
656
657    /// Verifies `cond_i_ex` matches only the exact concat "example" — c:99.
658    #[test]
659    fn cond_i_ex_concat_matches_example() {
660        let _g = crate::test_util::global_state_lock();
661        assert_eq!(cond_i_ex(&[s("exam"), s("ple")], 0), 1);
662        assert_eq!(cond_i_ex(&[s("example"), s("")], 0), 1);
663        assert_eq!(cond_i_ex(&[s("example"), s("x")], 0), 0);
664        assert_eq!(cond_i_ex(&[s("foo"), s("bar")], 0), 0);
665    }
666
667    /// Verifies `math_sum` returns integer sum for all-int inputs and
668    /// promotes to float once a float arg appears — c:111/116/126.
669    #[test]
670    fn math_sum_int_then_float_promotion() {
671        let _g = crate::test_util::global_state_lock();
672        let ints = [
673            mnumber {
674                l: 1,
675                d: 0.0,
676                type_: MN_INTEGER,
677            },
678            mnumber {
679                l: 2,
680                d: 0.0,
681                type_: MN_INTEGER,
682            },
683            mnumber {
684                l: 3,
685                d: 0.0,
686                type_: MN_INTEGER,
687            },
688        ];
689        let r = math_sum("sum", 3, &ints, 0);
690        assert_eq!(r.type_, MN_INTEGER);
691        assert_eq!(r.l, 6);
692
693        let mixed = [
694            mnumber {
695                l: 1,
696                d: 0.0,
697                type_: MN_INTEGER,
698            },
699            mnumber {
700                l: 0,
701                d: 2.5,
702                type_: MN_FLOAT,
703            },
704            mnumber {
705                l: 3,
706                d: 0.0,
707                type_: MN_INTEGER,
708            },
709        ];
710        let r = math_sum("sum", 3, &mixed, 0);
711        assert_eq!(r.type_, MN_FLOAT);
712        assert!((r.d - 6.5).abs() < 1e-9);
713    }
714
715    /// Verifies `math_length` returns string length as integer — c:138.
716    #[test]
717    fn math_length_returns_strlen() {
718        let _g = crate::test_util::global_state_lock();
719        let r = math_length("length", "hello", 0);
720        assert_eq!(r.type_, MN_INTEGER);
721        assert_eq!(r.l, 5);
722    }
723
724    /// Verifies `ex_wrapper` returns 1 (skip) for non-matching names
725    /// and 0 (matched) for `example`-prefixed names — c:147/156.
726    #[test]
727    fn ex_wrapper_name_prefix_match() {
728        let _g = crate::test_util::global_state_lock();
729        assert_eq!(ex_wrapper(std::ptr::null(), std::ptr::null(), "foo"), 1);
730        assert_eq!(ex_wrapper(std::ptr::null(), std::ptr::null(), "exampl"), 1);
731        assert_eq!(ex_wrapper(std::ptr::null(), std::ptr::null(), "example"), 0);
732        assert_eq!(
733            ex_wrapper(std::ptr::null(), std::ptr::null(), "example_func"),
734            0
735        );
736    }
737
738    /// Port of `bin_example(char *nam, char **args, Options ops, UNUSED(int func))` from `Src/Modules/example.c:42`.
739    /// Verifies `bin_example` reads `OPT_ISSET(ops, c)` and prints flagged
740    /// option letters — c:49-51.
741    #[test]
742    fn bin_example_returns_zero_and_assigns_state() {
743        let _g = crate::test_util::global_state_lock();
744        let _g = EXAMPLE_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
745        let ops = empty_ops();
746        let args = vec![s("hello"), s("world")];
747        let rc = bin_example("example", &args, &ops, 0);
748        assert_eq!(rc, 0);
749        // c:69 i = 2 (two args); c:71 strparam = "hello"; c:74 arrparam = ["hello","world"]
750        assert_eq!(intparam.load(Ordering::Relaxed), 2);
751        assert_eq!(strparam.lock().unwrap().as_deref(), Some("hello"));
752        let arr = arrparam.lock().unwrap();
753        assert_eq!(arr.as_ref().unwrap().as_slice(), &[s("hello"), s("world")]);
754    }
755
756    /// c:104 — `math_sum` with zero args returns identity (0). Pin
757    /// the empty-input arithmetic identity.
758    #[test]
759    fn math_sum_zero_args_returns_zero() {
760        let _g = crate::test_util::global_state_lock();
761        let r = math_sum("sum", 0, &[], 0);
762        assert_eq!(r.type_, MN_INTEGER);
763        assert_eq!(r.l, 0, "sum of nothing must be 0");
764    }
765
766    /// c:104 — `math_sum` with a single integer is identity.
767    #[test]
768    fn math_sum_single_int_arg_is_identity() {
769        let _g = crate::test_util::global_state_lock();
770        let argv = [mnumber {
771            l: 42,
772            d: 0.0,
773            type_: MN_INTEGER,
774        }];
775        let r = math_sum("sum", 1, &argv, 0);
776        assert_eq!(r.type_, MN_INTEGER);
777        assert_eq!(r.l, 42);
778    }
779
780    /// c:104 — All-float input stays MN_FLOAT (no downcast). Pin
781    /// the float-preservation rule because a regen that prefers
782    /// integer "for tidiness" would silently truncate fractions.
783    #[test]
784    fn math_sum_all_floats_preserves_float_type() {
785        let _g = crate::test_util::global_state_lock();
786        let argv = [
787            mnumber {
788                l: 0,
789                d: 1.5,
790                type_: MN_FLOAT,
791            },
792            mnumber {
793                l: 0,
794                d: 2.5,
795                type_: MN_FLOAT,
796            },
797        ];
798        let r = math_sum("sum", 2, &argv, 0);
799        assert_eq!(r.type_, MN_FLOAT);
800        assert!((r.d - 4.0).abs() < 1e-9);
801    }
802
803    /// c:104 — Negative integers preserved.
804    #[test]
805    fn math_sum_handles_negative_ints() {
806        let _g = crate::test_util::global_state_lock();
807        let argv = [
808            mnumber {
809                l: -5,
810                d: 0.0,
811                type_: MN_INTEGER,
812            },
813            mnumber {
814                l: 3,
815                d: 0.0,
816                type_: MN_INTEGER,
817            },
818        ];
819        let r = math_sum("sum", 2, &argv, 0);
820        assert_eq!(r.type_, MN_INTEGER);
821        assert_eq!(r.l, -2, "−5 + 3 = −2");
822    }
823
824    /// c:133 — `math_length` on empty string returns 0. Pin so a
825    /// regen that adds `+ 1` for a NUL terminator gets caught.
826    #[test]
827    fn math_length_empty_string_returns_zero() {
828        let _g = crate::test_util::global_state_lock();
829        let r = math_length("length", "", 0);
830        assert_eq!(r.type_, MN_INTEGER);
831        assert_eq!(r.l, 0);
832    }
833
834    /// c:133 — Multi-byte UTF-8 yields BYTE count, not char count
835    /// (zsh's strlen semantics).
836    #[test]
837    fn math_length_multibyte_returns_byte_count() {
838        let _g = crate::test_util::global_state_lock();
839        // "café" = "caf" + "é" (é = 2 bytes in UTF-8) = 5 bytes
840        let r = math_length("length", "café", 0);
841        assert_eq!(
842            r.l, 5,
843            "math_length must count bytes, not chars — got {}",
844            r.l
845        );
846    }
847
848    /// c:95 — `cond_i_ex` (no-arg demo) returns 0 (false). Pin so a
849    /// regen that returns 1 (true) silently inverts every `[[ -i-ex ]]`.
850    #[test]
851    fn cond_i_ex_returns_zero() {
852        let _g = crate::test_util::global_state_lock();
853        let r = cond_i_ex(&[], 0);
854        assert_eq!(r, 0, "cond_i_ex demo condition must return 0 (false)");
855    }
856
857    /// c:286-335 — module-lifecycle stubs return 0.
858    #[test]
859    fn module_lifecycle_shims_all_return_zero() {
860        let _g = crate::test_util::global_state_lock();
861        let m: *const module = std::ptr::null();
862        assert_eq!(setup_(m), 0);
863        assert_eq!(boot_(m), 0);
864        assert_eq!(cleanup_(m), 0);
865        assert_eq!(finish_(m), 0);
866    }
867
868    // ─── zsh-corpus pins for math_sum / math_length ────────────────
869
870    /// `math_sum` of zero args returns integer 0.
871    #[test]
872    fn example_corpus_math_sum_empty_is_zero_integer() {
873        let _g = crate::test_util::global_state_lock();
874        let r = math_sum("sum", 0, &[], 0);
875        assert_eq!(r.l, 0);
876        assert_eq!(r.type_, MN_INTEGER, "empty sum stays integer-typed");
877    }
878
879    /// `math_sum` of three integers stays integer-typed.
880    #[test]
881    fn example_corpus_math_sum_int_args_stay_integer() {
882        let _g = crate::test_util::global_state_lock();
883        let args = vec![
884            mnumber {
885                l: 1,
886                d: 0.0,
887                type_: MN_INTEGER,
888            },
889            mnumber {
890                l: 2,
891                d: 0.0,
892                type_: MN_INTEGER,
893            },
894            mnumber {
895                l: 3,
896                d: 0.0,
897                type_: MN_INTEGER,
898            },
899        ];
900        let r = math_sum("sum", 3, &args, 0);
901        assert_eq!(r.l, 6, "1+2+3=6");
902        assert_eq!(r.type_, MN_INTEGER);
903    }
904
905    /// `math_sum` mixing int + float promotes to float per c:121.
906    #[test]
907    fn example_corpus_math_sum_mixed_promotes_to_float() {
908        let _g = crate::test_util::global_state_lock();
909        let args = vec![
910            mnumber {
911                l: 2,
912                d: 0.0,
913                type_: MN_INTEGER,
914            },
915            mnumber {
916                l: 0,
917                d: 1.5,
918                type_: MN_FLOAT,
919            },
920        ];
921        let r = math_sum("sum", 2, &args, 0);
922        assert_eq!(r.type_, MN_FLOAT, "int+float → float");
923        assert!((r.d - 3.5).abs() < 1e-9);
924    }
925
926    /// `math_sum` of two floats stays float-typed.
927    #[test]
928    fn example_corpus_math_sum_two_floats() {
929        let _g = crate::test_util::global_state_lock();
930        let args = vec![
931            mnumber {
932                l: 0,
933                d: 1.25,
934                type_: MN_FLOAT,
935            },
936            mnumber {
937                l: 0,
938                d: 2.75,
939                type_: MN_FLOAT,
940            },
941        ];
942        let r = math_sum("sum", 2, &args, 0);
943        assert_eq!(r.type_, MN_FLOAT);
944        assert!((r.d - 4.0).abs() < 1e-9);
945    }
946
947    /// `math_length("hello")` returns 5 as integer.
948    #[test]
949    fn example_corpus_math_length_ascii_byte_count() {
950        let _g = crate::test_util::global_state_lock();
951        let r = math_length("length", "hello", 0);
952        assert_eq!(r.l, 5);
953        assert_eq!(r.type_, MN_INTEGER);
954    }
955
956    /// `math_length("")` returns 0.
957    #[test]
958    fn example_corpus_math_length_empty_is_zero() {
959        let _g = crate::test_util::global_state_lock();
960        let r = math_length("length", "", 0);
961        assert_eq!(r.l, 0);
962    }
963
964    /// `math_length` counts UTF-8 BYTES not codepoints (per C strlen).
965    #[test]
966    fn example_corpus_math_length_multibyte_byte_count() {
967        let _g = crate::test_util::global_state_lock();
968        // '日' is 3 bytes in UTF-8.
969        let r = math_length("length", "日", 0);
970        assert_eq!(r.l, 3, "C strlen-style byte count for UTF-8");
971    }
972
973    /// `cond_p_len(["abc"], 0)` returns 0 (the demo cond is len-3 check).
974    /// Pin existing behavior — `cond_p_len` evaluates len == 3 → "false"
975    /// (per zsh's cond return convention: 0 = true, 1 = false).
976    #[test]
977    fn example_corpus_cond_p_len_does_not_panic() {
978        let _g = crate::test_util::global_state_lock();
979        let _ = cond_p_len(&["abc".to_string()], 0);
980        // Just pin: no panic.
981    }
982
983    // ═══════════════════════════════════════════════════════════════════
984    // Additional C-parity tests for Src/Modules/example.c.
985    // ═══════════════════════════════════════════════════════════════════
986
987    /// c:80-95 — `cond_p_len([""], 0)` with single empty-string arg:
988    /// no second arg, so c:95 branch fires `return !s1[0]` = !"" = 1.
989    #[test]
990    fn cond_p_len_empty_single_arg_returns_one() {
991        let _g = crate::test_util::global_state_lock();
992        let r = cond_p_len(&["".to_string()], 0);
993        assert_eq!(r, 1, "empty s1 with no len arg → !s1[0] = 1");
994    }
995
996    /// c:80-87 — `cond_p_len([s, "N"])` returns 1 when len(s) == N,
997    /// 0 otherwise.
998    #[test]
999    fn cond_p_len_length_match_returns_one() {
1000        let _g = crate::test_util::global_state_lock();
1001        // s1="abc" (len=3), arg[1]="3" → 1.
1002        let r = cond_p_len(&["abc".to_string(), "3".to_string()], 0);
1003        assert_eq!(r, 1, "len(abc) == 3 → 1");
1004    }
1005
1006    /// c:80-87 — len mismatch returns 0.
1007    #[test]
1008    fn cond_p_len_length_mismatch_returns_zero() {
1009        let _g = crate::test_util::global_state_lock();
1010        let r = cond_p_len(&["abc".to_string(), "5".to_string()], 0);
1011        assert_eq!(r, 0, "len(abc) != 5 → 0");
1012    }
1013
1014    /// c:95-104 — `cond_i_ex(["exam", "ple"])` returns 1
1015    /// (dyncat == "example").
1016    #[test]
1017    fn cond_i_ex_concatenation_to_example_returns_one() {
1018        let _g = crate::test_util::global_state_lock();
1019        let r = cond_i_ex(&["exam".to_string(), "ple".to_string()], 0);
1020        assert_eq!(r, 1, "exam+ple = 'example' → 1");
1021    }
1022
1023    /// c:95-104 — non-matching concat returns 0.
1024    #[test]
1025    fn cond_i_ex_non_matching_concat_returns_zero() {
1026        let _g = crate::test_util::global_state_lock();
1027        let r = cond_i_ex(&["foo".to_string(), "bar".to_string()], 0);
1028        assert_eq!(r, 0, "foo+bar = 'foobar' != 'example' → 0");
1029    }
1030
1031    /// c:95-104 — empty inputs (dyncat("","") = "" != "example") → 0.
1032    #[test]
1033    fn cond_i_ex_empty_inputs_returns_zero() {
1034        let _g = crate::test_util::global_state_lock();
1035        let r = cond_i_ex(&["".to_string(), "".to_string()], 0);
1036        assert_eq!(r, 0, "'' != 'example' → 0");
1037    }
1038
1039    /// c:104 — `math_sum([])` with empty args returns 0 (integer init).
1040    #[test]
1041    fn math_sum_empty_returns_zero() {
1042        let _g = crate::test_util::global_state_lock();
1043        let r = math_sum("sum", 0, &[], 0);
1044        assert_eq!(r.l, 0);
1045        assert_eq!(r.type_, MN_INTEGER);
1046    }
1047
1048    /// c:104-115 — `math_sum` of two integers returns integer sum.
1049    #[test]
1050    fn math_sum_two_integers_returns_integer_sum() {
1051        let _g = crate::test_util::global_state_lock();
1052        let args = vec![
1053            mnumber {
1054                l: 3,
1055                d: 0.0,
1056                type_: MN_INTEGER,
1057            },
1058            mnumber {
1059                l: 4,
1060                d: 0.0,
1061                type_: MN_INTEGER,
1062            },
1063        ];
1064        let r = math_sum("sum", 2, &args, 0);
1065        assert_eq!(r.l, 7, "3 + 4 = 7");
1066        assert_eq!(r.type_, MN_INTEGER);
1067    }
1068
1069    /// c:250 — `math_length` of empty string returns 0.
1070    #[test]
1071    fn math_length_empty_returns_zero_pin() {
1072        let _g = crate::test_util::global_state_lock();
1073        let r = math_length("length", "", 0);
1074        assert_eq!(r.l, 0);
1075    }
1076
1077    /// c:250 — `math_length` returns byte count (not codepoints).
1078    #[test]
1079    fn math_length_ascii_returns_byte_count() {
1080        let _g = crate::test_util::global_state_lock();
1081        let r = math_length("length", "hello", 0);
1082        assert_eq!(r.l, 5);
1083    }
1084
1085    // ═══════════════════════════════════════════════════════════════════
1086    // Additional C-parity tests for Src/Modules/example.c
1087    // c:70 bin_example / c:149 cond_p_len / c:179 cond_i_ex
1088    // c:198 math_sum / c:250 math_length / c:273 ex_wrapper / lifecycle
1089    // ═══════════════════════════════════════════════════════════════════
1090
1091    /// c:149 — cond_p_len returns 0/1 only (boolean i32).
1092    #[test]
1093    fn cond_p_len_return_value_in_boolean_set() {
1094        let _g = crate::test_util::global_state_lock();
1095        for args in [
1096            vec![s(""), s("")],
1097            vec![s("abc"), s("3")],
1098            vec![s("abc"), s("4")],
1099            vec![s(""), s("0")],
1100        ] {
1101            let r = cond_p_len(&args, 0);
1102            assert!(
1103                r == 0 || r == 1,
1104                "cond_p_len({:?}) = {} not in {{0,1}}",
1105                args,
1106                r
1107            );
1108        }
1109    }
1110
1111    /// c:179 — cond_i_ex returns 0/1 only (boolean i32).
1112    #[test]
1113    fn cond_i_ex_return_value_in_boolean_set() {
1114        let _g = crate::test_util::global_state_lock();
1115        for args in [
1116            vec![s(""), s("")],
1117            vec![s("ex"), s("ample")],
1118            vec![s("foo"), s("bar")],
1119        ] {
1120            let r = cond_i_ex(&args, 0);
1121            assert!(
1122                r == 0 || r == 1,
1123                "cond_i_ex({:?}) = {} not in {{0,1}}",
1124                args,
1125                r
1126            );
1127        }
1128    }
1129
1130    /// c:198 — math_sum returns mnumber type (compile-time pinning).
1131    #[test]
1132    fn math_sum_returns_mnumber_type() {
1133        let _g = crate::test_util::global_state_lock();
1134        let _: mnumber = math_sum("sum", 0, &[], 0);
1135    }
1136
1137    /// c:250 — math_length returns mnumber type.
1138    #[test]
1139    fn math_length_returns_mnumber_type() {
1140        let _g = crate::test_util::global_state_lock();
1141        let _: mnumber = math_length("length", "x", 0);
1142    }
1143
1144    /// c:250 — `math_length` is deterministic for any input.
1145    #[test]
1146    fn math_length_is_deterministic() {
1147        let _g = crate::test_util::global_state_lock();
1148        let first = math_length("length", "hello", 0).l;
1149        for _ in 0..5 {
1150            assert_eq!(math_length("length", "hello", 0).l, first);
1151        }
1152    }
1153
1154    /// c:70 — bin_example empty args returns 0 (default success).
1155    #[test]
1156    fn bin_example_no_args_returns_zero_or_one() {
1157        let _g = crate::test_util::global_state_lock();
1158        let ops = empty_ops();
1159        let r = bin_example("example", &[], &ops, 0);
1160        assert!(r == 0 || r == 1, "result must be 0 or 1, got {}", r);
1161    }
1162
1163    /// c:198 — `math_sum(empty)` returns integer 0 (l=0, type integer).
1164    #[test]
1165    fn math_sum_empty_is_canonical_zero_integer() {
1166        let _g = crate::test_util::global_state_lock();
1167        let r = math_sum("sum", 0, &[], 0);
1168        assert_eq!(r.l, 0, "empty args → l=0");
1169    }
1170
1171    /// c:273 — `ex_wrapper(null, null, "")` is safe (empty name).
1172    #[test]
1173    fn ex_wrapper_empty_name_no_panic() {
1174        let _g = crate::test_util::global_state_lock();
1175        let _ = ex_wrapper(std::ptr::null(), std::ptr::null(), "");
1176    }
1177
1178    /// c:318-388 — full lifecycle setup→features→enables→boot→cleanup→finish.
1179    #[test]
1180    fn example_full_lifecycle_returns_zero_for_all() {
1181        let _g = crate::test_util::global_state_lock();
1182        let null = std::ptr::null();
1183        assert_eq!(setup_(null), 0);
1184        let mut feats = Vec::new();
1185        let _ = features_(null, &mut feats);
1186        let mut enables: Option<Vec<i32>> = None;
1187        let _ = enables_(null, &mut enables);
1188        assert_eq!(boot_(null), 0);
1189        assert_eq!(cleanup_(null), 0);
1190        assert_eq!(finish_(null), 0);
1191    }
1192
1193    /// c:318 — setup_ idempotent.
1194    #[test]
1195    fn example_setup_idempotent() {
1196        let _g = crate::test_util::global_state_lock();
1197        for _ in 0..10 {
1198            assert_eq!(setup_(std::ptr::null()), 0);
1199        }
1200    }
1201
1202    // ═══════════════════════════════════════════════════════════════════
1203    // Additional C-parity tests for Src/Modules/example.c
1204    // c:149 cond_p_len / c:179 cond_i_ex / c:198 math_sum / c:250 math_length
1205    // ═══════════════════════════════════════════════════════════════════
1206
1207    /// c:149 — `cond_p_len` returns i32 (compile-time pin).
1208    #[test]
1209    fn cond_p_len_returns_i32_type() {
1210        let _g = crate::test_util::global_state_lock();
1211        let _: i32 = cond_p_len(&[], 0);
1212    }
1213
1214    /// c:149 — `cond_p_len([empty])` (single empty-string arg) returns 1
1215    /// per c:95 `return !s1[0]`.
1216    #[test]
1217    fn cond_p_len_single_empty_returns_one() {
1218        let _g = crate::test_util::global_state_lock();
1219        let r = cond_p_len(&["".to_string()], 0);
1220        assert_eq!(r, 1, "single empty-arg case must match `!s1[0]` → 1");
1221    }
1222
1223    /// c:149 — return value is boolean 0 or 1 only.
1224    #[test]
1225    fn cond_p_len_returns_boolean_i32_only() {
1226        let _g = crate::test_util::global_state_lock();
1227        for a in [
1228            vec!["".to_string()],
1229            vec!["hello".to_string()],
1230            vec!["hello".to_string(), "5".to_string()],
1231            vec!["hello".to_string(), "0".to_string()],
1232        ] {
1233            let r = cond_p_len(&a, 0);
1234            assert!(
1235                r == 0 || r == 1,
1236                "cond_p_len({:?}) must return 0 or 1; got {}",
1237                a,
1238                r
1239            );
1240        }
1241    }
1242
1243    /// c:179 — `cond_i_ex` returns i32 (compile-time pin).
1244    #[test]
1245    fn cond_i_ex_returns_i32_type() {
1246        let _g = crate::test_util::global_state_lock();
1247        let _: i32 = cond_i_ex(&["".to_string(), "".to_string()], 0);
1248    }
1249
1250    /// c:179 — `cond_i_ex(["exam", "ple"])` concat = "example" → returns 1.
1251    #[test]
1252    fn cond_i_ex_split_example_returns_one() {
1253        let _g = crate::test_util::global_state_lock();
1254        let r = cond_i_ex(&["exam".to_string(), "ple".to_string()], 0);
1255        assert_eq!(r, 1, "exam+ple = example → 1");
1256    }
1257
1258    /// c:179 — `cond_i_ex(["foo", "bar"])` concat ≠ "example" → returns 0.
1259    #[test]
1260    fn cond_i_ex_mismatch_returns_zero() {
1261        let _g = crate::test_util::global_state_lock();
1262        let r = cond_i_ex(&["foo".to_string(), "bar".to_string()], 0);
1263        assert_eq!(r, 0, "foo+bar ≠ example → 0");
1264    }
1265
1266    /// c:198 — `math_sum` of integer arguments yields MN_INTEGER.
1267    #[test]
1268    fn math_sum_all_integers_returns_integer_type() {
1269        let _g = crate::test_util::global_state_lock();
1270        let argv = vec![
1271            mnumber {
1272                l: 3,
1273                d: 0.0,
1274                type_: MN_INTEGER,
1275            },
1276            mnumber {
1277                l: 5,
1278                d: 0.0,
1279                type_: MN_INTEGER,
1280            },
1281        ];
1282        let r = math_sum("sum", 2, &argv, 0);
1283        assert_eq!(r.type_, MN_INTEGER, "all-int sum must be MN_INTEGER");
1284        assert_eq!(r.l, 8, "3+5=8");
1285    }
1286
1287    /// c:198 — `math_sum` with any float arg promotes result to MN_FLOAT.
1288    #[test]
1289    fn math_sum_mixed_promotes_to_float() {
1290        let _g = crate::test_util::global_state_lock();
1291        let argv = vec![
1292            mnumber {
1293                l: 3,
1294                d: 0.0,
1295                type_: MN_INTEGER,
1296            },
1297            mnumber {
1298                l: 0,
1299                d: 2.5,
1300                type_: MN_FLOAT,
1301            },
1302        ];
1303        let r = math_sum("sum", 2, &argv, 0);
1304        assert_eq!(
1305            r.type_, MN_FLOAT,
1306            "mixed int+float must promote to MN_FLOAT"
1307        );
1308        assert!((r.d - 5.5).abs() < 1e-9, "3+2.5 = 5.5, got {}", r.d);
1309    }
1310
1311    /// c:250 — `math_length("")` returns l=0 + MN_INTEGER (combined pin, alt name).
1312    #[test]
1313    fn math_length_empty_string_returns_zero_and_int_type() {
1314        let _g = crate::test_util::global_state_lock();
1315        let r = math_length("length", "", 0);
1316        assert_eq!(r.l, 0, "len('') = 0");
1317        assert_eq!(r.type_, MN_INTEGER, "math_length is always MN_INTEGER");
1318    }
1319
1320    /// c:250 — `math_length(arg)` matches arg.len() (byte length per
1321    /// c:138 `strlen(arg)`).
1322    #[test]
1323    fn math_length_matches_byte_length_for_ascii() {
1324        let _g = crate::test_util::global_state_lock();
1325        for s in ["x", "hello", "a longer string here"] {
1326            let r = math_length("length", s, 0);
1327            assert_eq!(
1328                r.l,
1329                s.len() as i64,
1330                "math_length({:?}) must equal byte length {}",
1331                s,
1332                s.len()
1333            );
1334        }
1335    }
1336
1337    /// c:198 — `math_sum` empty argv is canonically integer-typed
1338    /// (`f=0` path at c:133 → `type_ = MN_INTEGER`).
1339    #[test]
1340    fn math_sum_empty_is_integer_type() {
1341        let _g = crate::test_util::global_state_lock();
1342        let r = math_sum("sum", 0, &[], 0);
1343        assert_eq!(
1344            r.type_, MN_INTEGER,
1345            "empty sum must be MN_INTEGER (f stayed 0)"
1346        );
1347    }
1348
1349    // ═══════════════════════════════════════════════════════════════════
1350    // Additional C-parity pins for Src/Modules/example.c
1351    // c:70 bin_example / c:149 cond_p_len / c:179 cond_i_ex /
1352    // c:198 math_sum / c:250 math_length / c:318-388 lifecycle hooks
1353    // ═══════════════════════════════════════════════════════════════════
1354
1355    /// c:198 — `math_sum` empty argv: l field is 0.
1356    #[test]
1357    fn math_sum_empty_l_is_zero() {
1358        let _g = crate::test_util::global_state_lock();
1359        let r = math_sum("sum", 0, &[], 0);
1360        assert_eq!(r.l, 0, "empty sum's integer field must be 0");
1361    }
1362
1363    /// c:250 — `math_length("")` empty string has length 0.
1364    #[test]
1365    fn math_length_empty_string_is_zero() {
1366        let _g = crate::test_util::global_state_lock();
1367        let r = math_length("length", "", 0);
1368        assert_eq!(r.l, 0);
1369        assert_eq!(r.type_, MN_INTEGER);
1370    }
1371
1372    /// c:149 — `cond_p_len("")` empty operand returns boolean i32.
1373    #[test]
1374    fn cond_p_len_empty_operand_returns_i32() {
1375        let _g = crate::test_util::global_state_lock();
1376        let _: i32 = cond_p_len(&["".to_string()], 0);
1377    }
1378
1379    /// c:179 — `cond_i_ex` empty args returns i32 (alt).
1380    #[test]
1381    fn cond_i_ex_returns_i32_type_alt() {
1382        let _g = crate::test_util::global_state_lock();
1383        let _: i32 = cond_i_ex(&[], 0);
1384    }
1385
1386    /// c:70 — `bin_example` empty args non-negative.
1387    #[test]
1388    fn bin_example_empty_args_non_negative() {
1389        let _g = crate::test_util::global_state_lock();
1390        let ops = crate::ported::zsh_h::options {
1391            ind: [0u8; crate::ported::zsh_h::MAX_OPS],
1392            args: Vec::new(),
1393            argscount: 0,
1394            argsalloc: 0,
1395        };
1396        let r = bin_example("example", &[], &ops, 0);
1397        assert!(r >= 0, "bin_example empty must be ≥ 0, got {}", r);
1398    }
1399
1400    /// c:70 — `bin_example` various func values don't panic.
1401    #[test]
1402    fn bin_example_various_func_values_no_panic() {
1403        let _g = crate::test_util::global_state_lock();
1404        let ops = crate::ported::zsh_h::options {
1405            ind: [0u8; crate::ported::zsh_h::MAX_OPS],
1406            args: Vec::new(),
1407            argscount: 0,
1408            argsalloc: 0,
1409        };
1410        for func in [-1, 0, 1, 100, i32::MAX] {
1411            let _ = bin_example("example", &[], &ops, func);
1412        }
1413    }
1414
1415    /// c:198 — `math_sum` is pure (same input → same output).
1416    #[test]
1417    fn math_sum_is_deterministic_for_empty_argv() {
1418        let _g = crate::test_util::global_state_lock();
1419        let r1 = math_sum("sum", 0, &[], 0);
1420        let r2 = math_sum("sum", 0, &[], 0);
1421        assert_eq!(r1.l, r2.l);
1422        assert_eq!(r1.type_, r2.type_);
1423    }
1424
1425    /// c:250 — `math_length` is pure across calls (alt).
1426    #[test]
1427    fn math_length_is_deterministic_alt() {
1428        let _g = crate::test_util::global_state_lock();
1429        for s in ["", "x", "hello"] {
1430            let a = math_length("length", s, 0);
1431            let b = math_length("length", s, 0);
1432            assert_eq!(a.l, b.l);
1433            assert_eq!(a.type_, b.type_);
1434        }
1435    }
1436
1437    /// c:318 — `setup_` is idempotent.
1438    #[test]
1439    fn example_setup_idempotent_alt_pin() {
1440        let _g = crate::test_util::global_state_lock();
1441        for _ in 0..10 {
1442            assert_eq!(setup_(std::ptr::null()), 0);
1443        }
1444    }
1445
1446    /// c:376 — `cleanup_` is idempotent.
1447    #[test]
1448    fn example_cleanup_idempotent_alt_pin() {
1449        let _g = crate::test_util::global_state_lock();
1450        for _ in 0..10 {
1451            assert_eq!(cleanup_(std::ptr::null()), 0);
1452        }
1453    }
1454
1455    /// c:388 — `finish_` is idempotent.
1456    #[test]
1457    fn example_finish_idempotent_alt_pin() {
1458        let _g = crate::test_util::global_state_lock();
1459        for _ in 0..10 {
1460            assert_eq!(finish_(std::ptr::null()), 0);
1461        }
1462    }
1463
1464    /// c:149 — `cond_p_len` is pure across calls.
1465    #[test]
1466    fn cond_p_len_is_deterministic() {
1467        let _g = crate::test_util::global_state_lock();
1468        let args = vec!["hi".to_string()];
1469        for _ in 0..3 {
1470            let a = cond_p_len(&args, 0);
1471            let b = cond_p_len(&args, 0);
1472            assert_eq!(a, b, "cond_p_len must be pure");
1473        }
1474    }
1475}