/* Data-Util/DataUtil.xs */ #define NEED_mro_get_linear_isa #include "data-util.h" #define MY_CXT_KEY "Data::Util::_guts" XS_VERSION #define NotReached assert(((void)"PANIC: NOT REACHED", 0)) #define is_special_nv(nv) (nv == NV_INF || nv == -NV_INF || Perl_isnan(nv)) typedef struct{ GV* universal_isa; GV* croak; } my_cxt_t; START_MY_CXT; /* null magic virtual table to identify magic functions */ extern MGVTBL curried_vtbl; extern MGVTBL modified_vtbl; MGVTBL subr_name_vtbl; typedef enum{ T_NOT_REF, T_SV, T_AV, T_HV, T_CV, T_GV, T_IO, T_FM, T_RX, T_OBJECT, T_VALUE, T_STR, T_NUM, T_INT } my_type_t; static const char* const ref_names[] = { NULL, /* NOT_REF */ "a SCALAR reference", "an ARRAY reference", "a HASH reference", "a CODE reference", "a GLOB reference", NULL, /* IO */ NULL, /* FM */ "a regular expression reference", /* RX */ NULL /* OBJECT */ }; static void my_croak(pTHX_ const char* const fmt, ...) __attribute__format__(__printf__, pTHX_1, pTHX_2); static void my_croak(pTHX_ const char* const fmt, ...){ dMY_CXT; dSP; SV* message; va_list args; ENTER; SAVETMPS; if(!MY_CXT.croak){ Perl_load_module(aTHX_ PERL_LOADMOD_NOIMPORT, newSVpvs("Data::Util::Error"), NULL, NULL); MY_CXT.croak = CvGV(get_cv("Data::Util::Error::croak", GV_ADD)); SvREFCNT_inc_simple_void_NN(MY_CXT.croak); } va_start(args, fmt); message = vnewSVpvf(fmt, &args); va_end(args); PUSHMARK(SP); mXPUSHs(message); PUTBACK; call_sv((SV*)MY_CXT.croak, G_VOID); NotReached; /* FREETMPS; LEAVE; */ } static void my_fail(pTHX_ const char* const name, SV* value){ my_croak(aTHX_ "Validation failed: you must supply %s, not %s", name, neat(value)); } static int S_nv_is_integer(pTHX_ NV const nv) { if(nv == (NV)(IV)nv){ return TRUE; } else { char buf[64]; /* Must fit sprintf/Gconvert of longest NV */ char* p; (void)Gconvert(nv, NV_DIG, 0, buf); p = &buf[0]; /* -?[0-9]+ */ if(*p == '-') p++; while(*p){ if(!isDIGIT(*p)){ return FALSE; } p++; } return TRUE; } } static int my_check_type_primitive(pTHX_ SV* const sv, const my_type_t t){ if(!SvOK(sv) || SvROK(sv) || isGV(sv)){ return FALSE; } switch(t){ case T_INT: /* check POK, NOK and IOK respectively */ if(SvPOKp(sv)){ int const num_type = grok_number(SvPVX(sv), SvCUR(sv), NULL); if(num_type && !strEQ(SvPVX(sv), "0 but true")){ return !(num_type & IS_NUMBER_NOT_INT); } } else if(SvNOKp(sv)){ NV const nv = SvNVX(sv); return S_nv_is_integer(aTHX_ nv); } else if(SvIOKp(sv)){ return TRUE; } break; case T_NUM: if(SvPOKp(sv)){ int const num_type = grok_number(SvPVX(sv), SvCUR(sv), NULL); if(num_type && !strEQ(SvPVX(sv), "0 but true")){ return !(num_type & (IS_NUMBER_INFINITY | IS_NUMBER_NAN)); } } else if(SvNOKp(sv)){ NV const nv = SvNVX(sv); return !is_special_nv(nv); } else if(SvIOKp(sv)){ return TRUE; } break; case T_STR: if(SvPOKp(sv)){ return SvCUR(sv) > 0; } /* fall throught */ default:/* T_VALUE */ return TRUE; } return FALSE; } static bool my_has_amagic_converter(pTHX_ SV* const sv, const my_type_t t){ const AMT* amt; int o = 0; if(!SvAMAGIC(sv)) return FALSE; amt = (AMT*)mg_find((SV*)SvSTASH(SvRV(sv)), PERL_MAGIC_overload_table)->mg_ptr; assert(amt); assert(AMT_AMAGIC(amt)); switch(t){ case T_SV: o = to_sv_amg; break; case T_AV: o = to_av_amg; break; case T_HV: o = to_hv_amg; break; case T_CV: o = to_cv_amg; break; case T_GV: o = to_gv_amg; break; default: NotReached; } return amt->table[o] ? TRUE : FALSE; } #define check_type(sv, t) my_check_type(aTHX_ sv, t) static int my_check_type(pTHX_ SV* const sv, const my_type_t t){ if(!SvROK(sv)){ return FALSE; } if(SvOBJECT(SvRV(sv))){ if(t == T_RX){ /* regex? */ return SvRXOK(sv); } else{ return my_has_amagic_converter(aTHX_ sv, t); } } switch(SvTYPE(SvRV(sv))){ case SVt_PVAV: return T_AV == t; case SVt_PVHV: return T_HV == t; case SVt_PVCV: return T_CV == t; case SVt_PVGV: return T_GV == t; case SVt_PVIO: return T_IO == t; case SVt_PVFM: return T_FM == t; default: NOOP; } return T_SV == t; } #define deref_av(sv) my_deref_av(aTHX_ sv) #define deref_hv(sv) my_deref_hv(aTHX_ sv) #define deref_cv(sv) my_deref_cv(aTHX_ sv) static AV* my_deref_av(pTHX_ SV* sv){ SvGETMAGIC(sv); if(my_has_amagic_converter(aTHX_ sv, T_AV)){ SV* const* sp = &sv; /* used in tryAMAGICunDEREF macro */ tryAMAGICunDEREF(to_av); } if(!(SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV)){ my_fail(aTHX_ ref_names[T_AV], sv); } return (AV*)SvRV(sv); } static HV* my_deref_hv(pTHX_ SV* sv){ SvGETMAGIC(sv); if(my_has_amagic_converter(aTHX_ sv, T_HV)){ SV* const* sp = &sv; /* used in tryAMAGICunDEREF macro */ tryAMAGICunDEREF(to_hv); } if(!(SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV)){ my_fail(aTHX_ ref_names[T_HV], sv); } return (HV*)SvRV(sv); } static CV* my_deref_cv(pTHX_ SV* sv){ SvGETMAGIC(sv); if(my_has_amagic_converter(aTHX_ sv, T_CV)){ SV* const* sp = &sv; /* used in tryAMAGICunDEREF macro */ tryAMAGICunDEREF(to_cv); } if(!(SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVCV)){ my_fail(aTHX_ ref_names[T_CV], sv); } return (CV*)SvRV(sv); } #define validate(sv, t) my_validate(aTHX_ sv, t) static SV* my_validate(pTHX_ SV* const sv, my_type_t const ref_type){ SvGETMAGIC(sv); if(!check_type(sv, ref_type)){ my_fail(aTHX_ ref_names[ref_type], sv); } return sv; } static SV* my_string(pTHX_ SV* const sv, const char* const name){ SvGETMAGIC(sv); if(!is_string(sv)) my_fail(aTHX_ name, sv); return sv; } static const char* my_canon_pkg(pTHX_ const char* name){ /* "::Foo" -> "Foo" */ if(name[0] == ':' && name[1] == ':'){ name += 2; } /* "main::main::main::Foo" -> "Foo" */ while(strnEQ(name, "main::", sizeof("main::")-1)){ name += sizeof("main::")-1; } return name; } static int my_isa_lookup(pTHX_ HV* const stash, const char* klass_name){ const char* const stash_name = my_canon_pkg(aTHX_ HvNAME_get(stash)); klass_name = my_canon_pkg(aTHX_ klass_name); if(strEQ(stash_name, klass_name)){ return TRUE; } else if(strEQ(klass_name, "UNIVERSAL")){ return TRUE; } else{ AV* const linearized_isa = mro_get_linear_isa(stash); SV** svp = AvARRAY(linearized_isa) + 1; /* skip this class */ SV** const end = svp + AvFILLp(linearized_isa); /* start + 1 + last index */ while(svp != end){ if(strEQ(klass_name, my_canon_pkg(aTHX_ SvPVX(*svp)))){ return TRUE; } svp++; } } return FALSE; } static int my_instance_of(pTHX_ SV* const x, SV* const klass){ if( !is_string(klass) ){ my_fail(aTHX_ "a class name", klass); } if( SvROK(x) && SvOBJECT(SvRV(x)) ){ dMY_CXT; HV* const stash = SvSTASH(SvRV(x)); GV* const isa = gv_fetchmeth_autoload(stash, "isa", sizeof("isa")-1, 0 /* special zero, not flags nor bool */); /* common cases */ if(isa == NULL || GvCV(isa) == GvCV(MY_CXT.universal_isa)){ return my_isa_lookup(aTHX_ stash, SvPV_nolen_const(klass)); } /* special cases */ /* call their own ->isa() method */ { int retval; dSP; ENTER; SAVETMPS; PUSHMARK(SP); EXTEND(SP, 2); PUSHs(x); PUSHs(klass); PUTBACK; call_sv((SV*)isa, G_SCALAR | G_METHOD); SPAGAIN; retval = SvTRUE(TOPs); (void)POPs; PUTBACK; FREETMPS; LEAVE; return retval; } } return FALSE; } #define type_isa(sv, type) my_type_isa(aTHX_ sv, type) static bool my_type_isa(pTHX_ SV* const sv, SV* const type){ const char* const typestr = SvPV_nolen_const(type); switch(typestr[0]){ case 'S': if(strEQ(typestr, "SCALAR")){ return check_type(sv, T_SV); } break; case 'A': if(strEQ(typestr, "ARRAY")){ return check_type(sv, T_AV); } break; case 'H': if(strEQ(typestr, "HASH")){ return check_type(sv, T_HV); } break; case 'C': if(strEQ(typestr, "CODE")){ return check_type(sv, T_CV); } break; case 'G': if(strEQ(typestr, "GLOB")){ return check_type(sv, T_GV); } break; } return my_instance_of(aTHX_ sv, type); } static void my_opt_add(pTHX_ AV* const result_av, HV* const result_hv, SV* const moniker, SV* const name, SV* const value, bool const with_validation, SV* vsv, AV* vav, HV* const vhv ){ if(with_validation && SvOK(value)){ if(vhv){ HE* const he = hv_fetch_ent(vhv, name, FALSE, 0U); vav = NULL; if(he){ SV* const sv = hv_iterval(vhv, he); if(check_type(sv, T_AV)){ vav = deref_av(sv); } else if(SvOK(sv)){ vsv = sv; } else{ goto store_pair; } } else{ goto store_pair; } } if(vav){ I32 const len = av_len(vav)+1; I32 i; for(i = 0; i < len; i++){ if(type_isa(value, *av_fetch(vav, i, TRUE))){ break; } } if(i == len) goto validation_failed; } else{ if(!type_isa(value, vsv)){ validation_failed: my_croak(aTHX_ "%s-ref values are not valid for %"SVf" in %"SVf" opt list", sv_reftype(SvRV(value), TRUE), name, moniker); } } } store_pair: if(result_av){ /* push @result, [$name => $value] */ SV* pair[2]; pair[0] = name; pair[1] = value; av_push(result_av, newRV_noinc((SV*) av_make(2, pair))); } else{ /* $result{$name} = $value */ (void)hv_store_ent(result_hv, name, newSVsv(value), 0U); } } static SV* my_mkopt(pTHX_ SV* const opt_list, SV* const moniker, const bool require_unique, SV* must_be, const my_type_t result_type){ SV* ret; AV* result_av = NULL; HV* result_hv = NULL; HV* vhv = NULL; /* validator HV */ AV* vav = NULL; /* validator AV */ bool const with_validation = SvOK(must_be) ? TRUE : FALSE; if(with_validation){ if(check_type(must_be, T_HV)){ vhv = deref_hv(must_be); } else if(check_type(must_be, T_AV)){ vav = deref_av(must_be); } else if(!is_string(must_be)){ my_fail(aTHX_ "type constraints", must_be); } } if(result_type == T_AV){ result_av = newAV(); ret = (SV*)result_av; } else{ result_hv = newHV(); ret = (SV*)result_hv; } sv_2mortal(ret); if(check_type(opt_list, T_AV)){ HV* seen = NULL; AV* const opt_av = deref_av(opt_list); I32 const len = av_len(opt_av) + 1; I32 i; if(require_unique){ seen = newHV(); sv_2mortal((SV*)seen); } for(i = 0; i < len; i++){ SV* const name = my_string(aTHX_ *av_fetch(opt_av, i, TRUE), "an option name"); SV* value; if(require_unique){ HE* const he = hv_fetch_ent(seen, name, TRUE, 0U); SV* const count = hv_iterval(seen, he); if(SvTRUE(count)){ my_croak(aTHX_ "Multiple definitions provided for %"SVf" in %"SVf" opt list", name, moniker); } sv_inc(count); /* count++ */ } if( (i+1) == len ){ /* last */ value = &PL_sv_undef; } else{ value = *av_fetch(opt_av, i+1, TRUE); if(SvROK(value) || !SvOK(value)){ i++; } else{ value = &PL_sv_undef; } } my_opt_add(aTHX_ result_av, result_hv, moniker, name, value, with_validation, must_be, vav, vhv); } } else if(check_type(opt_list, T_HV)){ HV* const opt_hv = deref_hv(opt_list); I32 keylen; char* key; SV* value; SV* const name = sv_newmortal(); hv_iterinit(opt_hv); while((value = hv_iternextsv(opt_hv, &key, &keylen))){ sv_setpvn(name, key, keylen); /* copied in my_opt_add */ if(!SvROK(value) && SvOK(value)){ value = &PL_sv_undef; } my_opt_add(aTHX_ result_av, result_hv, moniker, name, value, with_validation, must_be, vav, vhv); } } else if(SvOK(opt_list)){ my_fail(aTHX_ "an ARRAY or HASH reference", opt_list); } return newRV_inc(ret); } /* $code = curry($_, (my $tmp = $code_ref), *_) for @around; */ static SV* my_build_around_code(pTHX_ SV* code_ref, AV* const around){ I32 i; for(i = av_len(around); i >= 0; i--){ CV* current; MAGIC* mg; SV* const sv = validate(*av_fetch(around, i, TRUE), T_CV); AV* const params = newAV(); AV* const placeholders = newAV(); av_store(params, 0, newSVsv(sv)); /* base proc */ av_store(params, 1, newSVsv(code_ref)); /* first argument (next proc) */ av_store(params, 2, &PL_sv_undef); /* placeholder hole */ av_store(placeholders, 2, (SV*)PL_defgv); // *_ SvREFCNT_inc_simple_void_NN(PL_defgv); current = newXS(NULL /* anonymous */, XS_Data__Util_curried, __FILE__); mg = sv_magicext((SV*)current, (SV*)params, PERL_MAGIC_ext, &curried_vtbl, (const char*)placeholders, HEf_SVKEY); SvREFCNT_dec(params); /* because: refcnt++ in sv_magicext() */ SvREFCNT_dec(placeholders); /* because: refcnt++ in sv_magicext() */ CvXSUBANY(current).any_ptr = (void*)mg; code_ref = newRV_noinc((SV*)current); sv_2mortal(code_ref); } return newSVsv(code_ref); } static void my_gv_setsv(pTHX_ GV* const gv, SV* const sv){ ENTER; SAVETMPS; sv_setsv_mg((SV*)gv, sv_2mortal(newRV_inc((sv)))); FREETMPS; LEAVE; } static void my_install_sub(pTHX_ HV* const stash, const char* const name, STRLEN const namelen, SV* code_ref){ CV* const code = deref_cv(code_ref); GV* const gv = (GV*)*hv_fetch(stash, name, namelen, TRUE); if(!isGV(gv)) gv_init(gv, stash, name, namelen, GV_ADDMULTI); my_gv_setsv(aTHX_ gv, (SV*)code); /* *foo = \&bar */ if(CvANON(code) && CvGV(code) /* under construction? */ && isGV(CvGV(code)) /* released? */){ /* rename cv with gv */ CvGV_set(code, gv); CvANON_off(code); } } static void my_uninstall_sub(pTHX_ HV* const stash, const char* const name, STRLEN const namelen, SV* const specified_code_ref){ GV** const gvp = (GV**)hv_fetch(stash, name, namelen, FALSE); if(gvp){ GV* const gv = *gvp; CV* const specified_code = SvOK(specified_code_ref) ? deref_cv(specified_code_ref) : NULL; GV* newgv; CV* code; if(!isGV(gv)){ /* a subroutine stub or special constant*/ if(SvROK((SV*)gv) && ckWARN(WARN_MISC)){ Perl_warner(aTHX_ packWARN(WARN_MISC), "Constant subroutine %s uninstalled", name); } (void)hv_delete(stash, name, namelen, G_DISCARD); return; } if(!(code = GvCVu(gv))){ return; } /* when an uninstalled subroutine is supplied ... */ if( specified_code && specified_code != code ){ return; /* skip */ } if(CvCONST(code) && ckWARN(WARN_MISC)){ Perl_warner(aTHX_ packWARN(WARN_MISC), "Constant subroutine %s uninstalled", name); } (void)hv_delete(stash, name, namelen, G_DISCARD); if(SvREFCNT(gv) == 0 || !( GvSV(gv) || GvAV(gv) || GvHV(gv) || GvIO(gv) || GvFORM(gv))){ return; /* no need to retrieve gv */ } newgv = (GV*)*hv_fetch(stash, name, namelen, TRUE); gv_init(newgv, stash, name, namelen, GV_ADDMULTI); /* vivify */ /* restore all slots other than GvCV */ if(GvSV(gv)) my_gv_setsv(aTHX_ newgv, GvSV(gv)); if(GvAV(gv)) my_gv_setsv(aTHX_ newgv, (SV*)GvAV(gv)); if(GvHV(gv)) my_gv_setsv(aTHX_ newgv, (SV*)GvHV(gv)); if(GvIO(gv)) my_gv_setsv(aTHX_ newgv, (SV*)GvIOp(gv)); if(GvFORM(gv)) my_gv_setsv(aTHX_ newgv, (SV*)GvFORM(gv)); } } static void initialize_my_cxt(pTHX_ my_cxt_t* const cxt){ cxt->universal_isa = CvGV(get_cv("UNIVERSAL::isa", GV_ADD)); SvREFCNT_inc_simple_void_NN(cxt->universal_isa); cxt->croak = NULL; } #define UNDEF &PL_sv_undef MODULE = Data::Util PACKAGE = Data::Util PROTOTYPES: DISABLE BOOT: { MY_CXT_INIT; initialize_my_cxt(aTHX_ &MY_CXT); } void CLONE(...) CODE: MY_CXT_CLONE; initialize_my_cxt(aTHX_ &MY_CXT); PERL_UNUSED_VAR(items); #define T_RX_deprecated T_RX void is_scalar_ref(x) SV* x ALIAS: is_scalar_ref = T_SV is_array_ref = T_AV is_hash_ref = T_HV is_code_ref = T_CV is_glob_ref = T_GV is_regex_ref = T_RX_deprecated is_rx = T_RX CODE: SvGETMAGIC(x); ST(0) = boolSV(check_type(x, (my_type_t)ix)); XSRETURN(1); void scalar_ref(x) SV* x ALIAS: scalar_ref = T_SV array_ref = T_AV hash_ref = T_HV code_ref = T_CV glob_ref = T_GV regex_ref = T_RX_deprecated rx = T_RX CODE: SvGETMAGIC(x); if(check_type(x, (my_type_t)ix)){ XSRETURN(1); /* return the first value */ } my_fail(aTHX_ ref_names[ix], x); void is_instance(x, klass) SV* x SV* klass CODE: SvGETMAGIC(x); SvGETMAGIC(klass); ST(0) = boolSV(my_instance_of(aTHX_ x, klass)); XSRETURN(1); void instance(x, klass) SV* x SV* klass CODE: SvGETMAGIC(x); SvGETMAGIC(klass); if( my_instance_of(aTHX_ x, klass) ){ XSRETURN(1); /* return $_[0] */ } my_croak(aTHX_ "Validation failed: you must supply an instance of %"SVf", not %s", klass, neat(x)); void invocant(x) SV* x ALIAS: is_invocant = 0 invocant = 1 PREINIT: bool result; CODE: SvGETMAGIC(x); if(SvROK(x)){ result = SvOBJECT(SvRV(x)) ? TRUE : FALSE; } else if(is_string(x)){ result = gv_stashsv(x, FALSE) ? TRUE : FALSE; } else{ result = FALSE; } if(ix == 0){ /* is_invocant() */ ST(0) = boolSV(result); XSRETURN(1); } else{ /* invocant() */ if(result){ /* XXX: do{ package ::Foo; ::Foo->something; } causes an fatal error */ if(!SvROK(x)){ dXSTARG; sv_setsv(TARG, x); /* copy the pv and flags */ sv_setpv(TARG, my_canon_pkg(aTHX_ SvPV_nolen_const(x))); ST(0) = TARG; } XSRETURN(1); } my_fail(aTHX_ "an invocant", x); } void is_value(x) SV* x ALIAS: is_value = T_VALUE is_string = T_STR is_number = T_NUM is_integer = T_INT CODE: SvGETMAGIC(x); ST(0) = boolSV(my_check_type_primitive(aTHX_ x, (my_type_t)ix)); XSRETURN(1); HV* get_stash(invocant) SV* invocant CODE: SvGETMAGIC(invocant); if(SvROK(invocant) && SvOBJECT(SvRV(invocant))){ RETVAL = SvSTASH(SvRV(invocant)); } else if(is_string(invocant)){ RETVAL = gv_stashsv(invocant, FALSE); } else{ RETVAL = NULL; } if(!RETVAL){ XSRETURN_UNDEF; } OUTPUT: RETVAL SV* anon_scalar(referent = undef) CODE: RETVAL = newRV_noinc(items == 0 ? newSV(0) : newSVsv(ST(0))); OUTPUT: RETVAL const char* neat(expr) SV* expr void install_subroutine(into, ...) SV* into PREINIT: HV* stash; int i; CODE: stash = gv_stashsv(my_string(aTHX_ into, "a package name"), TRUE); if(items == 2){ HV* const hv = deref_hv(ST(1)); I32 namelen; char* name; SV* code_ref; hv_iterinit(hv); while((code_ref = hv_iternextsv(hv, &name, &namelen))){ my_install_sub(aTHX_ stash, name, namelen, code_ref); } } else{ if( ((items-1) % 2) != 0 ){ my_croak(aTHX_ "Odd number of arguments for %s", GvNAME(CvGV(cv))); } for(i = 1; i < items; i += 2){ SV* const as = my_string(aTHX_ ST(i), "a subroutine name"); STRLEN namelen; const char* const name = SvPV_const(as, namelen); SV* const code_ref = ST(i+1); my_install_sub(aTHX_ stash, name, namelen, code_ref); } } void uninstall_subroutine(package, ...) SV* package PREINIT: HV* stash; int i; CODE: stash = gv_stashsv(my_string(aTHX_ package, "a package name"), FALSE); if(!stash) XSRETURN_EMPTY; if(items == 2 && SvROK(ST(1))){ HV* const hv = deref_hv(ST(1)); I32 namelen; char* name; SV* specified_code_ref; hv_iterinit(hv); while((specified_code_ref = hv_iternextsv(hv, &name, &namelen))){ my_uninstall_sub(aTHX_ stash, name, namelen, specified_code_ref); } } else{ for(i = 1; i < items; i++){ SV* const namesv = my_string(aTHX_ ST(i), "a subroutine name"); STRLEN namelen; const char* const name = SvPV_const(namesv, namelen); SV* specified_code_ref; if( (i+1) < items && SvROK(ST(i+1)) ){ i++; specified_code_ref = ST(i); } else{ specified_code_ref = &PL_sv_undef; } my_uninstall_sub(aTHX_ stash, name, namelen, specified_code_ref); } } mro_method_changed_in(stash); void get_code_info(code) CV* code PREINIT: GV* gv; HV* stash; PPCODE: if( (gv = CvGV(code)) && isGV_with_GP(gv) && (stash = (GvSTASH(gv))) && HvNAME_get(stash) ){ if(GIMME_V == G_ARRAY){ EXTEND(SP, 2); mPUSHs(newSVpvn_share(HvNAME_get(stash), HvNAMELEN_get(stash), 0U)); mPUSHs(newSVpvn_share(GvNAME(gv), GvNAMELEN(gv), 0U)); } else{ SV* const sv = newSVpvf("%s::%s", HvNAME_get(stash), GvNAME(gv)); mXPUSHs(sv); } } SV* get_code_ref(package, name, ...) SV* package SV* name INIT: I32 flags = 0; RETVAL = &PL_sv_undef; CODE: (void)my_string(aTHX_ package, "a package name"); (void)my_string(aTHX_ name, "a subroutine name"); if(items > 2){ /* with flags */ I32 i; for(i = 2; i < items; i++){ SV* const sv = my_string(aTHX_ ST(i), "a flag"); if(strEQ(SvPV_nolen_const(sv), "-create")){ flags |= GV_ADD; } else{ my_fail(aTHX_ "a flag", sv); } } } { HV* const stash = gv_stashsv(package, flags); if(stash){ STRLEN len; const char* const pv = SvPV_const(name, len); GV** const gvp = (GV**)hv_fetch(stash, pv, len, flags); GV* const gv = gvp ? *gvp : NULL; if(gv){ if(!isGV(gv)) gv_init(gv, stash, pv, len, GV_ADDMULTI); if(GvCVu(gv)){ RETVAL = newRV_inc((SV*)GvCV(gv)); } else if(flags & GV_ADD){ SV* const sv = Perl_newSVpvf(aTHX_ "%"SVf"::%"SVf, package, name); /* from Perl_get_cvn_flags() in perl.c */ CV* const cv = newSUB( start_subparse(FALSE, 0), newSVOP(OP_CONST, 0, sv), NULL, NULL); RETVAL = newRV_inc((SV*)cv); } } } } OUTPUT: RETVAL SV* curry(code, ...) SV* code PREINIT: CV* curried; AV* params; AV* placeholders; U16 is_method; I32 i; MAGIC* mg; CODE: SvGETMAGIC(code); is_method = check_type(code, T_CV) ? 0 : G_METHOD; params = newAV(); placeholders = newAV(); av_extend(params, items-1); av_extend(placeholders, items-1); for(i = 0; i < items; i++){ SV* const sv = ST(i); SvGETMAGIC(sv); if(SvROK(sv) && SvIOKp(SvRV(sv)) && !SvOBJECT(SvRV(sv))){ // \0, \1, ... av_store(params, i, &PL_sv_undef); av_store(placeholders, i, newSVsv(SvRV(sv))); } else if(sv == (SV*)PL_defgv){ // *_ (always *main::_) av_store(params, i, &PL_sv_undef); av_store(placeholders, i, sv); /* not copy */ SvREFCNT_inc_simple_void_NN(sv); } else{ av_store(params, i, sv); /* not copy */ av_store(placeholders, i, &PL_sv_undef); SvREFCNT_inc_simple_void_NN(sv); } } curried = newXS(NULL /* anonymous */, XS_Data__Util_curried, __FILE__); mg = sv_magicext((SV*)curried, (SV*)params, PERL_MAGIC_ext, &curried_vtbl, (const char*)placeholders, HEf_SVKEY); SvREFCNT_dec((SV*)params); /* refcnt++ in sv_magicext() */ SvREFCNT_dec((SV*)placeholders); /* refcnt++ in sv_magicext() */ mg->mg_private = is_method; CvXSUBANY(curried).any_ptr = mg; RETVAL = newRV_noinc((SV*)curried); OUTPUT: RETVAL SV* modify_subroutine(code, ...) SV* code PREINIT: CV* modified; AV* before; AV* around; AV* after; AV* modifiers; /* (before, around, after, original, current) */ I32 i; MAGIC* mg; CODE: validate(code, T_CV); if( ((items - 1) % 2) != 0 ){ my_croak(aTHX_ "Odd number of arguments for %s", GvNAME(CvGV(cv))); } before = newAV(); sv_2mortal((SV*)before); around = newAV(); sv_2mortal((SV*)around); after = newAV(); sv_2mortal((SV*)after ); for(i = 1; i < items; i += 2){ /* modifier_type => [subroutine(s)] */ SV* const mtsv = my_string(aTHX_ ST(i), "a modifier type"); const char* const modifier_type = SvPV_nolen_const(mtsv); AV* const subs = deref_av(ST(i+1)); I32 const subs_len = av_len(subs) + 1; AV* av = NULL; I32 j; if(strEQ(modifier_type, "before")){ av = before; } else if(strEQ(modifier_type, "around")){ av = around; } else if(strEQ(modifier_type, "after")){ av = after; } else{ my_fail(aTHX_ "a modifier type", mtsv); } av_extend(av, AvFILLp(av) + subs_len - 1); for(j = 0; j < subs_len; j++){ SV* const code_ref = newSVsv(validate(*av_fetch(subs, j, TRUE), T_CV)); av_push(av, code_ref); } } modifiers = newAV(); av_extend(modifiers, 3); av_store(modifiers, M_CURRENT, my_build_around_code(aTHX_ code, around)); av_store(modifiers, M_BEFORE, SvREFCNT_inc_simple_NN(before)); av_store(modifiers, M_AROUND, SvREFCNT_inc_simple_NN(around)); av_store(modifiers, M_AFTER, SvREFCNT_inc_simple_NN(after)); modified = newXS(NULL /* anonymous */, XS_Data__Util_modified, __FILE__); mg = sv_magicext((SV*)modified, (SV*)modifiers, PERL_MAGIC_ext, &modified_vtbl, NULL, 0); SvREFCNT_dec((SV*)modifiers); /* refcnt++ in sv_magicext() */ CvXSUBANY(modified).any_ptr = (void*)mg; RETVAL = newRV_noinc((SV*)modified); OUTPUT: RETVAL void subroutine_modifier(code, ...) CV* code PREINIT: /* Usage: subroutine_modifier(code) # check subroutine_modifier(code, property) # get subroutine_modifier(code, property, subs) # set */ MAGIC* mg; AV* modifiers; /* (before, around, after, original, current) */ SV* property; const char* property_pv; PPCODE: mg = mg_find_by_vtbl((SV*)code, &modified_vtbl); if(items == 1){ /* check only */ ST(0) = boolSV(mg); XSRETURN(1); } if(!mg){ my_fail(aTHX_ "a modified subroutine", ST(0) /* ref to code */); } modifiers = (AV*)mg->mg_obj; assert(modifiers); property = my_string(aTHX_ ST(1), "a modifier property"); property_pv = SvPV_nolen_const(property); if(strEQ(property_pv, "before") || strEQ(property_pv, "around") || strEQ(property_pv, "after")){ I32 const idx = strEQ(property_pv, "before") ? M_BEFORE : strEQ(property_pv, "around") ? M_AROUND : M_AFTER; AV* const av = (AV*)*av_fetch(modifiers, idx, FALSE); if(items != 2){ /* add */ I32 i; for(i = 2; i < items; i++){ SV* const code_ref = newSVsv(validate(ST(i), T_CV)); if(idx == M_AFTER){ av_push(av, code_ref); } else{ av_unshift(av, 1); av_store(av, 0, code_ref); } } if(idx == M_AROUND){ AV* const around = (AV*)sv_2mortal((SV*)av_make(items-2, &ST(2))); SV* const current = my_build_around_code(aTHX_ *av_fetch(modifiers, M_CURRENT, FALSE), around ); av_store(modifiers, M_CURRENT, current); } } XPUSHary(AvARRAY(av), 0, AvFILLp(av)+1); } else{ my_fail(aTHX_ "a modifier property", property); } #define mkopt(opt_list, moniker, require_unique, must_be) \ my_mkopt(aTHX_ opt_list, moniker, require_unique, must_be, T_AV) #define mkopt_hash(opt_list, moniker, must_be) \ my_mkopt(aTHX_ opt_list, moniker, TRUE, must_be, T_HV) SV* mkopt(opt_list = UNDEF, moniker = UNDEF, require_unique = FALSE, must_be = UNDEF) SV* opt_list SV* moniker bool require_unique SV* must_be SV* mkopt_hash(opt_list = UNDEF, moniker = UNDEF, must_be = UNDEF) SV* opt_list SV* moniker SV* must_be