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}