Implement type parameterization in XS
[gitmo/Mouse.git] / xs-src / MouseTypeConstraints.xs
index ffae815..6f0fe6c 100644 (file)
 #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(CvISXSUB(cv)){ /* can be built-in tc */
+        if(CvXSUB(cv) == XS_Mouse__Util__TypeConstraints_Item){
+            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){
+            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 {
+
+    /* user-defined type constraints */
+    {
         int ok;
         dSP;
 
@@ -67,11 +86,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 +108,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 +203,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 +271,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 +280,53 @@ mouse_tc_Object(pTHX_ SV* const sv) {
     return SvROK(sv) && SvOBJECT(SvRV(sv)) && !SvRXOK(sv);
 }
 
+/* Parameterized type constraints */
+
+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;
+}
+
+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;
+}
+
+int
+mouse_parameterized_Maybe(pTHX_ SV* const param, SV* const sv) {
+    if(SvOK(sv)){
+        return mouse_tc_check(aTHX_ param, sv);
+    }
+    return TRUE;
+}
+
 /*
  *  This class_type generator is taken from Scalar::Util::Instance
  */
@@ -275,7 +341,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 +358,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 +373,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;
+
+    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;
 
-XS(XS_isa_check_for_universal){
+    }
+    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 +543,32 @@ 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
+