File Coverage

FuncUtil.c
Criterion Covered Total %
statement 3291 3773 87.2
branch 1618 2664 60.7
condition n/a
subroutine n/a
pod n/a
total 4909 6437 76.2


line stmt bran cond sub pod time code
1             #define _GNU_SOURCE
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5             #include "funcutil_compat.h"
6             #include "multicall_compat.h"
7             #include
8              
9             /* Portable memmem - use system version if available, else our own */
10             #ifndef HAVE_MEMMEM
11             #if defined(__GLIBC__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
12             #define HAVE_MEMMEM 1
13             #endif
14             #endif
15              
16             #if HAVE_MEMMEM
17             #define util_memmem memmem
18             #else
19             static void *util_memmem(const void *haystack, size_t haystacklen,
20             const void *needle, size_t needlelen) {
21             if (needlelen == 0) return (void*)haystack;
22             if (needlelen > haystacklen) return NULL;
23            
24             const char *h = (const char*)haystack;
25             const char *n = (const char*)needle;
26             const char *end = h + haystacklen - needlelen + 1;
27             char first = *n;
28            
29             for (; h < end; h++) {
30             if (*h == first && memcmp(h, n, needlelen) == 0) {
31             return (void*)h;
32             }
33             }
34             return NULL;
35             }
36             #endif
37              
38             /* ============================================
39             Custom op structures
40             ============================================ */
41              
42             static XOP identity_xop;
43             static XOP always_xop;
44             static XOP clamp_xop;
45             static XOP nvl_xop;
46             static XOP coalesce_xop;
47              
48             /* Type predicate custom ops - blazing fast, single SV flag check */
49             static XOP is_ref_xop;
50             static XOP is_array_xop;
51             static XOP is_hash_xop;
52             static XOP is_code_xop;
53             static XOP is_defined_xop;
54              
55             /* String predicate custom ops - direct SvPV/SvCUR access */
56             static XOP is_empty_xop;
57             static XOP starts_with_xop;
58             static XOP ends_with_xop;
59             /* Boolean/Truthiness custom ops - fast truth checks */
60             static XOP is_true_xop;
61             static XOP is_false_xop;
62             static XOP bool_xop;
63              
64             /* Extended type predicate custom ops */
65             static XOP is_num_xop;
66             static XOP is_int_xop;
67             static XOP is_blessed_xop;
68             static XOP is_scalar_ref_xop;
69             static XOP is_regex_xop;
70             static XOP is_glob_xop;
71             static XOP is_string_xop;
72              
73             /* Numeric predicate custom ops */
74             static XOP is_positive_xop;
75             static XOP is_negative_xop;
76             static XOP is_zero_xop;
77              
78             /* Numeric utility custom ops */
79             static XOP is_even_xop;
80             static XOP is_odd_xop;
81             static XOP is_between_xop;
82              
83             /* Collection custom ops - direct AvFILL/HvKEYS access */
84             static XOP is_empty_array_xop;
85             static XOP is_empty_hash_xop;
86             static XOP array_len_xop;
87             static XOP hash_size_xop;
88             static XOP array_first_xop;
89             static XOP array_last_xop;
90              
91             /* String manipulation custom ops */
92             static XOP trim_xop;
93             static XOP ltrim_xop;
94             static XOP rtrim_xop;
95              
96             /* Conditional custom ops */
97             static XOP maybe_xop;
98              
99             /* Numeric custom ops */
100             static XOP sign_xop;
101             static XOP min2_xop;
102             static XOP max2_xop;
103              
104             /* ============================================
105             Memoization structures
106             ============================================ */
107              
108             typedef struct {
109             SV *func; /* Original coderef */
110             HV *cache; /* Result cache */
111             IV hits; /* Cache hits (stats) */
112             IV misses; /* Cache misses (stats) */
113             } MemoizedFunc;
114              
115             static MemoizedFunc *g_memos = NULL;
116             static IV g_memo_size = 0;
117             static IV g_memo_count = 0;
118              
119             /* ============================================
120             Lazy evaluation structures
121             ============================================ */
122              
123             typedef struct {
124             SV *thunk; /* Deferred computation (coderef) */
125             SV *value; /* Cached result */
126             bool forced; /* Has been evaluated? */
127             } LazyValue;
128              
129             static LazyValue *g_lazies = NULL;
130             static IV g_lazy_size = 0;
131             static IV g_lazy_count = 0;
132              
133             /* ============================================
134             Always (constant) structures
135             ============================================ */
136              
137             static SV **g_always_values = NULL;
138             static IV g_always_size = 0;
139             static IV g_always_count = 0;
140              
141             /* ============================================
142             Once (execute once) structures
143             ============================================ */
144              
145             typedef struct {
146             SV *func; /* Original function */
147             SV *result; /* Cached result */
148             bool called; /* Has been called? */
149             } OnceFunc;
150              
151             static OnceFunc *g_onces = NULL;
152             static IV g_once_size = 0;
153             static IV g_once_count = 0;
154              
155             /* ============================================
156             Partial application structures
157             ============================================ */
158              
159             typedef struct {
160             SV *func; /* Original function */
161             AV *bound_args; /* Pre-bound arguments */
162             } PartialFunc;
163              
164             static PartialFunc *g_partials = NULL;
165             static IV g_partial_size = 0;
166             static IV g_partial_count = 0;
167              
168             /* ============================================
169             Loop callback registry structures
170             ============================================ */
171              
172             /* Function pointer types for loop callbacks */
173             typedef bool (*UtilPredicateFunc)(pTHX_ SV *elem);
174             typedef SV* (*UtilMapFunc)(pTHX_ SV *elem);
175             typedef SV* (*UtilReduceFunc)(pTHX_ SV *accum, SV *elem);
176              
177             /* Registered callback entry */
178             typedef struct {
179             char *name; /* Callback name (e.g., ":is_positive") */
180             UtilPredicateFunc predicate; /* C function for predicates */
181             UtilMapFunc mapper; /* C function for map */
182             UtilReduceFunc reducer; /* C function for reduce */
183             SV *perl_callback; /* Fallback Perl callback */
184             } RegisteredCallback;
185              
186             /* Global callback registry */
187             static HV *g_callback_registry = NULL;
188              
189             /* ============================================
190             Forward declarations
191             ============================================ */
192              
193             XS_INTERNAL(xs_memo_call);
194             XS_INTERNAL(xs_compose_call);
195             XS_INTERNAL(xs_always_call);
196             XS_INTERNAL(xs_negate_call);
197             XS_INTERNAL(xs_once_call);
198             XS_INTERNAL(xs_partial_call);
199              
200             /* ============================================
201             Magic destructor infrastructure
202             ============================================ */
203              
204             /* Magic free function for "once" wrappers */
205 1005           static int util_once_free(pTHX_ SV *sv, MAGIC *mg) {
206             PERL_UNUSED_ARG(sv);
207 1005           IV idx = mg->mg_len;
208 1005 50         if (idx >= 0 && idx < g_once_count) {
    50          
209 1005           OnceFunc *of = &g_onces[idx];
210 1005 50         if (of->func) {
211 0           SvREFCNT_dec(of->func);
212 0           of->func = NULL;
213             }
214 1005 50         if (of->result) {
215 1005           SvREFCNT_dec(of->result);
216 1005           of->result = NULL;
217             }
218 1005           of->called = FALSE;
219             }
220 1005           return 0;
221             }
222              
223             static MGVTBL util_once_vtbl = {
224             NULL, /* get */
225             NULL, /* set */
226             NULL, /* len */
227             NULL, /* clear */
228             util_once_free, /* free */
229             NULL, /* copy */
230             NULL, /* dup */
231             NULL /* local */
232             };
233              
234             /* Magic free function for "partial" wrappers */
235 1012           static int util_partial_free(pTHX_ SV *sv, MAGIC *mg) {
236             PERL_UNUSED_ARG(sv);
237 1012           IV idx = mg->mg_len;
238 1012 50         if (idx >= 0 && idx < g_partial_count) {
    50          
239 1012           PartialFunc *pf = &g_partials[idx];
240 1012 50         if (pf->func) {
241 1012           SvREFCNT_dec(pf->func);
242 1012           pf->func = NULL;
243             }
244 1012 50         if (pf->bound_args) {
245 1012           SvREFCNT_dec((SV*)pf->bound_args);
246 1012           pf->bound_args = NULL;
247             }
248             }
249 1012           return 0;
250             }
251              
252             static MGVTBL util_partial_vtbl = {
253             NULL, NULL, NULL, NULL, util_partial_free, NULL, NULL, NULL
254             };
255              
256             /* Magic free function for "memo" wrappers */
257 212           static int util_memo_free(pTHX_ SV *sv, MAGIC *mg) {
258             PERL_UNUSED_ARG(sv);
259 212           IV idx = mg->mg_len;
260 212 50         if (idx >= 0 && idx < g_memo_count) {
    50          
261 212           MemoizedFunc *mf = &g_memos[idx];
262 212 50         if (mf->func) {
263 212           SvREFCNT_dec(mf->func);
264 212           mf->func = NULL;
265             }
266 212 50         if (mf->cache) {
267 212           SvREFCNT_dec((SV*)mf->cache);
268 212           mf->cache = NULL;
269             }
270 212           mf->hits = 0;
271 212           mf->misses = 0;
272             }
273 212           return 0;
274             }
275              
276             static MGVTBL util_memo_vtbl = {
277             NULL, NULL, NULL, NULL, util_memo_free, NULL, NULL, NULL
278             };
279              
280             /* Magic free function for "lazy" wrappers */
281 1008           static int util_lazy_free(pTHX_ SV *sv, MAGIC *mg) {
282             PERL_UNUSED_ARG(sv);
283 1008           IV idx = mg->mg_len;
284 1008 50         if (idx >= 0 && idx < g_lazy_count) {
    50          
285 1008           LazyValue *lv = &g_lazies[idx];
286 1008 50         if (lv->thunk) {
287 0           SvREFCNT_dec(lv->thunk);
288 0           lv->thunk = NULL;
289             }
290 1008 50         if (lv->value) {
291 1008           SvREFCNT_dec(lv->value);
292 1008           lv->value = NULL;
293             }
294 1008           lv->forced = FALSE;
295             }
296 1008           return 0;
297             }
298              
299             static MGVTBL util_lazy_vtbl = {
300             NULL, NULL, NULL, NULL, util_lazy_free, NULL, NULL, NULL
301             };
302              
303             /* Magic free function for "compose" wrappers */
304 1010           static int util_compose_free(pTHX_ SV *sv, MAGIC *mg) {
305             PERL_UNUSED_ARG(sv);
306 1010           AV *funcs = (AV*)mg->mg_ptr;
307 1010 50         if (funcs) {
308 1010           SvREFCNT_dec((SV*)funcs);
309             }
310 1010           return 0;
311             }
312              
313             static MGVTBL util_compose_vtbl = {
314             NULL, NULL, NULL, NULL, util_compose_free, NULL, NULL, NULL
315             };
316              
317             /* Magic free function for "always" wrappers */
318 8           static int util_always_free(pTHX_ SV *sv, MAGIC *mg) {
319             PERL_UNUSED_ARG(sv);
320 8           IV idx = mg->mg_len;
321 8 50         if (idx >= 0 && idx < g_always_count && g_always_values[idx]) {
    50          
    50          
322 8           SvREFCNT_dec(g_always_values[idx]);
323 8           g_always_values[idx] = NULL;
324             }
325 8           return 0;
326             }
327              
328             static MGVTBL util_always_vtbl = {
329             NULL, NULL, NULL, NULL, util_always_free, NULL, NULL, NULL
330             };
331              
332             /* ============================================
333             Utility functions
334             ============================================ */
335              
336 213           static void ensure_memo_capacity(IV needed) {
337 213 100         if (needed >= g_memo_size) {
338 4 50         IV new_size = g_memo_size ? g_memo_size * 2 : 16;
339 4 50         while (new_size <= needed) new_size *= 2;
340 4 50         Renew(g_memos, new_size, MemoizedFunc);
341 4           g_memo_size = new_size;
342             }
343 213           }
344              
345 1008           static void ensure_lazy_capacity(IV needed) {
346 1008 100         if (needed >= g_lazy_size) {
347 6 50         IV new_size = g_lazy_size ? g_lazy_size * 2 : 16;
348 6 50         while (new_size <= needed) new_size *= 2;
349 6 50         Renew(g_lazies, new_size, LazyValue);
350 6           g_lazy_size = new_size;
351             }
352 1008           }
353              
354 8           static void ensure_always_capacity(IV needed) {
355 8 50         if (needed >= g_always_size) {
356 0 0         IV new_size = g_always_size ? g_always_size * 2 : 16;
357 0 0         while (new_size <= needed) new_size *= 2;
358 0 0         Renew(g_always_values, new_size, SV*);
359 0           g_always_size = new_size;
360             }
361 8           }
362              
363 1005           static void ensure_once_capacity(IV needed) {
364 1005 100         if (needed >= g_once_size) {
365 6 50         IV new_size = g_once_size ? g_once_size * 2 : 16;
366 6 50         while (new_size <= needed) new_size *= 2;
367 6 50         Renew(g_onces, new_size, OnceFunc);
368 6           g_once_size = new_size;
369             }
370 1005           }
371              
372 1012           static void ensure_partial_capacity(IV needed) {
373 1012 100         if (needed >= g_partial_size) {
374 6 50         IV new_size = g_partial_size ? g_partial_size * 2 : 16;
375 6 50         while (new_size <= needed) new_size *= 2;
376 6 50         Renew(g_partials, new_size, PartialFunc);
377 6           g_partial_size = new_size;
378             }
379 1012           }
380              
381             /* Build cache key from stack arguments */
382 849           static SV* build_cache_key(pTHX_ SV **args, IV count) {
383 849           SV *key = newSVpvs("");
384             IV i;
385 1699 100         for (i = 0; i < count; i++) {
386 850 100         if (i > 0) sv_catpvs(key, "\x00");
387 850 100         if (SvOK(args[i])) {
388             STRLEN len;
389 846           const char *pv = SvPV(args[i], len);
390 846           sv_catpvn(key, pv, len);
391             } else {
392 4           sv_catpvs(key, "\x01UNDEF\x01");
393             }
394             }
395 849           return key;
396             }
397              
398             /* ============================================
399             Built-in predicates for loop callbacks
400             (prefixed with ':' for built-in names)
401             ============================================ */
402              
403 16147           static bool builtin_is_defined(pTHX_ SV *elem) {
404 16147           return SvOK(elem) ? TRUE : FALSE;
405             }
406              
407 4044           static bool builtin_is_true(pTHX_ SV *elem) {
408 4044           return SvTRUE(elem) ? TRUE : FALSE;
409             }
410              
411 1042           static bool builtin_is_false(pTHX_ SV *elem) {
412 1042           return !SvTRUE(elem) ? TRUE : FALSE;
413             }
414              
415 15044           static bool builtin_is_ref(pTHX_ SV *elem) {
416 15044           return SvROK(elem) ? TRUE : FALSE;
417             }
418              
419 6026           static bool builtin_is_array(pTHX_ SV *elem) {
420 6026 100         return (SvROK(elem) && SvTYPE(SvRV(elem)) == SVt_PVAV) ? TRUE : FALSE;
    100          
421             }
422              
423 7027           static bool builtin_is_hash(pTHX_ SV *elem) {
424 7027 100         return (SvROK(elem) && SvTYPE(SvRV(elem)) == SVt_PVHV) ? TRUE : FALSE;
    100          
425             }
426              
427 23           static bool builtin_is_code(pTHX_ SV *elem) {
428 23 100         return (SvROK(elem) && SvTYPE(SvRV(elem)) == SVt_PVCV) ? TRUE : FALSE;
    100          
429             }
430              
431 2041259           static bool builtin_is_positive(pTHX_ SV *elem) {
432 2041259 50         if (SvIOK(elem)) return SvIV(elem) > 0;
433 0 0         if (SvNOK(elem)) return SvNV(elem) > 0;
434 0 0         if (SvPOK(elem) && looks_like_number(elem)) return SvNV(elem) > 0;
    0          
435 0           return FALSE;
436             }
437              
438 19081           static bool builtin_is_negative(pTHX_ SV *elem) {
439 19081 50         if (SvIOK(elem)) return SvIV(elem) < 0;
440 0 0         if (SvNOK(elem)) return SvNV(elem) < 0;
441 0 0         if (SvPOK(elem) && looks_like_number(elem)) return SvNV(elem) < 0;
    0          
442 0           return FALSE;
443             }
444              
445 15069           static bool builtin_is_zero(pTHX_ SV *elem) {
446 15069 50         if (SvIOK(elem)) return SvIV(elem) == 0;
447 0 0         if (SvNOK(elem)) return SvNV(elem) == 0.0;
448 0 0         if (SvPOK(elem) && looks_like_number(elem)) return SvNV(elem) == 0.0;
    0          
449 0           return FALSE;
450             }
451              
452 30113           static bool builtin_is_even(pTHX_ SV *elem) {
453 30113 50         if (!SvIOK(elem) && !SvNOK(elem)) {
    0          
454 0 0         if (!SvPOK(elem) || !looks_like_number(elem)) return FALSE;
    0          
455             }
456 30113           IV val = SvIV(elem);
457 30113           return (val % 2) == 0;
458             }
459              
460 1054           static bool builtin_is_odd(pTHX_ SV *elem) {
461 1054 50         if (!SvIOK(elem) && !SvNOK(elem)) {
    0          
462 0 0         if (!SvPOK(elem) || !looks_like_number(elem)) return FALSE;
    0          
463             }
464 1054           IV val = SvIV(elem);
465 1054           return (val % 2) != 0;
466             }
467              
468 7045           static bool builtin_is_empty(pTHX_ SV *elem) {
469 7045 100         if (!SvOK(elem)) return TRUE;
470 6038 100         if (SvROK(elem)) {
471 2016           SV *rv = SvRV(elem);
472 2016 100         if (SvTYPE(rv) == SVt_PVAV) return AvFILL((AV*)rv) < 0;
    50          
473 1008 50         if (SvTYPE(rv) == SVt_PVHV) return HvKEYS((HV*)rv) == 0;
    50          
474 0           return FALSE;
475             }
476 4022 100         if (SvPOK(elem)) return SvCUR(elem) == 0;
477 2000           return FALSE;
478             }
479              
480 20           static bool builtin_is_nonempty(pTHX_ SV *elem) {
481 20           return !builtin_is_empty(aTHX_ elem);
482             }
483              
484 2015           static bool builtin_is_string(pTHX_ SV *elem) {
485 2015 100         return (SvPOK(elem) && !SvIOK(elem) && !SvNOK(elem) && !SvROK(elem)) ? TRUE : FALSE;
    50          
    50          
    50          
486             }
487              
488 9005           static bool builtin_is_number(pTHX_ SV *elem) {
489 9005 100         if (SvIOK(elem) || SvNOK(elem)) return TRUE;
    100          
490 2 50         if (SvPOK(elem) && looks_like_number(elem)) return TRUE;
    100          
491 1           return FALSE;
492             }
493              
494 6           static bool builtin_is_integer(pTHX_ SV *elem) {
495 6 100         if (SvIOK(elem) && !SvNOK(elem)) return TRUE;
    50          
496 4 100         if (SvNOK(elem)) {
497 3           NV val = SvNV(elem);
498 3           return val == (NV)(IV)val;
499             }
500 1 50         if (SvPOK(elem) && looks_like_number(elem)) {
    50          
501 1           NV val = SvNV(elem);
502 1           return val == (NV)(IV)val;
503             }
504 0           return FALSE;
505             }
506              
507             /* ============================================
508             Callback registry functions
509             ============================================ */
510              
511 921           static void init_callback_registry(pTHX) {
512 921 100         if (!g_callback_registry) {
513 53           g_callback_registry = newHV();
514             }
515 921           }
516              
517             /* Cleanup callback registry during global destruction */
518 53           static void cleanup_callback_registry(pTHX_ void *data) {
519             HE *entry;
520             PERL_UNUSED_ARG(data);
521              
522 53 50         if (!g_callback_registry) return;
523              
524             /* During global destruction, just NULL out the registry pointer.
525             * Perl will handle freeing the SVs. Trying to free them ourselves
526             * can cause crashes due to destruction order issues. */
527 53 50         if (PL_dirty) {
528 53           g_callback_registry = NULL;
529 53           return;
530             }
531              
532             /* Normal cleanup (not during global destruction) */
533 0           hv_iterinit(g_callback_registry);
534 0 0         while ((entry = hv_iternext(g_callback_registry))) {
535 0           SV *sv = HeVAL(entry);
536 0 0         if (sv && SvOK(sv)) {
    0          
537 0           RegisteredCallback *cb = (RegisteredCallback*)SvIVX(sv);
538 0 0         if (cb) {
539 0 0         if (cb->perl_callback) {
540 0           SvREFCNT_dec(cb->perl_callback);
541 0           cb->perl_callback = NULL;
542             }
543 0 0         if (cb->name) {
544 0           Safefree(cb->name);
545 0           cb->name = NULL;
546             }
547 0           Safefree(cb);
548             }
549             }
550             }
551 0           SvREFCNT_dec((SV*)g_callback_registry);
552 0           g_callback_registry = NULL;
553             }
554              
555 41580           static RegisteredCallback* get_registered_callback(pTHX_ const char *name) {
556             SV **svp;
557 41580 50         if (!g_callback_registry) return NULL;
558 41580           svp = hv_fetch(g_callback_registry, name, strlen(name), 0);
559 41580 100         if (!svp || !SvOK(*svp)) return NULL;
    50          
560 39550           return (RegisteredCallback*)SvIVX(*svp);
561             }
562              
563             /* Register a built-in predicate */
564 901           static void register_builtin_predicate(pTHX_ const char *name, UtilPredicateFunc func) {
565             RegisteredCallback *cb;
566             SV *sv;
567              
568 901           init_callback_registry(aTHX);
569              
570 901           Newxz(cb, 1, RegisteredCallback);
571 901           cb->name = savepv(name);
572 901           cb->predicate = func;
573 901           cb->mapper = NULL;
574 901           cb->reducer = NULL;
575 901           cb->perl_callback = NULL;
576              
577 901           sv = newSViv(PTR2IV(cb));
578 901           hv_store(g_callback_registry, name, strlen(name), sv, 0);
579 901           }
580              
581             /* Public API for XS modules to register predicates */
582 0           PERL_CALLCONV void funcutil_register_predicate_xs(pTHX_ const char *name,
583             UtilPredicateFunc func) {
584             RegisteredCallback *cb;
585             SV *sv;
586              
587 0           init_callback_registry(aTHX);
588              
589             /* Check if already registered */
590 0 0         if (get_registered_callback(aTHX_ name)) {
591 0           croak("Callback '%s' is already registered", name);
592             }
593              
594 0           Newxz(cb, 1, RegisteredCallback);
595 0           cb->name = savepv(name);
596 0           cb->predicate = func;
597 0           cb->mapper = NULL;
598 0           cb->reducer = NULL;
599 0           cb->perl_callback = NULL;
600              
601 0           sv = newSViv(PTR2IV(cb));
602 0           hv_store(g_callback_registry, name, strlen(name), sv, 0);
603 0           }
604              
605             /* Public API for XS modules to register mappers */
606 0           PERL_CALLCONV void funcutil_register_mapper_xs(pTHX_ const char *name,
607             UtilMapFunc func) {
608             RegisteredCallback *cb;
609             SV *sv;
610              
611 0           init_callback_registry(aTHX);
612              
613 0 0         if (get_registered_callback(aTHX_ name)) {
614 0           croak("Callback '%s' is already registered", name);
615             }
616              
617 0           Newxz(cb, 1, RegisteredCallback);
618 0           cb->name = savepv(name);
619 0           cb->predicate = NULL;
620 0           cb->mapper = func;
621 0           cb->reducer = NULL;
622 0           cb->perl_callback = NULL;
623              
624 0           sv = newSViv(PTR2IV(cb));
625 0           hv_store(g_callback_registry, name, strlen(name), sv, 0);
626 0           }
627              
628             /* Public API for XS modules to register reducers */
629 0           PERL_CALLCONV void funcutil_register_reducer_xs(pTHX_ const char *name,
630             UtilReduceFunc func) {
631             RegisteredCallback *cb;
632             SV *sv;
633              
634 0           init_callback_registry(aTHX);
635              
636 0 0         if (get_registered_callback(aTHX_ name)) {
637 0           croak("Callback '%s' is already registered", name);
638             }
639              
640 0           Newxz(cb, 1, RegisteredCallback);
641 0           cb->name = savepv(name);
642 0           cb->predicate = NULL;
643 0           cb->mapper = NULL;
644 0           cb->reducer = func;
645 0           cb->perl_callback = NULL;
646              
647 0           sv = newSViv(PTR2IV(cb));
648 0           hv_store(g_callback_registry, name, strlen(name), sv, 0);
649 0           }
650              
651             /* Check if a callback exists */
652 6018           static bool has_callback(pTHX_ const char *name) {
653 6018           return get_registered_callback(aTHX_ name) != NULL;
654             }
655              
656             /* List all registered callbacks */
657 1006           static AV* list_callbacks(pTHX) {
658             AV *result;
659             HE *entry;
660              
661 1006           result = newAV();
662 1006 50         if (!g_callback_registry) return result;
663              
664 1006           hv_iterinit(g_callback_registry);
665 18116 100         while ((entry = hv_iternext(g_callback_registry))) {
666             I32 klen;
667 17110           char *key = hv_iterkey(entry, &klen);
668 17110           av_push(result, newSVpvn(key, klen));
669             }
670 1006           return result;
671             }
672              
673             /* Initialize built-in callbacks (called from BOOT) */
674 53           static void init_builtin_callbacks(pTHX) {
675 53           register_builtin_predicate(aTHX_ ":is_defined", builtin_is_defined);
676 53           register_builtin_predicate(aTHX_ ":is_true", builtin_is_true);
677 53           register_builtin_predicate(aTHX_ ":is_false", builtin_is_false);
678 53           register_builtin_predicate(aTHX_ ":is_ref", builtin_is_ref);
679 53           register_builtin_predicate(aTHX_ ":is_array", builtin_is_array);
680 53           register_builtin_predicate(aTHX_ ":is_hash", builtin_is_hash);
681 53           register_builtin_predicate(aTHX_ ":is_code", builtin_is_code);
682 53           register_builtin_predicate(aTHX_ ":is_positive", builtin_is_positive);
683 53           register_builtin_predicate(aTHX_ ":is_negative", builtin_is_negative);
684 53           register_builtin_predicate(aTHX_ ":is_zero", builtin_is_zero);
685 53           register_builtin_predicate(aTHX_ ":is_even", builtin_is_even);
686 53           register_builtin_predicate(aTHX_ ":is_odd", builtin_is_odd);
687 53           register_builtin_predicate(aTHX_ ":is_empty", builtin_is_empty);
688 53           register_builtin_predicate(aTHX_ ":is_nonempty", builtin_is_nonempty);
689 53           register_builtin_predicate(aTHX_ ":is_string", builtin_is_string);
690 53           register_builtin_predicate(aTHX_ ":is_number", builtin_is_number);
691 53           register_builtin_predicate(aTHX_ ":is_integer", builtin_is_integer);
692 53           }
693              
694             /* ============================================
695             Custom OP implementations - fastest path
696             ============================================ */
697              
698             /* identity: just return the top of stack */
699 0           static OP* pp_identity(pTHX) {
700             /* Value already on stack, nothing to do */
701 0           return NORMAL;
702             }
703              
704             /* always: push stored value from op_targ index */
705 0           static OP* pp_always(pTHX) {
706 0           dSP;
707 0           IV idx = PL_op->op_targ;
708 0 0         XPUSHs(g_always_values[idx]);
709 0           RETURN;
710             }
711              
712             /* clamp: 3 values on stack, return clamped */
713 30           static OP* pp_clamp(pTHX) {
714 30           dSP; dMARK; dORIGMARK;
715             SV *val_sv, *min_sv, *max_sv;
716             NV value, min, max, result;
717            
718             /* We get 3 args on stack after the mark */
719 30 50         if (SP - MARK != 3) {
720             /* Fallback: just use direct POPs if no mark context */
721 0           SP = ORIGMARK;
722 0           PUTBACK;
723             /* Pop without mark - shouldn't happen in list context */
724 0           dSP;
725 0           max_sv = POPs;
726 0           min_sv = POPs;
727 0           val_sv = POPs;
728             } else {
729 30           val_sv = MARK[1];
730 30           min_sv = MARK[2];
731 30           max_sv = MARK[3];
732 30           SP = ORIGMARK; /* reset stack to before args */
733             }
734              
735 30           value = SvNV(val_sv);
736 30           min = SvNV(min_sv);
737 30           max = SvNV(max_sv);
738              
739 30 100         if (value < min) {
740 6           result = min;
741 24 100         } else if (value > max) {
742 7           result = max;
743             } else {
744 17           result = value;
745             }
746            
747 30           PUSHs(sv_2mortal(newSVnv(result)));
748 30           RETURN;
749             }
750              
751             /* nvl: 2 values on stack, return first if defined */
752 0           static OP* pp_nvl(pTHX) {
753 0           dSP;
754 0           SV *def_sv = POPs;
755 0           SV *val_sv = TOPs;
756              
757 0 0         if (!SvOK(val_sv)) {
758 0           SETs(def_sv);
759             }
760 0           RETURN;
761             }
762              
763             /* ============================================
764             Type predicate custom ops - blazing fast!
765             These are the fastest possible type checks:
766             single SV flag check, no function call overhead
767             ============================================ */
768              
769             /* is_ref: check if value is a reference */
770 10           static OP* pp_is_ref(pTHX) {
771 10           dSP;
772 10           SV *sv = TOPs;
773 10 100         SETs(SvROK(sv) ? &PL_sv_yes : &PL_sv_no);
774 10           RETURN;
775             }
776              
777             /* is_array: check if value is an arrayref */
778 4           static OP* pp_is_array(pTHX) {
779 4           dSP;
780 4           SV *sv = TOPs;
781 4 100         SETs((SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) ? &PL_sv_yes : &PL_sv_no);
    100          
782 4           RETURN;
783             }
784              
785             /* is_hash: check if value is a hashref */
786 3           static OP* pp_is_hash(pTHX) {
787 3           dSP;
788 3           SV *sv = TOPs;
789 3 100         SETs((SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV) ? &PL_sv_yes : &PL_sv_no);
    100          
790 3           RETURN;
791             }
792              
793             /* is_code: check if value is a coderef */
794 3           static OP* pp_is_code(pTHX) {
795 3           dSP;
796 3           SV *sv = TOPs;
797 3 100         SETs((SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVCV) ? &PL_sv_yes : &PL_sv_no);
    100          
798 3           RETURN;
799             }
800              
801             /* is_defined: check if value is defined */
802 9           static OP* pp_is_defined(pTHX) {
803 9           dSP;
804 9           SV *sv = TOPs;
805 9 100         SETs(SvOK(sv) ? &PL_sv_yes : &PL_sv_no);
806 9           RETURN;
807             }
808              
809             /* ============================================
810             String predicate custom ops - blazing fast!
811             Direct SvPV/SvCUR access, no function overhead
812             ============================================ */
813              
814             /* is_empty: check if string is undefined or empty */
815 4           static OP* pp_is_empty(pTHX) {
816 4           dSP;
817 4           SV *sv = TOPs;
818             /* Empty if: undefined OR length is 0 */
819 4 100         if (!SvOK(sv)) {
820 1           SETs(&PL_sv_yes);
821             } else {
822             STRLEN len;
823 3           SvPV(sv, len);
824 3 100         SETs(len == 0 ? &PL_sv_yes : &PL_sv_no);
825             }
826 4           RETURN;
827             }
828              
829             /* starts_with: check if string starts with prefix */
830 13           static OP* pp_starts_with(pTHX) {
831 13           dSP;
832 13           SV *prefix_sv = POPs;
833 13           SV *str_sv = TOPs;
834              
835 13 50         if (!SvOK(str_sv) || !SvOK(prefix_sv)) {
    50          
836 0           SETs(&PL_sv_no);
837 0           RETURN;
838             }
839              
840             STRLEN str_len, prefix_len;
841 13           const char *str = SvPV(str_sv, str_len);
842 13           const char *prefix = SvPV(prefix_sv, prefix_len);
843              
844 13 50         if (prefix_len > str_len) {
845 0           SETs(&PL_sv_no);
846 13 100         } else if (prefix_len == 0) {
847 1           SETs(&PL_sv_yes); /* Empty prefix always matches */
848             } else {
849 12 100         SETs(memcmp(str, prefix, prefix_len) == 0 ? &PL_sv_yes : &PL_sv_no);
850             }
851 13           RETURN;
852             }
853              
854             /* ends_with: check if string ends with suffix */
855 4           static OP* pp_ends_with(pTHX) {
856 4           dSP;
857 4           SV *suffix_sv = POPs;
858 4           SV *str_sv = TOPs;
859              
860 4 50         if (!SvOK(str_sv) || !SvOK(suffix_sv)) {
    50          
861 0           SETs(&PL_sv_no);
862 0           RETURN;
863             }
864              
865             STRLEN str_len, suffix_len;
866 4           const char *str = SvPV(str_sv, str_len);
867 4           const char *suffix = SvPV(suffix_sv, suffix_len);
868              
869 4 50         if (suffix_len > str_len) {
870 0           SETs(&PL_sv_no);
871 4 100         } else if (suffix_len == 0) {
872 1           SETs(&PL_sv_yes); /* Empty suffix always matches */
873             } else {
874 3           const char *str_end = str + str_len - suffix_len;
875 3 100         SETs(memcmp(str_end, suffix, suffix_len) == 0 ? &PL_sv_yes : &PL_sv_no);
876             }
877 4           RETURN;
878             }
879              
880             /* ============================================
881             Boolean/Truthiness custom ops - blazing fast!
882             Direct SvTRUE check, minimal overhead
883             ============================================ */
884              
885             /* is_true: check if value is truthy (Perl truth semantics) */
886 5           static OP* pp_is_true(pTHX) {
887 5           dSP;
888 5           SV *sv = TOPs;
889 5 100         SETs(SvTRUE(sv) ? &PL_sv_yes : &PL_sv_no);
890 5           RETURN;
891             }
892              
893             /* is_false: check if value is falsy (Perl truth semantics) */
894 5           static OP* pp_is_false(pTHX) {
895 5           dSP;
896 5           SV *sv = TOPs;
897 5 100         SETs(SvTRUE(sv) ? &PL_sv_no : &PL_sv_yes);
898 5           RETURN;
899             }
900              
901             /* bool: normalize to boolean (1 or empty string) */
902 7           static OP* pp_bool(pTHX) {
903 7           dSP;
904 7           SV *sv = TOPs;
905 7 100         SETs(SvTRUE(sv) ? &PL_sv_yes : &PL_sv_no);
906 7           RETURN;
907             }
908              
909             /* ============================================
910             Extended type predicate custom ops - blazing fast!
911             ============================================ */
912              
913             /* is_num: check if value is numeric (has numeric value or looks like number) */
914 6           static OP* pp_is_num(pTHX) {
915 6           dSP;
916 6           SV *sv = TOPs;
917             /* SvNIOK: has numeric (NV or IV) value cached */
918             /* Also check looks_like_number for strings that can be numbers */
919 6 100         SETs((SvNIOK(sv) || looks_like_number(sv)) ? &PL_sv_yes : &PL_sv_no);
    100          
920 6           RETURN;
921             }
922              
923             /* is_int: check if value is an integer */
924 5           static OP* pp_is_int(pTHX) {
925 5           dSP;
926 5           SV *sv = TOPs;
927             /* SvIOK: has integer value cached */
928 5 100         if (SvIOK(sv)) {
929 3           SETs(&PL_sv_yes);
930 2 100         } else if (SvNOK(sv)) {
931             /* It's a float - check if it's a whole number */
932 1           NV nv = SvNV(sv);
933 1 50         SETs((nv == (NV)(IV)nv) ? &PL_sv_yes : &PL_sv_no);
934 1 50         } else if (looks_like_number(sv)) {
935             /* String that looks like a number - check if integer */
936             STRLEN len;
937 0           const char *pv = SvPV(sv, len);
938             /* Simple check: no decimal point or exponent */
939 0           bool has_dot = FALSE;
940             STRLEN i;
941 0 0         for (i = 0; i < len; i++) {
942 0 0         if (pv[i] == '.' || pv[i] == 'e' || pv[i] == 'E') {
    0          
    0          
943 0           has_dot = TRUE;
944 0           break;
945             }
946             }
947 0 0         if (has_dot) {
948             /* Has decimal - check if value is actually integer */
949 0           NV nv = SvNV(sv);
950 0 0         SETs((nv == (NV)(IV)nv) ? &PL_sv_yes : &PL_sv_no);
951             } else {
952 0           SETs(&PL_sv_yes);
953             }
954             } else {
955 1           SETs(&PL_sv_no);
956             }
957 5           RETURN;
958             }
959              
960             /* is_blessed: check if value is a blessed reference */
961 3           static OP* pp_is_blessed(pTHX) {
962 3           dSP;
963 3           SV *sv = TOPs;
964 3 100         SETs(sv_isobject(sv) ? &PL_sv_yes : &PL_sv_no);
965 3           RETURN;
966             }
967              
968             /* is_scalar_ref: check if value is a scalar reference (not array/hash/code/etc) */
969 3           static OP* pp_is_scalar_ref(pTHX) {
970 3           dSP;
971 3           SV *sv = TOPs;
972 3 100         if (SvROK(sv)) {
973 2           SV *rv = SvRV(sv);
974 2           svtype type = SvTYPE(rv);
975             /* Scalar refs are < SVt_PVAV (array) */
976 2 100         SETs((type < SVt_PVAV) ? &PL_sv_yes : &PL_sv_no);
977             } else {
978 1           SETs(&PL_sv_no);
979             }
980 3           RETURN;
981             }
982              
983             /* is_regex: check if value is a compiled regex */
984 3           static OP* pp_is_regex(pTHX) {
985 3           dSP;
986 3           SV *sv = TOPs;
987             /* SvRXOK: check if SV is a regex (qr//) - available since Perl 5.10 */
988 3 100         SETs(SvRXOK(sv) ? &PL_sv_yes : &PL_sv_no);
989 3           RETURN;
990             }
991              
992             /* is_glob: check if value is a glob (*foo) */
993 2           static OP* pp_is_glob(pTHX) {
994 2           dSP;
995 2           SV *sv = TOPs;
996 2 100         SETs((SvTYPE(sv) == SVt_PVGV) ? &PL_sv_yes : &PL_sv_no);
997 2           RETURN;
998             }
999              
1000             /* is_string: check if value is a plain scalar (defined, not a reference) */
1001 0           static OP* pp_is_string(pTHX) {
1002 0           dSP;
1003 0           SV *sv = TOPs;
1004 0 0         SETs((SvOK(sv) && !SvROK(sv)) ? &PL_sv_yes : &PL_sv_no);
    0          
1005 0           RETURN;
1006             }
1007              
1008             /* ============================================
1009             Numeric predicate custom ops - blazing fast!
1010             Direct SvNV comparison, minimal overhead
1011             ============================================ */
1012              
1013             /* is_positive: check if value is > 0 */
1014 8           static OP* pp_is_positive(pTHX) {
1015 8           dSP;
1016 8           SV *sv = TOPs;
1017 8 50         if (SvNIOK(sv) || looks_like_number(sv)) {
    0          
1018 8           NV nv = SvNV(sv);
1019 8 100         SETs((nv > 0) ? &PL_sv_yes : &PL_sv_no);
1020             } else {
1021 0           SETs(&PL_sv_no);
1022             }
1023 8           RETURN;
1024             }
1025              
1026             /* is_negative: check if value is < 0 */
1027 4           static OP* pp_is_negative(pTHX) {
1028 4           dSP;
1029 4           SV *sv = TOPs;
1030 4 50         if (SvNIOK(sv) || looks_like_number(sv)) {
    0          
1031 4           NV nv = SvNV(sv);
1032 4 100         SETs((nv < 0) ? &PL_sv_yes : &PL_sv_no);
1033             } else {
1034 0           SETs(&PL_sv_no);
1035             }
1036 4           RETURN;
1037             }
1038              
1039             /* is_zero: check if value is == 0 */
1040 4           static OP* pp_is_zero(pTHX) {
1041 4           dSP;
1042 4           SV *sv = TOPs;
1043 4 50         if (SvNIOK(sv) || looks_like_number(sv)) {
    0          
1044 4           NV nv = SvNV(sv);
1045 4 100         SETs((nv == 0) ? &PL_sv_yes : &PL_sv_no);
1046             } else {
1047 0           SETs(&PL_sv_no);
1048             }
1049 4           RETURN;
1050             }
1051              
1052             /* ============================================
1053             Numeric utility custom ops
1054             ============================================ */
1055              
1056             /* is_even: check if integer is even (single bitwise AND) */
1057 5           static OP* pp_is_even(pTHX) {
1058 5           dSP;
1059 5           SV *sv = TOPs;
1060 5 50         if (SvIOK(sv)) {
1061 5 100         SETs((SvIVX(sv) & 1) == 0 ? &PL_sv_yes : &PL_sv_no);
1062 0 0         } else if (SvNIOK(sv)) {
1063 0           NV nv = SvNV(sv);
1064 0 0         if (nv == (NV)(IV)nv) {
1065 0 0         SETs(((IV)nv & 1) == 0 ? &PL_sv_yes : &PL_sv_no);
1066             } else {
1067 0           SETs(&PL_sv_no);
1068             }
1069 0 0         } else if (looks_like_number(sv)) {
1070 0 0         SETs((SvIV(sv) & 1) == 0 ? &PL_sv_yes : &PL_sv_no);
1071             } else {
1072 0           SETs(&PL_sv_no);
1073             }
1074 5           RETURN;
1075             }
1076              
1077             /* is_odd: check if integer is odd (single bitwise AND) */
1078 5           static OP* pp_is_odd(pTHX) {
1079 5           dSP;
1080 5           SV *sv = TOPs;
1081 5 50         if (SvIOK(sv)) {
1082 5 100         SETs((SvIVX(sv) & 1) == 1 ? &PL_sv_yes : &PL_sv_no);
1083 0 0         } else if (SvNIOK(sv)) {
1084 0           NV nv = SvNV(sv);
1085 0 0         if (nv == (NV)(IV)nv) {
1086 0 0         SETs(((IV)nv & 1) == 1 ? &PL_sv_yes : &PL_sv_no);
1087             } else {
1088 0           SETs(&PL_sv_no);
1089             }
1090 0 0         } else if (looks_like_number(sv)) {
1091 0 0         SETs((SvIV(sv) & 1) == 1 ? &PL_sv_yes : &PL_sv_no);
1092             } else {
1093 0           SETs(&PL_sv_no);
1094             }
1095 5           RETURN;
1096             }
1097              
1098             /* is_between: check if value is between min and max (inclusive) */
1099 0           static OP* pp_is_between(pTHX) {
1100 0           dSP;
1101 0           SV *max_sv = POPs;
1102 0           SV *min_sv = POPs;
1103 0           SV *val_sv = TOPs;
1104              
1105 0 0         if (SvNIOK(val_sv) || looks_like_number(val_sv)) {
    0          
1106 0           NV val = SvNV(val_sv);
1107 0           NV min = SvNV(min_sv);
1108 0           NV max = SvNV(max_sv);
1109 0 0         SETs((val >= min && val <= max) ? &PL_sv_yes : &PL_sv_no);
    0          
1110             } else {
1111 0           SETs(&PL_sv_no);
1112             }
1113 0           RETURN;
1114             }
1115              
1116             /* ============================================
1117             Collection custom ops - direct AvFILL/HvKEYS access
1118             ============================================ */
1119              
1120             /* is_empty_array: check if arrayref is empty - direct AvFILL */
1121 3           static OP* pp_is_empty_array(pTHX) {
1122 3           dSP;
1123 3           SV *sv = TOPs;
1124 3 100         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
    50          
1125 2           AV *av = (AV*)SvRV(sv);
1126 2 50         SETs(AvFILL(av) < 0 ? &PL_sv_yes : &PL_sv_no);
    100          
1127             } else {
1128 1           SETs(&PL_sv_no); /* Not an arrayref */
1129             }
1130 3           RETURN;
1131             }
1132              
1133             /* is_empty_hash: check if hashref is empty - direct HvKEYS */
1134 3           static OP* pp_is_empty_hash(pTHX) {
1135 3           dSP;
1136 3           SV *sv = TOPs;
1137 3 100         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV) {
    50          
1138 2           HV *hv = (HV*)SvRV(sv);
1139 2 50         SETs(HvKEYS(hv) == 0 ? &PL_sv_yes : &PL_sv_no);
    100          
1140             } else {
1141 1           SETs(&PL_sv_no); /* Not a hashref */
1142             }
1143 3           RETURN;
1144             }
1145              
1146             /* array_len: get array length - direct AvFILL + 1 */
1147 3           static OP* pp_array_len(pTHX) {
1148 3           dSP;
1149 3           SV *sv = TOPs;
1150 3 50         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
    50          
1151 3           AV *av = (AV*)SvRV(sv);
1152 3 50         SV *len = sv_2mortal(newSViv(AvFILL(av) + 1));
1153 3           SETs(len);
1154             } else {
1155 0           SETs(&PL_sv_undef); /* Not an arrayref */
1156             }
1157 3           RETURN;
1158             }
1159              
1160             /* hash_size: get hash key count - direct HvKEYS */
1161 3           static OP* pp_hash_size(pTHX) {
1162 3           dSP;
1163 3           SV *sv = TOPs;
1164 3 50         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV) {
    50          
1165 3           HV *hv = (HV*)SvRV(sv);
1166 3 50         SV *size = sv_2mortal(newSViv(HvKEYS(hv)));
1167 3           SETs(size);
1168             } else {
1169 0           SETs(&PL_sv_undef); /* Not a hashref */
1170             }
1171 3           RETURN;
1172             }
1173              
1174             /* array_first: get first element without slice overhead */
1175 3           static OP* pp_array_first(pTHX) {
1176 3           dSP;
1177 3           SV *sv = TOPs;
1178 6 50         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
    50          
1179 3           AV *av = (AV*)SvRV(sv);
1180 3 50         if (AvFILL(av) >= 0) {
    100          
1181 2           SV **elem = av_fetch(av, 0, 0);
1182 2 50         SETs(elem ? *elem : &PL_sv_undef);
1183             } else {
1184 1           SETs(&PL_sv_undef); /* Empty array */
1185             }
1186             } else {
1187 0           SETs(&PL_sv_undef); /* Not an arrayref */
1188             }
1189 3           RETURN;
1190             }
1191              
1192             /* array_last: get last element without slice overhead */
1193 3           static OP* pp_array_last(pTHX) {
1194 3           dSP;
1195 3           SV *sv = TOPs;
1196 6 50         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
    50          
1197 3           AV *av = (AV*)SvRV(sv);
1198 3 50         IV last_idx = AvFILL(av);
1199 3 100         if (last_idx >= 0) {
1200 2           SV **elem = av_fetch(av, last_idx, 0);
1201 2 50         SETs(elem ? *elem : &PL_sv_undef);
1202             } else {
1203 1           SETs(&PL_sv_undef); /* Empty array */
1204             }
1205             } else {
1206 0           SETs(&PL_sv_undef); /* Not an arrayref */
1207             }
1208 3           RETURN;
1209             }
1210              
1211              
1212             /* ============================================
1213             String manipulation custom ops
1214             ============================================ */
1215              
1216             /* trim: remove leading/trailing whitespace */
1217 5           static OP* pp_trim(pTHX) {
1218 5           dSP;
1219 5           SV *sv = TOPs;
1220              
1221 5 50         if (!SvOK(sv)) {
1222 0           SETs(&PL_sv_undef);
1223 0           RETURN;
1224             }
1225              
1226             STRLEN len;
1227 5           const char *str = SvPV(sv, len);
1228 5           const char *start = str;
1229 5           const char *end = str + len;
1230              
1231             /* Skip leading whitespace */
1232 11 50         while (start < end && isSPACE(*start)) {
    100          
1233 6           start++;
1234             }
1235              
1236             /* Skip trailing whitespace */
1237 11 50         while (end > start && isSPACE(*(end - 1))) {
    100          
1238 6           end--;
1239             }
1240              
1241             /* Create new SV with trimmed content */
1242 5           SV *result = sv_2mortal(newSVpvn(start, end - start));
1243 5           SETs(result);
1244 5           RETURN;
1245             }
1246              
1247             /* ltrim: remove leading whitespace only */
1248 3           static OP* pp_ltrim(pTHX) {
1249 3           dSP;
1250 3           SV *sv = TOPs;
1251              
1252 3 50         if (!SvOK(sv)) {
1253 0           SETs(&PL_sv_undef);
1254 0           RETURN;
1255             }
1256              
1257             STRLEN len;
1258 3           const char *str = SvPV(sv, len);
1259 3           const char *start = str;
1260 3           const char *end = str + len;
1261              
1262             /* Skip leading whitespace */
1263 7 50         while (start < end && isSPACE(*start)) {
    100          
1264 4           start++;
1265             }
1266              
1267 3           SV *result = sv_2mortal(newSVpvn(start, end - start));
1268 3           SETs(result);
1269 3           RETURN;
1270             }
1271              
1272             /* rtrim: remove trailing whitespace only */
1273 3           static OP* pp_rtrim(pTHX) {
1274 3           dSP;
1275 3           SV *sv = TOPs;
1276              
1277 3 50         if (!SvOK(sv)) {
1278 0           SETs(&PL_sv_undef);
1279 0           RETURN;
1280             }
1281              
1282             STRLEN len;
1283 3           const char *str = SvPV(sv, len);
1284 3           const char *end = str + len;
1285              
1286             /* Skip trailing whitespace */
1287 7 50         while (end > str && isSPACE(*(end - 1))) {
    100          
1288 4           end--;
1289             }
1290              
1291 3           SV *result = sv_2mortal(newSVpvn(str, end - str));
1292 3           SETs(result);
1293 3           RETURN;
1294             }
1295              
1296             /* ============================================
1297             Conditional custom ops
1298             ============================================ */
1299              
1300             /* maybe: return $then if $val is defined, else undef */
1301 2           static OP* pp_maybe(pTHX) {
1302 2           dSP;
1303 2           SV *then_sv = POPs;
1304 2           SV *val_sv = TOPs;
1305              
1306 2 100         if (SvOK(val_sv)) {
1307 1           SETs(then_sv);
1308             } else {
1309 1           SETs(&PL_sv_undef);
1310             }
1311 2           RETURN;
1312             }
1313              
1314             /* ============================================
1315             Numeric custom ops
1316             ============================================ */
1317              
1318             /* sign: return -1, 0, or 1 based on value */
1319 3           static OP* pp_sign(pTHX) {
1320 3           dSP;
1321 3           SV *sv = TOPs;
1322              
1323 3 50         if (!SvNIOK(sv) && !looks_like_number(sv)) {
    0          
1324 0           SETs(&PL_sv_undef);
1325 0           RETURN;
1326             }
1327              
1328 3           NV nv = SvNV(sv);
1329 3 100         if (nv > 0) {
1330 1           SETs(sv_2mortal(newSViv(1)));
1331 2 100         } else if (nv < 0) {
1332 1           SETs(sv_2mortal(newSViv(-1)));
1333             } else {
1334 1           SETs(sv_2mortal(newSViv(0)));
1335             }
1336 3           RETURN;
1337             }
1338              
1339             /* min2: return smaller of two values */
1340 4           static OP* pp_min2(pTHX) {
1341 4           dSP;
1342 4           SV *b_sv = POPs;
1343 4           SV *a_sv = TOPs;
1344              
1345 4           NV a = SvNV(a_sv);
1346 4           NV b = SvNV(b_sv);
1347              
1348 4 100         SETs(a <= b ? a_sv : b_sv);
1349 4           RETURN;
1350             }
1351              
1352             /* max2: return larger of two values */
1353 4           static OP* pp_max2(pTHX) {
1354 4           dSP;
1355 4           SV *b_sv = POPs;
1356 4           SV *a_sv = TOPs;
1357              
1358 4           NV a = SvNV(a_sv);
1359 4           NV b = SvNV(b_sv);
1360              
1361 4 100         SETs(a >= b ? a_sv : b_sv);
1362 4           RETURN;
1363             }
1364              
1365              
1366             /* ============================================
1367             Call checkers - replace function calls with custom ops
1368             ============================================ */
1369              
1370             /*
1371             * Check if an op is accessing $_ (the default variable).
1372             * Custom ops now properly handle list context with marks,
1373             * but we still fall back to XS for $_ because of how map/grep
1374             * set up the op tree with $_ - the argument evaluation is different.
1375             * Returns TRUE if we should fall back to XS.
1376             */
1377 152           static bool op_is_dollar_underscore(pTHX_ OP *op) {
1378 152 50         if (!op) return FALSE;
1379            
1380             /* Check for $_ access: rv2sv -> gv for "_" */
1381 152 100         if (op->op_type == OP_RV2SV) {
1382 4           OP *gvop = cUNOPx(op)->op_first;
1383 4 50         if (gvop && gvop->op_type == OP_GV) {
    50          
1384 4           GV *gv = cGVOPx_gv(gvop);
1385 4 50         if (gv && GvNAMELEN(gv) == 1 && GvNAME(gv)[0] == '_') {
    50          
    50          
1386 4           return TRUE;
1387             }
1388             }
1389             }
1390            
1391 148           return FALSE;
1392             }
1393              
1394             /* identity call checker - replaces identity($x) with just $x */
1395 3           static OP* identity_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1396             OP *pushop, *argop, *cvop;
1397              
1398             PERL_UNUSED_ARG(namegv);
1399             PERL_UNUSED_ARG(ckobj);
1400              
1401             /* Get the argument list */
1402 3           pushop = cUNOPx(entersubop)->op_first;
1403 3 50         if (!OpHAS_SIBLING(pushop)) {
1404 3           pushop = cUNOPx(pushop)->op_first;
1405             }
1406              
1407             /* Find first real arg (skip pushmark) */
1408 3 50         argop = OpSIBLING(pushop);
1409              
1410             /* Find the cv op (last sibling) */
1411 3           cvop = argop;
1412 6 100         while (OpHAS_SIBLING(cvop)) {
1413 3 50         cvop = OpSIBLING(cvop);
1414             }
1415              
1416             /* Check for exactly one argument */
1417 3 50         if (argop != cvop && OpSIBLING(argop) == cvop) {
    50          
    50          
1418             /* Single arg - just return the arg itself */
1419 3           OP *arg = argop;
1420              
1421             /* If arg is $_, fall back to XS (map/grep context) */
1422 3 50         if (op_is_dollar_underscore(aTHX_ arg)) {
1423 0           return entersubop;
1424             }
1425              
1426             /* Detach arg from list */
1427 3           OpMORESIB_set(pushop, cvop);
1428 3           OpLASTSIB_set(arg, NULL);
1429              
1430 3           op_free(entersubop);
1431 3           return arg; /* Just return the argument op directly! */
1432             }
1433              
1434             /* Fall through to XS for edge cases */
1435 0           return entersubop;
1436             }
1437              
1438             /* clamp call checker - replaces clamp($v, $min, $max) with custom op */
1439 8           static OP* clamp_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1440             OP *pushop, *arg1, *arg2, *arg3, *cvop;
1441             OP *listop;
1442              
1443             PERL_UNUSED_ARG(namegv);
1444             PERL_UNUSED_ARG(ckobj);
1445              
1446             /* Get the argument list */
1447 8           pushop = cUNOPx(entersubop)->op_first;
1448 8 50         if (!OpHAS_SIBLING(pushop)) {
1449 8           pushop = cUNOPx(pushop)->op_first;
1450             }
1451              
1452             /* Find args (skip pushmark) */
1453 8 50         arg1 = OpSIBLING(pushop); /* value */
1454 8 50         if (!arg1) return entersubop;
1455              
1456 8 50         arg2 = OpSIBLING(arg1); /* min */
1457 8 50         if (!arg2) return entersubop;
1458              
1459 8 50         arg3 = OpSIBLING(arg2); /* max */
1460 8 50         if (!arg3) return entersubop;
1461              
1462 8 50         cvop = OpSIBLING(arg3); /* cv op (should be last) */
1463 8 50         if (!cvop || OpHAS_SIBLING(cvop)) return entersubop;
    50          
1464              
1465             /*
1466             * If arg1 is accessing $_, we're likely in map/grep.
1467             * The custom op doesn't work correctly in these contexts.
1468             * Fall back to XS.
1469             */
1470 8 100         if (op_is_dollar_underscore(aTHX_ arg1)) {
1471 2           return entersubop;
1472             }
1473              
1474             /* Detach args from the entersub tree */
1475 6           OpMORESIB_set(pushop, cvop);
1476              
1477             /* Chain arg1 -> arg2 -> arg3 */
1478 6           OpMORESIB_set(arg1, arg2);
1479 6           OpMORESIB_set(arg2, arg3);
1480 6           OpLASTSIB_set(arg3, NULL);
1481              
1482             /*
1483             * Create a LISTOP with 3 children for clamp.
1484             * We use op_convert_list to properly set up a list context.
1485             */
1486 6           listop = op_convert_list(OP_LIST, OPf_STACKED, arg1);
1487 6           listop->op_type = OP_CUSTOM;
1488 6           listop->op_ppaddr = pp_clamp;
1489 6           listop->op_flags = (listop->op_flags & ~OPf_WANT) | OPf_WANT_SCALAR | OPf_STACKED;
1490 6           listop->op_targ = pad_alloc(OP_NULL, SVs_PADTMP);
1491              
1492 6           op_free(entersubop);
1493 6           return listop;
1494             }
1495              
1496             /* Generic call checker for single-arg type predicates */
1497 120           static OP* type_predicate_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj, OP* (*pp_func)(pTHX)) {
1498             OP *pushop, *argop, *cvop;
1499              
1500             PERL_UNUSED_ARG(namegv);
1501             PERL_UNUSED_ARG(ckobj);
1502              
1503             /* Get the argument list */
1504 120           pushop = cUNOPx(entersubop)->op_first;
1505 120 50         if (!OpHAS_SIBLING(pushop)) {
1506 120           pushop = cUNOPx(pushop)->op_first;
1507             }
1508              
1509             /* Find first real arg (skip pushmark) */
1510 120 50         argop = OpSIBLING(pushop);
1511              
1512             /* Find the cv op (last sibling) */
1513 120           cvop = argop;
1514 240 100         while (OpHAS_SIBLING(cvop)) {
1515 120 50         cvop = OpSIBLING(cvop);
1516             }
1517              
1518             /* Check for exactly one argument */
1519 120 50         if (argop != cvop && OpSIBLING(argop) == cvop) {
    50          
    50          
1520 120           OP *arg = argop;
1521              
1522             /* If arg is $_, fall back to XS (map/grep context) */
1523 120 100         if (op_is_dollar_underscore(aTHX_ arg)) {
1524 1           return entersubop;
1525             }
1526              
1527             /* Detach arg from list */
1528 119           OpMORESIB_set(pushop, cvop);
1529 119           OpLASTSIB_set(arg, NULL);
1530              
1531             /* Create unary custom op with arg as child */
1532 119           OP *newop = newUNOP(OP_CUSTOM, 0, arg);
1533 119           newop->op_ppaddr = pp_func;
1534              
1535 119           op_free(entersubop);
1536 119           return newop;
1537             }
1538              
1539             /* Fall through to XS for edge cases */
1540 0           return entersubop;
1541             }
1542              
1543             /* Individual call checkers for each type predicate */
1544 6           static OP* is_ref_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1545 6           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_ref);
1546             }
1547              
1548 4           static OP* is_array_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1549 4           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_array);
1550             }
1551              
1552 3           static OP* is_hash_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1553 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_hash);
1554             }
1555              
1556 3           static OP* is_code_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1557 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_code);
1558             }
1559              
1560 5           static OP* is_defined_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1561 5           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_defined);
1562             }
1563              
1564             /* String predicate call checkers */
1565 4           static OP* is_empty_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1566 4           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_empty);
1567             }
1568              
1569             /* Generic two-arg string predicate call checker */
1570 21           static OP* two_arg_string_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj, OP* (*pp_func)(pTHX)) {
1571             OP *pushop, *arg1, *arg2, *cvop;
1572              
1573             PERL_UNUSED_ARG(namegv);
1574             PERL_UNUSED_ARG(ckobj);
1575              
1576             /* Get the argument list */
1577 21           pushop = cUNOPx(entersubop)->op_first;
1578 21 50         if (!OpHAS_SIBLING(pushop)) {
1579 21           pushop = cUNOPx(pushop)->op_first;
1580             }
1581              
1582             /* Find args (skip pushmark) */
1583 21 50         arg1 = OpSIBLING(pushop); /* string */
1584 21 50         if (!arg1) return entersubop;
1585              
1586 21 50         arg2 = OpSIBLING(arg1); /* prefix/suffix */
1587 21 50         if (!arg2) return entersubop;
1588              
1589 21 50         cvop = OpSIBLING(arg2); /* cv op (should be last) */
1590 21 50         if (!cvop || OpHAS_SIBLING(cvop)) return entersubop;
    50          
1591              
1592             /* If arg1 is $_, fall back to XS (map/grep context) */
1593 21 100         if (op_is_dollar_underscore(aTHX_ arg1)) {
1594 1           return entersubop;
1595             }
1596              
1597             /* Detach args from the entersub tree */
1598 20           OpMORESIB_set(pushop, cvop);
1599              
1600             /* Chain arg1 -> arg2 */
1601 20           OpMORESIB_set(arg1, arg2);
1602 20           OpLASTSIB_set(arg2, NULL);
1603              
1604             /*
1605             * Create a custom BINOP-style op.
1606             * Use newBINOP to create a proper binary op structure where
1607             * both arguments are children. The optimizer won't eliminate
1608             * children of an op that's going to use them.
1609             */
1610 20           OP *binop = newBINOP(OP_NULL, 0, arg1, arg2);
1611 20           binop->op_type = OP_CUSTOM;
1612 20           binop->op_ppaddr = pp_func;
1613 20           binop->op_flags = OPf_WANT_SCALAR | OPf_KIDS | OPf_STACKED;
1614              
1615 20           op_free(entersubop);
1616 20           return binop;
1617             }
1618              
1619 7           static OP* starts_with_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1620 7           return two_arg_string_call_checker(aTHX_ entersubop, namegv, ckobj, pp_starts_with);
1621             }
1622              
1623 4           static OP* ends_with_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1624 4           return two_arg_string_call_checker(aTHX_ entersubop, namegv, ckobj, pp_ends_with);
1625             }
1626              
1627             /* Boolean/Truthiness call checkers */
1628 5           static OP* is_true_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1629 5           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_true);
1630             }
1631              
1632 5           static OP* is_false_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1633 5           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_false);
1634             }
1635              
1636 7           static OP* bool_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1637 7           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_bool);
1638             }
1639              
1640             /* Extended type predicate call checkers */
1641 6           static OP* is_num_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1642 6           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_num);
1643             }
1644              
1645 5           static OP* is_int_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1646 5           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_int);
1647             }
1648              
1649 3           static OP* is_blessed_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1650 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_blessed);
1651             }
1652              
1653 3           static OP* is_scalar_ref_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1654 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_scalar_ref);
1655             }
1656              
1657 3           static OP* is_regex_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1658 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_regex);
1659             }
1660              
1661 2           static OP* is_glob_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1662 2           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_glob);
1663             }
1664              
1665 0           static OP* is_string_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1666 0           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_string);
1667             }
1668              
1669             /* Numeric predicate call checkers */
1670 5           static OP* is_positive_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1671 5           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_positive);
1672             }
1673              
1674 4           static OP* is_negative_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1675 4           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_negative);
1676             }
1677              
1678 4           static OP* is_zero_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1679 4           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_zero);
1680             }
1681              
1682             /* Numeric utility call checkers */
1683 6           static OP* is_even_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1684 6           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_even);
1685             }
1686              
1687 5           static OP* is_odd_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1688 5           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_odd);
1689             }
1690              
1691             /* is_between needs 3 args - use same pattern as clamp */
1692 5           static OP* is_between_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1693             /* 3-arg ops are complex to optimize with custom ops.
1694             * Fall back to XS function for now. */
1695             PERL_UNUSED_ARG(namegv);
1696             PERL_UNUSED_ARG(ckobj);
1697 5           return entersubop;
1698             }
1699              
1700             /* Collection call checkers */
1701 3           static OP* is_empty_array_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1702 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_empty_array);
1703             }
1704              
1705 3           static OP* is_empty_hash_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1706 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_is_empty_hash);
1707             }
1708              
1709 3           static OP* array_len_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1710 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_array_len);
1711             }
1712              
1713 3           static OP* hash_size_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1714 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_hash_size);
1715             }
1716              
1717 3           static OP* array_first_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1718 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_array_first);
1719             }
1720              
1721 3           static OP* array_last_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1722 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_array_last);
1723             }
1724              
1725             /* trim uses single-arg pattern */
1726 5           static OP* trim_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1727 5           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_trim);
1728             }
1729              
1730 3           static OP* ltrim_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1731 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_ltrim);
1732             }
1733              
1734 3           static OP* rtrim_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1735 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_rtrim);
1736             }
1737              
1738             /* maybe uses two-arg pattern */
1739 2           static OP* maybe_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1740 2           return two_arg_string_call_checker(aTHX_ entersubop, namegv, ckobj, pp_maybe);
1741             }
1742              
1743             /* Numeric ops */
1744 3           static OP* sign_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1745 3           return type_predicate_call_checker(aTHX_ entersubop, namegv, ckobj, pp_sign);
1746             }
1747              
1748 4           static OP* min2_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1749 4           return two_arg_string_call_checker(aTHX_ entersubop, namegv, ckobj, pp_min2);
1750             }
1751              
1752 4           static OP* max2_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
1753 4           return two_arg_string_call_checker(aTHX_ entersubop, namegv, ckobj, pp_max2);
1754             }
1755              
1756             /* ============================================
1757             Memo implementation
1758             ============================================ */
1759              
1760 213           XS_INTERNAL(xs_memo) {
1761 213           dXSARGS;
1762 213 50         if (items != 1) croak("Usage: Func::Util::memo(\\&func)");
1763              
1764 213           SV *func = ST(0);
1765 213 50         if (!SvROK(func) || SvTYPE(SvRV(func)) != SVt_PVCV) {
    50          
1766 0           croak("Func::Util::memo requires a coderef");
1767             }
1768              
1769             /* Allocate memo slot */
1770 213           IV idx = g_memo_count++;
1771 213           ensure_memo_capacity(idx);
1772              
1773 213           MemoizedFunc *mf = &g_memos[idx];
1774 213           mf->func = SvREFCNT_inc_simple_NN(func);
1775 213           mf->cache = newHV();
1776 213           mf->hits = 0;
1777 213           mf->misses = 0;
1778              
1779             /* Create wrapper CV */
1780 213           CV *wrapper = newXS(NULL, xs_memo_call, __FILE__);
1781 213           CvXSUBANY(wrapper).any_iv = idx;
1782              
1783             /* Attach magic for cleanup when wrapper is freed */
1784 213           sv_magicext((SV*)wrapper, NULL, PERL_MAGIC_ext, &util_memo_vtbl, NULL, idx);
1785              
1786 213           ST(0) = sv_2mortal(newRV_noinc((SV*)wrapper));
1787 213           XSRETURN(1);
1788             }
1789              
1790 849           XS_INTERNAL(xs_memo_call) {
1791 849           dXSARGS;
1792 849           IV idx = CvXSUBANY(cv).any_iv;
1793 849           MemoizedFunc *mf = &g_memos[idx];
1794              
1795             /* Build cache key from arguments */
1796 849           SV *key = build_cache_key(aTHX_ &ST(0), items);
1797             STRLEN key_len;
1798 849           const char *key_pv = SvPV(key, key_len);
1799              
1800             /* Check cache */
1801 849           SV **cached = hv_fetch(mf->cache, key_pv, key_len, 0);
1802 849 100         if (cached && SvOK(*cached)) {
    50          
1803 421           mf->hits++;
1804 421           SvREFCNT_dec_NN(key);
1805 421 50         if (SvROK(*cached) && SvTYPE(SvRV(*cached)) == SVt_PVAV) {
    0          
1806 0           AV *av = (AV*)SvRV(*cached);
1807 0           IV len = av_len(av) + 1;
1808             IV i;
1809 0 0         EXTEND(SP, len);
    0          
1810 0 0         for (i = 0; i < len; i++) {
1811 0           SV **elem = av_fetch(av, i, 0);
1812 0 0         ST(i) = elem ? *elem : &PL_sv_undef;
1813             }
1814 0           XSRETURN(len);
1815             } else {
1816 421           ST(0) = *cached;
1817 421           XSRETURN(1);
1818             }
1819             }
1820              
1821 428           mf->misses++;
1822              
1823 428           ENTER;
1824 428           SAVETMPS;
1825 428 50         PUSHMARK(SP);
1826              
1827             IV i;
1828 428 50         EXTEND(SP, items);
    50          
1829 857 100         for (i = 0; i < items; i++) {
1830 429           PUSHs(ST(i));
1831             }
1832 428           PUTBACK;
1833              
1834 428           IV count = call_sv(mf->func, G_ARRAY);
1835              
1836 428           SPAGAIN;
1837              
1838 428 50         if (count == 1) {
1839 428           SV *result = SvREFCNT_inc(POPs);
1840 428           hv_store(mf->cache, key_pv, key_len, result, 0);
1841 428           PUTBACK;
1842 428 50         FREETMPS;
1843 428           LEAVE;
1844 428           SvREFCNT_dec_NN(key);
1845 428           ST(0) = result;
1846 428           XSRETURN(1);
1847 0 0         } else if (count > 0) {
1848 0           AV *av = newAV();
1849 0           av_extend(av, count - 1);
1850 0 0         for (i = count - 1; i >= 0; i--) {
1851 0           av_store(av, i, SvREFCNT_inc(POPs));
1852             }
1853 0           SV *result = newRV_noinc((SV*)av);
1854 0           hv_store(mf->cache, key_pv, key_len, result, 0);
1855 0           PUTBACK;
1856 0 0         FREETMPS;
1857 0           LEAVE;
1858 0           SvREFCNT_dec_NN(key);
1859 0 0         for (i = 0; i < count; i++) {
1860 0           SV **elem = av_fetch(av, i, 0);
1861 0 0         ST(i) = elem ? *elem : &PL_sv_undef;
1862             }
1863 0           XSRETURN(count);
1864             } else {
1865 0           hv_store(mf->cache, key_pv, key_len, &PL_sv_undef, 0);
1866 0           PUTBACK;
1867 0 0         FREETMPS;
1868 0           LEAVE;
1869 0           SvREFCNT_dec_NN(key);
1870 0           XSRETURN_EMPTY;
1871             }
1872             }
1873              
1874             /* ============================================
1875             Pipe/Compose implementation
1876             ============================================ */
1877              
1878 1011           XS_INTERNAL(xs_pipe) {
1879 1011           dXSARGS;
1880 1011 50         if (items < 2) croak("Usage: Func::Util::pipeline($value, \\&fn1, \\&fn2, ...)");
1881              
1882 1011           SV *value = SvREFCNT_inc(ST(0));
1883             IV i;
1884              
1885 4036 100         for (i = 1; i < items; i++) {
1886 3025           SV *func = ST(i);
1887 3025 50         if (!SvROK(func) || SvTYPE(SvRV(func)) != SVt_PVCV) {
    50          
1888 0           SvREFCNT_dec(value);
1889 0           croak("Func::Util::pipeline: argument %d is not a coderef", (int)i);
1890             }
1891              
1892 3025           ENTER;
1893 3025           SAVETMPS;
1894 3025 50         PUSHMARK(SP);
1895 3025 50         XPUSHs(value);
1896 3025           PUTBACK;
1897              
1898 3025           call_sv(func, G_SCALAR);
1899              
1900 3025           SPAGAIN;
1901 3025           SV *new_value = POPs;
1902 3025           SvREFCNT_inc(new_value);
1903 3025           PUTBACK;
1904 3025 100         FREETMPS;
1905 3025           LEAVE;
1906              
1907 3025           SvREFCNT_dec(value);
1908 3025           value = new_value;
1909             }
1910              
1911 1011           ST(0) = sv_2mortal(value);
1912 1011           XSRETURN(1);
1913             }
1914              
1915 1010           XS_INTERNAL(xs_compose) {
1916 1010           dXSARGS;
1917 1010 50         if (items < 1) croak("Usage: Func::Util::compose(\\&fn1, \\&fn2, ...)");
1918              
1919 1010           AV *funcs = newAV();
1920 1010           av_extend(funcs, items - 1);
1921             IV i;
1922 4030 100         for (i = 0; i < items; i++) {
1923 3020           SV *func = ST(i);
1924 3020 50         if (!SvROK(func) || SvTYPE(SvRV(func)) != SVt_PVCV) {
    50          
1925 0           croak("Func::Util::compose: argument %d is not a coderef", (int)(i+1));
1926             }
1927 3020           av_store(funcs, i, SvREFCNT_inc_simple_NN(func));
1928             }
1929              
1930 1010           CV *wrapper = newXS(NULL, xs_compose_call, __FILE__);
1931 1010           CvXSUBANY(wrapper).any_ptr = (void*)funcs;
1932              
1933             /* Attach magic for cleanup when wrapper is freed - pass AV via mg_ptr */
1934 1010           sv_magicext((SV*)wrapper, NULL, PERL_MAGIC_ext, &util_compose_vtbl, (char*)funcs, 0);
1935              
1936 1010           ST(0) = sv_2mortal(newRV_noinc((SV*)wrapper));
1937 1010           XSRETURN(1);
1938             }
1939              
1940 1011           XS_INTERNAL(xs_compose_call) {
1941 1011           dXSARGS;
1942 1011           AV *funcs = (AV*)CvXSUBANY(cv).any_ptr;
1943 1011           IV func_count = av_len(funcs) + 1;
1944              
1945 1011           SV *value = NULL;
1946              
1947             IV i;
1948 4033 100         for (i = func_count - 1; i >= 0; i--) {
1949 3022           SV **func_ptr = av_fetch(funcs, i, 0);
1950 3022 50         if (!func_ptr) continue;
1951              
1952 3022           ENTER;
1953 3022           SAVETMPS;
1954 3022 50         PUSHMARK(SP);
1955              
1956 3022 100         if (i == func_count - 1) {
1957             IV j;
1958 1011 50         EXTEND(SP, items);
    50          
1959 2022 100         for (j = 0; j < items; j++) {
1960 1011           PUSHs(ST(j));
1961             }
1962             } else {
1963 2011 50         XPUSHs(value);
1964             }
1965 3022           PUTBACK;
1966              
1967 3022           call_sv(*func_ptr, G_SCALAR);
1968              
1969 3022           SPAGAIN;
1970 3022           SV *new_value = POPs;
1971 3022           SvREFCNT_inc(new_value);
1972 3022           PUTBACK;
1973 3022 100         FREETMPS;
1974 3022           LEAVE;
1975              
1976 3022 100         if (value) SvREFCNT_dec(value);
1977 3022           value = new_value;
1978             }
1979              
1980 1011 50         ST(0) = value ? sv_2mortal(value) : &PL_sv_undef;
1981 1011           XSRETURN(1);
1982             }
1983              
1984             /* ============================================
1985             Lazy evaluation implementation
1986             ============================================ */
1987              
1988 1008           XS_INTERNAL(xs_lazy) {
1989 1008           dXSARGS;
1990 1008 50         if (items != 1) croak("Usage: Func::Util::lazy(sub { ... })");
1991              
1992 1008           SV *thunk = ST(0);
1993 1008 50         if (!SvROK(thunk) || SvTYPE(SvRV(thunk)) != SVt_PVCV) {
    50          
1994 0           croak("Func::Util::lazy requires a coderef");
1995             }
1996              
1997 1008           IV idx = g_lazy_count++;
1998 1008           ensure_lazy_capacity(idx);
1999              
2000 1008           LazyValue *lv = &g_lazies[idx];
2001 1008           lv->thunk = SvREFCNT_inc_simple_NN(thunk);
2002 1008           lv->value = NULL;
2003 1008           lv->forced = FALSE;
2004              
2005 1008           SV *obj = newSViv(idx);
2006 1008           SV *ref = newRV_noinc(obj);
2007 1008           sv_bless(ref, gv_stashpv("Func::Util::Lazy", GV_ADD));
2008              
2009             /* Attach magic for cleanup when lazy object is freed */
2010 1008           sv_magicext(obj, NULL, PERL_MAGIC_ext, &util_lazy_vtbl, NULL, idx);
2011              
2012 1008           ST(0) = sv_2mortal(ref);
2013 1008           XSRETURN(1);
2014             }
2015              
2016 2018           XS_INTERNAL(xs_force) {
2017 2018           dXSARGS;
2018 2018 50         if (items != 1) croak("Usage: Func::Util::force($lazy)");
2019              
2020 2018           SV *lazy = ST(0);
2021              
2022 2018 100         if (!SvROK(lazy) || !sv_derived_from(lazy, "Func::Util::Lazy")) {
    100          
2023 5           ST(0) = lazy;
2024 5           XSRETURN(1);
2025             }
2026              
2027 2013           IV idx = SvIV(SvRV(lazy));
2028 2013 50         if (idx < 0 || idx >= g_lazy_count) {
    50          
2029 0           croak("Func::Util::force: invalid lazy value");
2030             }
2031              
2032 2013           LazyValue *lv = &g_lazies[idx];
2033              
2034 2013 100         if (lv->forced) {
2035 1005           ST(0) = lv->value;
2036 1005           XSRETURN(1);
2037             }
2038              
2039 1008           ENTER;
2040 1008           SAVETMPS;
2041 1008 50         PUSHMARK(SP);
2042 1008           PUTBACK;
2043              
2044 1008           call_sv(lv->thunk, G_SCALAR);
2045              
2046 1008           SPAGAIN;
2047 1008           lv->value = SvREFCNT_inc(POPs);
2048 1008           lv->forced = TRUE;
2049 1008           PUTBACK;
2050 1008 50         FREETMPS;
2051 1008           LEAVE;
2052              
2053 1008           SvREFCNT_dec(lv->thunk);
2054 1008           lv->thunk = NULL;
2055              
2056 1008           ST(0) = lv->value;
2057 1008           XSRETURN(1);
2058             }
2059              
2060             /* ============================================
2061             Safe navigation (dig) implementation
2062             ============================================ */
2063              
2064 19124           XS_INTERNAL(xs_dig) {
2065 19124           dXSARGS;
2066 19124 50         if (items < 2) croak("Usage: Func::Util::dig($hash, @keys)");
2067              
2068 19124           SV *current = ST(0);
2069             IV i;
2070              
2071 60471 100         for (i = 1; i < items; i++) {
2072 47354 100         if (!SvROK(current) || SvTYPE(SvRV(current)) != SVt_PVHV) {
    100          
2073 6007           XSRETURN_UNDEF;
2074             }
2075              
2076 44353           HV *hv = (HV*)SvRV(current);
2077 44353           SV *key = ST(i);
2078             STRLEN key_len;
2079 44353           const char *key_pv = SvPV(key, key_len);
2080              
2081 44353           SV **val = hv_fetch(hv, key_pv, key_len, 0);
2082 44353 100         if (!val || !SvOK(*val)) {
    100          
2083 3006           XSRETURN_UNDEF;
2084             }
2085              
2086 41347           current = *val;
2087             }
2088              
2089 13117           ST(0) = current;
2090 13117           XSRETURN(1);
2091             }
2092              
2093             /* ============================================
2094             Tap implementation
2095             ============================================ */
2096              
2097 12105           XS_INTERNAL(xs_tap) {
2098 12105           dXSARGS;
2099 12105 50         if (items != 2) croak("Usage: Func::Util::tap(\\&block, $value)");
2100              
2101 12105           SV *func = ST(0);
2102 12105           SV *value = ST(1);
2103              
2104 12105 50         if (!SvROK(func) || SvTYPE(SvRV(func)) != SVt_PVCV) {
    50          
2105 0           croak("Func::Util::tap: first argument must be a coderef");
2106             }
2107              
2108 12105           ENTER;
2109 12105           SAVETMPS;
2110 12105           SAVE_DEFSV;
2111 12105           DEFSV_set(value);
2112              
2113 12105 50         PUSHMARK(SP);
2114 12105 50         XPUSHs(value);
2115 12105           PUTBACK;
2116              
2117 12105           call_sv(func, G_DISCARD | G_VOID);
2118              
2119 12105           SPAGAIN;
2120 12105 50         FREETMPS;
2121 12105           LEAVE;
2122              
2123 12105           ST(0) = value;
2124 12105           XSRETURN(1);
2125             }
2126              
2127             /* ============================================
2128             Clamp XS fallback
2129             ============================================ */
2130              
2131 18161           XS_INTERNAL(xs_clamp) {
2132 18161           dXSARGS;
2133             NV value, min, max, result;
2134 18161 50         if (items != 3) croak("Usage: Func::Util::clamp($value, $min, $max)");
2135              
2136 18161           value = SvNV(ST(0));
2137 18161           min = SvNV(ST(1));
2138 18161           max = SvNV(ST(2));
2139              
2140 18161 100         if (value < min) {
2141 3014           result = min;
2142 15147 100         } else if (value > max) {
2143 3014           result = max;
2144             } else {
2145 12133           result = value;
2146             }
2147              
2148 18161           ST(0) = sv_2mortal(newSVnv(result));
2149 18161           XSRETURN(1);
2150             }
2151              
2152             /* ============================================
2153             Identity XS fallback
2154             ============================================ */
2155              
2156 16119           XS_INTERNAL(xs_identity) {
2157 16119           dXSARGS;
2158 16119 50         if (items != 1) croak("Usage: Func::Util::identity($value)");
2159 16119           XSRETURN(1);
2160             }
2161              
2162             /* ============================================
2163             Always implementation
2164             ============================================ */
2165              
2166 8           XS_INTERNAL(xs_always) {
2167 8           dXSARGS;
2168 8 50         if (items != 1) croak("Usage: Func::Util::always($value)");
2169              
2170 8           IV idx = g_always_count++;
2171 8           ensure_always_capacity(idx);
2172              
2173 8           g_always_values[idx] = SvREFCNT_inc_simple_NN(ST(0));
2174              
2175 8           CV *wrapper = newXS(NULL, xs_always_call, __FILE__);
2176 8           CvXSUBANY(wrapper).any_iv = idx;
2177              
2178             /* Attach magic for cleanup when wrapper is freed */
2179 8           sv_magicext((SV*)wrapper, NULL, PERL_MAGIC_ext, &util_always_vtbl, NULL, idx);
2180              
2181 8           ST(0) = sv_2mortal(newRV_noinc((SV*)wrapper));
2182 8           XSRETURN(1);
2183             }
2184              
2185 4014           XS_INTERNAL(xs_always_call) {
2186 4014           dXSARGS;
2187             PERL_UNUSED_VAR(items);
2188 4014           IV idx = CvXSUBANY(cv).any_iv;
2189              
2190 4014           ST(0) = g_always_values[idx];
2191 4014           XSRETURN(1);
2192             }
2193              
2194             /* ============================================
2195             Stub/noop functions - return constants
2196             ============================================ */
2197              
2198             /* pp_noop - custom op that returns undef */
2199 2           static OP* pp_noop(pTHX) {
2200 2           dSP;
2201 2 50         XPUSHs(&PL_sv_undef);
2202 2           RETURN;
2203             }
2204              
2205             /* noop call checker - replace with ultra-fast custom op */
2206 2           static OP* noop_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
2207             OP *newop;
2208             PERL_UNUSED_ARG(namegv);
2209             PERL_UNUSED_ARG(ckobj);
2210              
2211 2           op_free(entersubop);
2212              
2213 2           NewOp(1101, newop, 1, OP);
2214 2           newop->op_type = OP_CUSTOM;
2215 2           newop->op_ppaddr = pp_noop;
2216 2           newop->op_flags = OPf_WANT_SCALAR;
2217 2           newop->op_next = newop;
2218              
2219 2           return newop;
2220             }
2221              
2222             /* noop() - does nothing, returns undef. Ignores all arguments. */
2223 4004           XS_INTERNAL(xs_noop) {
2224 4004           dXSARGS;
2225             PERL_UNUSED_VAR(items);
2226 4004           XSRETURN_UNDEF;
2227             }
2228              
2229             /* stub_true() - always returns true (1) */
2230 12105           XS_INTERNAL(xs_stub_true) {
2231 12105           dXSARGS;
2232             PERL_UNUSED_VAR(items);
2233 12105           XSRETURN_YES;
2234             }
2235              
2236             /* stub_false() - always returns false ('') */
2237 12105           XS_INTERNAL(xs_stub_false) {
2238 12105           dXSARGS;
2239             PERL_UNUSED_VAR(items);
2240 12105           XSRETURN_NO;
2241             }
2242              
2243             /* stub_array() - returns empty arrayref in scalar context, empty list in list context */
2244 12105           XS_INTERNAL(xs_stub_array) {
2245 12105           dXSARGS;
2246             PERL_UNUSED_VAR(items);
2247 12105 100         if (GIMME_V == G_ARRAY) {
2248 10102           XSRETURN_EMPTY;
2249             }
2250 2003           ST(0) = sv_2mortal(newRV_noinc((SV*)newAV()));
2251 2003           XSRETURN(1);
2252             }
2253              
2254             /* stub_hash() - returns empty hashref in scalar context, empty list in list context */
2255 12105           XS_INTERNAL(xs_stub_hash) {
2256 12105           dXSARGS;
2257             PERL_UNUSED_VAR(items);
2258 12105 100         if (GIMME_V == G_ARRAY) {
2259 10102           XSRETURN_EMPTY;
2260             }
2261 2003           ST(0) = sv_2mortal(newRV_noinc((SV*)newHV()));
2262 2003           XSRETURN(1);
2263             }
2264              
2265             /* stub_string() - always returns empty string '' */
2266 12103           XS_INTERNAL(xs_stub_string) {
2267 12103           dXSARGS;
2268             PERL_UNUSED_VAR(items);
2269             /* Return shared empty string constant - XSRETURN_NO returns '' */
2270 12103           XSRETURN_NO;
2271             }
2272              
2273             /* stub_zero() - always returns 0 */
2274 12104           XS_INTERNAL(xs_stub_zero) {
2275 12104           dXSARGS;
2276             PERL_UNUSED_VAR(items);
2277             /* Return shared 0 SV */
2278 12104           ST(0) = &PL_sv_zero;
2279 12104           XSRETURN(1);
2280             }
2281              
2282             /* ============================================
2283             Functional combinators
2284             ============================================ */
2285              
2286             /* negate(\&pred) - returns a function that returns the opposite */
2287 1008           XS_INTERNAL(xs_negate) {
2288 1008           dXSARGS;
2289 1008 50         if (items != 1) croak("Usage: Func::Util::negate(\\&predicate)");
2290              
2291 1008           SV *pred = ST(0);
2292 1008 50         if (!SvROK(pred) || SvTYPE(SvRV(pred)) != SVt_PVCV) {
    50          
2293 0           croak("Func::Util::negate: argument must be a coderef");
2294             }
2295              
2296 1008           CV *wrapper = newXS(NULL, xs_negate_call, __FILE__);
2297 1008           CvXSUBANY(wrapper).any_ptr = SvREFCNT_inc_simple_NN(pred);
2298              
2299 1008           ST(0) = sv_2mortal(newRV_noinc((SV*)wrapper));
2300 1008           XSRETURN(1);
2301             }
2302              
2303 12113           XS_INTERNAL(xs_negate_call) {
2304 12113           dXSARGS;
2305 12113           SV *pred = (SV*)CvXSUBANY(cv).any_ptr;
2306              
2307 12113           ENTER;
2308 12113           SAVETMPS;
2309 12113 50         PUSHMARK(SP);
2310              
2311             IV i;
2312 12113 50         EXTEND(SP, items);
    50          
2313 24226 100         for (i = 0; i < items; i++) {
2314 12113           PUSHs(ST(i));
2315             }
2316 12113           PUTBACK;
2317              
2318 12113           call_sv(pred, G_SCALAR);
2319              
2320 12113           SPAGAIN;
2321 12113           SV *result = POPs;
2322 12113           bool val = SvTRUE(result);
2323 12113           PUTBACK;
2324 12113 50         FREETMPS;
2325 12113           LEAVE;
2326              
2327 12113 100         ST(0) = val ? &PL_sv_no : &PL_sv_yes;
2328 12113           XSRETURN(1);
2329             }
2330              
2331             /* once(\&f) - execute once, cache forever */
2332 1005           XS_INTERNAL(xs_once) {
2333 1005           dXSARGS;
2334 1005 50         if (items != 1) croak("Usage: Func::Util::once(\\&func)");
2335              
2336 1005           SV *func = ST(0);
2337 1005 50         if (!SvROK(func) || SvTYPE(SvRV(func)) != SVt_PVCV) {
    50          
2338 0           croak("Func::Util::once: argument must be a coderef");
2339             }
2340              
2341 1005           IV idx = g_once_count++;
2342 1005           ensure_once_capacity(idx);
2343              
2344 1005           OnceFunc *of = &g_onces[idx];
2345 1005           of->func = SvREFCNT_inc_simple_NN(func);
2346 1005           of->result = NULL;
2347 1005           of->called = FALSE;
2348              
2349 1005           CV *wrapper = newXS(NULL, xs_once_call, __FILE__);
2350 1005           CvXSUBANY(wrapper).any_iv = idx;
2351              
2352             /* Attach magic for cleanup when wrapper is freed */
2353 1005           sv_magicext((SV*)wrapper, NULL, PERL_MAGIC_ext, &util_once_vtbl, NULL, idx);
2354              
2355 1005           ST(0) = sv_2mortal(newRV_noinc((SV*)wrapper));
2356 1005           XSRETURN(1);
2357             }
2358              
2359 3015           XS_INTERNAL(xs_once_call) {
2360 3015           dXSARGS;
2361             PERL_UNUSED_VAR(items);
2362 3015           IV idx = CvXSUBANY(cv).any_iv;
2363 3015           OnceFunc *of = &g_onces[idx];
2364              
2365 3015 100         if (of->called) {
2366 2010 50         ST(0) = of->result ? of->result : &PL_sv_undef;
2367 2010           XSRETURN(1);
2368             }
2369              
2370 1005           ENTER;
2371 1005           SAVETMPS;
2372 1005 50         PUSHMARK(SP);
2373 1005           PUTBACK;
2374              
2375 1005           call_sv(of->func, G_SCALAR);
2376              
2377 1005           SPAGAIN;
2378 1005           of->result = SvREFCNT_inc(POPs);
2379 1005           of->called = TRUE;
2380 1005           PUTBACK;
2381 1005 50         FREETMPS;
2382 1005           LEAVE;
2383              
2384             /* Free the original function, no longer needed */
2385 1005           SvREFCNT_dec(of->func);
2386 1005           of->func = NULL;
2387              
2388 1005           ST(0) = of->result;
2389 1005           XSRETURN(1);
2390             }
2391              
2392             /* partial(\&f, @bound) - bind first N args */
2393 1012           XS_INTERNAL(xs_partial) {
2394 1012           dXSARGS;
2395 1012 50         if (items < 1) croak("Usage: Func::Util::partial(\\&func, @bound_args)");
2396              
2397 1012           SV *func = ST(0);
2398 1012 50         if (!SvROK(func) || SvTYPE(SvRV(func)) != SVt_PVCV) {
    50          
2399 0           croak("Func::Util::partial: first argument must be a coderef");
2400             }
2401              
2402 1012           IV idx = g_partial_count++;
2403 1012           ensure_partial_capacity(idx);
2404              
2405 1012           PartialFunc *pf = &g_partials[idx];
2406 1012           pf->func = SvREFCNT_inc_simple_NN(func);
2407 1012           pf->bound_args = newAV();
2408              
2409             /* Store bound arguments */
2410             IV i;
2411 2024 100         for (i = 1; i < items; i++) {
2412 1012           av_push(pf->bound_args, SvREFCNT_inc_simple_NN(ST(i)));
2413             }
2414              
2415 1012           CV *wrapper = newXS(NULL, xs_partial_call, __FILE__);
2416 1012           CvXSUBANY(wrapper).any_iv = idx;
2417              
2418             /* Attach magic for cleanup when wrapper is freed */
2419 1012           sv_magicext((SV*)wrapper, NULL, PERL_MAGIC_ext, &util_partial_vtbl, NULL, idx);
2420              
2421 1012           ST(0) = sv_2mortal(newRV_noinc((SV*)wrapper));
2422 1012           XSRETURN(1);
2423             }
2424              
2425 2016           XS_INTERNAL(xs_partial_call) {
2426 2016           dXSARGS;
2427 2016           IV idx = CvXSUBANY(cv).any_iv;
2428 2016           PartialFunc *pf = &g_partials[idx];
2429              
2430 2016           IV bound_count = av_len(pf->bound_args) + 1;
2431 2016           IV total = bound_count + items;
2432              
2433 2016           ENTER;
2434 2016           SAVETMPS;
2435 2016 50         PUSHMARK(SP);
2436              
2437 2016 50         EXTEND(SP, total);
    50          
2438              
2439             /* Push bound args first */
2440             IV i;
2441 4032 100         for (i = 0; i < bound_count; i++) {
2442 2016           SV **elem = av_fetch(pf->bound_args, i, 0);
2443 2016 50         PUSHs(elem ? *elem : &PL_sv_undef);
2444             }
2445              
2446             /* Push call-time args */
2447 4031 100         for (i = 0; i < items; i++) {
2448 2015           PUSHs(ST(i));
2449             }
2450 2016           PUTBACK;
2451              
2452 2016           IV count = call_sv(pf->func, G_SCALAR);
2453              
2454 2016           SPAGAIN;
2455 2016 50         SV *result = count > 0 ? POPs : &PL_sv_undef;
2456 2016           SvREFCNT_inc(result);
2457 2016           PUTBACK;
2458 2016 50         FREETMPS;
2459 2016           LEAVE;
2460              
2461 2016           ST(0) = sv_2mortal(result);
2462 2016           XSRETURN(1);
2463             }
2464              
2465             /* ============================================
2466             Data extraction functions
2467             ============================================ */
2468              
2469             /* pick($hash, @keys) - extract subset of keys
2470             * Returns hashref in scalar context, flattened list in list context */
2471 12722           XS_INTERNAL(xs_pick) {
2472 12722           dXSARGS;
2473 12722 50         if (items < 1) croak("Usage: Func::Util::pick(\\%%hash, @keys)");
2474              
2475 12722           SV *href = ST(0);
2476 12722 50         if (!SvROK(href) || SvTYPE(SvRV(href)) != SVt_PVHV) {
    50          
2477 0           croak("Func::Util::pick: first argument must be a hashref");
2478             }
2479              
2480 12722           HV *src = (HV*)SvRV(href);
2481 12722           HV *dest = newHV();
2482              
2483             IV i;
2484 38566 100         for (i = 1; i < items; i++) {
2485 25844           SV *key = ST(i);
2486             STRLEN key_len;
2487 25844           const char *key_pv = SvPV(key, key_len);
2488              
2489 25844           SV **val = hv_fetch(src, key_pv, key_len, 0);
2490 25844 100         if (val && SvOK(*val)) {
    100          
2491 24238           hv_store(dest, key_pv, key_len, SvREFCNT_inc(*val), 0);
2492             }
2493             }
2494              
2495             /* Check calling context */
2496 12722 100         if (GIMME_V == G_ARRAY) {
2497             /* List context - return flattened key-value pairs */
2498 10100 50         IV n = HvUSEDKEYS(dest);
2499 10100           SP -= items; /* Reset stack pointer */
2500 10100 50         EXTEND(SP, n * 2);
    50          
2501              
2502 10100           hv_iterinit(dest);
2503             HE *he;
2504 30300 100         while ((he = hv_iternext(dest)) != NULL) {
2505             STRLEN klen;
2506 20200 50         const char *key = HePV(he, klen);
2507 20200           mPUSHp(key, klen);
2508 20200           mPUSHs(SvREFCNT_inc(HeVAL(he)));
2509             }
2510 10100           SvREFCNT_dec((SV*)dest); /* Free the temp hash */
2511 10100           PUTBACK;
2512 10100           return;
2513             }
2514              
2515             /* Scalar context - return hashref */
2516 2622           ST(0) = sv_2mortal(newRV_noinc((SV*)dest));
2517 2622           XSRETURN(1);
2518             }
2519              
2520             /* pluck(\@hashes, $field) - extract field from each hash */
2521 2211           XS_INTERNAL(xs_pluck) {
2522 2211           dXSARGS;
2523 2211 50         if (items != 2) croak("Usage: Func::Util::pluck(\\@array, $field)");
2524              
2525 2211           SV *aref = ST(0);
2526 2211 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
2527 0           croak("Func::Util::pluck: first argument must be an arrayref");
2528             }
2529              
2530 2211           SV *field = ST(1);
2531             STRLEN field_len;
2532 2211           const char *field_pv = SvPV(field, field_len);
2533              
2534 2211           AV *src = (AV*)SvRV(aref);
2535 2211           IV len = av_len(src) + 1;
2536 2211           AV *dest = newAV();
2537 2211           av_extend(dest, len - 1);
2538              
2539             IV i;
2540 8836 100         for (i = 0; i < len; i++) {
2541 6625           SV **elem = av_fetch(src, i, 0);
2542 13250 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
2543 6625           HV *hv = (HV*)SvRV(*elem);
2544 6625           SV **val = hv_fetch(hv, field_pv, field_len, 0);
2545 6625 100         if (val && SvOK(*val)) {
    50          
2546 5422           av_push(dest, SvREFCNT_inc(*val));
2547             } else {
2548 1203           av_push(dest, &PL_sv_undef);
2549             }
2550             } else {
2551 0           av_push(dest, &PL_sv_undef);
2552             }
2553             }
2554              
2555 2211           ST(0) = sv_2mortal(newRV_noinc((SV*)dest));
2556 2211           XSRETURN(1);
2557             }
2558              
2559             /* omit($hash, @keys) - exclude subset of keys (inverse of pick)
2560             * Returns hashref in scalar context, flattened list in list context */
2561 12710           XS_INTERNAL(xs_omit) {
2562 12710           dXSARGS;
2563 12710 50         if (items < 1) croak("Usage: Func::Util::omit(\\%%hash, @keys)");
2564              
2565 12710           SV *href = ST(0);
2566 12710 50         if (!SvROK(href) || SvTYPE(SvRV(href)) != SVt_PVHV) {
    50          
2567 0           croak("Func::Util::omit: first argument must be a hashref");
2568             }
2569              
2570 12710           HV *src = (HV*)SvRV(href);
2571 12710           HV *dest = newHV();
2572              
2573             /* Build exclusion set for O(1) lookup */
2574 12710           HV *exclude = newHV();
2575             IV i;
2576 28025 100         for (i = 1; i < items; i++) {
2577 15315           SV *key = ST(i);
2578             STRLEN key_len;
2579 15315           const char *key_pv = SvPV(key, key_len);
2580 15315           hv_store(exclude, key_pv, key_len, &PL_sv_yes, 0);
2581             }
2582              
2583             /* Iterate source, copy non-excluded keys */
2584 12710           hv_iterinit(src);
2585             HE *entry;
2586 53838 100         while ((entry = hv_iternext(src)) != NULL) {
2587 41128           SV *key = hv_iterkeysv(entry);
2588             STRLEN key_len;
2589 41128           const char *key_pv = SvPV(key, key_len);
2590              
2591 41128 100         if (!hv_exists(exclude, key_pv, key_len)) {
2592 26219           SV *val = hv_iterval(src, entry);
2593 26219 50         if (SvOK(val)) {
2594 26219           hv_store(dest, key_pv, key_len, SvREFCNT_inc(val), 0);
2595             }
2596             }
2597             }
2598              
2599 12710           SvREFCNT_dec((SV*)exclude);
2600              
2601             /* Check calling context */
2602 12710 100         if (GIMME_V == G_ARRAY) {
2603             /* List context - return flattened key-value pairs */
2604 10100 50         IV n = HvUSEDKEYS(dest);
2605 10100           SP -= items; /* Reset stack pointer */
2606 10100 50         EXTEND(SP, n * 2);
    50          
2607              
2608 10100           hv_iterinit(dest);
2609             HE *he;
2610 30300 100         while ((he = hv_iternext(dest)) != NULL) {
2611             STRLEN klen;
2612 20200 50         const char *key = HePV(he, klen);
2613 20200           mPUSHp(key, klen);
2614 20200           mPUSHs(SvREFCNT_inc(HeVAL(he)));
2615             }
2616 10100           SvREFCNT_dec((SV*)dest); /* Free the temp hash */
2617 10100           PUTBACK;
2618 10100           return;
2619             }
2620              
2621             /* Scalar context - return hashref */
2622 2610           ST(0) = sv_2mortal(newRV_noinc((SV*)dest));
2623 2610           XSRETURN(1);
2624             }
2625              
2626             /* uniq(@list) - return unique elements (preserves order) */
2627 2626           XS_INTERNAL(xs_uniq) {
2628 2626           dXSARGS;
2629              
2630 2626 100         if (items == 0) {
2631 1           XSRETURN(0);
2632             }
2633              
2634 2625 100         if (items == 1) {
2635 1611           XSRETURN(1);
2636             }
2637              
2638             /* For small lists, use simple O(n^2) - faster due to no hash overhead */
2639 1014 100         if (items <= 8) {
2640 13           IV out = 0;
2641             IV i, j;
2642 77 100         for (i = 0; i < items; i++) {
2643 64           SV *elem = ST(i);
2644             STRLEN len_i;
2645 64 100         const char *key_i = SvOK(elem) ? SvPV_const(elem, len_i) : "\x00UNDEF\x00";
2646 64 100         if (!SvOK(elem)) len_i = 7;
2647            
2648 64           bool dup = FALSE;
2649 131 100         for (j = 0; j < out; j++) {
2650 91           SV *prev = ST(j);
2651             STRLEN len_j;
2652 91 100         const char *key_j = SvOK(prev) ? SvPV_const(prev, len_j) : "\x00UNDEF\x00";
2653 91 100         if (!SvOK(prev)) len_j = 7;
2654            
2655 91 100         if (len_i == len_j && memcmp(key_i, key_j, len_i) == 0) {
    100          
2656 24           dup = TRUE;
2657 24           break;
2658             }
2659             }
2660 64 100         if (!dup) ST(out++) = elem;
2661             }
2662 13           XSRETURN(out);
2663             }
2664              
2665 1001           HV *seen = newHV();
2666 1001           IV out = 0;
2667 1001           hv_ksplit(seen, items);
2668              
2669             IV i;
2670 11011 100         for (i = 0; i < items; i++) {
2671 10010           SV *elem = ST(i);
2672             STRLEN len;
2673             const char *key;
2674             U32 hash;
2675              
2676 10010 50         key = SvOK(elem) ? SvPV_const(elem, len) : (len = 7, "\x00UNDEF\x00");
2677              
2678 10010 50         PERL_HASH(hash, key, len);
2679              
2680 10010 100         if (!hv_common(seen, NULL, key, len, 0, HV_FETCH_ISEXISTS, NULL, hash)) {
2681 4004           hv_common(seen, NULL, key, len, 0, HV_FETCH_ISSTORE, &PL_sv_yes, hash);
2682 4004           ST(out++) = elem;
2683             }
2684             }
2685              
2686 1001           SvREFCNT_dec_NN((SV*)seen);
2687 1001           XSRETURN(out);
2688             }
2689              
2690             /* partition(\&pred, @list) - split into [matches], [non-matches] */
2691 2210           XS_INTERNAL(xs_partition) {
2692 2210           dXSARGS;
2693 2210 50         if (items < 1) croak("Usage: Func::Util::partition(\\&block, @list)");
2694              
2695 2210           SV *block = ST(0);
2696 2210 50         if (!SvROK(block) || SvTYPE(SvRV(block)) != SVt_PVCV) {
    50          
2697 0           croak("Func::Util::partition: first argument must be a coderef");
2698             }
2699              
2700 2210           IV list_len = items - 1;
2701            
2702 2210 100         if (list_len == 0) {
2703 1           AV *pass = newAV();
2704 1           AV *fail = newAV();
2705 1           AV *outer = newAV();
2706 1           av_push(outer, newRV_noinc((SV*)pass));
2707 1           av_push(outer, newRV_noinc((SV*)fail));
2708 1           ST(0) = sv_2mortal(newRV_noinc((SV*)outer));
2709 1           XSRETURN(1);
2710             }
2711              
2712 2209           AV *pass = newAV();
2713 2209           AV *fail = newAV();
2714 2209           av_extend(pass, list_len >> 1);
2715 2209           av_extend(fail, list_len >> 1);
2716              
2717 2209 50         SV *orig_defsv = DEFSV;
2718              
2719             IV i;
2720 4452 100         for (i = 1; i < items; i++) {
2721 2243           SV *elem = ST(i);
2722              
2723 2243           DEFSV_set(elem);
2724              
2725 2243           ENTER;
2726 2243           SAVETMPS;
2727 2243 50         PUSHMARK(SP);
2728 2243 50         XPUSHs(elem);
2729 2243           PUTBACK;
2730              
2731 2243           call_sv(block, G_SCALAR);
2732              
2733 2243           SPAGAIN;
2734 2243           SV *result = POPs;
2735 2243           bool matched = SvTRUE(result);
2736 2243           PUTBACK;
2737 2243 50         FREETMPS;
2738 2243           LEAVE;
2739              
2740 2243 100         if (matched) {
2741 2222           av_push(pass, SvREFCNT_inc_simple_NN(elem));
2742             } else {
2743 21           av_push(fail, SvREFCNT_inc_simple_NN(elem));
2744             }
2745             }
2746              
2747 2209           DEFSV_set(orig_defsv);
2748              
2749 2209           AV *outer = newAV();
2750 2209           av_push(outer, newRV_noinc((SV*)pass));
2751 2209           av_push(outer, newRV_noinc((SV*)fail));
2752              
2753 2209           ST(0) = sv_2mortal(newRV_noinc((SV*)outer));
2754 2209           XSRETURN(1);
2755             }
2756              
2757             /* defaults($hash, $defaults) - fill in missing keys from defaults
2758             * Returns hashref in scalar context, flattened list in list context */
2759 11507           XS_INTERNAL(xs_defaults) {
2760 11507           dXSARGS;
2761 11507 50         if (items != 2) croak("Usage: Func::Util::defaults(\\%%hash, \\%%defaults)");
2762              
2763 11507           SV *href = ST(0);
2764 11507           SV *dref = ST(1);
2765              
2766 11507 50         if (!SvROK(href) || SvTYPE(SvRV(href)) != SVt_PVHV) {
    50          
2767 0           croak("Func::Util::defaults: first argument must be a hashref");
2768             }
2769 11507 50         if (!SvROK(dref) || SvTYPE(SvRV(dref)) != SVt_PVHV) {
    50          
2770 0           croak("Func::Util::defaults: second argument must be a hashref");
2771             }
2772              
2773 11507           HV *src = (HV*)SvRV(href);
2774 11507           HV *def = (HV*)SvRV(dref);
2775              
2776             /* Pre-size dest hash */
2777 11507 50         IV src_keys = HvUSEDKEYS(src);
2778 11507 50         IV def_keys = HvUSEDKEYS(def);
2779 11507           HV *dest = newHV();
2780 11507           hv_ksplit(dest, src_keys + def_keys);
2781              
2782             /* Copy all from source first */
2783 11507           hv_iterinit(src);
2784             HE *entry;
2785 24014 100         while ((entry = hv_iternext(src)) != NULL) {
2786             STRLEN key_len;
2787 12507 50         const char *key_pv = HePV(entry, key_len);
2788 12507           SV *val = HeVAL(entry);
2789 12507           hv_store(dest, key_pv, key_len, SvREFCNT_inc_simple_NN(val), HeHASH(entry));
2790             }
2791              
2792             /* Fill in missing from defaults - use pre-computed hash */
2793 11507           hv_iterinit(def);
2794 35918 100         while ((entry = hv_iternext(def)) != NULL) {
2795             STRLEN key_len;
2796 24411 50         const char *key_pv = HePV(entry, key_len);
2797 24411           U32 hash = HeHASH(entry);
2798              
2799             /* Check if exists and is defined in dest */
2800 24411           SV **existing = hv_fetch(dest, key_pv, key_len, 0);
2801 24411 100         if (!existing || !SvOK(*existing)) {
    100          
2802 12909           SV *val = HeVAL(entry);
2803 12909           hv_store(dest, key_pv, key_len, SvREFCNT_inc_simple_NN(val), hash);
2804             }
2805             }
2806              
2807             /* Check calling context */
2808 11507 100         if (GIMME_V == G_ARRAY) {
2809             /* List context - return flattened key-value pairs */
2810 10100 50         IV n = HvUSEDKEYS(dest);
2811 10100           SP -= items; /* Reset stack pointer */
2812 10100 50         EXTEND(SP, n * 2);
    50          
2813              
2814 10100           hv_iterinit(dest);
2815             HE *he;
2816 30300 100         while ((he = hv_iternext(dest)) != NULL) {
2817             STRLEN klen;
2818 20200 50         const char *key = HePV(he, klen);
2819 20200           mPUSHp(key, klen);
2820 20200           mPUSHs(SvREFCNT_inc(HeVAL(he)));
2821             }
2822 10100           SvREFCNT_dec((SV*)dest); /* Free the temp hash */
2823 10100           PUTBACK;
2824 10100           return;
2825             }
2826              
2827             /* Scalar context - return hashref */
2828 1407           ST(0) = sv_2mortal(newRV_noinc((SV*)dest));
2829 1407           XSRETURN(1);
2830             }
2831              
2832             /* ============================================
2833             Null coalescing functions
2834             ============================================ */
2835              
2836             /* nvl($x, $default) - return $x if defined, else $default */
2837 20128           XS_INTERNAL(xs_nvl) {
2838 20128           dXSARGS;
2839 20128 50         if (items != 2) croak("Usage: Func::Util::nvl($value, $default)");
2840              
2841 20128           SV *val = ST(0);
2842 20128 100         if (SvOK(val)) {
2843 8010           XSRETURN(1); /* Return first arg */
2844             }
2845 12118           ST(0) = ST(1);
2846 12118           XSRETURN(1);
2847             }
2848              
2849             /* coalesce($a, $b, ...) - return first defined value */
2850 18116           XS_INTERNAL(xs_coalesce) {
2851 18116           dXSARGS;
2852 18116 50         if (items < 1) croak("Usage: Func::Util::coalesce($val, ...)");
2853              
2854             IV i;
2855 48332 100         for (i = 0; i < items; i++) {
2856 47329 100         if (SvOK(ST(i))) {
2857 17113           ST(0) = ST(i);
2858 17113           XSRETURN(1);
2859             }
2860             }
2861             /* All undefined, return undef */
2862 1003           ST(0) = &PL_sv_undef;
2863 1003           XSRETURN(1);
2864             }
2865              
2866             /* ============================================
2867             List functions (first, any, all, none)
2868              
2869             These use MULTICALL for pure Perl subs which is significantly
2870             faster than call_sv() for repeated invocations.
2871              
2872             For XS subs, we fall back to call_sv().
2873             ============================================ */
2874              
2875             /* Inline CALLRUNOPS - experimental optimization to skip function call overhead.
2876             Use cautiously - this inlines the runops loop directly. */
2877             #define INLINE_RUNOPS() \
2878             STMT_START { \
2879             OP *_inline_op = PL_op; \
2880             while ((_inline_op = _inline_op->op_ppaddr(aTHX))) ; \
2881             } STMT_END
2882              
2883             /* ============================================
2884             Specialized array predicates - pure C, no callback
2885             These are blazing fast because they avoid all Perl callback overhead
2886             ============================================ */
2887              
2888             /* first_gt(\@array, $threshold) or first_gt(\@array, $key, $threshold)
2889             first element > threshold, pure C
2890             With key: first hash where hash->{key} > threshold */
2891 3027           XS_INTERNAL(xs_first_gt) {
2892 3027           dXSARGS;
2893 3027 50         if (items < 2 || items > 3) croak("Usage: Func::Util::first_gt(\\@array, $threshold) or first_gt(\\@array, $key, $threshold)");
    50          
2894              
2895 3027           SV *aref = ST(0);
2896 3027 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
2897 0           croak("Func::Util::first_gt: first argument must be an arrayref");
2898             }
2899              
2900 3027           AV *av = (AV *)SvRV(aref);
2901 3027           SSize_t len = av_len(av) + 1;
2902             SSize_t i;
2903              
2904 3027 100         if (items == 2) {
2905             /* Simple array of scalars */
2906 2026           NV threshold = SvNV(ST(1));
2907 12105 100         for (i = 0; i < len; i++) {
2908 11097           SV **elem = av_fetch(av, i, 0);
2909 11097 50         if (elem && SvNV(*elem) > threshold) {
    100          
2910 1018           ST(0) = *elem;
2911 1018           XSRETURN(1);
2912             }
2913             }
2914             } else {
2915             /* Array of hashes with key */
2916 1001           char *key = SvPV_nolen(ST(1));
2917 1001           NV threshold = SvNV(ST(2));
2918 2002 50         for (i = 0; i < len; i++) {
2919 2002           SV **elem = av_fetch(av, i, 0);
2920 2002 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
2921 2002           HV *hv = (HV *)SvRV(*elem);
2922 2002           SV **val = hv_fetch(hv, key, strlen(key), 0);
2923 2002 50         if (val && SvNV(*val) > threshold) {
    100          
2924 1001           ST(0) = *elem;
2925 1001           XSRETURN(1);
2926             }
2927             }
2928             }
2929             }
2930              
2931 1008           XSRETURN_UNDEF;
2932             }
2933              
2934             /* first_lt(\@array, $threshold) or first_lt(\@array, $key, $threshold)
2935             first element < threshold, pure C */
2936 3012           XS_INTERNAL(xs_first_lt) {
2937 3012           dXSARGS;
2938 3012 50         if (items < 2 || items > 3) croak("Usage: Func::Util::first_lt(\\@array, $threshold) or first_lt(\\@array, $key, $threshold)");
    50          
2939              
2940 3012           SV *aref = ST(0);
2941 3012 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
2942 0           croak("Func::Util::first_lt: first argument must be an arrayref");
2943             }
2944              
2945 3012           AV *av = (AV *)SvRV(aref);
2946 3012           SSize_t len = av_len(av) + 1;
2947             SSize_t i;
2948              
2949 3012 100         if (items == 2) {
2950 2011           NV threshold = SvNV(ST(1));
2951 9038 100         for (i = 0; i < len; i++) {
2952 8034           SV **elem = av_fetch(av, i, 0);
2953 8034 50         if (elem && SvNV(*elem) < threshold) {
    100          
2954 1007           ST(0) = *elem;
2955 1007           XSRETURN(1);
2956             }
2957             }
2958             } else {
2959 1001           char *key = SvPV_nolen(ST(1));
2960 1001           NV threshold = SvNV(ST(2));
2961 1001 50         for (i = 0; i < len; i++) {
2962 1001           SV **elem = av_fetch(av, i, 0);
2963 1001 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
2964 1001           HV *hv = (HV *)SvRV(*elem);
2965 1001           SV **val = hv_fetch(hv, key, strlen(key), 0);
2966 1001 50         if (val && SvNV(*val) < threshold) {
    50          
2967 1001           ST(0) = *elem;
2968 1001           XSRETURN(1);
2969             }
2970             }
2971             }
2972             }
2973              
2974 1004           XSRETURN_UNDEF;
2975             }
2976              
2977             /* first_eq(\@array, $value) or first_eq(\@array, $key, $value)
2978             first element == value (numeric), pure C */
2979 3012           XS_INTERNAL(xs_first_eq) {
2980 3012           dXSARGS;
2981 3012 50         if (items < 2 || items > 3) croak("Usage: Func::Util::first_eq(\\@array, $value) or first_eq(\\@array, $key, $value)");
    50          
2982              
2983 3012           SV *aref = ST(0);
2984 3012 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
2985 0           croak("Func::Util::first_eq: first argument must be an arrayref");
2986             }
2987              
2988 3012           AV *av = (AV *)SvRV(aref);
2989 3012           SSize_t len = av_len(av) + 1;
2990             SSize_t i;
2991              
2992 3012 100         if (items == 2) {
2993 2011           NV target = SvNV(ST(1));
2994 11047 100         for (i = 0; i < len; i++) {
2995 10043           SV **elem = av_fetch(av, i, 0);
2996 10043 50         if (elem && SvNV(*elem) == target) {
    100          
2997 1007           ST(0) = *elem;
2998 1007           XSRETURN(1);
2999             }
3000             }
3001             } else {
3002 1001           char *key = SvPV_nolen(ST(1));
3003 1001           NV target = SvNV(ST(2));
3004 2003 50         for (i = 0; i < len; i++) {
3005 2003           SV **elem = av_fetch(av, i, 0);
3006 2003 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3007 2003           HV *hv = (HV *)SvRV(*elem);
3008 2003           SV **val = hv_fetch(hv, key, strlen(key), 0);
3009 2003 50         if (val && SvNV(*val) == target) {
    100          
3010 1001           ST(0) = *elem;
3011 1001           XSRETURN(1);
3012             }
3013             }
3014             }
3015             }
3016              
3017 1004           XSRETURN_UNDEF;
3018             }
3019              
3020             /* first_ge(\@array, $threshold) or first_ge(\@array, $key, $threshold)
3021             first element >= threshold, pure C */
3022 3014           XS_INTERNAL(xs_first_ge) {
3023 3014           dXSARGS;
3024 3014 50         if (items < 2 || items > 3) croak("Usage: Func::Util::first_ge(\\@array, $threshold) or first_ge(\\@array, $key, $threshold)");
    50          
3025              
3026 3014           SV *aref = ST(0);
3027 3014 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3028 0           croak("Func::Util::first_ge: first argument must be an arrayref");
3029             }
3030              
3031 3014           AV *av = (AV *)SvRV(aref);
3032 3014           SSize_t len = av_len(av) + 1;
3033             SSize_t i;
3034              
3035 3014 100         if (items == 2) {
3036 2013           NV threshold = SvNV(ST(1));
3037 11051 100         for (i = 0; i < len; i++) {
3038 10048           SV **elem = av_fetch(av, i, 0);
3039 10048 50         if (elem && SvNV(*elem) >= threshold) {
    100          
3040 1010           ST(0) = *elem;
3041 1010           XSRETURN(1);
3042             }
3043             }
3044             } else {
3045 1001           char *key = SvPV_nolen(ST(1));
3046 1001           NV threshold = SvNV(ST(2));
3047 1002 50         for (i = 0; i < len; i++) {
3048 1002           SV **elem = av_fetch(av, i, 0);
3049 1002 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3050 1002           HV *hv = (HV *)SvRV(*elem);
3051 1002           SV **val = hv_fetch(hv, key, strlen(key), 0);
3052 1002 50         if (val && SvNV(*val) >= threshold) {
    100          
3053 1001           ST(0) = *elem;
3054 1001           XSRETURN(1);
3055             }
3056             }
3057             }
3058             }
3059              
3060 1003           XSRETURN_UNDEF;
3061             }
3062              
3063             /* first_le(\@array, $threshold) or first_le(\@array, $key, $threshold)
3064             first element <= threshold, pure C */
3065 3009           XS_INTERNAL(xs_first_le) {
3066 3009           dXSARGS;
3067 3009 50         if (items < 2 || items > 3) croak("Usage: Func::Util::first_le(\\@array, $threshold) or first_le(\\@array, $key, $threshold)");
    50          
3068              
3069 3009           SV *aref = ST(0);
3070 3009 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3071 0           croak("Func::Util::first_le: first argument must be an arrayref");
3072             }
3073              
3074 3009           AV *av = (AV *)SvRV(aref);
3075 3009           SSize_t len = av_len(av) + 1;
3076             SSize_t i;
3077              
3078 3009 100         if (items == 2) {
3079 2009           NV threshold = SvNV(ST(1));
3080 9027 100         for (i = 0; i < len; i++) {
3081 8024           SV **elem = av_fetch(av, i, 0);
3082 8024 50         if (elem && SvNV(*elem) <= threshold) {
    100          
3083 1006           ST(0) = *elem;
3084 1006           XSRETURN(1);
3085             }
3086             }
3087             } else {
3088 1000           char *key = SvPV_nolen(ST(1));
3089 1000           NV threshold = SvNV(ST(2));
3090 1000 50         for (i = 0; i < len; i++) {
3091 1000           SV **elem = av_fetch(av, i, 0);
3092 1000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3093 1000           HV *hv = (HV *)SvRV(*elem);
3094 1000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3095 1000 50         if (val && SvNV(*val) <= threshold) {
    50          
3096 1000           ST(0) = *elem;
3097 1000           XSRETURN(1);
3098             }
3099             }
3100             }
3101             }
3102              
3103 1003           XSRETURN_UNDEF;
3104             }
3105              
3106             /* first_ne(\@array, $value) or first_ne(\@array, $key, $value)
3107             first element != value (numeric), pure C */
3108 2007           XS_INTERNAL(xs_first_ne) {
3109 2007           dXSARGS;
3110 2007 50         if (items < 2 || items > 3) croak("Usage: Func::Util::first_ne(\\@array, $value) or first_ne(\\@array, $key, $value)");
    50          
3111              
3112 2007           SV *aref = ST(0);
3113 2007 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3114 0           croak("Func::Util::first_ne: first argument must be an arrayref");
3115             }
3116              
3117 2007           AV *av = (AV *)SvRV(aref);
3118 2007           SSize_t len = av_len(av) + 1;
3119             SSize_t i;
3120              
3121 2007 100         if (items == 2) {
3122 1007           NV target = SvNV(ST(1));
3123 2020 100         for (i = 0; i < len; i++) {
3124 2018           SV **elem = av_fetch(av, i, 0);
3125 2018 50         if (elem && SvNV(*elem) != target) {
    100          
3126 1005           ST(0) = *elem;
3127 1005           XSRETURN(1);
3128             }
3129             }
3130             } else {
3131 1000           char *key = SvPV_nolen(ST(1));
3132 1000           NV target = SvNV(ST(2));
3133 2000 50         for (i = 0; i < len; i++) {
3134 2000           SV **elem = av_fetch(av, i, 0);
3135 2000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3136 2000           HV *hv = (HV *)SvRV(*elem);
3137 2000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3138 2000 50         if (val && SvNV(*val) != target) {
    100          
3139 1000           ST(0) = *elem;
3140 1000           XSRETURN(1);
3141             }
3142             }
3143             }
3144             }
3145              
3146 2           XSRETURN_UNDEF;
3147             }
3148              
3149             /* ============================================
3150             final_* - like first_* but iterates backwards
3151             ============================================ */
3152              
3153             /* final_gt(\@array, $threshold) or final_gt(\@array, $key, $threshold)
3154             last element > threshold, pure C, backwards iteration */
3155 3012           XS_INTERNAL(xs_final_gt) {
3156 3012           dXSARGS;
3157 3012 50         if (items < 2 || items > 3) croak("Usage: Func::Util::final_gt(\\@array, $threshold) or final_gt(\\@array, $key, $threshold)");
    50          
3158              
3159 3012           SV *aref = ST(0);
3160 3012 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3161 0           croak("Func::Util::final_gt: first argument must be an arrayref");
3162             }
3163              
3164 3012           AV *av = (AV *)SvRV(aref);
3165 3012           SSize_t len = av_len(av) + 1;
3166             SSize_t i;
3167              
3168 3012 100         if (items == 2) {
3169 2011           NV threshold = SvNV(ST(1));
3170 9028 100         for (i = len - 1; i >= 0; i--) {
3171 8023           SV **elem = av_fetch(av, i, 0);
3172 8023 50         if (elem && SvNV(*elem) > threshold) {
    100          
3173 1006           ST(0) = *elem;
3174 1006           XSRETURN(1);
3175             }
3176             }
3177             } else {
3178 1001           char *key = SvPV_nolen(ST(1));
3179 1001           NV threshold = SvNV(ST(2));
3180 1001 50         for (i = len - 1; i >= 0; i--) {
3181 1001           SV **elem = av_fetch(av, i, 0);
3182 1001 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3183 1001           HV *hv = (HV *)SvRV(*elem);
3184 1001           SV **val = hv_fetch(hv, key, strlen(key), 0);
3185 1001 50         if (val && SvNV(*val) > threshold) {
    50          
3186 1001           ST(0) = *elem;
3187 1001           XSRETURN(1);
3188             }
3189             }
3190             }
3191             }
3192              
3193 1005           XSRETURN_UNDEF;
3194             }
3195              
3196             /* final_lt(\@array, $threshold) or final_lt(\@array, $key, $threshold) */
3197 3011           XS_INTERNAL(xs_final_lt) {
3198 3011           dXSARGS;
3199 3011 50         if (items < 2 || items > 3) croak("Usage: Func::Util::final_lt(\\@array, $threshold) or final_lt(\\@array, $key, $threshold)");
    50          
3200              
3201 3011           SV *aref = ST(0);
3202 3011 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3203 0           croak("Func::Util::final_lt: first argument must be an arrayref");
3204             }
3205              
3206 3011           AV *av = (AV *)SvRV(aref);
3207 3011           SSize_t len = av_len(av) + 1;
3208             SSize_t i;
3209              
3210 3011 100         if (items == 2) {
3211 2010           NV threshold = SvNV(ST(1));
3212 12036 100         for (i = len - 1; i >= 0; i--) {
3213 11033           SV **elem = av_fetch(av, i, 0);
3214 11033 50         if (elem && SvNV(*elem) < threshold) {
    100          
3215 1007           ST(0) = *elem;
3216 1007           XSRETURN(1);
3217             }
3218             }
3219             } else {
3220 1001           char *key = SvPV_nolen(ST(1));
3221 1001           NV threshold = SvNV(ST(2));
3222 2001 50         for (i = len - 1; i >= 0; i--) {
3223 2001           SV **elem = av_fetch(av, i, 0);
3224 2001 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3225 2001           HV *hv = (HV *)SvRV(*elem);
3226 2001           SV **val = hv_fetch(hv, key, strlen(key), 0);
3227 2001 50         if (val && SvNV(*val) < threshold) {
    100          
3228 1001           ST(0) = *elem;
3229 1001           XSRETURN(1);
3230             }
3231             }
3232             }
3233             }
3234              
3235 1003           XSRETURN_UNDEF;
3236             }
3237              
3238             /* final_ge(\@array, $threshold) or final_ge(\@array, $key, $threshold) */
3239 2005           XS_INTERNAL(xs_final_ge) {
3240 2005           dXSARGS;
3241 2005 50         if (items < 2 || items > 3) croak("Usage: Func::Util::final_ge(\\@array, $threshold) or final_ge(\\@array, $key, $threshold)");
    50          
3242              
3243 2005           SV *aref = ST(0);
3244 2005 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3245 0           croak("Func::Util::final_ge: first argument must be an arrayref");
3246             }
3247              
3248 2005           AV *av = (AV *)SvRV(aref);
3249 2005           SSize_t len = av_len(av) + 1;
3250             SSize_t i;
3251              
3252 2005 100         if (items == 2) {
3253 1005           NV threshold = SvNV(ST(1));
3254 1017 100         for (i = len - 1; i >= 0; i--) {
3255 1015           SV **elem = av_fetch(av, i, 0);
3256 1015 50         if (elem && SvNV(*elem) >= threshold) {
    100          
3257 1003           ST(0) = *elem;
3258 1003           XSRETURN(1);
3259             }
3260             }
3261             } else {
3262 1000           char *key = SvPV_nolen(ST(1));
3263 1000           NV threshold = SvNV(ST(2));
3264 1000 50         for (i = len - 1; i >= 0; i--) {
3265 1000           SV **elem = av_fetch(av, i, 0);
3266 1000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3267 1000           HV *hv = (HV *)SvRV(*elem);
3268 1000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3269 1000 50         if (val && SvNV(*val) >= threshold) {
    50          
3270 1000           ST(0) = *elem;
3271 1000           XSRETURN(1);
3272             }
3273             }
3274             }
3275             }
3276              
3277 2           XSRETURN_UNDEF;
3278             }
3279              
3280             /* final_le(\@array, $threshold) or final_le(\@array, $key, $threshold) */
3281 2006           XS_INTERNAL(xs_final_le) {
3282 2006           dXSARGS;
3283 2006 50         if (items < 2 || items > 3) croak("Usage: Func::Util::final_le(\\@array, $threshold) or final_le(\\@array, $key, $threshold)");
    50          
3284              
3285 2006           SV *aref = ST(0);
3286 2006 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3287 0           croak("Func::Util::final_le: first argument must be an arrayref");
3288             }
3289              
3290 2006           AV *av = (AV *)SvRV(aref);
3291 2006           SSize_t len = av_len(av) + 1;
3292             SSize_t i;
3293              
3294 2006 100         if (items == 2) {
3295 1006           NV threshold = SvNV(ST(1));
3296 3016 100         for (i = len - 1; i >= 0; i--) {
3297 3015           SV **elem = av_fetch(av, i, 0);
3298 3015 50         if (elem && SvNV(*elem) <= threshold) {
    100          
3299 1005           ST(0) = *elem;
3300 1005           XSRETURN(1);
3301             }
3302             }
3303             } else {
3304 1000           char *key = SvPV_nolen(ST(1));
3305 1000           NV threshold = SvNV(ST(2));
3306 2000 50         for (i = len - 1; i >= 0; i--) {
3307 2000           SV **elem = av_fetch(av, i, 0);
3308 2000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3309 2000           HV *hv = (HV *)SvRV(*elem);
3310 2000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3311 2000 50         if (val && SvNV(*val) <= threshold) {
    100          
3312 1000           ST(0) = *elem;
3313 1000           XSRETURN(1);
3314             }
3315             }
3316             }
3317             }
3318              
3319 1           XSRETURN_UNDEF;
3320             }
3321              
3322             /* final_eq(\@array, $value) or final_eq(\@array, $key, $value) */
3323 2006           XS_INTERNAL(xs_final_eq) {
3324 2006           dXSARGS;
3325 2006 50         if (items < 2 || items > 3) croak("Usage: Func::Util::final_eq(\\@array, $value) or final_eq(\\@array, $key, $value)");
    50          
3326              
3327 2006           SV *aref = ST(0);
3328 2006 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3329 0           croak("Func::Util::final_eq: first argument must be an arrayref");
3330             }
3331              
3332 2006           AV *av = (AV *)SvRV(aref);
3333 2006           SSize_t len = av_len(av) + 1;
3334             SSize_t i;
3335              
3336 2006 100         if (items == 2) {
3337 1006           NV target = SvNV(ST(1));
3338 3025 100         for (i = len - 1; i >= 0; i--) {
3339 3023           SV **elem = av_fetch(av, i, 0);
3340 3023 50         if (elem && SvNV(*elem) == target) {
    100          
3341 1004           ST(0) = *elem;
3342 1004           XSRETURN(1);
3343             }
3344             }
3345             } else {
3346 1000           char *key = SvPV_nolen(ST(1));
3347 1000           NV target = SvNV(ST(2));
3348 3000 50         for (i = len - 1; i >= 0; i--) {
3349 3000           SV **elem = av_fetch(av, i, 0);
3350 3000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3351 3000           HV *hv = (HV *)SvRV(*elem);
3352 3000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3353 3000 50         if (val && SvNV(*val) == target) {
    100          
3354 1000           ST(0) = *elem;
3355 1000           XSRETURN(1);
3356             }
3357             }
3358             }
3359             }
3360              
3361 2           XSRETURN_UNDEF;
3362             }
3363              
3364             /* final_ne(\@array, $value) or final_ne(\@array, $key, $value) */
3365 2004           XS_INTERNAL(xs_final_ne) {
3366 2004           dXSARGS;
3367 2004 50         if (items < 2 || items > 3) croak("Usage: Func::Util::final_ne(\\@array, $value) or final_ne(\\@array, $key, $value)");
    50          
3368              
3369 2004           SV *aref = ST(0);
3370 2004 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3371 0           croak("Func::Util::final_ne: first argument must be an arrayref");
3372             }
3373              
3374 2004           AV *av = (AV *)SvRV(aref);
3375 2004           SSize_t len = av_len(av) + 1;
3376             SSize_t i;
3377              
3378 2004 100         if (items == 2) {
3379 1004           NV target = SvNV(ST(1));
3380 2009 100         for (i = len - 1; i >= 0; i--) {
3381 2008           SV **elem = av_fetch(av, i, 0);
3382 2008 50         if (elem && SvNV(*elem) != target) {
    100          
3383 1003           ST(0) = *elem;
3384 1003           XSRETURN(1);
3385             }
3386             }
3387             } else {
3388 1000           char *key = SvPV_nolen(ST(1));
3389 1000           NV target = SvNV(ST(2));
3390 2000 50         for (i = len - 1; i >= 0; i--) {
3391 2000           SV **elem = av_fetch(av, i, 0);
3392 2000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3393 2000           HV *hv = (HV *)SvRV(*elem);
3394 2000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3395 2000 50         if (val && SvNV(*val) != target) {
    100          
3396 1000           ST(0) = *elem;
3397 1000           XSRETURN(1);
3398             }
3399             }
3400             }
3401             }
3402              
3403 1           XSRETURN_UNDEF;
3404             }
3405              
3406             /* any_gt(\@array, $threshold) or any_gt(\@array, $key, $threshold)
3407             true if any element > threshold, pure C */
3408 3015           XS_INTERNAL(xs_any_gt) {
3409 3015           dXSARGS;
3410 3015 50         if (items < 2 || items > 3) croak("Usage: Func::Util::any_gt(\\@array, $threshold) or any_gt(\\@array, $key, $threshold)");
    50          
3411              
3412 3015           SV *aref = ST(0);
3413 3015 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3414 0           croak("Func::Util::any_gt: first argument must be an arrayref");
3415             }
3416              
3417 3015           AV *av = (AV *)SvRV(aref);
3418 3015           SSize_t len = av_len(av) + 1;
3419             SSize_t i;
3420              
3421 3015 100         if (items == 2) {
3422 2013           NV threshold = SvNV(ST(1));
3423 15056 100         for (i = 0; i < len; i++) {
3424 14048           SV **elem = av_fetch(av, i, 0);
3425 14048 50         if (elem && SvNV(*elem) > threshold) {
    100          
3426 1005           XSRETURN_YES;
3427             }
3428             }
3429             } else {
3430 1002           char *key = SvPV_nolen(ST(1));
3431 1002           NV threshold = SvNV(ST(2));
3432 4005 100         for (i = 0; i < len; i++) {
3433 4004           SV **elem = av_fetch(av, i, 0);
3434 4004 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3435 4004           HV *hv = (HV *)SvRV(*elem);
3436 4004           SV **val = hv_fetch(hv, key, strlen(key), 0);
3437 4004 50         if (val && SvNV(*val) > threshold) {
    100          
3438 1001           XSRETURN_YES;
3439             }
3440             }
3441             }
3442             }
3443              
3444 1009           XSRETURN_NO;
3445             }
3446              
3447             /* any_lt(\@array, $threshold) or any_lt(\@array, $key, $threshold) */
3448 3021           XS_INTERNAL(xs_any_lt) {
3449 3021           dXSARGS;
3450 3021 50         if (items < 2 || items > 3) croak("Usage: Func::Util::any_lt(\\@array, $threshold) or any_lt(\\@array, $key, $threshold)");
    50          
3451              
3452 3021           SV *aref = ST(0);
3453 3021 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3454 0           croak("Func::Util::any_lt: first argument must be an arrayref");
3455             }
3456              
3457 3021           AV *av = (AV *)SvRV(aref);
3458 3021           SSize_t len = av_len(av) + 1;
3459             SSize_t i;
3460              
3461 3021 100         if (items == 2) {
3462 2021           NV threshold = SvNV(ST(1));
3463 9049 100         for (i = 0; i < len; i++) {
3464 8044           SV **elem = av_fetch(av, i, 0);
3465 8044 50         if (elem && SvNV(*elem) < threshold) {
    100          
3466 1016           XSRETURN_YES;
3467             }
3468             }
3469             } else {
3470 1000           char *key = SvPV_nolen(ST(1));
3471 1000           NV threshold = SvNV(ST(2));
3472 3000 50         for (i = 0; i < len; i++) {
3473 3000           SV **elem = av_fetch(av, i, 0);
3474 3000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3475 3000           HV *hv = (HV *)SvRV(*elem);
3476 3000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3477 3000 50         if (val && SvNV(*val) < threshold) {
    100          
3478 1000           XSRETURN_YES;
3479             }
3480             }
3481             }
3482             }
3483              
3484 1005           XSRETURN_NO;
3485             }
3486              
3487             /* any_ge(\@array, $threshold) or any_ge(\@array, $key, $threshold) */
3488 2007           XS_INTERNAL(xs_any_ge) {
3489 2007           dXSARGS;
3490 2007 50         if (items < 2 || items > 3) croak("Usage: Func::Util::any_ge(\\@array, $threshold) or any_ge(\\@array, $key, $threshold)");
    50          
3491              
3492 2007           SV *aref = ST(0);
3493 2007 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3494 0           croak("Func::Util::any_ge: first argument must be an arrayref");
3495             }
3496              
3497 2007           AV *av = (AV *)SvRV(aref);
3498 2007           SSize_t len = av_len(av) + 1;
3499             SSize_t i;
3500              
3501 2007 100         if (items == 2) {
3502 1007           NV threshold = SvNV(ST(1));
3503 7037 100         for (i = 0; i < len; i++) {
3504 7035           SV **elem = av_fetch(av, i, 0);
3505 7035 50         if (elem && SvNV(*elem) >= threshold) {
    100          
3506 1005           XSRETURN_YES;
3507             }
3508             }
3509             } else {
3510 1000           char *key = SvPV_nolen(ST(1));
3511 1000           NV threshold = SvNV(ST(2));
3512 4000 50         for (i = 0; i < len; i++) {
3513 4000           SV **elem = av_fetch(av, i, 0);
3514 4000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3515 4000           HV *hv = (HV *)SvRV(*elem);
3516 4000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3517 4000 50         if (val && SvNV(*val) >= threshold) {
    100          
3518 1000           XSRETURN_YES;
3519             }
3520             }
3521             }
3522             }
3523              
3524 2           XSRETURN_NO;
3525             }
3526              
3527             /* any_le(\@array, $threshold) or any_le(\@array, $key, $threshold) */
3528 2005           XS_INTERNAL(xs_any_le) {
3529 2005           dXSARGS;
3530 2005 50         if (items < 2 || items > 3) croak("Usage: Func::Util::any_le(\\@array, $threshold) or any_le(\\@array, $key, $threshold)");
    50          
3531              
3532 2005           SV *aref = ST(0);
3533 2005 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3534 0           croak("Func::Util::any_le: first argument must be an arrayref");
3535             }
3536              
3537 2005           AV *av = (AV *)SvRV(aref);
3538 2005           SSize_t len = av_len(av) + 1;
3539             SSize_t i;
3540              
3541 2005 100         if (items == 2) {
3542 1005           NV threshold = SvNV(ST(1));
3543 1018 100         for (i = 0; i < len; i++) {
3544 1016           SV **elem = av_fetch(av, i, 0);
3545 1016 50         if (elem && SvNV(*elem) <= threshold) {
    100          
3546 1003           XSRETURN_YES;
3547             }
3548             }
3549             } else {
3550 1000           char *key = SvPV_nolen(ST(1));
3551 1000           NV threshold = SvNV(ST(2));
3552 3000 50         for (i = 0; i < len; i++) {
3553 3000           SV **elem = av_fetch(av, i, 0);
3554 3000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3555 3000           HV *hv = (HV *)SvRV(*elem);
3556 3000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3557 3000 50         if (val && SvNV(*val) <= threshold) {
    100          
3558 1000           XSRETURN_YES;
3559             }
3560             }
3561             }
3562             }
3563              
3564 2           XSRETURN_NO;
3565             }
3566              
3567             /* any_eq(\@array, $value) or any_eq(\@array, $key, $value) */
3568 3009           XS_INTERNAL(xs_any_eq) {
3569 3009           dXSARGS;
3570 3009 50         if (items < 2 || items > 3) croak("Usage: Func::Util::any_eq(\\@array, $value) or any_eq(\\@array, $key, $value)");
    50          
3571              
3572 3009           SV *aref = ST(0);
3573 3009 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3574 0           croak("Func::Util::any_eq: first argument must be an arrayref");
3575             }
3576              
3577 3009           AV *av = (AV *)SvRV(aref);
3578 3009           SSize_t len = av_len(av) + 1;
3579             SSize_t i;
3580              
3581 3009 100         if (items == 2) {
3582 2009           NV target = SvNV(ST(1));
3583 12037 100         for (i = 0; i < len; i++) {
3584 11033           SV **elem = av_fetch(av, i, 0);
3585 11033 50         if (elem && SvNV(*elem) == target) {
    100          
3586 1005           XSRETURN_YES;
3587             }
3588             }
3589             } else {
3590 1000           char *key = SvPV_nolen(ST(1));
3591 1000           NV target = SvNV(ST(2));
3592 1000 50         for (i = 0; i < len; i++) {
3593 1000           SV **elem = av_fetch(av, i, 0);
3594 1000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3595 1000           HV *hv = (HV *)SvRV(*elem);
3596 1000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3597 1000 50         if (val && SvNV(*val) == target) {
    50          
3598 1000           XSRETURN_YES;
3599             }
3600             }
3601             }
3602             }
3603              
3604 1004           XSRETURN_NO;
3605             }
3606              
3607             /* any_ne(\@array, $value) or any_ne(\@array, $key, $value) */
3608 2004           XS_INTERNAL(xs_any_ne) {
3609 2004           dXSARGS;
3610 2004 50         if (items < 2 || items > 3) croak("Usage: Func::Util::any_ne(\\@array, $value) or any_ne(\\@array, $key, $value)");
    50          
3611              
3612 2004           SV *aref = ST(0);
3613 2004 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3614 0           croak("Func::Util::any_ne: first argument must be an arrayref");
3615             }
3616              
3617 2004           AV *av = (AV *)SvRV(aref);
3618 2004           SSize_t len = av_len(av) + 1;
3619             SSize_t i;
3620              
3621 2004 100         if (items == 2) {
3622 1004           NV target = SvNV(ST(1));
3623 2010 100         for (i = 0; i < len; i++) {
3624 2009           SV **elem = av_fetch(av, i, 0);
3625 2009 50         if (elem && SvNV(*elem) != target) {
    100          
3626 1003           XSRETURN_YES;
3627             }
3628             }
3629             } else {
3630 1000           char *key = SvPV_nolen(ST(1));
3631 1000           NV target = SvNV(ST(2));
3632 2000 50         for (i = 0; i < len; i++) {
3633 2000           SV **elem = av_fetch(av, i, 0);
3634 2000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3635 2000           HV *hv = (HV *)SvRV(*elem);
3636 2000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3637 2000 50         if (val && SvNV(*val) != target) {
    100          
3638 1000           XSRETURN_YES;
3639             }
3640             }
3641             }
3642             }
3643              
3644 1           XSRETURN_NO;
3645             }
3646              
3647             /* all_gt(\@array, $n) - true if all elements > n, pure C */
3648             /* all_gt(\@array, $threshold) or all_gt(\@array, $key, $threshold)
3649             true if all elements > threshold, pure C */
3650 3013           XS_INTERNAL(xs_all_gt) {
3651 3013           dXSARGS;
3652 3013 50         if (items < 2 || items > 3) croak("Usage: Func::Util::all_gt(\\@array, $threshold) or all_gt(\\@array, $key, $threshold)");
    50          
3653              
3654 3013           SV *aref = ST(0);
3655 3013 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3656 0           croak("Func::Util::all_gt: first argument must be an arrayref");
3657             }
3658              
3659 3013           AV *av = (AV *)SvRV(aref);
3660 3013           SSize_t len = av_len(av) + 1;
3661             SSize_t i;
3662              
3663 3013 100         if (len == 0) XSRETURN_YES; /* vacuous truth */
3664              
3665 3010 100         if (items == 2) {
3666 2010           NV threshold = SvNV(ST(1));
3667 9038 100         for (i = 0; i < len; i++) {
3668 8033           SV **elem = av_fetch(av, i, 0);
3669 8033 50         if (!elem || SvNV(*elem) <= threshold) {
    100          
3670 1005           XSRETURN_NO;
3671             }
3672             }
3673             } else {
3674 1000           char *key = SvPV_nolen(ST(1));
3675 1000           NV threshold = SvNV(ST(2));
3676 5000 100         for (i = 0; i < len; i++) {
3677 4000           SV **elem = av_fetch(av, i, 0);
3678 4000 50         if (!elem || !SvROK(*elem) || SvTYPE(SvRV(*elem)) != SVt_PVHV) {
    50          
    50          
3679 0           XSRETURN_NO;
3680             }
3681 4000           HV *hv = (HV *)SvRV(*elem);
3682 4000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3683 4000 50         if (!val || SvNV(*val) <= threshold) {
    50          
3684 0           XSRETURN_NO;
3685             }
3686             }
3687             }
3688              
3689 2005           XSRETURN_YES;
3690             }
3691              
3692             /* all_lt(\@array, $threshold) or all_lt(\@array, $key, $threshold) */
3693 3009           XS_INTERNAL(xs_all_lt) {
3694 3009           dXSARGS;
3695 3009 50         if (items < 2 || items > 3) croak("Usage: Func::Util::all_lt(\\@array, $threshold) or all_lt(\\@array, $key, $threshold)");
    50          
3696              
3697 3009           SV *aref = ST(0);
3698 3009 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3699 0           croak("Func::Util::all_lt: first argument must be an arrayref");
3700             }
3701              
3702 3009           AV *av = (AV *)SvRV(aref);
3703 3009           SSize_t len = av_len(av) + 1;
3704             SSize_t i;
3705              
3706 3009 100         if (len == 0) XSRETURN_YES;
3707              
3708 3008 100         if (items == 2) {
3709 2008           NV threshold = SvNV(ST(1));
3710 11044 100         for (i = 0; i < len; i++) {
3711 10040           SV **elem = av_fetch(av, i, 0);
3712 10040 50         if (!elem || SvNV(*elem) >= threshold) {
    100          
3713 1004           XSRETURN_NO;
3714             }
3715             }
3716             } else {
3717 1000           char *key = SvPV_nolen(ST(1));
3718 1000           NV threshold = SvNV(ST(2));
3719 5000 100         for (i = 0; i < len; i++) {
3720 4000           SV **elem = av_fetch(av, i, 0);
3721 4000 50         if (!elem || !SvROK(*elem) || SvTYPE(SvRV(*elem)) != SVt_PVHV) {
    50          
    50          
3722 0           XSRETURN_NO;
3723             }
3724 4000           HV *hv = (HV *)SvRV(*elem);
3725 4000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3726 4000 50         if (!val || SvNV(*val) >= threshold) {
    50          
3727 0           XSRETURN_NO;
3728             }
3729             }
3730             }
3731              
3732 2004           XSRETURN_YES;
3733             }
3734              
3735             /* all_ge(\@array, $threshold) or all_ge(\@array, $key, $threshold) */
3736 3019           XS_INTERNAL(xs_all_ge) {
3737 3019           dXSARGS;
3738 3019 50         if (items < 2 || items > 3) croak("Usage: Func::Util::all_ge(\\@array, $threshold) or all_ge(\\@array, $key, $threshold)");
    50          
3739              
3740 3019           SV *aref = ST(0);
3741 3019 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3742 0           croak("Func::Util::all_ge: first argument must be an arrayref");
3743             }
3744              
3745 3019           AV *av = (AV *)SvRV(aref);
3746 3019           SSize_t len = av_len(av) + 1;
3747             SSize_t i;
3748              
3749 3019 50         if (len == 0) XSRETURN_YES;
3750              
3751 3019 100         if (items == 2) {
3752 2017           NV threshold = SvNV(ST(1));
3753 9102 100         for (i = 0; i < len; i++) {
3754 8089           SV **elem = av_fetch(av, i, 0);
3755 8089 50         if (!elem || SvNV(*elem) < threshold) {
    100          
3756 1004           XSRETURN_NO;
3757             }
3758             }
3759             } else {
3760 1002           char *key = SvPV_nolen(ST(1));
3761 1002           NV threshold = SvNV(ST(2));
3762 5004 100         for (i = 0; i < len; i++) {
3763 4003           SV **elem = av_fetch(av, i, 0);
3764 4003 50         if (!elem || !SvROK(*elem) || SvTYPE(SvRV(*elem)) != SVt_PVHV) {
    50          
    50          
3765 0           XSRETURN_NO;
3766             }
3767 4003           HV *hv = (HV *)SvRV(*elem);
3768 4003           SV **val = hv_fetch(hv, key, strlen(key), 0);
3769 4003 50         if (!val || SvNV(*val) < threshold) {
    100          
3770 1           XSRETURN_NO;
3771             }
3772             }
3773             }
3774              
3775 2014           XSRETURN_YES;
3776             }
3777              
3778             /* all_le(\@array, $threshold) or all_le(\@array, $key, $threshold) */
3779 3004           XS_INTERNAL(xs_all_le) {
3780 3004           dXSARGS;
3781 3004 50         if (items < 2 || items > 3) croak("Usage: Func::Util::all_le(\\@array, $threshold) or all_le(\\@array, $key, $threshold)");
    50          
3782              
3783 3004           SV *aref = ST(0);
3784 3004 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3785 0           croak("Func::Util::all_le: first argument must be an arrayref");
3786             }
3787              
3788 3004           AV *av = (AV *)SvRV(aref);
3789 3004           SSize_t len = av_len(av) + 1;
3790             SSize_t i;
3791              
3792 3004 50         if (len == 0) XSRETURN_YES;
3793              
3794 3004 100         if (items == 2) {
3795 2004           NV threshold = SvNV(ST(1));
3796 12025 100         for (i = 0; i < len; i++) {
3797 11023           SV **elem = av_fetch(av, i, 0);
3798 11023 50         if (!elem || SvNV(*elem) > threshold) {
    100          
3799 1002           XSRETURN_NO;
3800             }
3801             }
3802             } else {
3803 1000           char *key = SvPV_nolen(ST(1));
3804 1000           NV threshold = SvNV(ST(2));
3805 5000 100         for (i = 0; i < len; i++) {
3806 4000           SV **elem = av_fetch(av, i, 0);
3807 4000 50         if (!elem || !SvROK(*elem) || SvTYPE(SvRV(*elem)) != SVt_PVHV) {
    50          
    50          
3808 0           XSRETURN_NO;
3809             }
3810 4000           HV *hv = (HV *)SvRV(*elem);
3811 4000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3812 4000 50         if (!val || SvNV(*val) > threshold) {
    50          
3813 0           XSRETURN_NO;
3814             }
3815             }
3816             }
3817              
3818 2002           XSRETURN_YES;
3819             }
3820              
3821             /* all_eq(\@array, $value) or all_eq(\@array, $key, $value) */
3822 2007           XS_INTERNAL(xs_all_eq) {
3823 2007           dXSARGS;
3824 2007 50         if (items < 2 || items > 3) croak("Usage: Func::Util::all_eq(\\@array, $value) or all_eq(\\@array, $key, $value)");
    50          
3825              
3826 2007           SV *aref = ST(0);
3827 2007 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3828 0           croak("Func::Util::all_eq: first argument must be an arrayref");
3829             }
3830              
3831 2007           AV *av = (AV *)SvRV(aref);
3832 2007           SSize_t len = av_len(av) + 1;
3833             SSize_t i;
3834              
3835 2007 100         if (len == 0) XSRETURN_YES;
3836              
3837 2006 50         if (items == 2) {
3838 2006           NV target = SvNV(ST(1));
3839 6018 100         for (i = 0; i < len; i++) {
3840 5014           SV **elem = av_fetch(av, i, 0);
3841 5014 50         if (!elem || SvNV(*elem) != target) {
    100          
3842 1002           XSRETURN_NO;
3843             }
3844             }
3845             } else {
3846 0           char *key = SvPV_nolen(ST(1));
3847 0           NV target = SvNV(ST(2));
3848 0 0         for (i = 0; i < len; i++) {
3849 0           SV **elem = av_fetch(av, i, 0);
3850 0 0         if (!elem || !SvROK(*elem) || SvTYPE(SvRV(*elem)) != SVt_PVHV) {
    0          
    0          
3851 0           XSRETURN_NO;
3852             }
3853 0           HV *hv = (HV *)SvRV(*elem);
3854 0           SV **val = hv_fetch(hv, key, strlen(key), 0);
3855 0 0         if (!val || SvNV(*val) != target) {
    0          
3856 0           XSRETURN_NO;
3857             }
3858             }
3859             }
3860              
3861 1004           XSRETURN_YES;
3862             }
3863              
3864             /* all_ne(\@array, $value) or all_ne(\@array, $key, $value) */
3865 2004           XS_INTERNAL(xs_all_ne) {
3866 2004           dXSARGS;
3867 2004 50         if (items < 2 || items > 3) croak("Usage: Func::Util::all_ne(\\@array, $value) or all_ne(\\@array, $key, $value)");
    50          
3868              
3869 2004           SV *aref = ST(0);
3870 2004 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3871 0           croak("Func::Util::all_ne: first argument must be an arrayref");
3872             }
3873              
3874 2004           AV *av = (AV *)SvRV(aref);
3875 2004           SSize_t len = av_len(av) + 1;
3876             SSize_t i;
3877              
3878 2004 50         if (len == 0) XSRETURN_YES;
3879              
3880 2004 50         if (items == 2) {
3881 2004           NV target = SvNV(ST(1));
3882 11020 100         for (i = 0; i < len; i++) {
3883 10018           SV **elem = av_fetch(av, i, 0);
3884 10018 50         if (!elem || SvNV(*elem) == target) {
    100          
3885 1002           XSRETURN_NO;
3886             }
3887             }
3888             } else {
3889 0           char *key = SvPV_nolen(ST(1));
3890 0           NV target = SvNV(ST(2));
3891 0 0         for (i = 0; i < len; i++) {
3892 0           SV **elem = av_fetch(av, i, 0);
3893 0 0         if (!elem || !SvROK(*elem) || SvTYPE(SvRV(*elem)) != SVt_PVHV) {
    0          
    0          
3894 0           XSRETURN_NO;
3895             }
3896 0           HV *hv = (HV *)SvRV(*elem);
3897 0           SV **val = hv_fetch(hv, key, strlen(key), 0);
3898 0 0         if (!val || SvNV(*val) == target) {
    0          
3899 0           XSRETURN_NO;
3900             }
3901             }
3902             }
3903              
3904 1002           XSRETURN_YES;
3905             }
3906              
3907             /* none_gt(\@array, $threshold) or none_gt(\@array, $key, $threshold)
3908             true if no element > threshold, pure C */
3909 3011           XS_INTERNAL(xs_none_gt) {
3910 3011           dXSARGS;
3911 3011 50         if (items < 2 || items > 3) croak("Usage: Func::Util::none_gt(\\@array, $threshold) or none_gt(\\@array, $key, $threshold)");
    50          
3912              
3913 3011           SV *aref = ST(0);
3914 3011 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3915 0           croak("Func::Util::none_gt: first argument must be an arrayref");
3916             }
3917              
3918 3011           AV *av = (AV *)SvRV(aref);
3919 3011           SSize_t len = av_len(av) + 1;
3920             SSize_t i;
3921              
3922 3011 100         if (items == 2) {
3923 2011           NV threshold = SvNV(ST(1));
3924 12046 100         for (i = 0; i < len; i++) {
3925 11039           SV **elem = av_fetch(av, i, 0);
3926 11039 50         if (elem && SvNV(*elem) > threshold) {
    100          
3927 1004           XSRETURN_NO;
3928             }
3929             }
3930             } else {
3931 1000           char *key = SvPV_nolen(ST(1));
3932 1000           NV threshold = SvNV(ST(2));
3933 5000 100         for (i = 0; i < len; i++) {
3934 4000           SV **elem = av_fetch(av, i, 0);
3935 4000 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3936 4000           HV *hv = (HV *)SvRV(*elem);
3937 4000           SV **val = hv_fetch(hv, key, strlen(key), 0);
3938 4000 50         if (val && SvNV(*val) > threshold) {
    50          
3939 0           XSRETURN_NO;
3940             }
3941             }
3942             }
3943             }
3944              
3945 2007           XSRETURN_YES;
3946             }
3947              
3948             /* none_lt(\@array, $threshold) or none_lt(\@array, $key, $threshold) */
3949 3010           XS_INTERNAL(xs_none_lt) {
3950 3010           dXSARGS;
3951 3010 50         if (items < 2 || items > 3) croak("Usage: Func::Util::none_lt(\\@array, $threshold) or none_lt(\\@array, $key, $threshold)");
    50          
3952              
3953 3010           SV *aref = ST(0);
3954 3010 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3955 0           croak("Func::Util::none_lt: first argument must be an arrayref");
3956             }
3957              
3958 3010           AV *av = (AV *)SvRV(aref);
3959 3010           SSize_t len = av_len(av) + 1;
3960             SSize_t i;
3961              
3962 3010 100         if (items == 2) {
3963 2008           NV threshold = SvNV(ST(1));
3964 9036 100         for (i = 0; i < len; i++) {
3965 8031           SV **elem = av_fetch(av, i, 0);
3966 8031 50         if (elem && SvNV(*elem) < threshold) {
    100          
3967 1003           XSRETURN_NO;
3968             }
3969             }
3970             } else {
3971 1002           char *key = SvPV_nolen(ST(1));
3972 1002           NV threshold = SvNV(ST(2));
3973 5004 100         for (i = 0; i < len; i++) {
3974 4003           SV **elem = av_fetch(av, i, 0);
3975 4003 50         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    50          
    50          
3976 4003           HV *hv = (HV *)SvRV(*elem);
3977 4003           SV **val = hv_fetch(hv, key, strlen(key), 0);
3978 4003 50         if (val && SvNV(*val) < threshold) {
    100          
3979 1           XSRETURN_NO;
3980             }
3981             }
3982             }
3983             }
3984              
3985 2006           XSRETURN_YES;
3986             }
3987              
3988             /* none_ge(\@array, $threshold) or none_ge(\@array, $key, $threshold) */
3989 2004           XS_INTERNAL(xs_none_ge) {
3990 2004           dXSARGS;
3991 2004 50         if (items < 2 || items > 3) croak("Usage: Func::Util::none_ge(\\@array, $threshold) or none_ge(\\@array, $key, $threshold)");
    50          
3992              
3993 2004           SV *aref = ST(0);
3994 2004 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
3995 0           croak("Func::Util::none_ge: first argument must be an arrayref");
3996             }
3997              
3998 2004           AV *av = (AV *)SvRV(aref);
3999 2004           SSize_t len = av_len(av) + 1;
4000             SSize_t i;
4001              
4002 2004 50         if (items == 2) {
4003 2004           NV threshold = SvNV(ST(1));
4004 9025 100         for (i = 0; i < len; i++) {
4005 8023           SV **elem = av_fetch(av, i, 0);
4006 8023 50         if (elem && SvNV(*elem) >= threshold) {
    100          
4007 1002           XSRETURN_NO;
4008             }
4009             }
4010             } else {
4011 0           char *key = SvPV_nolen(ST(1));
4012 0           NV threshold = SvNV(ST(2));
4013 0 0         for (i = 0; i < len; i++) {
4014 0           SV **elem = av_fetch(av, i, 0);
4015 0 0         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    0          
    0          
4016 0           HV *hv = (HV *)SvRV(*elem);
4017 0           SV **val = hv_fetch(hv, key, strlen(key), 0);
4018 0 0         if (val && SvNV(*val) >= threshold) {
    0          
4019 0           XSRETURN_NO;
4020             }
4021             }
4022             }
4023             }
4024              
4025 1002           XSRETURN_YES;
4026             }
4027              
4028             /* none_le(\@array, $threshold) or none_le(\@array, $key, $threshold) */
4029 2004           XS_INTERNAL(xs_none_le) {
4030 2004           dXSARGS;
4031 2004 50         if (items < 2 || items > 3) croak("Usage: Func::Util::none_le(\\@array, $threshold) or none_le(\\@array, $key, $threshold)");
    50          
4032              
4033 2004           SV *aref = ST(0);
4034 2004 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
4035 0           croak("Func::Util::none_le: first argument must be an arrayref");
4036             }
4037              
4038 2004           AV *av = (AV *)SvRV(aref);
4039 2004           SSize_t len = av_len(av) + 1;
4040             SSize_t i;
4041              
4042 2004 50         if (items == 2) {
4043 2004           NV threshold = SvNV(ST(1));
4044 9017 100         for (i = 0; i < len; i++) {
4045 8015           SV **elem = av_fetch(av, i, 0);
4046 8015 50         if (elem && SvNV(*elem) <= threshold) {
    100          
4047 1002           XSRETURN_NO;
4048             }
4049             }
4050             } else {
4051 0           char *key = SvPV_nolen(ST(1));
4052 0           NV threshold = SvNV(ST(2));
4053 0 0         for (i = 0; i < len; i++) {
4054 0           SV **elem = av_fetch(av, i, 0);
4055 0 0         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    0          
    0          
4056 0           HV *hv = (HV *)SvRV(*elem);
4057 0           SV **val = hv_fetch(hv, key, strlen(key), 0);
4058 0 0         if (val && SvNV(*val) <= threshold) {
    0          
4059 0           XSRETURN_NO;
4060             }
4061             }
4062             }
4063             }
4064              
4065 1002           XSRETURN_YES;
4066             }
4067              
4068             /* none_eq(\@array, $value) or none_eq(\@array, $key, $value) */
4069 2008           XS_INTERNAL(xs_none_eq) {
4070 2008           dXSARGS;
4071 2008 50         if (items < 2 || items > 3) croak("Usage: Func::Util::none_eq(\\@array, $value) or none_eq(\\@array, $key, $value)");
    50          
4072              
4073 2008           SV *aref = ST(0);
4074 2008 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
4075 0           croak("Func::Util::none_eq: first argument must be an arrayref");
4076             }
4077              
4078 2008           AV *av = (AV *)SvRV(aref);
4079 2008           SSize_t len = av_len(av) + 1;
4080             SSize_t i;
4081              
4082 2008 50         if (items == 2) {
4083 2008           NV target = SvNV(ST(1));
4084 11037 100         for (i = 0; i < len; i++) {
4085 10032           SV **elem = av_fetch(av, i, 0);
4086 10032 50         if (elem && SvNV(*elem) == target) {
    100          
4087 1003           XSRETURN_NO;
4088             }
4089             }
4090             } else {
4091 0           char *key = SvPV_nolen(ST(1));
4092 0           NV target = SvNV(ST(2));
4093 0 0         for (i = 0; i < len; i++) {
4094 0           SV **elem = av_fetch(av, i, 0);
4095 0 0         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    0          
    0          
4096 0           HV *hv = (HV *)SvRV(*elem);
4097 0           SV **val = hv_fetch(hv, key, strlen(key), 0);
4098 0 0         if (val && SvNV(*val) == target) {
    0          
4099 0           XSRETURN_NO;
4100             }
4101             }
4102             }
4103             }
4104              
4105 1005           XSRETURN_YES;
4106             }
4107              
4108             /* none_ne(\@array, $value) or none_ne(\@array, $key, $value) */
4109 2005           XS_INTERNAL(xs_none_ne) {
4110 2005           dXSARGS;
4111 2005 50         if (items < 2 || items > 3) croak("Usage: Func::Util::none_ne(\\@array, $value) or none_ne(\\@array, $key, $value)");
    50          
4112              
4113 2005           SV *aref = ST(0);
4114 2005 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
4115 0           croak("Func::Util::none_ne: first argument must be an arrayref");
4116             }
4117              
4118 2005           AV *av = (AV *)SvRV(aref);
4119 2005           SSize_t len = av_len(av) + 1;
4120             SSize_t i;
4121              
4122 2005 50         if (items == 2) {
4123 2005           NV target = SvNV(ST(1));
4124 6015 100         for (i = 0; i < len; i++) {
4125 5012           SV **elem = av_fetch(av, i, 0);
4126 5012 50         if (elem && SvNV(*elem) != target) {
    100          
4127 1002           XSRETURN_NO;
4128             }
4129             }
4130             } else {
4131 0           char *key = SvPV_nolen(ST(1));
4132 0           NV target = SvNV(ST(2));
4133 0 0         for (i = 0; i < len; i++) {
4134 0           SV **elem = av_fetch(av, i, 0);
4135 0 0         if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
    0          
    0          
4136 0           HV *hv = (HV *)SvRV(*elem);
4137 0           SV **val = hv_fetch(hv, key, strlen(key), 0);
4138 0 0         if (val && SvNV(*val) != target) {
    0          
4139 0           XSRETURN_NO;
4140             }
4141             }
4142             }
4143             }
4144              
4145 1003           XSRETURN_YES;
4146             }
4147              
4148             /* firstr(\&block, \@array) - first with arrayref, no stack flattening */
4149 3012           XS_INTERNAL(xs_firstr) {
4150 3012           dXSARGS;
4151 3012 50         if (items != 2) croak("Usage: Func::Util::firstr(\\&block, \\@array)");
4152              
4153 3012           SV *block = ST(0);
4154 3012           SV *aref = ST(1);
4155              
4156 3012 50         if (!SvROK(block) || SvTYPE(SvRV(block)) != SVt_PVCV) {
    50          
4157 0           croak("Func::Util::firstr: first argument must be a coderef");
4158             }
4159 3012 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
4160 0           croak("Func::Util::firstr: second argument must be an arrayref");
4161             }
4162              
4163 3012           CV *block_cv = (CV *)SvRV(block);
4164 3012           AV *av = (AV *)SvRV(aref);
4165 3012           SSize_t len = av_len(av) + 1;
4166             SSize_t i;
4167              
4168 3012 50         if (len == 0) {
4169 0           XSRETURN_UNDEF;
4170             }
4171              
4172             #ifdef dMULTICALL
4173 3012 50         if (!CvISXSUB(block_cv)) {
4174             dMULTICALL;
4175 3012           I32 gimme = G_SCALAR;
4176              
4177 3012           SAVESPTR(GvSV(PL_defgv));
4178 3012 50         PUSH_MULTICALL(block_cv);
4179              
4180 11045 100         for (i = 0; i < len; i++) {
4181 10045           SV **elem = av_fetch(av, i, 0);
4182 10045 50         if (!elem) continue;
4183              
4184 10045           SV *def_sv = GvSV(PL_defgv) = *elem;
4185 10045           SvTEMP_off(def_sv);
4186              
4187 10045           MULTICALL;
4188              
4189 10045 100         if (SvTRUE(*PL_stack_sp)) {
4190 2012 50         POP_MULTICALL;
4191 2012           ST(0) = *elem;
4192 2012           XSRETURN(1);
4193             }
4194             }
4195              
4196 1000 50         POP_MULTICALL;
4197 1000           XSRETURN_UNDEF;
4198             }
4199             #endif
4200              
4201             /* Fallback for XS subs */
4202 0 0         for (i = 0; i < len; i++) {
4203 0           SV **elem = av_fetch(av, i, 0);
4204 0 0         if (!elem) continue;
4205              
4206 0           dSP;
4207 0           GvSV(PL_defgv) = *elem;
4208              
4209 0 0         PUSHMARK(SP);
4210 0           call_sv((SV*)block_cv, G_SCALAR);
4211              
4212 0 0         if (SvTRUE(*PL_stack_sp)) {
4213 0           ST(0) = *elem;
4214 0           XSRETURN(1);
4215             }
4216             }
4217              
4218 0           XSRETURN_UNDEF;
4219             }
4220              
4221             /* final(\&block, \@array) - last element where block returns true (backwards iteration) */
4222 2014           XS_INTERNAL(xs_final) {
4223 2014           dXSARGS;
4224 2014 50         if (items != 2) croak("Usage: Func::Util::final(\\&block, \\@array)");
4225              
4226 2014           SV *block = ST(0);
4227 2014           SV *aref = ST(1);
4228              
4229 2014 50         if (!SvROK(block) || SvTYPE(SvRV(block)) != SVt_PVCV) {
    50          
4230 0           croak("Func::Util::final: first argument must be a coderef");
4231             }
4232 2014 50         if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
    50          
4233 0           croak("Func::Util::final: second argument must be an arrayref");
4234             }
4235              
4236 2014           CV *block_cv = (CV *)SvRV(block);
4237 2014           AV *av = (AV *)SvRV(aref);
4238 2014           SSize_t len = av_len(av) + 1;
4239             SSize_t i;
4240              
4241 2014 100         if (len == 0) {
4242 2           XSRETURN_UNDEF;
4243             }
4244              
4245             #ifdef dMULTICALL
4246 2012 50         if (!CvISXSUB(block_cv)) {
4247             dMULTICALL;
4248 2012           I32 gimme = G_SCALAR;
4249              
4250 2012           SAVESPTR(GvSV(PL_defgv));
4251 2012 50         PUSH_MULTICALL(block_cv);
4252              
4253             /* Iterate backwards for speed */
4254 9029 100         for (i = len - 1; i >= 0; i--) {
4255 8026           SV **elem = av_fetch(av, i, 0);
4256 8026 50         if (!elem) continue;
4257              
4258 8026           SV *def_sv = GvSV(PL_defgv) = *elem;
4259 8026           SvTEMP_off(def_sv);
4260              
4261 8026           MULTICALL;
4262              
4263 8026 100         if (SvTRUE(*PL_stack_sp)) {
4264 1009 50         POP_MULTICALL;
4265 1009           ST(0) = *elem;
4266 1009           XSRETURN(1);
4267             }
4268             }
4269              
4270 1003 50         POP_MULTICALL;
4271 1003           XSRETURN_UNDEF;
4272             }
4273             #endif
4274              
4275             /* Fallback for XS subs - backwards */
4276 0 0         for (i = len - 1; i >= 0; i--) {
4277 0           SV **elem = av_fetch(av, i, 0);
4278 0 0         if (!elem) continue;
4279              
4280 0           dSP;
4281 0           GvSV(PL_defgv) = *elem;
4282              
4283 0 0         PUSHMARK(SP);
4284 0           call_sv((SV*)block_cv, G_SCALAR);
4285              
4286 0 0         if (SvTRUE(*PL_stack_sp)) {
4287 0           ST(0) = *elem;
4288 0           XSRETURN(1);
4289             }
4290             }
4291              
4292 0           XSRETURN_UNDEF;
4293             }
4294              
4295             /* first { block } @list - return first element where block returns true */
4296 13118           XS_INTERNAL(xs_first) {
4297 13118           dXSARGS;
4298 13118 50         if (items < 1) croak("Usage: Func::Util::first(\\&block, @list)");
4299              
4300 13118           SV *block = ST(0);
4301 13118 50         if (!SvROK(block) || SvTYPE(SvRV(block)) != SVt_PVCV) {
    50          
4302 0           croak("Func::Util::first: first argument must be a coderef");
4303             }
4304              
4305 13118           CV *block_cv = (CV *)SvRV(block);
4306             /* Store args from stack base before any stack manipulation */
4307 13118           SV **args = &PL_stack_base[ax];
4308             IV index;
4309              
4310             /* Empty list - return undef */
4311 13118 100         if (items <= 1) {
4312 2           XSRETURN_UNDEF;
4313             }
4314              
4315             /* Use MULTICALL for pure Perl subs - much faster than call_sv */
4316             #ifdef dMULTICALL
4317 13116 50         if (!CvISXSUB(block_cv)) {
4318             dMULTICALL;
4319 13116           I32 gimme = G_SCALAR;
4320              
4321 13116           SAVESPTR(GvSV(PL_defgv));
4322 13116 50         PUSH_MULTICALL(block_cv);
4323              
4324 33340 100         for (index = 1; index < items; index++) {
4325 33336           SV *def_sv = GvSV(PL_defgv) = args[index];
4326 33336           SvTEMP_off(def_sv);
4327              
4328 33336           MULTICALL;
4329              
4330 33336 100         if (SvTRUE(*PL_stack_sp)) {
4331 13112 50         POP_MULTICALL;
4332 13112           ST(0) = ST(index);
4333 13112           XSRETURN(1);
4334             }
4335             }
4336              
4337 4 50         POP_MULTICALL;
4338 4           XSRETURN_UNDEF;
4339             }
4340             #endif
4341              
4342             /* Fallback for XS subs */
4343 0 0         for (index = 1; index < items; index++) {
4344 0           dSP;
4345 0           GvSV(PL_defgv) = args[index];
4346              
4347 0 0         PUSHMARK(SP);
4348 0           call_sv((SV*)block_cv, G_SCALAR);
4349              
4350 0 0         if (SvTRUE(*PL_stack_sp)) {
4351 0           ST(0) = ST(index);
4352 0           XSRETURN(1);
4353             }
4354             }
4355              
4356 0           XSRETURN_UNDEF;
4357             }
4358              
4359             /* any { block } @list - return true if any element matches */
4360 13126           XS_INTERNAL(xs_any) {
4361 13126           dXSARGS;
4362 13126 50         if (items < 1) croak("Usage: Func::Util::any(\\&block, @list)");
4363              
4364 13126           SV *block = ST(0);
4365 13126 50         if (!SvROK(block) || SvTYPE(SvRV(block)) != SVt_PVCV) {
    50          
4366 0           croak("Func::Util::any: first argument must be a coderef");
4367             }
4368              
4369 13126           CV *block_cv = (CV *)SvRV(block);
4370 13126           SV **args = &PL_stack_base[ax];
4371             IV index;
4372              
4373             /* Empty list returns false */
4374 13126 100         if (items <= 1) {
4375 2           XSRETURN_NO;
4376             }
4377              
4378             #ifdef dMULTICALL
4379 13124 50         if (!CvISXSUB(block_cv)) {
4380             dMULTICALL;
4381 13124           I32 gimme = G_SCALAR;
4382              
4383 13124           SAVESPTR(GvSV(PL_defgv));
4384 13124 50         PUSH_MULTICALL(block_cv);
4385              
4386 33345 100         for (index = 1; index < items; index++) {
4387 33341           SV *def_sv = GvSV(PL_defgv) = args[index];
4388 33341           SvTEMP_off(def_sv);
4389              
4390 33341           MULTICALL;
4391              
4392 33341 100         if (SvTRUE(*PL_stack_sp)) {
4393 13120 50         POP_MULTICALL;
4394 13120           XSRETURN_YES;
4395             }
4396             }
4397              
4398 4 50         POP_MULTICALL;
4399 4           XSRETURN_NO;
4400             }
4401             #endif
4402              
4403 0 0         for (index = 1; index < items; index++) {
4404 0           dSP;
4405 0           GvSV(PL_defgv) = args[index];
4406              
4407 0 0         PUSHMARK(SP);
4408 0           call_sv((SV*)block_cv, G_SCALAR);
4409              
4410 0 0         if (SvTRUE(*PL_stack_sp)) {
4411 0           XSRETURN_YES;
4412             }
4413             }
4414              
4415 0           XSRETURN_NO;
4416             }
4417              
4418             /* all { block } @list - return true if all elements match */
4419 13116           XS_INTERNAL(xs_all) {
4420 13116           dXSARGS;
4421 13116 50         if (items < 1) croak("Usage: Func::Util::all(\\&block, @list)");
4422              
4423 13116           SV *block = ST(0);
4424 13116 50         if (!SvROK(block) || SvTYPE(SvRV(block)) != SVt_PVCV) {
    50          
4425 0           croak("Func::Util::all: first argument must be a coderef");
4426             }
4427              
4428 13116           CV *block_cv = (CV *)SvRV(block);
4429 13116           SV **args = &PL_stack_base[ax];
4430             IV index;
4431              
4432             /* Empty list returns true (vacuous truth) */
4433 13116 100         if (items <= 1) {
4434 2           XSRETURN_YES;
4435             }
4436              
4437             #ifdef dMULTICALL
4438 13114 50         if (!CvISXSUB(block_cv)) {
4439             dMULTICALL;
4440 13114           I32 gimme = G_SCALAR;
4441              
4442 13114           SAVESPTR(GvSV(PL_defgv));
4443 13114 50         PUSH_MULTICALL(block_cv);
4444              
4445 66645 100         for (index = 1; index < items; index++) {
4446 53537           SV *def_sv = GvSV(PL_defgv) = args[index];
4447 53537           SvTEMP_off(def_sv);
4448              
4449 53537           MULTICALL;
4450              
4451 53537 100         if (!SvTRUE(*PL_stack_sp)) {
4452 6 50         POP_MULTICALL;
4453 6           XSRETURN_NO;
4454             }
4455             }
4456              
4457 13108 50         POP_MULTICALL;
4458 13108           XSRETURN_YES;
4459             }
4460             #endif
4461              
4462 0 0         for (index = 1; index < items; index++) {
4463 0           dSP;
4464 0           GvSV(PL_defgv) = args[index];
4465              
4466 0 0         PUSHMARK(SP);
4467 0           call_sv((SV*)block_cv, G_SCALAR);
4468              
4469 0 0         if (!SvTRUE(*PL_stack_sp)) {
4470 0           XSRETURN_NO;
4471             }
4472             }
4473              
4474 0           XSRETURN_YES;
4475             }
4476              
4477             /* none { block } @list - return true if no elements match */
4478 13114           XS_INTERNAL(xs_none) {
4479 13114           dXSARGS;
4480 13114 50         if (items < 1) croak("Usage: Func::Util::none(\\&block, @list)");
4481              
4482 13114           SV *block = ST(0);
4483 13114 50         if (!SvROK(block) || SvTYPE(SvRV(block)) != SVt_PVCV) {
    50          
4484 0           croak("Func::Util::none: first argument must be a coderef");
4485             }
4486              
4487 13114           CV *block_cv = (CV *)SvRV(block);
4488 13114           SV **args = &PL_stack_base[ax];
4489             IV index;
4490              
4491             /* Empty list returns true (no elements match = vacuous truth) */
4492 13114 100         if (items <= 1) {
4493 2           XSRETURN_YES;
4494             }
4495              
4496             #ifdef dMULTICALL
4497 13112 50         if (!CvISXSUB(block_cv)) {
4498             dMULTICALL;
4499 13112           I32 gimme = G_SCALAR;
4500              
4501 13112           SAVESPTR(GvSV(PL_defgv));
4502 13112 50         PUSH_MULTICALL(block_cv);
4503              
4504 63638 100         for (index = 1; index < items; index++) {
4505 53532           SV *def_sv = GvSV(PL_defgv) = args[index];
4506 53532           SvTEMP_off(def_sv);
4507              
4508 53532           MULTICALL;
4509              
4510 53532 100         if (SvTRUE(*PL_stack_sp)) {
4511 3006 50         POP_MULTICALL;
4512 3006           XSRETURN_NO;
4513             }
4514             }
4515              
4516 10106 50         POP_MULTICALL;
4517 10106           XSRETURN_YES;
4518             }
4519             #endif
4520              
4521 0 0         for (index = 1; index < items; index++) {
4522 0           dSP;
4523 0           GvSV(PL_defgv) = args[index];
4524              
4525 0 0         PUSHMARK(SP);
4526 0           call_sv((SV*)block_cv, G_SCALAR);
4527              
4528 0 0         if (SvTRUE(*PL_stack_sp)) {
4529 0           XSRETURN_NO;
4530             }
4531             }
4532              
4533 0           XSRETURN_YES;
4534             }
4535              
4536             /* ============================================
4537             Experimental: Inlined MULTICALL versions for benchmarking
4538              
4539             These versions inline the runops loop to skip the CALLRUNOPS
4540             function call overhead. For testing only.
4541             ============================================ */
4542              
4543             /* first_inline - experimental version with inlined runops loop
4544             * Requires MULTICALL API (5.11+) */
4545             #ifdef dMULTICALL
4546 6           XS_INTERNAL(xs_first_inline) {
4547 6           dXSARGS;
4548 6 50         if (items < 1) croak("Usage: Func::Util::first_inline(\\&block, @list)");
4549              
4550 6           SV *block = ST(0);
4551 6 50         if (!SvROK(block) || SvTYPE(SvRV(block)) != SVt_PVCV) {
    50          
4552 0           croak("Func::Util::first_inline: first argument must be a coderef");
4553             }
4554              
4555 6           CV *the_cv = (CV *)SvRV(block);
4556              
4557 6 100         if (items == 1) {
4558 1           XSRETURN_UNDEF;
4559             }
4560              
4561             /* Only works with pure Perl subs */
4562 5 50         if (CvISXSUB(the_cv)) {
4563 0           croak("Func::Util::first_inline: only works with pure Perl subs");
4564             }
4565              
4566 5           SV **args = &ST(1);
4567 5           IV num_args = items - 1;
4568             IV i;
4569              
4570             /* Use standard MULTICALL API for compatibility */
4571             dMULTICALL;
4572 5           I32 gimme = G_SCALAR;
4573              
4574 5 50         PUSH_MULTICALL(the_cv);
4575              
4576             /* Save and setup $_ */
4577 5           SAVESPTR(GvSV(PL_defgv));
4578              
4579 25 100         for (i = 0; i < num_args; i++) {
4580 24           SV *elem = args[i];
4581 24           GvSV(PL_defgv) = elem;
4582 24           SvTEMP_off(elem);
4583              
4584 24           MULTICALL;
4585              
4586 24 100         if (SvTRUE(*PL_stack_sp)) {
4587             /* Found it - cleanup and return */
4588 4 50         POP_MULTICALL;
4589 4           SPAGAIN;
4590              
4591 4           ST(0) = elem;
4592 4           XSRETURN(1);
4593             }
4594             }
4595              
4596             /* Cleanup */
4597 1 50         POP_MULTICALL;
4598 1           SPAGAIN;
4599              
4600 1           XSRETURN_UNDEF;
4601             }
4602             #endif /* dMULTICALL */
4603              
4604              
4605             /* ============================================
4606             Type predicate XS fallbacks
4607             ============================================ */
4608              
4609 17123           XS_INTERNAL(xs_is_ref) {
4610 17123           dXSARGS;
4611 17123 50         if (items != 1) croak("Usage: Func::Util::is_ref($value)");
4612 17123 100         ST(0) = SvROK(ST(0)) ? &PL_sv_yes : &PL_sv_no;
4613 17123           XSRETURN(1);
4614             }
4615              
4616 17132           XS_INTERNAL(xs_is_array) {
4617 17132           dXSARGS;
4618 17132 50         if (items != 1) croak("Usage: Func::Util::is_array($value)");
4619 17132           SV *sv = ST(0);
4620 17132 100         ST(0) = (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) ? &PL_sv_yes : &PL_sv_no;
    100          
4621 17132           XSRETURN(1);
4622             }
4623              
4624 17131           XS_INTERNAL(xs_is_hash) {
4625 17131           dXSARGS;
4626 17131 50         if (items != 1) croak("Usage: Func::Util::is_hash($value)");
4627 17131           SV *sv = ST(0);
4628 17131 100         ST(0) = (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV) ? &PL_sv_yes : &PL_sv_no;
    100          
4629 17131           XSRETURN(1);
4630             }
4631              
4632 17117           XS_INTERNAL(xs_is_code) {
4633 17117           dXSARGS;
4634 17117 50         if (items != 1) croak("Usage: Func::Util::is_code($value)");
4635 17117           SV *sv = ST(0);
4636 17117 100         ST(0) = (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVCV) ? &PL_sv_yes : &PL_sv_no;
    100          
4637 17117           XSRETURN(1);
4638             }
4639              
4640 17118           XS_INTERNAL(xs_is_defined) {
4641 17118           dXSARGS;
4642 17118 50         if (items != 1) croak("Usage: Func::Util::is_defined($value)");
4643 17118 100         ST(0) = SvOK(ST(0)) ? &PL_sv_yes : &PL_sv_no;
4644 17118           XSRETURN(1);
4645             }
4646              
4647             /* ============================================
4648             String predicate XS fallbacks
4649             ============================================ */
4650              
4651 12016           XS_INTERNAL(xs_is_empty) {
4652 12016           dXSARGS;
4653 12016 50         if (items != 1) croak("Usage: Func::Util::is_empty($value)");
4654 12016           SV *sv = ST(0);
4655 12016 100         if (!SvOK(sv)) {
4656 2002           ST(0) = &PL_sv_yes;
4657             } else {
4658             STRLEN len;
4659 10014           SvPV(sv, len);
4660 10014 100         ST(0) = len == 0 ? &PL_sv_yes : &PL_sv_no;
4661             }
4662 12016           XSRETURN(1);
4663             }
4664              
4665 19124           XS_INTERNAL(xs_starts_with) {
4666 19124           dXSARGS;
4667 19124 50         if (items != 2) croak("Usage: Func::Util::starts_with($string, $prefix)");
4668              
4669 19124           SV *str_sv = ST(0);
4670 19124           SV *prefix_sv = ST(1);
4671              
4672 19124 100         if (!SvOK(str_sv) || !SvOK(prefix_sv)) {
    100          
4673 1003           ST(0) = &PL_sv_no;
4674 1003           XSRETURN(1);
4675             }
4676              
4677             STRLEN str_len, prefix_len;
4678 18121           const char *str = SvPV(str_sv, str_len);
4679 18121           const char *prefix = SvPV(prefix_sv, prefix_len);
4680              
4681 18121 100         if (prefix_len > str_len) {
4682 3002           ST(0) = &PL_sv_no;
4683 15119 100         } else if (prefix_len == 0) {
4684 1002           ST(0) = &PL_sv_yes;
4685             } else {
4686 14117 100         ST(0) = memcmp(str, prefix, prefix_len) == 0 ? &PL_sv_yes : &PL_sv_no;
4687             }
4688 18121           XSRETURN(1);
4689             }
4690              
4691 19122           XS_INTERNAL(xs_ends_with) {
4692 19122           dXSARGS;
4693 19122 50         if (items != 2) croak("Usage: Func::Util::ends_with($string, $suffix)");
4694              
4695 19122           SV *str_sv = ST(0);
4696 19122           SV *suffix_sv = ST(1);
4697              
4698 19122 100         if (!SvOK(str_sv) || !SvOK(suffix_sv)) {
    100          
4699 1003           ST(0) = &PL_sv_no;
4700 1003           XSRETURN(1);
4701             }
4702              
4703             STRLEN str_len, suffix_len;
4704 18119           const char *str = SvPV(str_sv, str_len);
4705 18119           const char *suffix = SvPV(suffix_sv, suffix_len);
4706              
4707 18119 100         if (suffix_len > str_len) {
4708 3002           ST(0) = &PL_sv_no;
4709 15117 100         } else if (suffix_len == 0) {
4710 1002           ST(0) = &PL_sv_yes;
4711             } else {
4712 14115           const char *str_end = str + str_len - suffix_len;
4713 14115 100         ST(0) = memcmp(str_end, suffix, suffix_len) == 0 ? &PL_sv_yes : &PL_sv_no;
4714             }
4715 18119           XSRETURN(1);
4716             }
4717              
4718             /* count: count occurrences of substring using memmem */
4719 2000           XS_INTERNAL(xs_count) {
4720 2000           dXSARGS;
4721 2000 50         if (items != 2) croak("Usage: Func::Util::count($string, $substring)");
4722              
4723 2000           SV *str_sv = ST(0);
4724 2000           SV *needle_sv = ST(1);
4725              
4726 2000 50         if (!SvOK(str_sv) || !SvOK(needle_sv)) {
    50          
4727 0           ST(0) = sv_2mortal(newSViv(0));
4728 0           XSRETURN(1);
4729             }
4730              
4731             STRLEN str_len, needle_len;
4732 2000           const char *str = SvPV_const(str_sv, str_len);
4733 2000           const char *needle = SvPV_const(needle_sv, needle_len);
4734              
4735 2000 50         if (needle_len == 0 || needle_len > str_len) {
    50          
4736 2000           ST(0) = sv_2mortal(newSViv(0));
4737 2000           XSRETURN(1);
4738             }
4739              
4740 0           IV count = 0;
4741 0           const char *p = str;
4742 0           const char *end = str + str_len;
4743 0           STRLEN remaining = str_len;
4744              
4745 0 0         while (remaining >= needle_len) {
4746 0           const char *found = (const char *)util_memmem(p, remaining, needle, needle_len);
4747 0 0         if (!found) break;
4748 0           count++;
4749             /* Move past the match (non-overlapping) */
4750 0           p = found + needle_len;
4751 0           remaining = end - p;
4752             }
4753              
4754 0           ST(0) = sv_2mortal(newSViv(count));
4755 0           XSRETURN(1);
4756             }
4757              
4758             /* replace_all: replace all occurrences of old with new using memmem */
4759 5026           XS_INTERNAL(xs_replace_all) {
4760 5026           dXSARGS;
4761 5026 50         if (items != 3) croak("Usage: Func::Util::replace_all($string, $old, $new)");
4762              
4763 5026           SV *str_sv = ST(0);
4764 5026           SV *old_sv = ST(1);
4765 5026           SV *new_sv = ST(2);
4766              
4767             /* Handle undef - return undef */
4768 5026 50         if (!SvOK(str_sv)) {
4769 0           ST(0) = &PL_sv_undef;
4770 0           XSRETURN(1);
4771             }
4772              
4773             STRLEN str_len, old_len, new_len;
4774 5026           const char *str = SvPV_const(str_sv, str_len);
4775 5026           const char *old = SvPV_const(old_sv, old_len);
4776 5026           const char *replacement = SvPV_const(new_sv, new_len);
4777              
4778             /* Empty search string or not found - return original */
4779 5026 100         if (old_len == 0 || old_len > str_len) {
    100          
4780 1002           ST(0) = sv_2mortal(newSVpvn(str, str_len));
4781 1002           XSRETURN(1);
4782             }
4783              
4784             /* First pass: count occurrences to pre-size buffer */
4785 4024           IV count = 0;
4786 4024           const char *p = str;
4787 4024           const char *end = str + str_len;
4788 4024           STRLEN remaining = str_len;
4789              
4790 11061 100         while (remaining >= old_len) {
4791 9052           const char *found = (const char *)util_memmem(p, remaining, old, old_len);
4792 9052 100         if (!found) break;
4793 7037           count++;
4794 7037           p = found + old_len;
4795 7037           remaining = end - p;
4796             }
4797              
4798 4024 100         if (count == 0) {
4799             /* No matches - return copy of original */
4800 1002           ST(0) = sv_2mortal(newSVpvn(str, str_len));
4801 1002           XSRETURN(1);
4802             }
4803              
4804             /* Calculate result size and allocate */
4805 3022           STRLEN result_len = str_len + count * (new_len - old_len);
4806 3022           SV *result = sv_2mortal(newSV(result_len + 1));
4807 3022           SvPOK_on(result);
4808 3022           char *out = SvPVX(result);
4809 3022           char *out_ptr = out;
4810              
4811             /* Second pass: build result */
4812 3022           p = str;
4813 3022           remaining = str_len;
4814              
4815 10059 100         while (remaining >= old_len) {
4816 8050           const char *found = (const char *)util_memmem(p, remaining, old, old_len);
4817 8050 100         if (!found) break;
4818              
4819             /* Copy text before match */
4820 7037           STRLEN before_len = found - p;
4821 7037 100         if (before_len > 0) {
4822 2025           memcpy(out_ptr, p, before_len);
4823 2025           out_ptr += before_len;
4824             }
4825              
4826             /* Copy replacement */
4827 7037 100         if (new_len > 0) {
4828 5035           memcpy(out_ptr, replacement, new_len);
4829 5035           out_ptr += new_len;
4830             }
4831              
4832 7037           p = found + old_len;
4833 7037           remaining = end - p;
4834             }
4835              
4836             /* Copy remaining text after last match */
4837 3022 100         if (remaining > 0) {
4838 1016           memcpy(out_ptr, p, remaining);
4839 1016           out_ptr += remaining;
4840             }
4841              
4842 3022           *out_ptr = '\0';
4843 3022           SvCUR_set(result, out_ptr - out);
4844              
4845 3022           ST(0) = result;
4846 3022           XSRETURN(1);
4847             }
4848              
4849             /* before: get text before first occurrence of delimiter */
4850 0           XS_INTERNAL(xs_before) {
4851 0           dXSARGS;
4852 0 0         if (items != 2) croak("Usage: Func::Util::before($string, $delimiter)");
4853              
4854 0           SV *str_sv = ST(0);
4855 0           SV *delim_sv = ST(1);
4856              
4857 0 0         if (!SvOK(str_sv)) {
4858 0           ST(0) = &PL_sv_undef;
4859 0           XSRETURN(1);
4860             }
4861              
4862             STRLEN str_len, delim_len;
4863 0           const char *str = SvPV_const(str_sv, str_len);
4864 0           const char *delim = SvPV_const(delim_sv, delim_len);
4865              
4866 0 0         if (delim_len == 0 || delim_len > str_len) {
    0          
4867 0           ST(0) = sv_2mortal(newSVpvn(str, str_len));
4868 0           XSRETURN(1);
4869             }
4870              
4871 0           const char *found = (const char *)util_memmem(str, str_len, delim, delim_len);
4872 0 0         if (found) {
4873 0           ST(0) = sv_2mortal(newSVpvn(str, found - str));
4874             } else {
4875 0           ST(0) = sv_2mortal(newSVpvn(str, str_len));
4876             }
4877 0           XSRETURN(1);
4878             }
4879              
4880             /* after: get text after first occurrence of delimiter */
4881 0           XS_INTERNAL(xs_after) {
4882 0           dXSARGS;
4883 0 0         if (items != 2) croak("Usage: Func::Util::after($string, $delimiter)");
4884              
4885 0           SV *str_sv = ST(0);
4886 0           SV *delim_sv = ST(1);
4887              
4888 0 0         if (!SvOK(str_sv)) {
4889 0           ST(0) = &PL_sv_undef;
4890 0           XSRETURN(1);
4891             }
4892              
4893             STRLEN str_len, delim_len;
4894 0           const char *str = SvPV_const(str_sv, str_len);
4895 0           const char *delim = SvPV_const(delim_sv, delim_len);
4896              
4897 0 0         if (delim_len == 0 || delim_len > str_len) {
    0          
4898 0           ST(0) = sv_2mortal(newSVpvn("", 0));
4899 0           XSRETURN(1);
4900             }
4901              
4902 0           const char *found = (const char *)util_memmem(str, str_len, delim, delim_len);
4903 0 0         if (found) {
4904 0           const char *after_delim = found + delim_len;
4905 0           ST(0) = sv_2mortal(newSVpvn(after_delim, str + str_len - after_delim));
4906             } else {
4907 0           ST(0) = sv_2mortal(newSVpvn("", 0));
4908             }
4909 0           XSRETURN(1);
4910             }
4911              
4912             /* ============================================
4913             Boolean/Truthiness XS fallbacks
4914             ============================================ */
4915              
4916 24129           XS_INTERNAL(xs_is_true) {
4917 24129           dXSARGS;
4918 24129 50         if (items != 1) croak("Usage: Func::Util::is_true($value)");
4919 24129 100         ST(0) = SvTRUE(ST(0)) ? &PL_sv_yes : &PL_sv_no;
4920 24129           XSRETURN(1);
4921             }
4922              
4923 22118           XS_INTERNAL(xs_is_false) {
4924 22118           dXSARGS;
4925 22118 50         if (items != 1) croak("Usage: Func::Util::is_false($value)");
4926 22118 100         ST(0) = SvTRUE(ST(0)) ? &PL_sv_no : &PL_sv_yes;
4927 22118           XSRETURN(1);
4928             }
4929              
4930 30119           XS_INTERNAL(xs_bool) {
4931 30119           dXSARGS;
4932 30119 50         if (items != 1) croak("Usage: Func::Util::bool($value)");
4933 30119 100         ST(0) = SvTRUE(ST(0)) ? &PL_sv_yes : &PL_sv_no;
4934 30119           XSRETURN(1);
4935             }
4936              
4937             /* ============================================
4938             Extended type predicate XS fallbacks
4939             ============================================ */
4940              
4941 19155           XS_INTERNAL(xs_is_num) {
4942 19155           dXSARGS;
4943 19155 50         if (items != 1) croak("Usage: Func::Util::is_num($value)");
4944 19155           SV *sv = ST(0);
4945 19155 100         ST(0) = (SvNIOK(sv) || looks_like_number(sv)) ? &PL_sv_yes : &PL_sv_no;
    100          
4946 19155           XSRETURN(1);
4947             }
4948              
4949 17120           XS_INTERNAL(xs_is_int) {
4950 17120           dXSARGS;
4951 17120 50         if (items != 1) croak("Usage: Func::Util::is_int($value)");
4952 17120           SV *sv = ST(0);
4953 17120 100         if (SvIOK(sv)) {
4954 14106           ST(0) = &PL_sv_yes;
4955 3014 100         } else if (SvNOK(sv)) {
4956 3009           NV nv = SvNV(sv);
4957 3009 100         ST(0) = (nv == (NV)(IV)nv) ? &PL_sv_yes : &PL_sv_no;
4958 5 100         } else if (looks_like_number(sv)) {
4959             STRLEN len;
4960 2           const char *pv = SvPV(sv, len);
4961 2           bool has_dot = FALSE;
4962             STRLEN i;
4963 6 100         for (i = 0; i < len; i++) {
4964 4 50         if (pv[i] == '.' || pv[i] == 'e' || pv[i] == 'E') {
    50          
    50          
4965 0           has_dot = TRUE;
4966 0           break;
4967             }
4968             }
4969 2 50         if (has_dot) {
4970 0           NV nv = SvNV(sv);
4971 0 0         ST(0) = (nv == (NV)(IV)nv) ? &PL_sv_yes : &PL_sv_no;
4972             } else {
4973 2           ST(0) = &PL_sv_yes;
4974             }
4975             } else {
4976 3           ST(0) = &PL_sv_no;
4977             }
4978 17120           XSRETURN(1);
4979             }
4980              
4981 6011           XS_INTERNAL(xs_is_blessed) {
4982 6011           dXSARGS;
4983 6011 50         if (items != 1) croak("Usage: Func::Util::is_blessed($value)");
4984 6011 100         ST(0) = sv_isobject(ST(0)) ? &PL_sv_yes : &PL_sv_no;
4985 6011           XSRETURN(1);
4986             }
4987              
4988 4009           XS_INTERNAL(xs_is_scalar_ref) {
4989 4009           dXSARGS;
4990 4009 50         if (items != 1) croak("Usage: Func::Util::is_scalar_ref($value)");
4991 4009           SV *sv = ST(0);
4992 4009 100         if (SvROK(sv)) {
4993 4006           SV *rv = SvRV(sv);
4994 4006           svtype type = SvTYPE(rv);
4995 4006 100         ST(0) = (type < SVt_PVAV) ? &PL_sv_yes : &PL_sv_no;
4996             } else {
4997 3           ST(0) = &PL_sv_no;
4998             }
4999 4009           XSRETURN(1);
5000             }
5001              
5002 6009           XS_INTERNAL(xs_is_regex) {
5003 6009           dXSARGS;
5004 6009 50         if (items != 1) croak("Usage: Func::Util::is_regex($value)");
5005 6009 100         ST(0) = SvRXOK(ST(0)) ? &PL_sv_yes : &PL_sv_no;
5006 6009           XSRETURN(1);
5007             }
5008              
5009 6008           XS_INTERNAL(xs_is_glob) {
5010 6008           dXSARGS;
5011 6008 50         if (items != 1) croak("Usage: Func::Util::is_glob($value)");
5012 6008 100         ST(0) = (SvTYPE(ST(0)) == SVt_PVGV) ? &PL_sv_yes : &PL_sv_no;
5013 6008           XSRETURN(1);
5014             }
5015              
5016 8017           XS_INTERNAL(xs_is_string) {
5017 8017           dXSARGS;
5018 8017 50         if (items != 1) croak("Usage: Func::Util::is_string($value)");
5019 8017           SV *sv = ST(0);
5020 8017 100         ST(0) = (SvOK(sv) && !SvROK(sv)) ? &PL_sv_yes : &PL_sv_no;
    100          
5021 8017           XSRETURN(1);
5022             }
5023              
5024             /* ============================================
5025             Numeric predicate XS fallbacks
5026             ============================================ */
5027              
5028 15123           XS_INTERNAL(xs_is_positive) {
5029 15123           dXSARGS;
5030 15123 50         if (items != 1) croak("Usage: Func::Util::is_positive($value)");
5031 15123           SV *sv = ST(0);
5032 15123 100         if (SvNIOK(sv) || looks_like_number(sv)) {
    50          
5033 15121           NV nv = SvNV(sv);
5034 15121 100         ST(0) = (nv > 0) ? &PL_sv_yes : &PL_sv_no;
5035             } else {
5036 2           ST(0) = &PL_sv_no;
5037             }
5038 15123           XSRETURN(1);
5039             }
5040              
5041 13122           XS_INTERNAL(xs_is_negative) {
5042 13122           dXSARGS;
5043 13122 50         if (items != 1) croak("Usage: Func::Util::is_negative($value)");
5044 13122           SV *sv = ST(0);
5045 13122 100         if (SvNIOK(sv) || looks_like_number(sv)) {
    50          
5046 13120           NV nv = SvNV(sv);
5047 13120 100         ST(0) = (nv < 0) ? &PL_sv_yes : &PL_sv_no;
5048             } else {
5049 2           ST(0) = &PL_sv_no;
5050             }
5051 13122           XSRETURN(1);
5052             }
5053              
5054 13123           XS_INTERNAL(xs_is_zero) {
5055 13123           dXSARGS;
5056 13123 50         if (items != 1) croak("Usage: Func::Util::is_zero($value)");
5057 13123           SV *sv = ST(0);
5058 13123 100         if (SvNIOK(sv) || looks_like_number(sv)) {
    100          
5059 13121           NV nv = SvNV(sv);
5060 13121 100         ST(0) = (nv == 0) ? &PL_sv_yes : &PL_sv_no;
5061             } else {
5062 2           ST(0) = &PL_sv_no;
5063             }
5064 13123           XSRETURN(1);
5065             }
5066              
5067             /* ============================================
5068             Numeric utility XS fallbacks
5069             ============================================ */
5070              
5071 18156           XS_INTERNAL(xs_is_even) {
5072 18156           dXSARGS;
5073 18156 50         if (items != 1) croak("Usage: Func::Util::is_even($value)");
5074 18156           SV *sv = ST(0);
5075 18156 100         if (SvIOK(sv)) {
5076 18144 100         ST(0) = (SvIVX(sv) & 1) == 0 ? &PL_sv_yes : &PL_sv_no;
5077 12 100         } else if (SvNIOK(sv)) {
5078 5           NV nv = SvNV(sv);
5079 5 100         if (nv == (NV)(IV)nv) {
5080 3 100         ST(0) = ((IV)nv & 1) == 0 ? &PL_sv_yes : &PL_sv_no;
5081             } else {
5082 2           ST(0) = &PL_sv_no;
5083             }
5084 7 100         } else if (looks_like_number(sv)) {
5085 2 50         ST(0) = (SvIV(sv) & 1) == 0 ? &PL_sv_yes : &PL_sv_no;
5086             } else {
5087 5           ST(0) = &PL_sv_no;
5088             }
5089 18156           XSRETURN(1);
5090             }
5091              
5092 18155           XS_INTERNAL(xs_is_odd) {
5093 18155           dXSARGS;
5094 18155 50         if (items != 1) croak("Usage: Func::Util::is_odd($value)");
5095 18155           SV *sv = ST(0);
5096 18155 100         if (SvIOK(sv)) {
5097 18144 100         ST(0) = (SvIVX(sv) & 1) == 1 ? &PL_sv_yes : &PL_sv_no;
5098 11 100         } else if (SvNIOK(sv)) {
5099 5           NV nv = SvNV(sv);
5100 5 100         if (nv == (NV)(IV)nv) {
5101 3 100         ST(0) = ((IV)nv & 1) == 1 ? &PL_sv_yes : &PL_sv_no;
5102             } else {
5103 2           ST(0) = &PL_sv_no;
5104             }
5105 6 100         } else if (looks_like_number(sv)) {
5106 2 50         ST(0) = (SvIV(sv) & 1) == 1 ? &PL_sv_yes : &PL_sv_no;
5107             } else {
5108 4           ST(0) = &PL_sv_no;
5109             }
5110 18155           XSRETURN(1);
5111             }
5112              
5113 15070           XS_INTERNAL(xs_is_between) {
5114 15070           dXSARGS;
5115 15070 50         if (items != 3) croak("Usage: Func::Util::is_between($value, $min, $max)");
5116 15070           SV *val_sv = ST(0);
5117 15070           SV *min_sv = ST(1);
5118 15070           SV *max_sv = ST(2);
5119              
5120 15070 100         if (SvNIOK(val_sv) || looks_like_number(val_sv)) {
    100          
5121 15066           NV val = SvNV(val_sv);
5122 15066           NV min = SvNV(min_sv);
5123 15066           NV max = SvNV(max_sv);
5124 15066 100         ST(0) = (val >= min && val <= max) ? &PL_sv_yes : &PL_sv_no;
    100          
5125             } else {
5126 4           ST(0) = &PL_sv_no;
5127             }
5128 15070           XSRETURN(1);
5129             }
5130              
5131             /* ============================================
5132             Collection XS fallbacks
5133             ============================================ */
5134              
5135 4008           XS_INTERNAL(xs_is_empty_array) {
5136 4008           dXSARGS;
5137 4008 50         if (items != 1) croak("Usage: Func::Util::is_empty_array($arrayref)");
5138 4008           SV *sv = ST(0);
5139 4008 100         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
    100          
5140 4004           AV *av = (AV*)SvRV(sv);
5141 4004 50         ST(0) = AvFILL(av) < 0 ? &PL_sv_yes : &PL_sv_no;
    100          
5142             } else {
5143 4           ST(0) = &PL_sv_no;
5144             }
5145 4008           XSRETURN(1);
5146             }
5147              
5148 5008           XS_INTERNAL(xs_is_empty_hash) {
5149 5008           dXSARGS;
5150 5008 50         if (items != 1) croak("Usage: Func::Util::is_empty_hash($hashref)");
5151 5008           SV *sv = ST(0);
5152 5008 100         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV) {
    100          
5153 5004           HV *hv = (HV*)SvRV(sv);
5154 5004 50         ST(0) = HvKEYS(hv) == 0 ? &PL_sv_yes : &PL_sv_no;
    100          
5155             } else {
5156 4           ST(0) = &PL_sv_no;
5157             }
5158 5008           XSRETURN(1);
5159             }
5160              
5161 14126           XS_INTERNAL(xs_array_len) {
5162 14126           dXSARGS;
5163 14126 50         if (items != 1) croak("Usage: Func::Util::array_len($arrayref)");
5164 14126           SV *sv = ST(0);
5165 14126 100         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
    100          
5166 13123           AV *av = (AV*)SvRV(sv);
5167 13123 50         ST(0) = sv_2mortal(newSViv(AvFILL(av) + 1));
5168             } else {
5169 1003           ST(0) = &PL_sv_undef;
5170             }
5171 14126           XSRETURN(1);
5172             }
5173              
5174 4018           XS_INTERNAL(xs_hash_size) {
5175 4018           dXSARGS;
5176 4018 50         if (items != 1) croak("Usage: Func::Util::hash_size($hashref)");
5177 4018           SV *sv = ST(0);
5178 4018 100         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV) {
    100          
5179 3015           HV *hv = (HV*)SvRV(sv);
5180 3015 50         ST(0) = sv_2mortal(newSViv(HvKEYS(hv)));
5181             } else {
5182 1003           ST(0) = &PL_sv_undef;
5183             }
5184 4018           XSRETURN(1);
5185             }
5186              
5187 13114           XS_INTERNAL(xs_array_first) {
5188 13114           dXSARGS;
5189 13114 50         if (items != 1) croak("Usage: Func::Util::array_first($arrayref)");
5190 13114           SV *sv = ST(0);
5191 26225 100         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
    100          
5192 13111           AV *av = (AV*)SvRV(sv);
5193 13111 50         if (AvFILL(av) >= 0) {
    100          
5194 12110           SV **elem = av_fetch(av, 0, 0);
5195 12110 50         ST(0) = elem ? *elem : &PL_sv_undef;
5196             } else {
5197 1001           ST(0) = &PL_sv_undef;
5198             }
5199             } else {
5200 3           ST(0) = &PL_sv_undef;
5201             }
5202 13114           XSRETURN(1);
5203             }
5204              
5205 13113           XS_INTERNAL(xs_array_last) {
5206 13113           dXSARGS;
5207 13113 50         if (items != 1) croak("Usage: Func::Util::array_last($arrayref)");
5208 13113           SV *sv = ST(0);
5209 26223 100         if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
    100          
5210 13110           AV *av = (AV*)SvRV(sv);
5211 13110 50         IV last_idx = AvFILL(av);
5212 13110 100         if (last_idx >= 0) {
5213 12109           SV **elem = av_fetch(av, last_idx, 0);
5214 12109 50         ST(0) = elem ? *elem : &PL_sv_undef;
5215             } else {
5216 1001           ST(0) = &PL_sv_undef;
5217             }
5218             } else {
5219 3           ST(0) = &PL_sv_undef;
5220             }
5221 13113           XSRETURN(1);
5222             }
5223              
5224             /* ============================================
5225             String manipulation XS fallbacks
5226             ============================================ */
5227              
5228 20141           XS_INTERNAL(xs_trim) {
5229 20141           dXSARGS;
5230 20141 50         if (items != 1) croak("Usage: Func::Util::trim($string)");
5231              
5232 20141           SV *sv = ST(0);
5233 20141 100         if (!SvOK(sv)) {
5234 1001           ST(0) = &PL_sv_undef;
5235 1001           XSRETURN(1);
5236             }
5237              
5238             STRLEN len;
5239 19140           const char *str = SvPV(sv, len);
5240 19140           const char *start = str;
5241 19140           const char *end = str + len;
5242              
5243             /* Skip leading whitespace */
5244 50410 100         while (start < end && isSPACE(*start)) {
    100          
5245 31270           start++;
5246             }
5247              
5248             /* Skip trailing whitespace */
5249 47401 100         while (end > start && isSPACE(*(end - 1))) {
    100          
5250 28261           end--;
5251             }
5252              
5253 19140           ST(0) = sv_2mortal(newSVpvn(start, end - start));
5254 19140           XSRETURN(1);
5255             }
5256              
5257 4013           XS_INTERNAL(xs_ltrim) {
5258 4013           dXSARGS;
5259 4013 50         if (items != 1) croak("Usage: Func::Util::ltrim($string)");
5260              
5261 4013           SV *sv = ST(0);
5262 4013 100         if (!SvOK(sv)) {
5263 1           ST(0) = &PL_sv_undef;
5264 1           XSRETURN(1);
5265             }
5266              
5267             STRLEN len;
5268 4012           const char *str = SvPV(sv, len);
5269 4012           const char *start = str;
5270 4012           const char *end = str + len;
5271              
5272 8031 100         while (start < end && isSPACE(*start)) {
    100          
5273 4019           start++;
5274             }
5275              
5276 4012           ST(0) = sv_2mortal(newSVpvn(start, end - start));
5277 4012           XSRETURN(1);
5278             }
5279              
5280 4013           XS_INTERNAL(xs_rtrim) {
5281 4013           dXSARGS;
5282 4013 50         if (items != 1) croak("Usage: Func::Util::rtrim($string)");
5283              
5284 4013           SV *sv = ST(0);
5285 4013 100         if (!SvOK(sv)) {
5286 1           ST(0) = &PL_sv_undef;
5287 1           XSRETURN(1);
5288             }
5289              
5290             STRLEN len;
5291 4012           const char *str = SvPV(sv, len);
5292 4012           const char *end = str + len;
5293              
5294 8026 100         while (end > str && isSPACE(*(end - 1))) {
    100          
5295 4014           end--;
5296             }
5297              
5298 4012           ST(0) = sv_2mortal(newSVpvn(str, end - str));
5299 4012           XSRETURN(1);
5300             }
5301              
5302             /* ============================================
5303             Conditional XS fallbacks
5304             ============================================ */
5305              
5306 10028           XS_INTERNAL(xs_maybe) {
5307 10028           dXSARGS;
5308 10028 50         if (items != 2) croak("Usage: Func::Util::maybe($value, $then)");
5309              
5310 10028           SV *val = ST(0);
5311 10028 100         if (SvOK(val)) {
5312 8023           ST(0) = ST(1);
5313             } else {
5314 2005           ST(0) = &PL_sv_undef;
5315             }
5316 10028           XSRETURN(1);
5317             }
5318              
5319             /* ============================================
5320             Numeric XS fallbacks
5321             ============================================ */
5322              
5323 21131           XS_INTERNAL(xs_sign) {
5324 21131           dXSARGS;
5325 21131 50         if (items != 1) croak("Usage: Func::Util::sign($number)");
5326              
5327 21131           SV *sv = ST(0);
5328 21131 100         if (!SvNIOK(sv) && !looks_like_number(sv)) {
    100          
5329 2           ST(0) = &PL_sv_undef;
5330 2           XSRETURN(1);
5331             }
5332              
5333 21129           NV nv = SvNV(sv);
5334 21129 100         if (nv > 0) {
5335 4011           ST(0) = sv_2mortal(newSViv(1));
5336 17118 100         } else if (nv < 0) {
5337 14113           ST(0) = sv_2mortal(newSViv(-1));
5338             } else {
5339 3005           ST(0) = sv_2mortal(newSViv(0));
5340             }
5341 21129           XSRETURN(1);
5342             }
5343              
5344 15116           XS_INTERNAL(xs_min2) {
5345 15116           dXSARGS;
5346 15116 50         if (items != 2) croak("Usage: Func::Util::min2($a, $b)");
5347              
5348 15116           NV a = SvNV(ST(0));
5349 15116           NV b = SvNV(ST(1));
5350              
5351 15116 100         ST(0) = a <= b ? ST(0) : ST(1);
5352 15116           XSRETURN(1);
5353             }
5354              
5355 15116           XS_INTERNAL(xs_max2) {
5356 15116           dXSARGS;
5357 15116 50         if (items != 2) croak("Usage: Func::Util::max2($a, $b)");
5358              
5359 15116           NV a = SvNV(ST(0));
5360 15116           NV b = SvNV(ST(1));
5361              
5362 15116 100         ST(0) = a >= b ? ST(0) : ST(1);
5363 15116           XSRETURN(1);
5364             }
5365              
5366             /* ============================================
5367             Named callback loop functions
5368             These accept a callback name instead of coderef
5369             ============================================ */
5370              
5371             /* any_cb(\@list, ':predicate') - true if any element matches */
5372 11130           XS_INTERNAL(xs_any_cb) {
5373 11130           dXSARGS;
5374 11130 50         if (items != 2) croak("Usage: Func::Util::any_cb(\\@list, $callback_name)");
5375              
5376 11130           SV *list_sv = ST(0);
5377 11130 100         if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
    100          
5378 3           croak("Func::Util::any_cb: first argument must be an arrayref");
5379             }
5380 11127           AV *list = (AV*)SvRV(list_sv);
5381              
5382             STRLEN name_len;
5383 11127           const char *name = SvPV(ST(1), name_len);
5384              
5385 11127           RegisteredCallback *cb = get_registered_callback(aTHX_ name);
5386 11127 100         if (!cb) {
5387 2           croak("Func::Util::any_cb: unknown callback '%s'", name);
5388             }
5389 11125 100         if (!cb->predicate && !cb->perl_callback) {
    50          
5390 0           croak("Func::Util::any_cb: callback '%s' is not a predicate", name);
5391             }
5392              
5393 11125           IV len = av_len(list) + 1;
5394             IV i;
5395              
5396 11125 100         if (cb->predicate) {
5397             /* Fast C path */
5398 35174 100         for (i = 0; i < len; i++) {
5399 35172           SV **svp = av_fetch(list, i, 0);
5400 35172 50         if (svp && cb->predicate(aTHX_ *svp)) {
    100          
5401 10123           XSRETURN_YES;
5402             }
5403             }
5404 1000 50         } else if (cb->perl_callback) {
5405             /* Perl callback fallback - use isolated stack scope */
5406 7000 50         for (i = 0; i < len; i++) {
5407 7000           SV **svp = av_fetch(list, i, 0);
5408 7000 50         if (!svp) continue;
5409              
5410 7000           bool matches = FALSE;
5411             {
5412 7000           dSP;
5413             int count;
5414             SV *result;
5415              
5416 7000           ENTER;
5417 7000           SAVETMPS;
5418              
5419 7000 50         PUSHMARK(SP);
5420 7000 50         XPUSHs(*svp);
5421 7000           PUTBACK;
5422              
5423 7000           count = call_sv(cb->perl_callback, G_SCALAR);
5424              
5425 7000           SPAGAIN;
5426 7000 50         if (count > 0) {
5427 7000           result = POPs;
5428 7000           matches = SvTRUE(result);
5429             }
5430 7000           PUTBACK;
5431              
5432 7000 50         FREETMPS;
5433 7000           LEAVE;
5434             }
5435              
5436 7000 100         if (matches) {
5437 1000           XSRETURN_YES;
5438             }
5439             }
5440             }
5441              
5442 2           XSRETURN_NO;
5443             }
5444              
5445             /* all_cb(\@list, ':predicate') - true if all elements match */
5446 3128           XS_INTERNAL(xs_all_cb) {
5447 3128           dXSARGS;
5448 3128 50         if (items != 2) croak("Usage: Func::Util::all_cb(\\@list, $callback_name)");
5449              
5450 3128           SV *list_sv = ST(0);
5451 3128 50         if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
    50          
5452 0           croak("Func::Util::all_cb: first argument must be an arrayref");
5453             }
5454 3128           AV *list = (AV*)SvRV(list_sv);
5455              
5456             STRLEN name_len;
5457 3128           const char *name = SvPV(ST(1), name_len);
5458              
5459 3128           RegisteredCallback *cb = get_registered_callback(aTHX_ name);
5460 3128 100         if (!cb) {
5461 1           croak("Func::Util::all_cb: unknown callback '%s'", name);
5462             }
5463 3127 50         if (!cb->predicate && !cb->perl_callback) {
    0          
5464 0           croak("Func::Util::all_cb: callback '%s' is not a predicate", name);
5465             }
5466              
5467 3127           IV len = av_len(list) + 1;
5468             IV i;
5469              
5470             /* Empty list returns true (vacuous truth) */
5471 3127 100         if (len == 0) {
5472 5           XSRETURN_YES;
5473             }
5474              
5475 3122 50         if (cb->predicate) {
5476 1021228 100         for (i = 0; i < len; i++) {
5477 1019114           SV **svp = av_fetch(list, i, 0);
5478 1019114 50         if (!svp || !cb->predicate(aTHX_ *svp)) {
    100          
5479 1008           XSRETURN_NO;
5480             }
5481             }
5482 0 0         } else if (cb->perl_callback) {
5483 0 0         for (i = 0; i < len; i++) {
5484 0           SV **svp = av_fetch(list, i, 0);
5485 0 0         if (!svp) { XSRETURN_NO; }
5486 0           bool matches = FALSE;
5487             {
5488 0           dSP;
5489             int count;
5490             SV *result;
5491 0           ENTER; SAVETMPS;
5492 0 0         PUSHMARK(SP);
5493 0 0         XPUSHs(*svp);
5494 0           PUTBACK;
5495 0           count = call_sv(cb->perl_callback, G_SCALAR);
5496 0           SPAGAIN;
5497 0 0         if (count > 0) {
5498 0           result = POPs;
5499 0           matches = SvTRUE(result);
5500             }
5501 0           PUTBACK;
5502 0 0         FREETMPS; LEAVE;
5503             }
5504 0 0         if (!matches) {
5505 0           XSRETURN_NO;
5506             }
5507             }
5508             }
5509              
5510 2114           XSRETURN_YES;
5511             }
5512              
5513             /* none_cb(\@list, ':predicate') - true if no elements match */
5514 2012           XS_INTERNAL(xs_none_cb) {
5515 2012           dXSARGS;
5516 2012 50         if (items != 2) croak("Usage: Func::Util::none_cb(\\@list, $callback_name)");
5517              
5518 2012           SV *list_sv = ST(0);
5519 2012 50         if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
    50          
5520 0           croak("Func::Util::none_cb: first argument must be an arrayref");
5521             }
5522 2012           AV *list = (AV*)SvRV(list_sv);
5523              
5524             STRLEN name_len;
5525 2012           const char *name = SvPV(ST(1), name_len);
5526              
5527 2012           RegisteredCallback *cb = get_registered_callback(aTHX_ name);
5528 2012 100         if (!cb) {
5529 1           croak("Func::Util::none_cb: unknown callback '%s'", name);
5530             }
5531 2011 50         if (!cb->predicate && !cb->perl_callback) {
    0          
5532 0           croak("Func::Util::none_cb: callback '%s' is not a predicate", name);
5533             }
5534              
5535 2011           IV len = av_len(list) + 1;
5536             IV i;
5537              
5538 2011 50         if (cb->predicate) {
5539 14027 100         for (i = 0; i < len; i++) {
5540 13021           SV **svp = av_fetch(list, i, 0);
5541 13021 50         if (svp && cb->predicate(aTHX_ *svp)) {
    100          
5542 1005           XSRETURN_NO;
5543             }
5544             }
5545 0 0         } else if (cb->perl_callback) {
5546 0 0         for (i = 0; i < len; i++) {
5547 0           SV **svp = av_fetch(list, i, 0);
5548 0 0         if (!svp) continue;
5549 0           bool matches = FALSE;
5550             {
5551 0           dSP;
5552             int count;
5553             SV *result;
5554 0           ENTER; SAVETMPS;
5555 0 0         PUSHMARK(SP);
5556 0 0         XPUSHs(*svp);
5557 0           PUTBACK;
5558 0           count = call_sv(cb->perl_callback, G_SCALAR);
5559 0           SPAGAIN;
5560 0 0         if (count > 0) {
5561 0           result = POPs;
5562 0           matches = SvTRUE(result);
5563             }
5564 0           PUTBACK;
5565 0 0         FREETMPS; LEAVE;
5566             }
5567 0 0         if (matches) {
5568 0           XSRETURN_NO;
5569             }
5570             }
5571             }
5572              
5573 1006           XSRETURN_YES;
5574             }
5575              
5576             /* first_cb(\@list, ':predicate') - first matching element */
5577 5043           XS_INTERNAL(xs_first_cb) {
5578 5043           dXSARGS;
5579 5043 50         if (items != 2) croak("Usage: Func::Util::first_cb(\\@list, $callback_name)");
5580              
5581 5043           SV *list_sv = ST(0);
5582 5043 50         if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
    50          
5583 0           croak("Func::Util::first_cb: first argument must be an arrayref");
5584             }
5585 5043           AV *list = (AV*)SvRV(list_sv);
5586              
5587             STRLEN name_len;
5588 5043           const char *name = SvPV(ST(1), name_len);
5589              
5590 5043           RegisteredCallback *cb = get_registered_callback(aTHX_ name);
5591 5043 100         if (!cb) {
5592 1           croak("Func::Util::first_cb: unknown callback '%s'", name);
5593             }
5594 5042 100         if (!cb->predicate && !cb->perl_callback) {
    50          
5595 0           croak("Func::Util::first_cb: callback '%s' is not a predicate", name);
5596             }
5597              
5598 5042           IV len = av_len(list) + 1;
5599             IV i;
5600              
5601 5042 100         if (cb->predicate) {
5602 10082 100         for (i = 0; i < len; i++) {
5603 10080           SV **svp = av_fetch(list, i, 0);
5604 10080 50         if (svp && cb->predicate(aTHX_ *svp)) {
    100          
5605 4030           ST(0) = *svp;
5606 4030           XSRETURN(1);
5607             }
5608             }
5609 1010 50         } else if (cb->perl_callback) {
5610 7014 100         for (i = 0; i < len; i++) {
5611 7012           SV **svp = av_fetch(list, i, 0);
5612 7012 50         if (!svp) continue;
5613 7012           bool matches = FALSE;
5614             {
5615 7012           dSP;
5616             int count;
5617             SV *result;
5618 7012           ENTER; SAVETMPS;
5619 7012 50         PUSHMARK(SP);
5620 7012 50         XPUSHs(*svp);
5621 7012           PUTBACK;
5622 7012           count = call_sv(cb->perl_callback, G_SCALAR);
5623 7012           SPAGAIN;
5624 7012 50         if (count > 0) {
5625 7012           result = POPs;
5626 7012           matches = SvTRUE(result);
5627             }
5628 7012           PUTBACK;
5629 7012 50         FREETMPS; LEAVE;
5630             }
5631 7012 100         if (matches) {
5632 1008           ST(0) = *svp;
5633 1008           XSRETURN(1);
5634             }
5635             }
5636             }
5637              
5638 4           XSRETURN_UNDEF;
5639             }
5640              
5641             /* grep_cb(\@list, ':predicate') - all matching elements */
5642 3037           XS_INTERNAL(xs_grep_cb) {
5643 3037           dXSARGS;
5644 3037 50         if (items != 2) croak("Usage: Func::Util::grep_cb(\\@list, $callback_name)");
5645              
5646 3037           SV *list_sv = ST(0);
5647 3037 50         if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
    50          
5648 0           croak("Func::Util::grep_cb: first argument must be an arrayref");
5649             }
5650 3037           AV *list = (AV*)SvRV(list_sv);
5651              
5652             STRLEN name_len;
5653 3037           const char *name = SvPV(ST(1), name_len);
5654              
5655 3037           RegisteredCallback *cb = get_registered_callback(aTHX_ name);
5656 3037 100         if (!cb) {
5657 1           croak("Func::Util::grep_cb: unknown callback '%s'", name);
5658             }
5659 3036 100         if (!cb->predicate && !cb->perl_callback) {
    50          
5660 0           croak("Func::Util::grep_cb: callback '%s' is not a predicate", name);
5661             }
5662              
5663 3036           IV len = av_len(list) + 1;
5664             IV i;
5665 3036           IV count = 0;
5666              
5667             /* Collect matching elements in a temporary array first */
5668 3036           AV *results = newAV();
5669 3036           sv_2mortal((SV*)results);
5670              
5671 3036 100         if (cb->predicate) {
5672 30175 100         for (i = 0; i < len; i++) {
5673 27151           SV **svp = av_fetch(list, i, 0);
5674 27151 50         if (svp && cb->predicate(aTHX_ *svp)) {
    100          
5675 13075           av_push(results, SvREFCNT_inc(*svp));
5676 13075           count++;
5677             }
5678             }
5679 12 50         } else if (cb->perl_callback) {
5680 88 100         for (i = 0; i < len; i++) {
5681 76           SV **svp = av_fetch(list, i, 0);
5682 76 50         if (!svp) continue;
5683 76           SV *elem = *svp;
5684 76           bool matches = FALSE;
5685             {
5686 76           dSP;
5687             int call_count;
5688             SV *result;
5689 76           ENTER; SAVETMPS;
5690 76 50         PUSHMARK(SP);
5691 76 50         XPUSHs(elem);
5692 76           PUTBACK;
5693 76           call_count = call_sv(cb->perl_callback, G_SCALAR);
5694 76           SPAGAIN;
5695 76 50         if (call_count > 0) {
5696 76           result = POPs;
5697 76           matches = SvTRUE(result);
5698             }
5699 76           PUTBACK;
5700 76 50         FREETMPS; LEAVE;
5701             }
5702 76 100         if (matches) {
5703 39           av_push(results, SvREFCNT_inc(elem));
5704 39           count++;
5705             }
5706             }
5707             }
5708              
5709             /* Now push all results to the stack */
5710 3036           SP -= items;
5711 16150 100         for (i = 0; i < count; i++) {
5712 13114           SV **svp = av_fetch(results, i, 0);
5713 13114 50         if (svp) {
5714 13114 50         XPUSHs(sv_2mortal(SvREFCNT_inc(*svp)));
5715             }
5716             }
5717              
5718 3036           PUTBACK;
5719 3036           XSRETURN(count);
5720             }
5721              
5722             /* count_cb(\@list, ':predicate') - count matching elements */
5723 6165           XS_INTERNAL(xs_count_cb) {
5724 6165           dXSARGS;
5725 6165 50         if (items != 2) croak("Usage: Func::Util::count_cb(\\@list, $callback_name)");
5726              
5727 6165           SV *list_sv = ST(0);
5728 6165 50         if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
    50          
5729 0           croak("Func::Util::count_cb: first argument must be an arrayref");
5730             }
5731 6165           AV *list = (AV*)SvRV(list_sv);
5732              
5733             STRLEN name_len;
5734 6165           const char *name = SvPV(ST(1), name_len);
5735              
5736 6165           RegisteredCallback *cb = get_registered_callback(aTHX_ name);
5737 6165 100         if (!cb) {
5738 1           croak("Func::Util::count_cb: unknown callback '%s'", name);
5739             }
5740 6164 100         if (!cb->predicate && !cb->perl_callback) {
    50          
5741 0           croak("Func::Util::count_cb: callback '%s' is not a predicate", name);
5742             }
5743              
5744 6164           IV len = av_len(list) + 1;
5745             IV i;
5746 6164           IV count = 0;
5747              
5748 6164 100         if (cb->predicate) {
5749 1046549 100         for (i = 0; i < len; i++) {
5750 1041389           SV **svp = av_fetch(list, i, 0);
5751 1041389 50         if (svp && cb->predicate(aTHX_ *svp)) {
    100          
5752 1022177           count++;
5753             }
5754             }
5755 1004 50         } else if (cb->perl_callback) {
5756 10033 100         for (i = 0; i < len; i++) {
5757 9029           SV **svp = av_fetch(list, i, 0);
5758 9029 50         if (!svp) continue;
5759 9029           bool matches = FALSE;
5760             {
5761 9029           dSP;
5762             int call_count;
5763             SV *result;
5764 9029           ENTER; SAVETMPS;
5765 9029 50         PUSHMARK(SP);
5766 9029 50         XPUSHs(*svp);
5767 9029           PUTBACK;
5768 9029           call_count = call_sv(cb->perl_callback, G_SCALAR);
5769 9029           SPAGAIN;
5770 9029 50         if (call_count > 0) {
5771 9029           result = POPs;
5772 9029           matches = SvTRUE(result);
5773             }
5774 9029           PUTBACK;
5775 9029 50         FREETMPS; LEAVE;
5776             }
5777 9029 100         if (matches) {
5778 3010           count++;
5779             }
5780             }
5781             }
5782              
5783 6164           XSRETURN_IV(count);
5784             }
5785              
5786             /* partition_cb(\@list, ':predicate') - split into [matches], [non-matches] */
5787 2013           XS_INTERNAL(xs_partition_cb) {
5788 2013           dXSARGS;
5789 2013 50         if (items != 2) croak("Usage: Func::Util::partition_cb(\\@list, $callback_name)");
5790              
5791 2013           SV *list_sv = ST(0);
5792 2013 100         if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
    50          
5793 1           croak("Func::Util::partition_cb: first argument must be an arrayref");
5794             }
5795 2012           AV *list = (AV*)SvRV(list_sv);
5796              
5797             STRLEN name_len;
5798 2012           const char *name = SvPV(ST(1), name_len);
5799              
5800 2012           RegisteredCallback *cb = get_registered_callback(aTHX_ name);
5801 2012 100         if (!cb) {
5802 1           croak("Func::Util::partition_cb: unknown callback '%s'", name);
5803             }
5804 2011 100         if (!cb->predicate && !cb->perl_callback) {
    50          
5805 0           croak("Func::Util::partition_cb: callback '%s' is not a predicate", name);
5806             }
5807              
5808 2011           IV len = av_len(list) + 1;
5809 2011           AV *pass = newAV();
5810 2011           AV *fail = newAV();
5811 2011           av_extend(pass, len >> 1);
5812 2011           av_extend(fail, len >> 1);
5813              
5814             IV i;
5815 2011 100         if (cb->predicate) {
5816 20049 100         for (i = 0; i < len; i++) {
5817 18040           SV **svp = av_fetch(list, i, 0);
5818 18040 50         if (!svp) continue;
5819 18040 100         if (cb->predicate(aTHX_ *svp)) {
5820 11020           av_push(pass, SvREFCNT_inc_simple_NN(*svp));
5821             } else {
5822 7020           av_push(fail, SvREFCNT_inc_simple_NN(*svp));
5823             }
5824             }
5825 2 50         } else if (cb->perl_callback) {
5826 11 100         for (i = 0; i < len; i++) {
5827 9           SV **svp = av_fetch(list, i, 0);
5828 9 50         if (!svp) continue;
5829 9           bool matches = FALSE;
5830             {
5831 9           dSP;
5832             int call_count;
5833             SV *result;
5834              
5835 9           ENTER;
5836 9           SAVETMPS;
5837              
5838 9 50         PUSHMARK(SP);
5839 9 50         XPUSHs(*svp);
5840 9           PUTBACK;
5841              
5842 9           call_count = call_sv(cb->perl_callback, G_SCALAR);
5843              
5844 9           SPAGAIN;
5845 9 50         if (call_count > 0) {
5846 9           result = POPs;
5847 9           matches = SvTRUE(result);
5848             }
5849 9           PUTBACK;
5850              
5851 9 50         FREETMPS;
5852 9           LEAVE;
5853             }
5854 9 100         if (matches) {
5855 4           av_push(pass, SvREFCNT_inc_simple_NN(*svp));
5856             } else {
5857 5           av_push(fail, SvREFCNT_inc_simple_NN(*svp));
5858             }
5859             }
5860             }
5861              
5862             /* Return list of two arrayrefs */
5863 2011           ST(0) = sv_2mortal(newRV_noinc((SV*)pass));
5864 2011           ST(1) = sv_2mortal(newRV_noinc((SV*)fail));
5865 2011           XSRETURN(2);
5866             }
5867              
5868             /* final_cb(\@list, ':predicate') - find last matching element */
5869 3019           XS_INTERNAL(xs_final_cb) {
5870 3019           dXSARGS;
5871 3019 50         if (items != 2) croak("Usage: Func::Util::final_cb(\\@list, $callback_name)");
5872              
5873 3019           SV *list_sv = ST(0);
5874 3019 100         if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
    50          
5875 1           croak("Func::Util::final_cb: first argument must be an arrayref");
5876             }
5877 3018           AV *list = (AV*)SvRV(list_sv);
5878              
5879             STRLEN name_len;
5880 3018           const char *name = SvPV(ST(1), name_len);
5881              
5882 3018           RegisteredCallback *cb = get_registered_callback(aTHX_ name);
5883 3018 100         if (!cb) {
5884 1           croak("Func::Util::final_cb: unknown callback '%s'", name);
5885             }
5886 3017 100         if (!cb->predicate && !cb->perl_callback) {
    50          
5887 0           croak("Func::Util::final_cb: callback '%s' is not a predicate", name);
5888             }
5889              
5890 3017           IV len = av_len(list) + 1;
5891             IV i;
5892              
5893 3017 100         if (cb->predicate) {
5894             /* Search from end - C predicate path */
5895 10039 100         for (i = len - 1; i >= 0; i--) {
5896 10033           SV **svp = av_fetch(list, i, 0);
5897 10033 50         if (svp && cb->predicate(aTHX_ *svp)) {
    100          
5898 3010           ST(0) = *svp;
5899 3010           XSRETURN(1);
5900             }
5901             }
5902 1 50         } else if (cb->perl_callback) {
5903             /* Search from end - Perl callback path */
5904 1 50         for (i = len - 1; i >= 0; i--) {
5905 1           SV **svp = av_fetch(list, i, 0);
5906 1 50         if (!svp) continue;
5907 1           bool matches = FALSE;
5908             {
5909 1           dSP;
5910             int count;
5911             SV *result;
5912 1           ENTER; SAVETMPS;
5913 1 50         PUSHMARK(SP);
5914 1 50         XPUSHs(*svp);
5915 1           PUTBACK;
5916 1           count = call_sv(cb->perl_callback, G_SCALAR);
5917 1           SPAGAIN;
5918 1 50         if (count > 0) {
5919 1           result = POPs;
5920 1           matches = SvTRUE(result);
5921             }
5922 1           PUTBACK;
5923 1 50         FREETMPS; LEAVE;
5924             }
5925 1 50         if (matches) {
5926 1           ST(0) = *svp;
5927 1           XSRETURN(1);
5928             }
5929             }
5930             }
5931              
5932 6           XSRETURN_UNDEF;
5933             }
5934              
5935             /* Perl-level callback registration */
5936 20           XS_INTERNAL(xs_register_callback) {
5937 20           dXSARGS;
5938 20 50         if (items != 2) croak("Usage: Func::Util::register_callback($name, \\&coderef)");
5939              
5940             STRLEN name_len;
5941 20           const char *name = SvPV(ST(0), name_len);
5942              
5943 20           SV *coderef = ST(1);
5944 20 50         if (!SvROK(coderef) || SvTYPE(SvRV(coderef)) != SVt_PVCV) {
    50          
5945 0           croak("Func::Util::register_callback: second argument must be a coderef");
5946             }
5947              
5948             RegisteredCallback *cb;
5949             SV *sv;
5950              
5951 20           init_callback_registry(aTHX);
5952              
5953             /* Check if already registered */
5954 20 100         if (get_registered_callback(aTHX_ name)) {
5955 3           croak("Callback '%s' is already registered", name);
5956             }
5957              
5958 17           Newxz(cb, 1, RegisteredCallback);
5959 17           cb->name = savepv(name);
5960 17           cb->predicate = NULL;
5961 17           cb->mapper = NULL;
5962 17           cb->reducer = NULL;
5963             /* Store a copy of the coderef (RV to CV) */
5964 17           cb->perl_callback = newSVsv(coderef);
5965              
5966 17           sv = newSViv(PTR2IV(cb));
5967 17           hv_store(g_callback_registry, name, name_len, sv, 0);
5968              
5969 17           XSRETURN_YES;
5970             }
5971              
5972             /* Check if callback exists */
5973 6018           XS_INTERNAL(xs_has_callback) {
5974 6018           dXSARGS;
5975 6018 50         if (items != 1) croak("Usage: Func::Util::has_callback($name)");
5976              
5977             STRLEN name_len;
5978 6018           const char *name = SvPV(ST(0), name_len);
5979              
5980 6018 100         if (has_callback(aTHX_ name)) {
5981 4014           XSRETURN_YES;
5982             }
5983 2004           XSRETURN_NO;
5984             }
5985              
5986             /* List all callbacks */
5987 1006           XS_INTERNAL(xs_list_callbacks) {
5988 1006           dXSARGS;
5989             PERL_UNUSED_ARG(items);
5990              
5991 1006           AV *result = list_callbacks(aTHX);
5992 1006           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
5993 1006           XSRETURN(1);
5994             }
5995              
5996             /* ============================================
5997             Import function - O(1) hash-based lookup
5998             ============================================ */
5999              
6000             /* Export entry: supports XS functions, Perl coderefs, or both */
6001             typedef struct {
6002             XSUBADDR_t xs_func; /* XS function pointer (NULL for Perl-only) */
6003             Perl_call_checker call_checker; /* Optional call checker for XS */
6004             SV *perl_cv; /* Perl coderef (NULL for XS-only) */
6005             } ExportEntry;
6006              
6007             /* Global export hash - initialized at boot */
6008             static HV *g_export_hash = NULL;
6009              
6010             /* Register an XS export with optional call checker (internal) */
6011 6095           static void register_export(pTHX_ const char *name, XSUBADDR_t xs_func, Perl_call_checker checker) {
6012             ExportEntry *entry;
6013 6095           Newx(entry, 1, ExportEntry);
6014 6095           entry->xs_func = xs_func;
6015 6095           entry->call_checker = checker;
6016 6095           entry->perl_cv = NULL;
6017 6095           (void)hv_store(g_export_hash, name, strlen(name), newSViv(PTR2IV(entry)), 0);
6018 6095           }
6019              
6020             /* ============================================
6021             Public API: Register custom exports
6022             ============================================ */
6023              
6024             /* Register a Perl coderef as an export - called from Perl */
6025 15           XS_INTERNAL(xs_register_export) {
6026 15           dXSARGS;
6027 15 50         if (items != 2)
6028 0           croak("Usage: Func::Util::register_export($name, \\&coderef)");
6029              
6030             STRLEN name_len;
6031 15           char *name = SvPV(ST(0), name_len);
6032 15           SV *cv_sv = ST(1);
6033              
6034             /* Validate it's a coderef */
6035 15 100         if (!SvROK(cv_sv) || SvTYPE(SvRV(cv_sv)) != SVt_PVCV)
    100          
6036 3           croak("Func::Util::register_export: second argument must be a coderef");
6037              
6038             /* Check if name already exists */
6039 12 100         if (hv_exists(g_export_hash, name, name_len))
6040 1           croak("Func::Util::register_export: '%s' is already registered", name);
6041              
6042             /* Create entry for Perl coderef */
6043             ExportEntry *entry;
6044 11           Newx(entry, 1, ExportEntry);
6045 11           entry->xs_func = NULL;
6046 11           entry->call_checker = NULL;
6047 11           entry->perl_cv = SvREFCNT_inc(cv_sv); /* Keep a reference */
6048              
6049 11           (void)hv_store(g_export_hash, name, name_len, newSViv(PTR2IV(entry)), 0);
6050              
6051 11           XSRETURN_YES;
6052             }
6053              
6054             /* Check if an export name is registered */
6055 17           XS_INTERNAL(xs_has_export) {
6056 17           dXSARGS;
6057 17 50         if (items != 1)
6058 0           croak("Usage: Func::Util::has_export($name)");
6059              
6060             STRLEN name_len;
6061 17           char *name = SvPV(ST(0), name_len);
6062              
6063 17 100         if (hv_exists(g_export_hash, name, name_len)) {
6064 14           XSRETURN_YES;
6065             } else {
6066 3           XSRETURN_NO;
6067             }
6068             }
6069              
6070             /* List all registered export names */
6071 3           XS_INTERNAL(xs_list_exports) {
6072 3           dXSARGS;
6073             PERL_UNUSED_ARG(items);
6074              
6075 3           AV *result = newAV();
6076             HE *entry;
6077              
6078 3           hv_iterinit(g_export_hash);
6079 357 100         while ((entry = hv_iternext(g_export_hash))) {
6080 354           SV *key = hv_iterkeysv(entry);
6081 354           av_push(result, SvREFCNT_inc(key));
6082             }
6083              
6084 3           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
6085 3           XSRETURN(1);
6086             }
6087              
6088             /* ============================================
6089             C API for XS modules to register exports
6090             ============================================ */
6091              
6092             /*
6093             * Register an XS function as a util export.
6094             * Call this from your BOOT section:
6095             * funcutil_register_export_xs(aTHX_ "my_func", xs_my_func);
6096             */
6097 7           void funcutil_register_export_xs(pTHX_ const char *name, XSUBADDR_t xs_func) {
6098 7 50         if (!g_export_hash) {
6099 0           croak("funcutil_register_export_xs: Func::Util module not yet loaded");
6100             }
6101              
6102 7           STRLEN name_len = strlen(name);
6103 7 50         if (hv_exists(g_export_hash, name, name_len)) {
6104 0           croak("funcutil_register_export_xs: '%s' is already registered", name);
6105             }
6106              
6107             ExportEntry *entry;
6108 7           Newx(entry, 1, ExportEntry);
6109 7           entry->xs_func = xs_func;
6110 7           entry->call_checker = NULL;
6111 7           entry->perl_cv = NULL;
6112              
6113 7           (void)hv_store(g_export_hash, name, name_len, newSViv(PTR2IV(entry)), 0);
6114 7           }
6115              
6116             /* Initialize export hash at boot - called once */
6117 53           static void init_export_hash(pTHX) {
6118 53           g_export_hash = newHV();
6119              
6120             /* Functional */
6121 53           register_export(aTHX_ "memo", xs_memo, NULL);
6122 53           register_export(aTHX_ "pipeline", xs_pipe, NULL);
6123 53           register_export(aTHX_ "compose", xs_compose, NULL);
6124 53           register_export(aTHX_ "lazy", xs_lazy, NULL);
6125 53           register_export(aTHX_ "force", xs_force, NULL);
6126 53           register_export(aTHX_ "dig", xs_dig, NULL);
6127 53           register_export(aTHX_ "clamp", xs_clamp, clamp_call_checker);
6128 53           register_export(aTHX_ "tap", xs_tap, NULL);
6129 53           register_export(aTHX_ "identity", xs_identity, identity_call_checker);
6130 53           register_export(aTHX_ "always", xs_always, NULL);
6131 53           register_export(aTHX_ "noop", xs_noop, noop_call_checker);
6132 53           register_export(aTHX_ "partial", xs_partial, NULL);
6133 53           register_export(aTHX_ "negate", xs_negate, NULL);
6134 53           register_export(aTHX_ "once", xs_once, NULL);
6135              
6136             /* Stubs */
6137 53           register_export(aTHX_ "stub_true", xs_stub_true, NULL);
6138 53           register_export(aTHX_ "stub_false", xs_stub_false, NULL);
6139 53           register_export(aTHX_ "stub_array", xs_stub_array, NULL);
6140 53           register_export(aTHX_ "stub_hash", xs_stub_hash, NULL);
6141 53           register_export(aTHX_ "stub_string", xs_stub_string, NULL);
6142 53           register_export(aTHX_ "stub_zero", xs_stub_zero, NULL);
6143              
6144             /* Null coalescing */
6145 53           register_export(aTHX_ "nvl", xs_nvl, NULL);
6146 53           register_export(aTHX_ "coalesce", xs_coalesce, NULL);
6147              
6148             /* List operations */
6149 53           register_export(aTHX_ "first", xs_first, NULL);
6150 53           register_export(aTHX_ "firstr", xs_firstr, NULL);
6151 53           register_export(aTHX_ "any", xs_any, NULL);
6152 53           register_export(aTHX_ "all", xs_all, NULL);
6153 53           register_export(aTHX_ "none", xs_none, NULL);
6154 53           register_export(aTHX_ "final", xs_final, NULL);
6155             #ifdef dMULTICALL
6156 53           register_export(aTHX_ "first_inline", xs_first_inline, NULL);
6157             #endif
6158              
6159             /* Callback-based loop functions */
6160 53           register_export(aTHX_ "any_cb", xs_any_cb, NULL);
6161 53           register_export(aTHX_ "all_cb", xs_all_cb, NULL);
6162 53           register_export(aTHX_ "none_cb", xs_none_cb, NULL);
6163 53           register_export(aTHX_ "first_cb", xs_first_cb, NULL);
6164 53           register_export(aTHX_ "grep_cb", xs_grep_cb, NULL);
6165 53           register_export(aTHX_ "count_cb", xs_count_cb, NULL);
6166 53           register_export(aTHX_ "partition_cb", xs_partition_cb, NULL);
6167 53           register_export(aTHX_ "final_cb", xs_final_cb, NULL);
6168 53           register_export(aTHX_ "register_callback", xs_register_callback, NULL);
6169 53           register_export(aTHX_ "has_callback", xs_has_callback, NULL);
6170 53           register_export(aTHX_ "list_callbacks", xs_list_callbacks, NULL);
6171              
6172             /* Specialized predicates - first_* */
6173 53           register_export(aTHX_ "first_gt", xs_first_gt, NULL);
6174 53           register_export(aTHX_ "first_lt", xs_first_lt, NULL);
6175 53           register_export(aTHX_ "first_ge", xs_first_ge, NULL);
6176 53           register_export(aTHX_ "first_le", xs_first_le, NULL);
6177 53           register_export(aTHX_ "first_eq", xs_first_eq, NULL);
6178 53           register_export(aTHX_ "first_ne", xs_first_ne, NULL);
6179              
6180             /* Specialized predicates - final_* */
6181 53           register_export(aTHX_ "final_gt", xs_final_gt, NULL);
6182 53           register_export(aTHX_ "final_lt", xs_final_lt, NULL);
6183 53           register_export(aTHX_ "final_ge", xs_final_ge, NULL);
6184 53           register_export(aTHX_ "final_le", xs_final_le, NULL);
6185 53           register_export(aTHX_ "final_eq", xs_final_eq, NULL);
6186 53           register_export(aTHX_ "final_ne", xs_final_ne, NULL);
6187              
6188             /* Specialized predicates - any_* */
6189 53           register_export(aTHX_ "any_gt", xs_any_gt, NULL);
6190 53           register_export(aTHX_ "any_lt", xs_any_lt, NULL);
6191 53           register_export(aTHX_ "any_ge", xs_any_ge, NULL);
6192 53           register_export(aTHX_ "any_le", xs_any_le, NULL);
6193 53           register_export(aTHX_ "any_eq", xs_any_eq, NULL);
6194 53           register_export(aTHX_ "any_ne", xs_any_ne, NULL);
6195              
6196             /* Specialized predicates - all_* */
6197 53           register_export(aTHX_ "all_gt", xs_all_gt, NULL);
6198 53           register_export(aTHX_ "all_lt", xs_all_lt, NULL);
6199 53           register_export(aTHX_ "all_ge", xs_all_ge, NULL);
6200 53           register_export(aTHX_ "all_le", xs_all_le, NULL);
6201 53           register_export(aTHX_ "all_eq", xs_all_eq, NULL);
6202 53           register_export(aTHX_ "all_ne", xs_all_ne, NULL);
6203              
6204             /* Specialized predicates - none_* */
6205 53           register_export(aTHX_ "none_gt", xs_none_gt, NULL);
6206 53           register_export(aTHX_ "none_lt", xs_none_lt, NULL);
6207 53           register_export(aTHX_ "none_ge", xs_none_ge, NULL);
6208 53           register_export(aTHX_ "none_le", xs_none_le, NULL);
6209 53           register_export(aTHX_ "none_eq", xs_none_eq, NULL);
6210 53           register_export(aTHX_ "none_ne", xs_none_ne, NULL);
6211              
6212             /* Collection functions */
6213 53           register_export(aTHX_ "pick", xs_pick, NULL);
6214 53           register_export(aTHX_ "pluck", xs_pluck, NULL);
6215 53           register_export(aTHX_ "omit", xs_omit, NULL);
6216 53           register_export(aTHX_ "uniq", xs_uniq, NULL);
6217 53           register_export(aTHX_ "partition", xs_partition, NULL);
6218 53           register_export(aTHX_ "defaults", xs_defaults, NULL);
6219 53           register_export(aTHX_ "count", xs_count, NULL);
6220 53           register_export(aTHX_ "replace_all", xs_replace_all, NULL);
6221              
6222             /* Type predicates */
6223 53           register_export(aTHX_ "is_ref", xs_is_ref, is_ref_call_checker);
6224 53           register_export(aTHX_ "is_array", xs_is_array, is_array_call_checker);
6225 53           register_export(aTHX_ "is_hash", xs_is_hash, is_hash_call_checker);
6226 53           register_export(aTHX_ "is_code", xs_is_code, is_code_call_checker);
6227 53           register_export(aTHX_ "is_defined", xs_is_defined, is_defined_call_checker);
6228 53           register_export(aTHX_ "is_string", xs_is_string, is_string_call_checker);
6229              
6230             /* String predicates */
6231 53           register_export(aTHX_ "is_empty", xs_is_empty, is_empty_call_checker);
6232 53           register_export(aTHX_ "starts_with", xs_starts_with, starts_with_call_checker);
6233 53           register_export(aTHX_ "ends_with", xs_ends_with, ends_with_call_checker);
6234 53           register_export(aTHX_ "trim", xs_trim, trim_call_checker);
6235 53           register_export(aTHX_ "ltrim", xs_ltrim, ltrim_call_checker);
6236 53           register_export(aTHX_ "rtrim", xs_rtrim, rtrim_call_checker);
6237              
6238             /* Boolean predicates */
6239 53           register_export(aTHX_ "is_true", xs_is_true, is_true_call_checker);
6240 53           register_export(aTHX_ "is_false", xs_is_false, is_false_call_checker);
6241 53           register_export(aTHX_ "bool", xs_bool, bool_call_checker);
6242              
6243             /* Extended type predicates */
6244 53           register_export(aTHX_ "is_num", xs_is_num, is_num_call_checker);
6245 53           register_export(aTHX_ "is_int", xs_is_int, is_int_call_checker);
6246 53           register_export(aTHX_ "is_blessed", xs_is_blessed, is_blessed_call_checker);
6247 53           register_export(aTHX_ "is_scalar_ref", xs_is_scalar_ref, is_scalar_ref_call_checker);
6248 53           register_export(aTHX_ "is_regex", xs_is_regex, is_regex_call_checker);
6249 53           register_export(aTHX_ "is_glob", xs_is_glob, is_glob_call_checker);
6250              
6251             /* Numeric predicates */
6252 53           register_export(aTHX_ "is_positive", xs_is_positive, is_positive_call_checker);
6253 53           register_export(aTHX_ "is_negative", xs_is_negative, is_negative_call_checker);
6254 53           register_export(aTHX_ "is_zero", xs_is_zero, is_zero_call_checker);
6255 53           register_export(aTHX_ "is_even", xs_is_even, is_even_call_checker);
6256 53           register_export(aTHX_ "is_odd", xs_is_odd, is_odd_call_checker);
6257 53           register_export(aTHX_ "is_between", xs_is_between, is_between_call_checker);
6258              
6259             /* Collection predicates */
6260 53           register_export(aTHX_ "is_empty_array", xs_is_empty_array, is_empty_array_call_checker);
6261 53           register_export(aTHX_ "is_empty_hash", xs_is_empty_hash, is_empty_hash_call_checker);
6262 53           register_export(aTHX_ "array_len", xs_array_len, array_len_call_checker);
6263 53           register_export(aTHX_ "hash_size", xs_hash_size, hash_size_call_checker);
6264 53           register_export(aTHX_ "array_first", xs_array_first, array_first_call_checker);
6265 53           register_export(aTHX_ "array_last", xs_array_last, array_last_call_checker);
6266              
6267             /* Conditional/numeric ops */
6268 53           register_export(aTHX_ "maybe", xs_maybe, maybe_call_checker);
6269 53           register_export(aTHX_ "sign", xs_sign, sign_call_checker);
6270 53           register_export(aTHX_ "min2", xs_min2, min2_call_checker);
6271 53           register_export(aTHX_ "max2", xs_max2, max2_call_checker);
6272 53           }
6273              
6274 85           static char* get_caller(pTHX) {
6275 85 50         return HvNAME((HV*)CopSTASH(PL_curcop));
    50          
    50          
    0          
    50          
    50          
6276             }
6277              
6278             /* Fast O(1) import using hash lookup */
6279 85           XS_INTERNAL(xs_import) {
6280 85           dXSARGS;
6281 85           char *pkg = get_caller(aTHX);
6282             IV i;
6283             STRLEN name_len;
6284             char full[512];
6285              
6286 539 100         for (i = 1; i < items; i++) {
6287 456           char *name = SvPV(ST(i), name_len);
6288 456           SV **entry_sv = hv_fetch(g_export_hash, name, name_len, 0);
6289              
6290 456 100         if (!entry_sv || !*entry_sv) {
    50          
6291 2           croak("util: unknown export '%s'", name);
6292             }
6293              
6294 454           ExportEntry *entry = INT2PTR(ExportEntry*, SvIV(*entry_sv));
6295 454           snprintf(full, sizeof(full), "%s::%s", pkg, name);
6296              
6297 454 100         if (entry->xs_func) {
6298             /* XS function: create XS stub in caller's namespace.
6299             * Note: We intentionally do NOT install call checkers on exported
6300             * functions. Call checkers are compile-time optimizations that work
6301             * by transforming the op tree. They work on util::* functions because
6302             * those are installed at boot time before any user code compiles.
6303             * Users who want compile-time optimization should call util::func()
6304             * directly instead of importing. */
6305 446           CV *cv = newXS(full, entry->xs_func, __FILE__);
6306             PERL_UNUSED_VAR(cv);
6307 8 50         } else if (entry->perl_cv) {
6308             /* Perl coderef: create alias in caller's namespace */
6309 8           GV *gv = gv_fetchpv(full, GV_ADD, SVt_PVCV);
6310 8 50         if (gv) {
6311             /* Get the actual CV from the reference */
6312 8           CV *src_cv = (CV*)SvRV(entry->perl_cv);
6313             /* Assign the CV to the glob's CODE slot */
6314 8           SvREFCNT_inc((SV*)src_cv);
6315 8           GvCV_set(gv, src_cv);
6316             }
6317             }
6318             }
6319              
6320 83           XSRETURN_EMPTY;
6321             }
6322              
6323             /* ============================================
6324             Boot
6325             ============================================ */
6326              
6327 53           XS_EXTERNAL(boot_Func__Util) {
6328 53           dXSBOOTARGSXSAPIVERCHK;
6329             PERL_UNUSED_VAR(items);
6330              
6331             /* Initialize built-in loop callbacks */
6332 53           init_builtin_callbacks(aTHX);
6333              
6334             /* Register custom ops */
6335 53           XopENTRY_set(&identity_xop, xop_name, "identity");
6336 53           XopENTRY_set(&identity_xop, xop_desc, "identity passthrough");
6337 53           Perl_custom_op_register(aTHX_ pp_identity, &identity_xop);
6338              
6339 53           XopENTRY_set(&always_xop, xop_name, "always");
6340 53           XopENTRY_set(&always_xop, xop_desc, "always return stored value");
6341 53           Perl_custom_op_register(aTHX_ pp_always, &always_xop);
6342              
6343 53           XopENTRY_set(&clamp_xop, xop_name, "clamp");
6344 53           XopENTRY_set(&clamp_xop, xop_desc, "clamp value between min and max");
6345 53           Perl_custom_op_register(aTHX_ pp_clamp, &clamp_xop);
6346              
6347             /* Register type predicate custom ops */
6348 53           XopENTRY_set(&is_ref_xop, xop_name, "is_ref");
6349 53           XopENTRY_set(&is_ref_xop, xop_desc, "check if value is a reference");
6350 53           Perl_custom_op_register(aTHX_ pp_is_ref, &is_ref_xop);
6351              
6352 53           XopENTRY_set(&is_array_xop, xop_name, "is_array");
6353 53           XopENTRY_set(&is_array_xop, xop_desc, "check if value is an arrayref");
6354 53           Perl_custom_op_register(aTHX_ pp_is_array, &is_array_xop);
6355              
6356 53           XopENTRY_set(&is_hash_xop, xop_name, "is_hash");
6357 53           XopENTRY_set(&is_hash_xop, xop_desc, "check if value is a hashref");
6358 53           Perl_custom_op_register(aTHX_ pp_is_hash, &is_hash_xop);
6359              
6360 53           XopENTRY_set(&is_code_xop, xop_name, "is_code");
6361 53           XopENTRY_set(&is_code_xop, xop_desc, "check if value is a coderef");
6362 53           Perl_custom_op_register(aTHX_ pp_is_code, &is_code_xop);
6363              
6364 53           XopENTRY_set(&is_defined_xop, xop_name, "is_defined");
6365 53           XopENTRY_set(&is_defined_xop, xop_desc, "check if value is defined");
6366 53           Perl_custom_op_register(aTHX_ pp_is_defined, &is_defined_xop);
6367              
6368             /* Register string predicate custom ops */
6369 53           XopENTRY_set(&is_empty_xop, xop_name, "is_empty");
6370 53           XopENTRY_set(&is_empty_xop, xop_desc, "check if string is empty");
6371 53           Perl_custom_op_register(aTHX_ pp_is_empty, &is_empty_xop);
6372              
6373 53           XopENTRY_set(&starts_with_xop, xop_name, "starts_with");
6374 53           XopENTRY_set(&starts_with_xop, xop_desc, "check if string starts with prefix");
6375 53           Perl_custom_op_register(aTHX_ pp_starts_with, &starts_with_xop);
6376              
6377 53           XopENTRY_set(&ends_with_xop, xop_name, "ends_with");
6378 53           XopENTRY_set(&ends_with_xop, xop_desc, "check if string ends with suffix");
6379 53           Perl_custom_op_register(aTHX_ pp_ends_with, &ends_with_xop);
6380              
6381             /* Register boolean/truthiness custom ops */
6382 53           XopENTRY_set(&is_true_xop, xop_name, "is_true");
6383 53           XopENTRY_set(&is_true_xop, xop_desc, "check if value is truthy");
6384 53           Perl_custom_op_register(aTHX_ pp_is_true, &is_true_xop);
6385              
6386 53           XopENTRY_set(&is_false_xop, xop_name, "is_false");
6387 53           XopENTRY_set(&is_false_xop, xop_desc, "check if value is falsy");
6388 53           Perl_custom_op_register(aTHX_ pp_is_false, &is_false_xop);
6389              
6390 53           XopENTRY_set(&bool_xop, xop_name, "bool");
6391 53           XopENTRY_set(&bool_xop, xop_desc, "normalize to boolean");
6392 53           Perl_custom_op_register(aTHX_ pp_bool, &bool_xop);
6393              
6394             /* Register extended type predicate custom ops */
6395 53           XopENTRY_set(&is_num_xop, xop_name, "is_num");
6396 53           XopENTRY_set(&is_num_xop, xop_desc, "check if value is numeric");
6397 53           Perl_custom_op_register(aTHX_ pp_is_num, &is_num_xop);
6398              
6399 53           XopENTRY_set(&is_int_xop, xop_name, "is_int");
6400 53           XopENTRY_set(&is_int_xop, xop_desc, "check if value is integer");
6401 53           Perl_custom_op_register(aTHX_ pp_is_int, &is_int_xop);
6402              
6403 53           XopENTRY_set(&is_blessed_xop, xop_name, "is_blessed");
6404 53           XopENTRY_set(&is_blessed_xop, xop_desc, "check if value is blessed");
6405 53           Perl_custom_op_register(aTHX_ pp_is_blessed, &is_blessed_xop);
6406              
6407 53           XopENTRY_set(&is_scalar_ref_xop, xop_name, "is_scalar_ref");
6408 53           XopENTRY_set(&is_scalar_ref_xop, xop_desc, "check if value is scalar reference");
6409 53           Perl_custom_op_register(aTHX_ pp_is_scalar_ref, &is_scalar_ref_xop);
6410              
6411 53           XopENTRY_set(&is_regex_xop, xop_name, "is_regex");
6412 53           XopENTRY_set(&is_regex_xop, xop_desc, "check if value is compiled regex");
6413 53           Perl_custom_op_register(aTHX_ pp_is_regex, &is_regex_xop);
6414              
6415 53           XopENTRY_set(&is_glob_xop, xop_name, "is_glob");
6416 53           XopENTRY_set(&is_glob_xop, xop_desc, "check if value is glob");
6417 53           Perl_custom_op_register(aTHX_ pp_is_glob, &is_glob_xop);
6418              
6419 53           XopENTRY_set(&is_string_xop, xop_name, "is_string");
6420 53           XopENTRY_set(&is_string_xop, xop_desc, "check if value is plain scalar");
6421 53           Perl_custom_op_register(aTHX_ pp_is_string, &is_string_xop);
6422              
6423             /* Register numeric predicate custom ops */
6424 53           XopENTRY_set(&is_positive_xop, xop_name, "is_positive");
6425 53           XopENTRY_set(&is_positive_xop, xop_desc, "check if value is positive");
6426 53           Perl_custom_op_register(aTHX_ pp_is_positive, &is_positive_xop);
6427              
6428 53           XopENTRY_set(&is_negative_xop, xop_name, "is_negative");
6429 53           XopENTRY_set(&is_negative_xop, xop_desc, "check if value is negative");
6430 53           Perl_custom_op_register(aTHX_ pp_is_negative, &is_negative_xop);
6431              
6432 53           XopENTRY_set(&is_zero_xop, xop_name, "is_zero");
6433 53           XopENTRY_set(&is_zero_xop, xop_desc, "check if value is zero");
6434 53           Perl_custom_op_register(aTHX_ pp_is_zero, &is_zero_xop);
6435              
6436             /* Register numeric utility custom ops */
6437 53           XopENTRY_set(&is_even_xop, xop_name, "is_even");
6438 53           XopENTRY_set(&is_even_xop, xop_desc, "check if integer is even");
6439 53           Perl_custom_op_register(aTHX_ pp_is_even, &is_even_xop);
6440              
6441 53           XopENTRY_set(&is_odd_xop, xop_name, "is_odd");
6442 53           XopENTRY_set(&is_odd_xop, xop_desc, "check if integer is odd");
6443 53           Perl_custom_op_register(aTHX_ pp_is_odd, &is_odd_xop);
6444              
6445 53           XopENTRY_set(&is_between_xop, xop_name, "is_between");
6446 53           XopENTRY_set(&is_between_xop, xop_desc, "check if value is between min and max");
6447 53           Perl_custom_op_register(aTHX_ pp_is_between, &is_between_xop);
6448              
6449             /* Register collection custom ops */
6450 53           XopENTRY_set(&is_empty_array_xop, xop_name, "is_empty_array");
6451 53           XopENTRY_set(&is_empty_array_xop, xop_desc, "check if arrayref is empty");
6452 53           Perl_custom_op_register(aTHX_ pp_is_empty_array, &is_empty_array_xop);
6453              
6454 53           XopENTRY_set(&is_empty_hash_xop, xop_name, "is_empty_hash");
6455 53           XopENTRY_set(&is_empty_hash_xop, xop_desc, "check if hashref is empty");
6456 53           Perl_custom_op_register(aTHX_ pp_is_empty_hash, &is_empty_hash_xop);
6457              
6458 53           XopENTRY_set(&array_len_xop, xop_name, "array_len");
6459 53           XopENTRY_set(&array_len_xop, xop_desc, "get array length");
6460 53           Perl_custom_op_register(aTHX_ pp_array_len, &array_len_xop);
6461              
6462 53           XopENTRY_set(&hash_size_xop, xop_name, "hash_size");
6463 53           XopENTRY_set(&hash_size_xop, xop_desc, "get hash key count");
6464 53           Perl_custom_op_register(aTHX_ pp_hash_size, &hash_size_xop);
6465              
6466 53           XopENTRY_set(&array_first_xop, xop_name, "array_first");
6467 53           XopENTRY_set(&array_first_xop, xop_desc, "get first array element");
6468 53           Perl_custom_op_register(aTHX_ pp_array_first, &array_first_xop);
6469              
6470 53           XopENTRY_set(&array_last_xop, xop_name, "array_last");
6471 53           XopENTRY_set(&array_last_xop, xop_desc, "get last array element");
6472 53           Perl_custom_op_register(aTHX_ pp_array_last, &array_last_xop);
6473              
6474             /* Register string manipulation custom ops */
6475 53           XopENTRY_set(&trim_xop, xop_name, "trim");
6476 53           XopENTRY_set(&trim_xop, xop_desc, "trim whitespace from string");
6477 53           Perl_custom_op_register(aTHX_ pp_trim, &trim_xop);
6478              
6479 53           XopENTRY_set(<rim_xop, xop_name, "ltrim");
6480 53           XopENTRY_set(<rim_xop, xop_desc, "trim leading whitespace");
6481 53           Perl_custom_op_register(aTHX_ pp_ltrim, <rim_xop);
6482              
6483 53           XopENTRY_set(&rtrim_xop, xop_name, "rtrim");
6484 53           XopENTRY_set(&rtrim_xop, xop_desc, "trim trailing whitespace");
6485 53           Perl_custom_op_register(aTHX_ pp_rtrim, &rtrim_xop);
6486              
6487             /* Register conditional custom ops */
6488 53           XopENTRY_set(&maybe_xop, xop_name, "maybe");
6489 53           XopENTRY_set(&maybe_xop, xop_desc, "return value if defined");
6490 53           Perl_custom_op_register(aTHX_ pp_maybe, &maybe_xop);
6491              
6492             /* Register numeric custom ops */
6493 53           XopENTRY_set(&sign_xop, xop_name, "sign");
6494 53           XopENTRY_set(&sign_xop, xop_desc, "return sign of number");
6495 53           Perl_custom_op_register(aTHX_ pp_sign, &sign_xop);
6496              
6497 53           XopENTRY_set(&min2_xop, xop_name, "min2");
6498 53           XopENTRY_set(&min2_xop, xop_desc, "return smaller of two values");
6499 53           Perl_custom_op_register(aTHX_ pp_min2, &min2_xop);
6500              
6501 53           XopENTRY_set(&max2_xop, xop_name, "max2");
6502 53           XopENTRY_set(&max2_xop, xop_desc, "return larger of two values");
6503 53           Perl_custom_op_register(aTHX_ pp_max2, &max2_xop);
6504              
6505             /* Initialize memo storage */
6506 53           g_memo_size = 16;
6507 53 50         Newxz(g_memos, g_memo_size, MemoizedFunc);
6508              
6509             /* Initialize lazy storage */
6510 53           g_lazy_size = 16;
6511 53 50         Newxz(g_lazies, g_lazy_size, LazyValue);
6512              
6513             /* Initialize always storage */
6514 53           g_always_size = 16;
6515 53 50         Newxz(g_always_values, g_always_size, SV*);
6516              
6517             /* Initialize once storage */
6518 53           g_once_size = 16;
6519 53 50         Newxz(g_onces, g_once_size, OnceFunc);
6520              
6521             /* Initialize partial storage */
6522 53           g_partial_size = 16;
6523 53 50         Newxz(g_partials, g_partial_size, PartialFunc);
6524              
6525             /* Initialize export hash for O(1) import lookup */
6526 53           init_export_hash(aTHX);
6527              
6528             /* Export functions */
6529 53           newXS("Func::Util::import", xs_import, __FILE__);
6530              
6531             /* Export registry API */
6532 53           newXS("Func::Util::register_export", xs_register_export, __FILE__);
6533 53           newXS("Func::Util::has_export", xs_has_export, __FILE__);
6534 53           newXS("Func::Util::list_exports", xs_list_exports, __FILE__);
6535              
6536 53           newXS("Func::Util::memo", xs_memo, __FILE__);
6537 53           newXS("Func::Util::pipeline", xs_pipe, __FILE__);
6538 53           newXS("Func::Util::compose", xs_compose, __FILE__);
6539 53           newXS("Func::Util::lazy", xs_lazy, __FILE__);
6540 53           newXS("Func::Util::force", xs_force, __FILE__);
6541 53           newXS("Func::Util::dig", xs_dig, __FILE__);
6542            
6543             {
6544 53           CV *cv = newXS("Func::Util::clamp", xs_clamp, __FILE__);
6545 53           cv_set_call_checker(cv, clamp_call_checker, (SV*)cv);
6546             }
6547            
6548 53           newXS("Func::Util::tap", xs_tap, __FILE__);
6549              
6550             {
6551 53           CV *cv = newXS("Func::Util::identity", xs_identity, __FILE__);
6552 53           cv_set_call_checker(cv, identity_call_checker, (SV*)cv);
6553             }
6554              
6555 53           newXS("Func::Util::always", xs_always, __FILE__);
6556             {
6557 53           CV *cv = newXS("Func::Util::noop", xs_noop, __FILE__);
6558 53           cv_set_call_checker(cv, noop_call_checker, (SV*)cv);
6559             }
6560 53           newXS("Func::Util::stub_true", xs_stub_true, __FILE__);
6561 53           newXS("Func::Util::stub_false", xs_stub_false, __FILE__);
6562 53           newXS("Func::Util::stub_array", xs_stub_array, __FILE__);
6563 53           newXS("Func::Util::stub_hash", xs_stub_hash, __FILE__);
6564 53           newXS("Func::Util::stub_string", xs_stub_string, __FILE__);
6565 53           newXS("Func::Util::stub_zero", xs_stub_zero, __FILE__);
6566 53           newXS("Func::Util::nvl", xs_nvl, __FILE__);
6567 53           newXS("Func::Util::coalesce", xs_coalesce, __FILE__);
6568              
6569             /* List functions */
6570 53           newXS("Func::Util::first", xs_first, __FILE__);
6571 53           newXS("Func::Util::firstr", xs_firstr, __FILE__);
6572 53           newXS("Func::Util::any", xs_any, __FILE__);
6573 53           newXS("Func::Util::all", xs_all, __FILE__);
6574 53           newXS("Func::Util::none", xs_none, __FILE__);
6575             #ifdef dMULTICALL
6576 53           newXS("Func::Util::first_inline", xs_first_inline, __FILE__); /* experimental, 5.11+ only */
6577             #endif
6578              
6579             /* Named callback loop functions */
6580 53           newXS("Func::Util::any_cb", xs_any_cb, __FILE__);
6581 53           newXS("Func::Util::all_cb", xs_all_cb, __FILE__);
6582 53           newXS("Func::Util::none_cb", xs_none_cb, __FILE__);
6583 53           newXS("Func::Util::first_cb", xs_first_cb, __FILE__);
6584 53           newXS("Func::Util::grep_cb", xs_grep_cb, __FILE__);
6585 53           newXS("Func::Util::count_cb", xs_count_cb, __FILE__);
6586 53           newXS("Func::Util::partition_cb", xs_partition_cb, __FILE__);
6587 53           newXS("Func::Util::final_cb", xs_final_cb, __FILE__);
6588 53           newXS("Func::Util::register_callback", xs_register_callback, __FILE__);
6589 53           newXS("Func::Util::has_callback", xs_has_callback, __FILE__);
6590 53           newXS("Func::Util::list_callbacks", xs_list_callbacks, __FILE__);
6591              
6592             /* Specialized array predicates - pure C, no callback */
6593 53           newXS("Func::Util::first_gt", xs_first_gt, __FILE__);
6594 53           newXS("Func::Util::first_lt", xs_first_lt, __FILE__);
6595 53           newXS("Func::Util::first_ge", xs_first_ge, __FILE__);
6596 53           newXS("Func::Util::first_le", xs_first_le, __FILE__);
6597 53           newXS("Func::Util::first_eq", xs_first_eq, __FILE__);
6598 53           newXS("Func::Util::first_ne", xs_first_ne, __FILE__);
6599 53           newXS("Func::Util::final", xs_final, __FILE__);
6600 53           newXS("Func::Util::final_gt", xs_final_gt, __FILE__);
6601 53           newXS("Func::Util::final_lt", xs_final_lt, __FILE__);
6602 53           newXS("Func::Util::final_ge", xs_final_ge, __FILE__);
6603 53           newXS("Func::Util::final_le", xs_final_le, __FILE__);
6604 53           newXS("Func::Util::final_eq", xs_final_eq, __FILE__);
6605 53           newXS("Func::Util::final_ne", xs_final_ne, __FILE__);
6606 53           newXS("Func::Util::any_gt", xs_any_gt, __FILE__);
6607 53           newXS("Func::Util::any_lt", xs_any_lt, __FILE__);
6608 53           newXS("Func::Util::any_ge", xs_any_ge, __FILE__);
6609 53           newXS("Func::Util::any_le", xs_any_le, __FILE__);
6610 53           newXS("Func::Util::any_eq", xs_any_eq, __FILE__);
6611 53           newXS("Func::Util::any_ne", xs_any_ne, __FILE__);
6612 53           newXS("Func::Util::all_gt", xs_all_gt, __FILE__);
6613 53           newXS("Func::Util::all_lt", xs_all_lt, __FILE__);
6614 53           newXS("Func::Util::all_ge", xs_all_ge, __FILE__);
6615 53           newXS("Func::Util::all_le", xs_all_le, __FILE__);
6616 53           newXS("Func::Util::all_eq", xs_all_eq, __FILE__);
6617 53           newXS("Func::Util::all_ne", xs_all_ne, __FILE__);
6618 53           newXS("Func::Util::none_gt", xs_none_gt, __FILE__);
6619 53           newXS("Func::Util::none_lt", xs_none_lt, __FILE__);
6620 53           newXS("Func::Util::none_ge", xs_none_ge, __FILE__);
6621 53           newXS("Func::Util::none_le", xs_none_le, __FILE__);
6622 53           newXS("Func::Util::none_eq", xs_none_eq, __FILE__);
6623 53           newXS("Func::Util::none_ne", xs_none_ne, __FILE__);
6624              
6625             /* Functional combinators */
6626 53           newXS("Func::Util::negate", xs_negate, __FILE__);
6627 53           newXS("Func::Util::once", xs_once, __FILE__);
6628 53           newXS("Func::Util::partial", xs_partial, __FILE__);
6629              
6630             /* Data extraction */
6631 53           newXS("Func::Util::pick", xs_pick, __FILE__);
6632 53           newXS("Func::Util::pluck", xs_pluck, __FILE__);
6633 53           newXS("Func::Util::omit", xs_omit, __FILE__);
6634 53           newXS("Func::Util::uniq", xs_uniq, __FILE__);
6635 53           newXS("Func::Util::partition", xs_partition, __FILE__);
6636 53           newXS("Func::Util::defaults", xs_defaults, __FILE__);
6637              
6638             /* Type predicates with call checkers */
6639             {
6640 53           CV *cv = newXS("Func::Util::is_ref", xs_is_ref, __FILE__);
6641 53           cv_set_call_checker(cv, is_ref_call_checker, (SV*)cv);
6642             }
6643             {
6644 53           CV *cv = newXS("Func::Util::is_array", xs_is_array, __FILE__);
6645 53           cv_set_call_checker(cv, is_array_call_checker, (SV*)cv);
6646             }
6647             {
6648 53           CV *cv = newXS("Func::Util::is_hash", xs_is_hash, __FILE__);
6649 53           cv_set_call_checker(cv, is_hash_call_checker, (SV*)cv);
6650             }
6651             {
6652 53           CV *cv = newXS("Func::Util::is_code", xs_is_code, __FILE__);
6653 53           cv_set_call_checker(cv, is_code_call_checker, (SV*)cv);
6654             }
6655             {
6656 53           CV *cv = newXS("Func::Util::is_defined", xs_is_defined, __FILE__);
6657 53           cv_set_call_checker(cv, is_defined_call_checker, (SV*)cv);
6658             }
6659              
6660             /* String predicates with call checkers */
6661             {
6662 53           CV *cv = newXS("Func::Util::is_empty", xs_is_empty, __FILE__);
6663 53           cv_set_call_checker(cv, is_empty_call_checker, (SV*)cv);
6664             }
6665             {
6666 53           CV *cv = newXS("Func::Util::starts_with", xs_starts_with, __FILE__);
6667 53           cv_set_call_checker(cv, starts_with_call_checker, (SV*)cv);
6668             }
6669             {
6670 53           CV *cv = newXS("Func::Util::ends_with", xs_ends_with, __FILE__);
6671 53           cv_set_call_checker(cv, ends_with_call_checker, (SV*)cv);
6672             }
6673 53           newXS("Func::Util::count", xs_count, __FILE__);
6674 53           newXS("Func::Util::replace_all", xs_replace_all, __FILE__);
6675              
6676             /* Boolean/Truthiness predicates with call checkers */
6677             {
6678 53           CV *cv = newXS("Func::Util::is_true", xs_is_true, __FILE__);
6679 53           cv_set_call_checker(cv, is_true_call_checker, (SV*)cv);
6680             }
6681             {
6682 53           CV *cv = newXS("Func::Util::is_false", xs_is_false, __FILE__);
6683 53           cv_set_call_checker(cv, is_false_call_checker, (SV*)cv);
6684             }
6685             {
6686 53           CV *cv = newXS("Func::Util::bool", xs_bool, __FILE__);
6687 53           cv_set_call_checker(cv, bool_call_checker, (SV*)cv);
6688             }
6689              
6690             /* Extended type predicates with call checkers */
6691             {
6692 53           CV *cv = newXS("Func::Util::is_num", xs_is_num, __FILE__);
6693 53           cv_set_call_checker(cv, is_num_call_checker, (SV*)cv);
6694             }
6695             {
6696 53           CV *cv = newXS("Func::Util::is_int", xs_is_int, __FILE__);
6697 53           cv_set_call_checker(cv, is_int_call_checker, (SV*)cv);
6698             }
6699             {
6700 53           CV *cv = newXS("Func::Util::is_blessed", xs_is_blessed, __FILE__);
6701 53           cv_set_call_checker(cv, is_blessed_call_checker, (SV*)cv);
6702             }
6703             {
6704 53           CV *cv = newXS("Func::Util::is_scalar_ref", xs_is_scalar_ref, __FILE__);
6705 53           cv_set_call_checker(cv, is_scalar_ref_call_checker, (SV*)cv);
6706             }
6707             {
6708 53           CV *cv = newXS("Func::Util::is_regex", xs_is_regex, __FILE__);
6709 53           cv_set_call_checker(cv, is_regex_call_checker, (SV*)cv);
6710             }
6711             {
6712 53           CV *cv = newXS("Func::Util::is_glob", xs_is_glob, __FILE__);
6713 53           cv_set_call_checker(cv, is_glob_call_checker, (SV*)cv);
6714             }
6715              
6716             /* Numeric predicates with call checkers */
6717             {
6718 53           CV *cv = newXS("Func::Util::is_positive", xs_is_positive, __FILE__);
6719 53           cv_set_call_checker(cv, is_positive_call_checker, (SV*)cv);
6720             }
6721             {
6722 53           CV *cv = newXS("Func::Util::is_negative", xs_is_negative, __FILE__);
6723 53           cv_set_call_checker(cv, is_negative_call_checker, (SV*)cv);
6724             }
6725             {
6726 53           CV *cv = newXS("Func::Util::is_zero", xs_is_zero, __FILE__);
6727 53           cv_set_call_checker(cv, is_zero_call_checker, (SV*)cv);
6728             }
6729              
6730             /* Numeric utility ops with call checkers */
6731             {
6732 53           CV *cv = newXS("Func::Util::is_even", xs_is_even, __FILE__);
6733 53           cv_set_call_checker(cv, is_even_call_checker, (SV*)cv);
6734             }
6735             {
6736 53           CV *cv = newXS("Func::Util::is_odd", xs_is_odd, __FILE__);
6737 53           cv_set_call_checker(cv, is_odd_call_checker, (SV*)cv);
6738             }
6739             {
6740 53           CV *cv = newXS("Func::Util::is_between", xs_is_between, __FILE__);
6741 53           cv_set_call_checker(cv, is_between_call_checker, (SV*)cv);
6742             }
6743              
6744             /* Collection ops with call checkers */
6745             {
6746 53           CV *cv = newXS("Func::Util::is_empty_array", xs_is_empty_array, __FILE__);
6747 53           cv_set_call_checker(cv, is_empty_array_call_checker, (SV*)cv);
6748             }
6749             {
6750 53           CV *cv = newXS("Func::Util::is_empty_hash", xs_is_empty_hash, __FILE__);
6751 53           cv_set_call_checker(cv, is_empty_hash_call_checker, (SV*)cv);
6752             }
6753             {
6754 53           CV *cv = newXS("Func::Util::array_len", xs_array_len, __FILE__);
6755 53           cv_set_call_checker(cv, array_len_call_checker, (SV*)cv);
6756             }
6757             {
6758 53           CV *cv = newXS("Func::Util::hash_size", xs_hash_size, __FILE__);
6759 53           cv_set_call_checker(cv, hash_size_call_checker, (SV*)cv);
6760             }
6761             {
6762 53           CV *cv = newXS("Func::Util::array_first", xs_array_first, __FILE__);
6763 53           cv_set_call_checker(cv, array_first_call_checker, (SV*)cv);
6764             }
6765             {
6766 53           CV *cv = newXS("Func::Util::array_last", xs_array_last, __FILE__);
6767 53           cv_set_call_checker(cv, array_last_call_checker, (SV*)cv);
6768             }
6769              
6770             /* String manipulation ops with call checkers */
6771             {
6772 53           CV *cv = newXS("Func::Util::trim", xs_trim, __FILE__);
6773 53           cv_set_call_checker(cv, trim_call_checker, (SV*)cv);
6774             }
6775             {
6776 53           CV *cv = newXS("Func::Util::ltrim", xs_ltrim, __FILE__);
6777 53           cv_set_call_checker(cv, ltrim_call_checker, (SV*)cv);
6778             }
6779             {
6780 53           CV *cv = newXS("Func::Util::rtrim", xs_rtrim, __FILE__);
6781 53           cv_set_call_checker(cv, rtrim_call_checker, (SV*)cv);
6782             }
6783              
6784             /* Conditional ops with call checkers */
6785             {
6786 53           CV *cv = newXS("Func::Util::maybe", xs_maybe, __FILE__);
6787 53           cv_set_call_checker(cv, maybe_call_checker, (SV*)cv);
6788             }
6789              
6790             /* Numeric ops with call checkers */
6791             {
6792 53           CV *cv = newXS("Func::Util::sign", xs_sign, __FILE__);
6793 53           cv_set_call_checker(cv, sign_call_checker, (SV*)cv);
6794             }
6795             {
6796 53           CV *cv = newXS("Func::Util::min2", xs_min2, __FILE__);
6797 53           cv_set_call_checker(cv, min2_call_checker, (SV*)cv);
6798             }
6799             {
6800 53           CV *cv = newXS("Func::Util::max2", xs_max2, __FILE__);
6801 53           cv_set_call_checker(cv, max2_call_checker, (SV*)cv);
6802             }
6803              
6804             /* Register cleanup for global destruction */
6805 53           Perl_call_atexit(aTHX_ cleanup_callback_registry, NULL);
6806              
6807 53           Perl_xs_boot_epilog(aTHX_ ax);
6808 53           }