Ancient
view release on metacpan or search on metacpan
xs/util/util.c view on Meta::CPAN
if (!g_callback_registry) return result;
hv_iterinit(g_callback_registry);
while ((entry = hv_iternext(g_callback_registry))) {
I32 klen;
char *key = hv_iterkey(entry, &klen);
av_push(result, newSVpvn(key, klen));
}
return result;
}
/* Initialize built-in callbacks (called from BOOT) */
static void init_builtin_callbacks(pTHX) {
register_builtin_predicate(aTHX_ ":is_defined", builtin_is_defined);
register_builtin_predicate(aTHX_ ":is_true", builtin_is_true);
register_builtin_predicate(aTHX_ ":is_false", builtin_is_false);
register_builtin_predicate(aTHX_ ":is_ref", builtin_is_ref);
register_builtin_predicate(aTHX_ ":is_array", builtin_is_array);
register_builtin_predicate(aTHX_ ":is_hash", builtin_is_hash);
register_builtin_predicate(aTHX_ ":is_code", builtin_is_code);
register_builtin_predicate(aTHX_ ":is_positive", builtin_is_positive);
register_builtin_predicate(aTHX_ ":is_negative", builtin_is_negative);
register_builtin_predicate(aTHX_ ":is_zero", builtin_is_zero);
register_builtin_predicate(aTHX_ ":is_even", builtin_is_even);
register_builtin_predicate(aTHX_ ":is_odd", builtin_is_odd);
register_builtin_predicate(aTHX_ ":is_empty", builtin_is_empty);
register_builtin_predicate(aTHX_ ":is_nonempty", builtin_is_nonempty);
register_builtin_predicate(aTHX_ ":is_string", builtin_is_string);
register_builtin_predicate(aTHX_ ":is_number", builtin_is_number);
register_builtin_predicate(aTHX_ ":is_integer", builtin_is_integer);
}
/* ============================================
Custom OP implementations - fastest path
============================================ */
/* identity: just return the top of stack */
static OP* pp_identity(pTHX) {
/* Value already on stack, nothing to do */
return NORMAL;
}
/* always: push stored value from op_targ index */
static OP* pp_always(pTHX) {
dSP;
IV idx = PL_op->op_targ;
XPUSHs(g_always_values[idx]);
RETURN;
}
/* clamp: 3 values on stack, return clamped */
static OP* pp_clamp(pTHX) {
dSP; dMARK; dORIGMARK;
SV *val_sv, *min_sv, *max_sv;
NV value, min, max, result;
/* We get 3 args on stack after the mark */
if (SP - MARK != 3) {
/* Fallback: just use direct POPs if no mark context */
SP = ORIGMARK;
PUTBACK;
/* Pop without mark - shouldn't happen in list context */
dSP;
max_sv = POPs;
min_sv = POPs;
val_sv = POPs;
} else {
val_sv = MARK[1];
min_sv = MARK[2];
max_sv = MARK[3];
SP = ORIGMARK; /* reset stack to before args */
}
value = SvNV(val_sv);
min = SvNV(min_sv);
max = SvNV(max_sv);
if (value < min) {
result = min;
} else if (value > max) {
result = max;
} else {
result = value;
}
PUSHs(sv_2mortal(newSVnv(result)));
RETURN;
}
/* nvl: 2 values on stack, return first if defined */
static OP* pp_nvl(pTHX) {
dSP;
SV *def_sv = POPs;
SV *val_sv = TOPs;
if (!SvOK(val_sv)) {
SETs(def_sv);
}
RETURN;
}
/* ============================================
Type predicate custom ops - blazing fast!
These are the fastest possible type checks:
single SV flag check, no function call overhead
============================================ */
/* is_ref: check if value is a reference */
static OP* pp_is_ref(pTHX) {
dSP;
SV *sv = TOPs;
SETs(SvROK(sv) ? &PL_sv_yes : &PL_sv_no);
RETURN;
}
/* is_array: check if value is an arrayref */
static OP* pp_is_array(pTHX) {
dSP;
SV *sv = TOPs;
SETs((SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) ? &PL_sv_yes : &PL_sv_no);
RETURN;
xs/util/util.c view on Meta::CPAN
MemoizedFunc *mf = &g_memos[idx];
mf->func = SvREFCNT_inc_simple_NN(func);
mf->cache = newHV();
mf->hits = 0;
mf->misses = 0;
/* Create wrapper CV */
CV *wrapper = newXS(NULL, xs_memo_call, __FILE__);
CvXSUBANY(wrapper).any_iv = idx;
/* Attach magic for cleanup when wrapper is freed */
sv_magicext((SV*)wrapper, NULL, PERL_MAGIC_ext, &util_memo_vtbl, NULL, idx);
ST(0) = sv_2mortal(newRV_noinc((SV*)wrapper));
XSRETURN(1);
}
static XS(xs_memo_call) {
dXSARGS;
IV idx = CvXSUBANY(cv).any_iv;
MemoizedFunc *mf = &g_memos[idx];
/* Build cache key from arguments */
SV *key = build_cache_key(aTHX_ &ST(0), items);
STRLEN key_len;
const char *key_pv = SvPV(key, key_len);
/* Check cache */
SV **cached = hv_fetch(mf->cache, key_pv, key_len, 0);
if (cached && SvOK(*cached)) {
mf->hits++;
SvREFCNT_dec_NN(key);
if (SvROK(*cached) && SvTYPE(SvRV(*cached)) == SVt_PVAV) {
AV *av = (AV*)SvRV(*cached);
IV len = av_len(av) + 1;
IV i;
EXTEND(SP, len);
for (i = 0; i < len; i++) {
SV **elem = av_fetch(av, i, 0);
ST(i) = elem ? *elem : &PL_sv_undef;
}
XSRETURN(len);
} else {
ST(0) = *cached;
XSRETURN(1);
}
}
mf->misses++;
ENTER;
SAVETMPS;
PUSHMARK(SP);
IV i;
EXTEND(SP, items);
for (i = 0; i < items; i++) {
PUSHs(ST(i));
}
PUTBACK;
IV count = call_sv(mf->func, G_ARRAY);
SPAGAIN;
if (count == 1) {
SV *result = SvREFCNT_inc(POPs);
hv_store(mf->cache, key_pv, key_len, result, 0);
PUTBACK;
FREETMPS;
LEAVE;
SvREFCNT_dec_NN(key);
ST(0) = result;
XSRETURN(1);
} else if (count > 0) {
AV *av = newAV();
av_extend(av, count - 1);
for (i = count - 1; i >= 0; i--) {
av_store(av, i, SvREFCNT_inc(POPs));
}
SV *result = newRV_noinc((SV*)av);
hv_store(mf->cache, key_pv, key_len, result, 0);
PUTBACK;
FREETMPS;
LEAVE;
SvREFCNT_dec_NN(key);
for (i = 0; i < count; i++) {
SV **elem = av_fetch(av, i, 0);
ST(i) = elem ? *elem : &PL_sv_undef;
}
XSRETURN(count);
} else {
hv_store(mf->cache, key_pv, key_len, &PL_sv_undef, 0);
PUTBACK;
FREETMPS;
LEAVE;
SvREFCNT_dec_NN(key);
XSRETURN_EMPTY;
}
}
/* ============================================
Pipe/Compose implementation
============================================ */
static XS(xs_pipe) {
dXSARGS;
if (items < 2) croak("Usage: util::pipeline($value, \\&fn1, \\&fn2, ...)");
SV *value = SvREFCNT_inc(ST(0));
IV i;
for (i = 1; i < items; i++) {
SV *func = ST(i);
if (!SvROK(func) || SvTYPE(SvRV(func)) != SVt_PVCV) {
SvREFCNT_dec(value);
croak("util::pipeline: argument %d is not a coderef", (int)i);
}
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(value);
PUTBACK;
call_sv(func, G_SCALAR);
SPAGAIN;
SV *new_value = POPs;
SvREFCNT_inc(new_value);
PUTBACK;
FREETMPS;
LEAVE;
SvREFCNT_dec(value);
value = new_value;
}
ST(0) = sv_2mortal(value);
XSRETURN(1);
}
static XS(xs_compose) {
dXSARGS;
if (items < 1) croak("Usage: util::compose(\\&fn1, \\&fn2, ...)");
AV *funcs = newAV();
av_extend(funcs, items - 1);
IV i;
for (i = 0; i < items; i++) {
SV *func = ST(i);
if (!SvROK(func) || SvTYPE(SvRV(func)) != SVt_PVCV) {
croak("util::compose: argument %d is not a coderef", (int)(i+1));
}
av_store(funcs, i, SvREFCNT_inc_simple_NN(func));
}
CV *wrapper = newXS(NULL, xs_compose_call, __FILE__);
CvXSUBANY(wrapper).any_ptr = (void*)funcs;
/* Attach magic for cleanup when wrapper is freed - pass AV via mg_ptr */
sv_magicext((SV*)wrapper, NULL, PERL_MAGIC_ext, &util_compose_vtbl, (char*)funcs, 0);
ST(0) = sv_2mortal(newRV_noinc((SV*)wrapper));
XSRETURN(1);
}
static XS(xs_compose_call) {
dXSARGS;
AV *funcs = (AV*)CvXSUBANY(cv).any_ptr;
IV func_count = av_len(funcs) + 1;
SV *value = NULL;
IV i;
for (i = func_count - 1; i >= 0; i--) {
SV **func_ptr = av_fetch(funcs, i, 0);
if (!func_ptr) continue;
ENTER;
SAVETMPS;
PUSHMARK(SP);
if (i == func_count - 1) {
IV j;
EXTEND(SP, items);
for (j = 0; j < items; j++) {
PUSHs(ST(j));
}
} else {
XPUSHs(value);
}
PUTBACK;
call_sv(*func_ptr, G_SCALAR);
SPAGAIN;
SV *new_value = POPs;
SvREFCNT_inc(new_value);
PUTBACK;
FREETMPS;
LEAVE;
if (value) SvREFCNT_dec(value);
value = new_value;
}
ST(0) = value ? sv_2mortal(value) : &PL_sv_undef;
XSRETURN(1);
}
/* ============================================
Lazy evaluation implementation
============================================ */
static XS(xs_lazy) {
dXSARGS;
if (items != 1) croak("Usage: util::lazy(sub { ... })");
SV *thunk = ST(0);
if (!SvROK(thunk) || SvTYPE(SvRV(thunk)) != SVt_PVCV) {
croak("util::lazy requires a coderef");
}
IV idx = g_lazy_count++;
ensure_lazy_capacity(idx);
LazyValue *lv = &g_lazies[idx];
lv->thunk = SvREFCNT_inc_simple_NN(thunk);
lv->value = NULL;
lv->forced = FALSE;
SV *obj = newSViv(idx);
SV *ref = newRV_noinc(obj);
sv_bless(ref, gv_stashpv("util::Lazy", GV_ADD));
/* Attach magic for cleanup when lazy object is freed */
sv_magicext(obj, NULL, PERL_MAGIC_ext, &util_lazy_vtbl, NULL, idx);
ST(0) = sv_2mortal(ref);
XSRETURN(1);
}
static XS(xs_force) {
dXSARGS;
if (items != 1) croak("Usage: util::force($lazy)");
SV *lazy = ST(0);
if (!SvROK(lazy) || !sv_derived_from(lazy, "util::Lazy")) {
ST(0) = lazy;
XSRETURN(1);
}
IV idx = SvIV(SvRV(lazy));
if (idx < 0 || idx >= g_lazy_count) {
croak("util::force: invalid lazy value");
}
LazyValue *lv = &g_lazies[idx];
if (lv->forced) {
ST(0) = lv->value;
XSRETURN(1);
}
ENTER;
SAVETMPS;
PUSHMARK(SP);
PUTBACK;
call_sv(lv->thunk, G_SCALAR);
SPAGAIN;
lv->value = SvREFCNT_inc(POPs);
lv->forced = TRUE;
PUTBACK;
FREETMPS;
LEAVE;
SvREFCNT_dec(lv->thunk);
lv->thunk = NULL;
ST(0) = lv->value;
XSRETURN(1);
}
/* ============================================
Safe navigation (dig) implementation
============================================ */
static XS(xs_dig) {
dXSARGS;
if (items < 2) croak("Usage: util::dig($hash, @keys)");
SV *current = ST(0);
IV i;
for (i = 1; i < items; i++) {
if (!SvROK(current) || SvTYPE(SvRV(current)) != SVt_PVHV) {
XSRETURN_UNDEF;
}
HV *hv = (HV*)SvRV(current);
SV *key = ST(i);
STRLEN key_len;
const char *key_pv = SvPV(key, key_len);
SV **val = hv_fetch(hv, key_pv, key_len, 0);
if (!val || !SvOK(*val)) {
XSRETURN_UNDEF;
}
current = *val;
}
ST(0) = current;
XSRETURN(1);
}
/* ============================================
Tap implementation
============================================ */
static XS(xs_tap) {
dXSARGS;
if (items != 2) croak("Usage: util::tap(\\&block, $value)");
SV *func = ST(0);
SV *value = ST(1);
if (!SvROK(func) || SvTYPE(SvRV(func)) != SVt_PVCV) {
croak("util::tap: first argument must be a coderef");
}
ENTER;
SAVETMPS;
SAVE_DEFSV;
DEFSV_set(value);
PUSHMARK(SP);
XPUSHs(value);
PUTBACK;
call_sv(func, G_DISCARD | G_VOID);
SPAGAIN;
FREETMPS;
LEAVE;
ST(0) = value;
XSRETURN(1);
}
/* ============================================
Clamp XS fallback
============================================ */
static XS(xs_clamp) {
dXSARGS;
NV value, min, max, result;
if (items != 3) croak("Usage: util::clamp($value, $min, $max)");
value = SvNV(ST(0));
min = SvNV(ST(1));
max = SvNV(ST(2));
if (value < min) {
result = min;
} else if (value > max) {
result = max;
} else {
result = value;
}
ST(0) = sv_2mortal(newSVnv(result));
XSRETURN(1);
}
/* ============================================
Identity XS fallback
============================================ */
static XS(xs_identity) {
dXSARGS;
if (items != 1) croak("Usage: util::identity($value)");
XSRETURN(1);
}
/* ============================================
Always implementation
============================================ */
static XS(xs_always) {
dXSARGS;
if (items != 1) croak("Usage: util::always($value)");
IV idx = g_always_count++;
ensure_always_capacity(idx);
g_always_values[idx] = SvREFCNT_inc_simple_NN(ST(0));
CV *wrapper = newXS(NULL, xs_always_call, __FILE__);
xs/util/util.c view on Meta::CPAN
dXSARGS;
PERL_UNUSED_VAR(items);
if (GIMME_V == G_ARRAY) {
XSRETURN_EMPTY;
}
ST(0) = sv_2mortal(newRV_noinc((SV*)newHV()));
XSRETURN(1);
}
/* stub_string() - always returns empty string '' */
static XS(xs_stub_string) {
dXSARGS;
PERL_UNUSED_VAR(items);
/* Return shared empty string constant - XSRETURN_NO returns '' */
XSRETURN_NO;
}
/* stub_zero() - always returns 0 */
static XS(xs_stub_zero) {
dXSARGS;
PERL_UNUSED_VAR(items);
/* Return shared 0 SV */
ST(0) = &PL_sv_zero;
XSRETURN(1);
}
/* ============================================
Functional combinators
============================================ */
/* negate(\&pred) - returns a function that returns the opposite */
static XS(xs_negate) {
dXSARGS;
if (items != 1) croak("Usage: util::negate(\\&predicate)");
SV *pred = ST(0);
if (!SvROK(pred) || SvTYPE(SvRV(pred)) != SVt_PVCV) {
croak("util::negate: argument must be a coderef");
}
CV *wrapper = newXS(NULL, xs_negate_call, __FILE__);
CvXSUBANY(wrapper).any_ptr = SvREFCNT_inc_simple_NN(pred);
ST(0) = sv_2mortal(newRV_noinc((SV*)wrapper));
XSRETURN(1);
}
static XS(xs_negate_call) {
dXSARGS;
SV *pred = (SV*)CvXSUBANY(cv).any_ptr;
ENTER;
SAVETMPS;
PUSHMARK(SP);
IV i;
EXTEND(SP, items);
for (i = 0; i < items; i++) {
PUSHs(ST(i));
}
PUTBACK;
call_sv(pred, G_SCALAR);
SPAGAIN;
SV *result = POPs;
bool val = SvTRUE(result);
PUTBACK;
FREETMPS;
LEAVE;
ST(0) = val ? &PL_sv_no : &PL_sv_yes;
XSRETURN(1);
}
/* once(\&f) - execute once, cache forever */
static XS(xs_once) {
dXSARGS;
if (items != 1) croak("Usage: util::once(\\&func)");
SV *func = ST(0);
if (!SvROK(func) || SvTYPE(SvRV(func)) != SVt_PVCV) {
croak("util::once: argument must be a coderef");
}
IV idx = g_once_count++;
ensure_once_capacity(idx);
OnceFunc *of = &g_onces[idx];
of->func = SvREFCNT_inc_simple_NN(func);
of->result = NULL;
of->called = FALSE;
CV *wrapper = newXS(NULL, xs_once_call, __FILE__);
CvXSUBANY(wrapper).any_iv = idx;
/* Attach magic for cleanup when wrapper is freed */
sv_magicext((SV*)wrapper, NULL, PERL_MAGIC_ext, &util_once_vtbl, NULL, idx);
ST(0) = sv_2mortal(newRV_noinc((SV*)wrapper));
XSRETURN(1);
}
static XS(xs_once_call) {
dXSARGS;
PERL_UNUSED_VAR(items);
IV idx = CvXSUBANY(cv).any_iv;
OnceFunc *of = &g_onces[idx];
if (of->called) {
ST(0) = of->result ? of->result : &PL_sv_undef;
XSRETURN(1);
}
ENTER;
SAVETMPS;
PUSHMARK(SP);
PUTBACK;
call_sv(of->func, G_SCALAR);
SPAGAIN;
of->result = SvREFCNT_inc(POPs);
of->called = TRUE;
PUTBACK;
FREETMPS;
LEAVE;
/* Free the original function, no longer needed */
SvREFCNT_dec(of->func);
of->func = NULL;
ST(0) = of->result;
XSRETURN(1);
}
/* partial(\&f, @bound) - bind first N args */
static XS(xs_partial) {
dXSARGS;
if (items < 1) croak("Usage: util::partial(\\&func, @bound_args)");
SV *func = ST(0);
if (!SvROK(func) || SvTYPE(SvRV(func)) != SVt_PVCV) {
croak("util::partial: first argument must be a coderef");
}
IV idx = g_partial_count++;
ensure_partial_capacity(idx);
PartialFunc *pf = &g_partials[idx];
pf->func = SvREFCNT_inc_simple_NN(func);
pf->bound_args = newAV();
/* Store bound arguments */
IV i;
for (i = 1; i < items; i++) {
av_push(pf->bound_args, SvREFCNT_inc_simple_NN(ST(i)));
}
CV *wrapper = newXS(NULL, xs_partial_call, __FILE__);
CvXSUBANY(wrapper).any_iv = idx;
/* Attach magic for cleanup when wrapper is freed */
sv_magicext((SV*)wrapper, NULL, PERL_MAGIC_ext, &util_partial_vtbl, NULL, idx);
ST(0) = sv_2mortal(newRV_noinc((SV*)wrapper));
XSRETURN(1);
}
static XS(xs_partial_call) {
dXSARGS;
IV idx = CvXSUBANY(cv).any_iv;
PartialFunc *pf = &g_partials[idx];
IV bound_count = av_len(pf->bound_args) + 1;
IV total = bound_count + items;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, total);
/* Push bound args first */
IV i;
for (i = 0; i < bound_count; i++) {
SV **elem = av_fetch(pf->bound_args, i, 0);
PUSHs(elem ? *elem : &PL_sv_undef);
}
/* Push call-time args */
for (i = 0; i < items; i++) {
PUSHs(ST(i));
}
PUTBACK;
IV count = call_sv(pf->func, G_SCALAR);
SPAGAIN;
SV *result = count > 0 ? POPs : &PL_sv_undef;
SvREFCNT_inc(result);
PUTBACK;
FREETMPS;
LEAVE;
ST(0) = sv_2mortal(result);
XSRETURN(1);
}
/* ============================================
Data extraction functions
============================================ */
/* pick($hash, @keys) - extract subset of keys
* Returns hashref in scalar context, flattened list in list context */
static XS(xs_pick) {
dXSARGS;
if (items < 1) croak("Usage: util::pick(\\%%hash, @keys)");
SV *href = ST(0);
if (!SvROK(href) || SvTYPE(SvRV(href)) != SVt_PVHV) {
croak("util::pick: first argument must be a hashref");
}
HV *src = (HV*)SvRV(href);
HV *dest = newHV();
IV i;
for (i = 1; i < items; i++) {
SV *key = ST(i);
STRLEN key_len;
const char *key_pv = SvPV(key, key_len);
SV **val = hv_fetch(src, key_pv, key_len, 0);
if (val && SvOK(*val)) {
hv_store(dest, key_pv, key_len, SvREFCNT_inc(*val), 0);
}
}
/* Check calling context */
if (GIMME_V == G_ARRAY) {
/* List context - return flattened key-value pairs */
IV n = HvUSEDKEYS(dest);
SP -= items; /* Reset stack pointer */
EXTEND(SP, n * 2);
hv_iterinit(dest);
HE *he;
while ((he = hv_iternext(dest)) != NULL) {
STRLEN klen;
const char *key = HePV(he, klen);
mPUSHp(key, klen);
mPUSHs(SvREFCNT_inc(HeVAL(he)));
}
SvREFCNT_dec((SV*)dest); /* Free the temp hash */
PUTBACK;
return;
}
/* Scalar context - return hashref */
ST(0) = sv_2mortal(newRV_noinc((SV*)dest));
XSRETURN(1);
}
/* pluck(\@hashes, $field) - extract field from each hash */
static XS(xs_pluck) {
dXSARGS;
if (items != 2) croak("Usage: util::pluck(\\@array, $field)");
SV *aref = ST(0);
if (!SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV) {
croak("util::pluck: first argument must be an arrayref");
}
SV *field = ST(1);
STRLEN field_len;
const char *field_pv = SvPV(field, field_len);
AV *src = (AV*)SvRV(aref);
IV len = av_len(src) + 1;
AV *dest = newAV();
av_extend(dest, len - 1);
IV i;
for (i = 0; i < len; i++) {
SV **elem = av_fetch(src, i, 0);
if (elem && SvROK(*elem) && SvTYPE(SvRV(*elem)) == SVt_PVHV) {
HV *hv = (HV*)SvRV(*elem);
SV **val = hv_fetch(hv, field_pv, field_len, 0);
if (val && SvOK(*val)) {
av_push(dest, SvREFCNT_inc(*val));
} else {
av_push(dest, &PL_sv_undef);
}
} else {
av_push(dest, &PL_sv_undef);
}
}
ST(0) = sv_2mortal(newRV_noinc((SV*)dest));
XSRETURN(1);
}
/* omit($hash, @keys) - exclude subset of keys (inverse of pick)
* Returns hashref in scalar context, flattened list in list context */
static XS(xs_omit) {
dXSARGS;
if (items < 1) croak("Usage: util::omit(\\%%hash, @keys)");
SV *href = ST(0);
if (!SvROK(href) || SvTYPE(SvRV(href)) != SVt_PVHV) {
croak("util::omit: first argument must be a hashref");
}
HV *src = (HV*)SvRV(href);
HV *dest = newHV();
/* Build exclusion set for O(1) lookup */
HV *exclude = newHV();
IV i;
for (i = 1; i < items; i++) {
SV *key = ST(i);
STRLEN key_len;
const char *key_pv = SvPV(key, key_len);
hv_store(exclude, key_pv, key_len, &PL_sv_yes, 0);
}
/* Iterate source, copy non-excluded keys */
hv_iterinit(src);
HE *entry;
while ((entry = hv_iternext(src)) != NULL) {
SV *key = hv_iterkeysv(entry);
STRLEN key_len;
const char *key_pv = SvPV(key, key_len);
if (!hv_exists(exclude, key_pv, key_len)) {
SV *val = hv_iterval(src, entry);
if (SvOK(val)) {
hv_store(dest, key_pv, key_len, SvREFCNT_inc(val), 0);
}
}
}
SvREFCNT_dec((SV*)exclude);
/* Check calling context */
if (GIMME_V == G_ARRAY) {
/* List context - return flattened key-value pairs */
IV n = HvUSEDKEYS(dest);
SP -= items; /* Reset stack pointer */
EXTEND(SP, n * 2);
hv_iterinit(dest);
HE *he;
while ((he = hv_iternext(dest)) != NULL) {
STRLEN klen;
const char *key = HePV(he, klen);
mPUSHp(key, klen);
mPUSHs(SvREFCNT_inc(HeVAL(he)));
}
SvREFCNT_dec((SV*)dest); /* Free the temp hash */
PUTBACK;
return;
}
/* Scalar context - return hashref */
ST(0) = sv_2mortal(newRV_noinc((SV*)dest));
XSRETURN(1);
}
/* uniq(@list) - return unique elements (preserves order) */
static XS(xs_uniq) {
dXSARGS;
if (items == 0) {
XSRETURN(0);
}
if (items == 1) {
XSRETURN(1);
}
/* For small lists, use simple O(n^2) - faster due to no hash overhead */
if (items <= 8) {
IV out = 0;
IV i, j;
for (i = 0; i < items; i++) {
SV *elem = ST(i);
STRLEN len_i;
const char *key_i = SvOK(elem) ? SvPV_const(elem, len_i) : "\x00UNDEF\x00";
if (!SvOK(elem)) len_i = 7;
bool dup = FALSE;
for (j = 0; j < out; j++) {
SV *prev = ST(j);
STRLEN len_j;
const char *key_j = SvOK(prev) ? SvPV_const(prev, len_j) : "\x00UNDEF\x00";
if (!SvOK(prev)) len_j = 7;
if (len_i == len_j && memcmp(key_i, key_j, len_i) == 0) {
dup = TRUE;
break;
}
}
if (!dup) ST(out++) = elem;
}
XSRETURN(out);
}
HV *seen = newHV();
IV out = 0;
hv_ksplit(seen, items);
IV i;
for (i = 0; i < items; i++) {
SV *elem = ST(i);
STRLEN len;
const char *key;
U32 hash;
key = SvOK(elem) ? SvPV_const(elem, len) : (len = 7, "\x00UNDEF\x00");
PERL_HASH(hash, key, len);
if (!hv_common(seen, NULL, key, len, 0, HV_FETCH_ISEXISTS, NULL, hash)) {
hv_common(seen, NULL, key, len, 0, HV_FETCH_ISSTORE, &PL_sv_yes, hash);
ST(out++) = elem;
}
}
SvREFCNT_dec_NN((SV*)seen);
XSRETURN(out);
}
/* partition(\&pred, @list) - split into [matches], [non-matches] */
static XS(xs_partition) {
dXSARGS;
if (items < 1) croak("Usage: util::partition(\\&block, @list)");
SV *block = ST(0);
if (!SvROK(block) || SvTYPE(SvRV(block)) != SVt_PVCV) {
croak("util::partition: first argument must be a coderef");
}
IV list_len = items - 1;
if (list_len == 0) {
AV *pass = newAV();
AV *fail = newAV();
AV *outer = newAV();
av_push(outer, newRV_noinc((SV*)pass));
av_push(outer, newRV_noinc((SV*)fail));
ST(0) = sv_2mortal(newRV_noinc((SV*)outer));
XSRETURN(1);
}
AV *pass = newAV();
AV *fail = newAV();
av_extend(pass, list_len >> 1);
av_extend(fail, list_len >> 1);
SV *orig_defsv = DEFSV;
IV i;
for (i = 1; i < items; i++) {
SV *elem = ST(i);
DEFSV_set(elem);
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(elem);
PUTBACK;
call_sv(block, G_SCALAR);
SPAGAIN;
SV *result = POPs;
bool matched = SvTRUE(result);
PUTBACK;
FREETMPS;
LEAVE;
if (matched) {
av_push(pass, SvREFCNT_inc_simple_NN(elem));
} else {
av_push(fail, SvREFCNT_inc_simple_NN(elem));
}
}
DEFSV_set(orig_defsv);
AV *outer = newAV();
av_push(outer, newRV_noinc((SV*)pass));
av_push(outer, newRV_noinc((SV*)fail));
ST(0) = sv_2mortal(newRV_noinc((SV*)outer));
XSRETURN(1);
}
/* defaults($hash, $defaults) - fill in missing keys from defaults
* Returns hashref in scalar context, flattened list in list context */
static XS(xs_defaults) {
dXSARGS;
if (items != 2) croak("Usage: util::defaults(\\%%hash, \\%%defaults)");
SV *href = ST(0);
SV *dref = ST(1);
if (!SvROK(href) || SvTYPE(SvRV(href)) != SVt_PVHV) {
croak("util::defaults: first argument must be a hashref");
}
if (!SvROK(dref) || SvTYPE(SvRV(dref)) != SVt_PVHV) {
croak("util::defaults: second argument must be a hashref");
}
HV *src = (HV*)SvRV(href);
HV *def = (HV*)SvRV(dref);
/* Pre-size dest hash */
IV src_keys = HvUSEDKEYS(src);
IV def_keys = HvUSEDKEYS(def);
HV *dest = newHV();
hv_ksplit(dest, src_keys + def_keys);
/* Copy all from source first */
hv_iterinit(src);
HE *entry;
while ((entry = hv_iternext(src)) != NULL) {
STRLEN key_len;
const char *key_pv = HePV(entry, key_len);
SV *val = HeVAL(entry);
hv_store(dest, key_pv, key_len, SvREFCNT_inc_simple_NN(val), HeHASH(entry));
}
/* Fill in missing from defaults - use pre-computed hash */
hv_iterinit(def);
while ((entry = hv_iternext(def)) != NULL) {
STRLEN key_len;
const char *key_pv = HePV(entry, key_len);
U32 hash = HeHASH(entry);
/* Check if exists and is defined in dest */
SV **existing = hv_fetch(dest, key_pv, key_len, 0);
if (!existing || !SvOK(*existing)) {
SV *val = HeVAL(entry);
hv_store(dest, key_pv, key_len, SvREFCNT_inc_simple_NN(val), hash);
}
}
/* Check calling context */
if (GIMME_V == G_ARRAY) {
/* List context - return flattened key-value pairs */
IV n = HvUSEDKEYS(dest);
SP -= items; /* Reset stack pointer */
EXTEND(SP, n * 2);
hv_iterinit(dest);
HE *he;
while ((he = hv_iternext(dest)) != NULL) {
STRLEN klen;
const char *key = HePV(he, klen);
mPUSHp(key, klen);
mPUSHs(SvREFCNT_inc(HeVAL(he)));
}
SvREFCNT_dec((SV*)dest); /* Free the temp hash */
PUTBACK;
return;
}
/* Scalar context - return hashref */
ST(0) = sv_2mortal(newRV_noinc((SV*)dest));
XSRETURN(1);
}
/* ============================================
Null coalescing functions
============================================ */
/* nvl($x, $default) - return $x if defined, else $default */
static XS(xs_nvl) {
dXSARGS;
if (items != 2) croak("Usage: util::nvl($value, $default)");
SV *val = ST(0);
if (SvOK(val)) {
XSRETURN(1); /* Return first arg */
}
ST(0) = ST(1);
XSRETURN(1);
}
/* coalesce($a, $b, ...) - return first defined value */
static XS(xs_coalesce) {
dXSARGS;
if (items < 1) croak("Usage: util::coalesce($val, ...)");
IV i;
for (i = 0; i < items; i++) {
if (SvOK(ST(i))) {
ST(0) = ST(i);
XSRETURN(1);
}
}
/* All undefined, return undef */
ST(0) = &PL_sv_undef;
XSRETURN(1);
}
/* ============================================
List functions (first, any, all, none)
These use MULTICALL for pure Perl subs which is significantly
faster than call_sv() for repeated invocations.
For XS subs, we fall back to call_sv().
============================================ */
/* Inline CALLRUNOPS - experimental optimization to skip function call overhead.
Use cautiously - this inlines the runops loop directly. */
#define INLINE_RUNOPS() \
STMT_START { \
OP *_inline_op = PL_op; \
while ((_inline_op = _inline_op->op_ppaddr(aTHX))) ; \
} STMT_END
/* ============================================
xs/util/util.c view on Meta::CPAN
ST(0) = a >= b ? ST(0) : ST(1);
XSRETURN(1);
}
/* ============================================
Named callback loop functions
These accept a callback name instead of coderef
============================================ */
/* any_cb(\@list, ':predicate') - true if any element matches */
static XS(xs_any_cb) {
dXSARGS;
if (items != 2) croak("Usage: util::any_cb(\\@list, $callback_name)");
SV *list_sv = ST(0);
if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
croak("util::any_cb: first argument must be an arrayref");
}
AV *list = (AV*)SvRV(list_sv);
STRLEN name_len;
const char *name = SvPV(ST(1), name_len);
RegisteredCallback *cb = get_registered_callback(aTHX_ name);
if (!cb) {
croak("util::any_cb: unknown callback '%s'", name);
}
if (!cb->predicate && !cb->perl_callback) {
croak("util::any_cb: callback '%s' is not a predicate", name);
}
IV len = av_len(list) + 1;
IV i;
if (cb->predicate) {
/* Fast C path */
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (svp && cb->predicate(aTHX_ *svp)) {
XSRETURN_YES;
}
}
} else if (cb->perl_callback) {
/* Perl callback fallback - use isolated stack scope */
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (!svp) continue;
bool matches = FALSE;
{
dSP;
int count;
SV *result;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(*svp);
PUTBACK;
count = call_sv(cb->perl_callback, G_SCALAR);
SPAGAIN;
if (count > 0) {
result = POPs;
matches = SvTRUE(result);
}
PUTBACK;
FREETMPS;
LEAVE;
}
if (matches) {
XSRETURN_YES;
}
}
}
XSRETURN_NO;
}
/* all_cb(\@list, ':predicate') - true if all elements match */
static XS(xs_all_cb) {
dXSARGS;
if (items != 2) croak("Usage: util::all_cb(\\@list, $callback_name)");
SV *list_sv = ST(0);
if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
croak("util::all_cb: first argument must be an arrayref");
}
AV *list = (AV*)SvRV(list_sv);
STRLEN name_len;
const char *name = SvPV(ST(1), name_len);
RegisteredCallback *cb = get_registered_callback(aTHX_ name);
if (!cb) {
croak("util::all_cb: unknown callback '%s'", name);
}
if (!cb->predicate && !cb->perl_callback) {
croak("util::all_cb: callback '%s' is not a predicate", name);
}
IV len = av_len(list) + 1;
IV i;
/* Empty list returns true (vacuous truth) */
if (len == 0) {
XSRETURN_YES;
}
if (cb->predicate) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (!svp || !cb->predicate(aTHX_ *svp)) {
XSRETURN_NO;
}
}
} else if (cb->perl_callback) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (!svp) { XSRETURN_NO; }
bool matches = FALSE;
{
dSP;
int count;
SV *result;
ENTER; SAVETMPS;
PUSHMARK(SP);
XPUSHs(*svp);
PUTBACK;
count = call_sv(cb->perl_callback, G_SCALAR);
SPAGAIN;
if (count > 0) {
result = POPs;
matches = SvTRUE(result);
}
PUTBACK;
FREETMPS; LEAVE;
}
if (!matches) {
XSRETURN_NO;
}
}
}
XSRETURN_YES;
}
/* none_cb(\@list, ':predicate') - true if no elements match */
static XS(xs_none_cb) {
dXSARGS;
if (items != 2) croak("Usage: util::none_cb(\\@list, $callback_name)");
SV *list_sv = ST(0);
if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
croak("util::none_cb: first argument must be an arrayref");
}
AV *list = (AV*)SvRV(list_sv);
STRLEN name_len;
const char *name = SvPV(ST(1), name_len);
RegisteredCallback *cb = get_registered_callback(aTHX_ name);
if (!cb) {
croak("util::none_cb: unknown callback '%s'", name);
}
if (!cb->predicate && !cb->perl_callback) {
croak("util::none_cb: callback '%s' is not a predicate", name);
}
IV len = av_len(list) + 1;
IV i;
if (cb->predicate) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (svp && cb->predicate(aTHX_ *svp)) {
XSRETURN_NO;
}
}
} else if (cb->perl_callback) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (!svp) continue;
bool matches = FALSE;
{
dSP;
int count;
SV *result;
ENTER; SAVETMPS;
PUSHMARK(SP);
XPUSHs(*svp);
PUTBACK;
count = call_sv(cb->perl_callback, G_SCALAR);
SPAGAIN;
if (count > 0) {
result = POPs;
matches = SvTRUE(result);
}
PUTBACK;
FREETMPS; LEAVE;
}
if (matches) {
XSRETURN_NO;
}
}
}
XSRETURN_YES;
}
/* first_cb(\@list, ':predicate') - first matching element */
static XS(xs_first_cb) {
dXSARGS;
if (items != 2) croak("Usage: util::first_cb(\\@list, $callback_name)");
SV *list_sv = ST(0);
if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
croak("util::first_cb: first argument must be an arrayref");
}
AV *list = (AV*)SvRV(list_sv);
STRLEN name_len;
const char *name = SvPV(ST(1), name_len);
RegisteredCallback *cb = get_registered_callback(aTHX_ name);
if (!cb) {
croak("util::first_cb: unknown callback '%s'", name);
}
if (!cb->predicate && !cb->perl_callback) {
croak("util::first_cb: callback '%s' is not a predicate", name);
}
IV len = av_len(list) + 1;
IV i;
if (cb->predicate) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (svp && cb->predicate(aTHX_ *svp)) {
ST(0) = *svp;
XSRETURN(1);
}
}
} else if (cb->perl_callback) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (!svp) continue;
bool matches = FALSE;
{
dSP;
int count;
SV *result;
ENTER; SAVETMPS;
PUSHMARK(SP);
XPUSHs(*svp);
PUTBACK;
count = call_sv(cb->perl_callback, G_SCALAR);
SPAGAIN;
if (count > 0) {
result = POPs;
matches = SvTRUE(result);
}
PUTBACK;
FREETMPS; LEAVE;
}
if (matches) {
ST(0) = *svp;
XSRETURN(1);
}
}
}
XSRETURN_UNDEF;
}
/* grep_cb(\@list, ':predicate') - all matching elements */
static XS(xs_grep_cb) {
dXSARGS;
if (items != 2) croak("Usage: util::grep_cb(\\@list, $callback_name)");
SV *list_sv = ST(0);
if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
croak("util::grep_cb: first argument must be an arrayref");
}
AV *list = (AV*)SvRV(list_sv);
STRLEN name_len;
const char *name = SvPV(ST(1), name_len);
RegisteredCallback *cb = get_registered_callback(aTHX_ name);
if (!cb) {
croak("util::grep_cb: unknown callback '%s'", name);
}
if (!cb->predicate && !cb->perl_callback) {
croak("util::grep_cb: callback '%s' is not a predicate", name);
}
IV len = av_len(list) + 1;
IV i;
IV count = 0;
/* Collect matching elements in a temporary array first */
AV *results = newAV();
sv_2mortal((SV*)results);
if (cb->predicate) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (svp && cb->predicate(aTHX_ *svp)) {
av_push(results, SvREFCNT_inc(*svp));
count++;
}
}
} else if (cb->perl_callback) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (!svp) continue;
SV *elem = *svp;
bool matches = FALSE;
{
dSP;
int call_count;
SV *result;
ENTER; SAVETMPS;
PUSHMARK(SP);
XPUSHs(elem);
PUTBACK;
call_count = call_sv(cb->perl_callback, G_SCALAR);
SPAGAIN;
if (call_count > 0) {
result = POPs;
matches = SvTRUE(result);
}
PUTBACK;
FREETMPS; LEAVE;
}
if (matches) {
av_push(results, SvREFCNT_inc(elem));
count++;
}
}
}
/* Now push all results to the stack */
SP -= items;
for (i = 0; i < count; i++) {
SV **svp = av_fetch(results, i, 0);
if (svp) {
XPUSHs(sv_2mortal(SvREFCNT_inc(*svp)));
}
}
PUTBACK;
XSRETURN(count);
}
/* count_cb(\@list, ':predicate') - count matching elements */
static XS(xs_count_cb) {
dXSARGS;
if (items != 2) croak("Usage: util::count_cb(\\@list, $callback_name)");
SV *list_sv = ST(0);
if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
croak("util::count_cb: first argument must be an arrayref");
}
AV *list = (AV*)SvRV(list_sv);
STRLEN name_len;
const char *name = SvPV(ST(1), name_len);
RegisteredCallback *cb = get_registered_callback(aTHX_ name);
if (!cb) {
croak("util::count_cb: unknown callback '%s'", name);
}
if (!cb->predicate && !cb->perl_callback) {
croak("util::count_cb: callback '%s' is not a predicate", name);
}
IV len = av_len(list) + 1;
IV i;
IV count = 0;
if (cb->predicate) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (svp && cb->predicate(aTHX_ *svp)) {
count++;
}
}
} else if (cb->perl_callback) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (!svp) continue;
bool matches = FALSE;
{
dSP;
int call_count;
SV *result;
ENTER; SAVETMPS;
PUSHMARK(SP);
XPUSHs(*svp);
PUTBACK;
call_count = call_sv(cb->perl_callback, G_SCALAR);
SPAGAIN;
if (call_count > 0) {
result = POPs;
matches = SvTRUE(result);
}
PUTBACK;
FREETMPS; LEAVE;
}
if (matches) {
count++;
}
}
}
XSRETURN_IV(count);
}
/* partition_cb(\@list, ':predicate') - split into [matches], [non-matches] */
static XS(xs_partition_cb) {
dXSARGS;
if (items != 2) croak("Usage: util::partition_cb(\\@list, $callback_name)");
SV *list_sv = ST(0);
if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
croak("util::partition_cb: first argument must be an arrayref");
}
AV *list = (AV*)SvRV(list_sv);
STRLEN name_len;
const char *name = SvPV(ST(1), name_len);
RegisteredCallback *cb = get_registered_callback(aTHX_ name);
if (!cb) {
croak("util::partition_cb: unknown callback '%s'", name);
}
if (!cb->predicate && !cb->perl_callback) {
croak("util::partition_cb: callback '%s' is not a predicate", name);
}
IV len = av_len(list) + 1;
AV *pass = newAV();
AV *fail = newAV();
av_extend(pass, len >> 1);
av_extend(fail, len >> 1);
IV i;
if (cb->predicate) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (!svp) continue;
if (cb->predicate(aTHX_ *svp)) {
av_push(pass, SvREFCNT_inc_simple_NN(*svp));
} else {
av_push(fail, SvREFCNT_inc_simple_NN(*svp));
}
}
} else if (cb->perl_callback) {
for (i = 0; i < len; i++) {
SV **svp = av_fetch(list, i, 0);
if (!svp) continue;
bool matches = FALSE;
{
dSP;
int call_count;
SV *result;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(*svp);
PUTBACK;
call_count = call_sv(cb->perl_callback, G_SCALAR);
SPAGAIN;
if (call_count > 0) {
result = POPs;
matches = SvTRUE(result);
}
PUTBACK;
FREETMPS;
LEAVE;
}
if (matches) {
av_push(pass, SvREFCNT_inc_simple_NN(*svp));
} else {
av_push(fail, SvREFCNT_inc_simple_NN(*svp));
}
}
}
/* Return list of two arrayrefs */
ST(0) = sv_2mortal(newRV_noinc((SV*)pass));
ST(1) = sv_2mortal(newRV_noinc((SV*)fail));
XSRETURN(2);
}
/* final_cb(\@list, ':predicate') - find last matching element */
static XS(xs_final_cb) {
dXSARGS;
if (items != 2) croak("Usage: util::final_cb(\\@list, $callback_name)");
SV *list_sv = ST(0);
if (!SvROK(list_sv) || SvTYPE(SvRV(list_sv)) != SVt_PVAV) {
croak("util::final_cb: first argument must be an arrayref");
}
AV *list = (AV*)SvRV(list_sv);
STRLEN name_len;
const char *name = SvPV(ST(1), name_len);
RegisteredCallback *cb = get_registered_callback(aTHX_ name);
if (!cb) {
croak("util::final_cb: unknown callback '%s'", name);
}
if (!cb->predicate && !cb->perl_callback) {
croak("util::final_cb: callback '%s' is not a predicate", name);
}
IV len = av_len(list) + 1;
IV i;
if (cb->predicate) {
/* Search from end - C predicate path */
for (i = len - 1; i >= 0; i--) {
SV **svp = av_fetch(list, i, 0);
if (svp && cb->predicate(aTHX_ *svp)) {
ST(0) = *svp;
XSRETURN(1);
}
}
} else if (cb->perl_callback) {
/* Search from end - Perl callback path */
for (i = len - 1; i >= 0; i--) {
SV **svp = av_fetch(list, i, 0);
if (!svp) continue;
bool matches = FALSE;
{
dSP;
int count;
SV *result;
ENTER; SAVETMPS;
PUSHMARK(SP);
XPUSHs(*svp);
PUTBACK;
count = call_sv(cb->perl_callback, G_SCALAR);
SPAGAIN;
if (count > 0) {
result = POPs;
matches = SvTRUE(result);
}
PUTBACK;
FREETMPS; LEAVE;
}
if (matches) {
ST(0) = *svp;
XSRETURN(1);
}
}
}
XSRETURN_UNDEF;
}
/* Perl-level callback registration */
static XS(xs_register_callback) {
dXSARGS;
if (items != 2) croak("Usage: util::register_callback($name, \\&coderef)");
STRLEN name_len;
const char *name = SvPV(ST(0), name_len);
SV *coderef = ST(1);
if (!SvROK(coderef) || SvTYPE(SvRV(coderef)) != SVt_PVCV) {
croak("util::register_callback: second argument must be a coderef");
}
RegisteredCallback *cb;
SV *sv;
init_callback_registry(aTHX);
/* Check if already registered */
if (get_registered_callback(aTHX_ name)) {
croak("Callback '%s' is already registered", name);
}
Newxz(cb, 1, RegisteredCallback);
cb->name = savepv(name);
cb->predicate = NULL;
cb->mapper = NULL;
cb->reducer = NULL;
/* Store a copy of the coderef (RV to CV) */
cb->perl_callback = newSVsv(coderef);
sv = newSViv(PTR2IV(cb));
hv_store(g_callback_registry, name, name_len, sv, 0);
XSRETURN_YES;
}
/* Check if callback exists */
static XS(xs_has_callback) {
dXSARGS;
if (items != 1) croak("Usage: util::has_callback($name)");
STRLEN name_len;
const char *name = SvPV(ST(0), name_len);
if (has_callback(aTHX_ name)) {
XSRETURN_YES;
}
( run in 1.746 second using v1.01-cache-2.11-cpan-13bb782fe5a )