X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=gitmo%2FMouse.git;a=blobdiff_plain;f=xs-src%2FMouseTypeConstraints.xs;h=48fa08e530250ad84d3d46388eb5409f6177df80;hp=ffae8159ffd1e3943ad7d3c00ba9dc23735192f2;hb=f790c46b83718b0665e24380b0df0c387925ea27;hpb=646c0371bdfda5817f842c54577c4d5605a4c3c0 diff --git a/xs-src/MouseTypeConstraints.xs b/xs-src/MouseTypeConstraints.xs index ffae815..48fa08e 100644 --- a/xs-src/MouseTypeConstraints.xs +++ b/xs-src/MouseTypeConstraints.xs @@ -14,12 +14,27 @@ #define SvRXOK(sv) (SvROK(sv) && SvMAGICAL(SvRV(sv)) && mg_find(SvRV(sv), PERL_MAGIC_qr)) #endif +typedef int (*check_fptr_t)(pTHX_ SV* const data, SV* const sv); + int mouse_tc_check(pTHX_ SV* const tc_code, SV* const sv) { - if(SvIOK(tc_code)){ /* built-in type constraints */ - return mouse_builtin_tc_check(aTHX_ SvIVX(tc_code), sv); + CV* const cv = (CV*)SvRV(tc_code); + assert(SvTYPE(cv) == SVt_PVCV); + + if(CvXSUB(cv) == XS_Mouse__Util__TypeConstraints_Item){ /* built-in */ + assert(CvXSUBANY(cv).any_iv > 0); + + return mouse_builtin_tc_check(aTHX_ CvXSUBANY(cv).any_iv, sv); + } + else if(CvXSUB(cv) == XS_Mouse_parameterized_check){ /* built-in, parameterizad */ + MAGIC* const mg = (MAGIC*)CvXSUBANY(cv).any_ptr; + + assert(CvXSUBANY(cv).any_ptr != NULL); + + /* call the check function directly, skipping call_sv() */ + return CALL_FPTR((check_fptr_t)mg->mg_ptr)(aTHX_ mg->mg_obj, sv); } - else { + else { /* custom */ int ok; dSP; @@ -48,6 +63,7 @@ mouse_builtin_tc_check(pTHX_ mouse_tc const tc, SV* const sv) { switch(tc){ case MOUSE_TC_ANY: return mouse_tc_Any(aTHX_ sv); case MOUSE_TC_ITEM: return mouse_tc_Any(aTHX_ sv); + case MOUSE_TC_MAYBE: return mouse_tc_Any(aTHX_ sv); case MOUSE_TC_UNDEF: return mouse_tc_Undef(aTHX_ sv); case MOUSE_TC_DEFINED: return mouse_tc_Defined(aTHX_ sv); case MOUSE_TC_BOOL: return mouse_tc_Bool(aTHX_ sv); @@ -67,11 +83,10 @@ mouse_builtin_tc_check(pTHX_ mouse_tc const tc, SV* const sv) { case MOUSE_TC_CLASS_NAME: return mouse_tc_ClassName(aTHX_ sv); case MOUSE_TC_ROLE_NAME: return mouse_tc_RoleName(aTHX_ sv); default: - /* custom type constraints */ NOOP; } - croak("Custom type constraint is not yet implemented"); + croak("Mouse-panic: unrecognized type constraint id: %d", (int)tc); return FALSE; /* not reached */ } @@ -90,22 +105,23 @@ mouse_tc_Any(pTHX_ SV* const sv PERL_UNUSED_DECL) { int mouse_tc_Bool(pTHX_ SV* const sv) { assert(sv); - if(SvOK(sv)){ + + if(SvTRUE(sv)){ if(SvIOKp(sv)){ - return SvIVX(sv) == 1 || SvIVX(sv) == 0; + return SvIVX(sv) == 1; } else if(SvNOKp(sv)){ - return SvNVX(sv) == 1.0 || SvNVX(sv) == 0.0; + return SvNVX(sv) == 1.0; } - else if(SvPOKp(sv)){ /* "" or "1" or "0" */ - return SvCUR(sv) == 0 - || ( SvCUR(sv) == 1 && ( SvPVX(sv)[0] == '1' || SvPVX(sv)[0] == '0' ) ); + else if(SvPOKp(sv)){ /* "1" */ + return SvCUR(sv) == 1 && SvPVX(sv)[0] == '1'; } else{ return FALSE; } } else{ + /* false must be boolean */ return TRUE; } } @@ -184,7 +200,7 @@ mouse_tc_RoleName(pTHX_ SV* const sv) { meta = POPs; PUTBACK; - ok = is_instance_of(meta, newSVpvs_flags("Mouse::Meta::Role", SVs_TEMP)); + ok = is_an_instance_of("Mouse::Meta::Role", meta); FREETMPS; LEAVE; @@ -252,7 +268,7 @@ mouse_tc_FileHandle(pTHX_ SV* const sv) { } } - return is_instance_of(sv, newSVpvs_flags("IO::Handle", SVs_TEMP)); + return is_an_instance_of("IO::Handle", sv); } int @@ -261,6 +277,88 @@ mouse_tc_Object(pTHX_ SV* const sv) { return SvROK(sv) && SvOBJECT(SvRV(sv)) && !SvRXOK(sv); } +/* Parameterized type constraints */ + +static int +mouse_parameterized_ArrayRef(pTHX_ SV* const param, SV* const sv) { + if(mouse_tc_ArrayRef(aTHX_ sv)){ + AV* const av = (AV*)SvRV(sv); + I32 const len = av_len(av) + 1; + I32 i; + for(i = 0; i < len; i++){ + SV* const value = *av_fetch(av, i, TRUE); + SvGETMAGIC(value); + if(!mouse_tc_check(aTHX_ param, value)){ + return FALSE; + } + } + return TRUE; + } + return FALSE; +} + +static int +mouse_parameterized_HashRef(pTHX_ SV* const param, SV* const sv) { + if(mouse_tc_HashRef(aTHX_ sv)){ + HV* const hv = (HV*)SvRV(sv); + HE* he; + + hv_iterinit(hv); + while((he = hv_iternext(hv))){ + SV* const value = hv_iterval(hv, he); + SvGETMAGIC(value); + if(!mouse_tc_check(aTHX_ param, value)){ + return FALSE; + } + } + return TRUE; + } + return FALSE; +} + +static int +mouse_parameterized_Maybe(pTHX_ SV* const param, SV* const sv) { + if(SvOK(sv)){ + return mouse_tc_check(aTHX_ param, sv); + } + return TRUE; +} + +static int +mouse_types_union_check(pTHX_ AV* const types, SV* const sv) { + I32 const len = AvFILLp(types) + 1; + I32 i; + + for(i = 0; i < len; i++){ + if(mouse_tc_check(aTHX_ AvARRAY(types)[i], sv)){ + return TRUE; + } + } + + return FALSE; +} + +static int +mouse_types_check(pTHX_ AV* const types, SV* const sv) { + I32 const len = AvFILLp(types) + 1; + I32 i; + + ENTER; + SAVE_DEFSV; + DEFSV_set(sv); + + for(i = 0; i < len; i++){ + if(!mouse_tc_check(aTHX_ AvARRAY(types)[i], sv)){ + LEAVE; + return FALSE; + } + } + + LEAVE; + + return TRUE; +} + /* * This class_type generator is taken from Scalar::Util::Instance */ @@ -275,7 +373,7 @@ START_MY_CXT #define MG_klass_pv(mg) ((mg)->mg_ptr) #define MG_klass_len(mg) ((mg)->mg_len) -const char* +static const char* mouse_canonicalize_package_name(const char* name){ /* "::Foo" -> "Foo" */ @@ -292,7 +390,7 @@ mouse_canonicalize_package_name(const char* name){ } static int -lookup_isa(pTHX_ HV* const instance_stash, const char* const klass_pv){ +mouse_lookup_isa(pTHX_ HV* const instance_stash, const char* const klass_pv){ AV* const linearized_isa = mro_get_linear_isa(instance_stash); SV** svp = AvARRAY(linearized_isa); SV** const end = svp + AvFILLp(linearized_isa) + 1; @@ -307,90 +405,114 @@ lookup_isa(pTHX_ HV* const instance_stash, const char* const klass_pv){ return FALSE; } -static int -instance_isa(pTHX_ SV* const instance, const MAGIC* const mg){ - dMY_CXT; - HV* const instance_stash = SvSTASH(SvRV(instance)); - GV* const instance_isa = gv_fetchmeth_autoload(instance_stash, "isa", sizeof("isa")-1, 0); - - /* the instance has no own isa method */ - if(instance_isa == NULL || GvCV(instance_isa) == GvCV(MY_CXT.universal_isa)){ - return MG_klass_stash(mg) == instance_stash - || lookup_isa(aTHX_ instance_stash, MG_klass_pv(mg)); - } - /* the instance has its own isa method */ - else { - int retval; - dSP; +int +mouse_is_an_instance_of(pTHX_ HV* const stash, SV* const instance){ + assert(stash); + assert(SvTYPE(stash) == SVt_PVHV); + + if(IsObject(instance)){ + dMY_CXT; + HV* const instance_stash = SvSTASH(SvRV(instance)); + GV* const instance_isa = gv_fetchmeth_autoload(instance_stash, "isa", sizeof("isa")-1, 0); + + /* the instance has no own isa method */ + if(instance_isa == NULL || GvCV(instance_isa) == GvCV(MY_CXT.universal_isa)){ + return stash == instance_stash + || mouse_lookup_isa(aTHX_ instance_stash, HvNAME_get(stash)); + } + /* the instance has its own isa method */ + else { + int retval; + dSP; - ENTER; - SAVETMPS; + ENTER; + SAVETMPS; - PUSHMARK(SP); - EXTEND(SP, 2); - PUSHs(instance); - mPUSHp(MG_klass_pv(mg), MG_klass_len(mg)); - PUTBACK; + PUSHMARK(SP); + EXTEND(SP, 2); + PUSHs(instance); + mPUSHp(HvNAME_get(stash), HvNAMELEN_get(stash)); + PUTBACK; - call_sv((SV*)instance_isa, G_SCALAR); + call_sv((SV*)instance_isa, G_SCALAR); - SPAGAIN; + SPAGAIN; - retval = SvTRUEx(POPs); + retval = SvTRUEx(POPs); - PUTBACK; + PUTBACK; - FREETMPS; - LEAVE; + FREETMPS; + LEAVE; - return retval; + return retval; + } } + return FALSE; } +static int +mouse_is_an_instance_of_universal(pTHX_ SV* const data, SV* const sv){ + PERL_UNUSED_ARG(data); + return SvROK(sv) && SvOBJECT(SvRV(sv)); +} -XS(XS_isa_check){ - dVAR; - dXSARGS; - SV* sv; +static MGVTBL mouse_util_type_constraints_vtbl; /* not used, only for identity */ - assert(XSANY.any_ptr != NULL); +static CV* +mouse_tc_parameterize(pTHX_ const char* const name, check_fptr_t const fptr, SV* const param) { + CV* xsub; - if(items != 1){ - if(items < 1){ - croak("Not enough arguments for is-a predicate"); - } - else{ - croak("Too many arguments for is-a predicate"); - } - } + xsub = newXS(name, XS_Mouse_parameterized_check, __FILE__); + CvXSUBANY(xsub).any_ptr = sv_magicext( + (SV*)xsub, + param, /* mg_obj: refcnt will be increased */ + PERL_MAGIC_ext, + &mouse_util_type_constraints_vtbl, + (void*)fptr, /* mg_ptr */ + 0 /* mg_len: 0 for static data */ + ); - sv = ST(0); - SvGETMAGIC(sv); + if(!name){ + sv_2mortal((SV*)xsub); + } - ST(0) = boolSV( SvROK(sv) && SvOBJECT(SvRV(sv)) && instance_isa(aTHX_ sv, (MAGIC*)XSANY.any_ptr) ); - XSRETURN(1); + return xsub; } +CV* +mouse_generate_isa_predicate_for(pTHX_ SV* const klass, const char* const predicate_name){ + STRLEN klass_len; + const char* klass_pv = SvPV_const(klass, klass_len); + SV* param; + void* fptr; -XS(XS_isa_check_for_universal){ + klass_pv = mouse_canonicalize_package_name(klass_pv); + + if(strNE(klass_pv, "UNIVERSAL")){ + param = (SV*)gv_stashpvn(klass_pv, klass_len, GV_ADD); + fptr = (void*)mouse_is_an_instance_of; + + } + else{ + param = NULL; + fptr = (void*)mouse_is_an_instance_of_universal; + } + + return mouse_tc_parameterize(aTHX_ predicate_name, fptr, param); +} + +XS(XS_Mouse_parameterized_check) { dVAR; dXSARGS; - SV* sv; - PERL_UNUSED_VAR(cv); + MAGIC* const mg = (MAGIC*)XSANY.any_ptr; - if(items != 1){ - if(items < 1){ - croak("Not enough arguments for is-a predicate"); - } - else{ - croak("Too many arguments for is-a predicate"); - } + if(items < 1){ + croak("Too few arguments for parameterized check functions"); } - sv = ST(0); - SvGETMAGIC(sv); - - ST(0) = boolSV( SvROK(sv) && SvOBJECT(SvRV(sv)) ); + SvGETMAGIC( ST(0) ); + ST(0) = boolSV( CALL_FPTR((check_fptr_t)mg->mg_ptr)(aTHX_ mg->mg_obj, ST(0)) ); XSRETURN(1); } @@ -453,3 +575,114 @@ CODE: XSRETURN(1); +CV* +_parameterize_ArrayRef_for(SV* param) +ALIAS: + _parameterize_ArrayRef_for = MOUSE_TC_ARRAY_REF + _parameterize_HashRef_for = MOUSE_TC_HASH_REF + _parameterize_Maybe_for = MOUSE_TC_MAYBE +CODE: +{ + check_fptr_t fptr; + SV* const tc_code = mcall0s(param, "_compiled_type_constraint"); + if(!(SvROK(tc_code) && SvTYPE(SvRV(tc_code)) == SVt_PVCV)){ + croak("_compiled_type_constraint didn't return a CODE reference"); + } + + switch(ix){ + case MOUSE_TC_ARRAY_REF: + fptr = mouse_parameterized_ArrayRef; + break; + case MOUSE_TC_HASH_REF: + fptr = mouse_parameterized_HashRef; + break; + default: /* Maybe type */ + fptr = mouse_parameterized_Maybe; + } + RETVAL = mouse_tc_parameterize(aTHX_ NULL, fptr, tc_code); +} +OUTPUT: + RETVAL + +MODULE = Mouse::Util::TypeConstraints PACKAGE = Mouse::Meta::TypeConstraint + +void +compile_type_constraint(SV* self) +CODE: +{ + AV* const checks = newAV(); + SV* check; /* check function */ + SV* parent; + SV* types_ref; + + sv_2mortal((SV*)checks); + + for(parent = get_slots(self, "parent"); parent; parent = get_slots(parent, "parent")){ + check = get_slots(parent, "hand_optimized_type_constraint"); + if(check && SvOK(check)){ + if(!mouse_tc_CodeRef(aTHX_ check)){ + croak("Not a CODE reference"); + } + av_unshift(checks, 1); + av_store(checks, 0, newSVsv(check)); + break; /* a hand optimized constraint must include all the parent */ + } + + check = get_slots(parent, "constraint"); + if(check && SvOK(check)){ + if(!mouse_tc_CodeRef(aTHX_ check)){ + croak("Not a CODE reference"); + } + av_unshift(checks, 1); + av_store(checks, 0, newSVsv(check)); + } + } + + check = get_slots(self, "constraint"); + if(check && SvOK(check)){ + if(!mouse_tc_CodeRef(aTHX_ check)){ + croak("Not a CODE reference"); + } + av_push(checks, newSVsv(check)); + } + + types_ref = get_slots(self, "type_constraints"); + if(types_ref && SvOK(types_ref)){ /* union type */ + AV* types; + AV* union_checks; + CV* union_check; + I32 len; + I32 i; + + if(!mouse_tc_ArrayRef(aTHX_ types_ref)){ + croak("Not an ARRAY reference"); + } + types = (AV*)SvRV(types_ref); + len = av_len(types) + 1; + + union_checks = newAV(); + sv_2mortal((SV*)union_checks); + + for(i = 0; i < len; i++){ + SV* const tc = *av_fetch(types, i, TRUE); + SV* const c = get_slots(tc, "compiled_type_constraint"); + if(!(c && mouse_tc_CodeRef(aTHX_ c))){ + sv_dump(self); + croak("'%"SVf"' has no compiled type constraint", self); + } + av_push(union_checks, newSVsv(c)); + } + + union_check = mouse_tc_parameterize(aTHX_ NULL, (check_fptr_t)mouse_types_union_check, (SV*)union_checks); + av_push(checks, newRV_inc((SV*)union_check)); + } + + if(AvFILLp(checks) < 0){ + check = newRV_inc((SV*)get_cv("Mouse::Util::TypeConstraints::Any", TRUE)); + } + else{ + check = newRV_inc((SV*)mouse_tc_parameterize(aTHX_ NULL, (check_fptr_t)mouse_types_check, (SV*)checks)); + } + set_slots(self, "compiled_type_constraint", check); +} +