Change TC hierarchy in XS
[gitmo/Mouse.git] / xs-src / MouseTypeConstraints.xs
1 /*
2  *   full definition of built-in type constraints (ware in Moose::Util::TypeConstraints::OptimizedConstraints)
3  */
4
5 #include "mouse.h"
6
7 #if PERL_BCDVERSION >= 0x5008005
8 #define LooksLikeNumber(sv) looks_like_number(sv)
9 #else
10 #define LooksLikeNumber(sv) ( SvPOKp(sv) ? looks_like_number(sv) : SvNIOKp(sv) )
11 #endif
12
13 #ifndef SvRXOK
14 #define SvRXOK(sv) (SvROK(sv) && SvMAGICAL(SvRV(sv)) && mg_find(SvRV(sv), PERL_MAGIC_qr))
15 #endif
16
17 typedef int (*check_fptr_t)(pTHX_ SV* const data, SV* const sv);
18
19 int
20 mouse_tc_check(pTHX_ SV* const tc_code, SV* const sv) {
21     CV* const cv = (CV*)SvRV(tc_code);
22     assert(SvTYPE(cv) == SVt_PVCV);
23
24     if(CvXSUB(cv) == XS_Mouse__Util__TypeConstraints_Item){ /* built-in */
25         return mouse_builtin_tc_check(aTHX_ CvXSUBANY(cv).any_iv, sv);
26     }
27     else if(CvXSUB(cv) == XS_Mouse_parameterized_check){ /* complex type constraints */
28         MAGIC* const mg = (MAGIC*)CvXSUBANY(cv).any_ptr;
29
30         assert(CvXSUBANY(cv).any_ptr != NULL);
31
32         /* call the check function directly, skipping call_sv() */
33         return CALL_FPTR((check_fptr_t)mg->mg_ptr)(aTHX_ mg->mg_obj, sv);
34     }
35     else { /* custom */
36         int ok;
37         dSP;
38
39         ENTER;
40         SAVETMPS;
41
42         PUSHMARK(SP);
43         XPUSHs(sv);
44         PUTBACK;
45
46         call_sv(tc_code, G_SCALAR);
47
48         SPAGAIN;
49         ok = SvTRUEx(POPs);
50         PUTBACK;
51
52         FREETMPS;
53         LEAVE;
54
55         return ok;
56     }
57 }
58
59 int
60 mouse_builtin_tc_check(pTHX_ mouse_tc const tc, SV* const sv) {
61     switch(tc){
62     case MOUSE_TC_ANY:        return mouse_tc_Any(aTHX_ sv);
63     case MOUSE_TC_ITEM:       return mouse_tc_Any(aTHX_ sv);
64     case MOUSE_TC_MAYBE:      return mouse_tc_Any(aTHX_ sv);
65     case MOUSE_TC_UNDEF:      return mouse_tc_Undef(aTHX_ sv);
66     case MOUSE_TC_DEFINED:    return mouse_tc_Defined(aTHX_ sv);
67     case MOUSE_TC_BOOL:       return mouse_tc_Bool(aTHX_ sv);
68     case MOUSE_TC_VALUE:      return mouse_tc_Value(aTHX_ sv);
69     case MOUSE_TC_REF:        return mouse_tc_Ref(aTHX_ sv);
70     case MOUSE_TC_STR:        return mouse_tc_Str(aTHX_ sv);
71     case MOUSE_TC_NUM:        return mouse_tc_Num(aTHX_ sv);
72     case MOUSE_TC_INT:        return mouse_tc_Int(aTHX_ sv);
73     case MOUSE_TC_SCALAR_REF: return mouse_tc_ScalarRef(aTHX_ sv);
74     case MOUSE_TC_ARRAY_REF:  return mouse_tc_ArrayRef(aTHX_ sv);
75     case MOUSE_TC_HASH_REF:   return mouse_tc_HashRef(aTHX_ sv);
76     case MOUSE_TC_CODE_REF:   return mouse_tc_CodeRef(aTHX_ sv);
77     case MOUSE_TC_GLOB_REF:   return mouse_tc_GlobRef(aTHX_ sv);
78     case MOUSE_TC_FILEHANDLE: return mouse_tc_FileHandle(aTHX_ sv);
79     case MOUSE_TC_REGEXP_REF: return mouse_tc_RegexpRef(aTHX_ sv);
80     case MOUSE_TC_OBJECT:     return mouse_tc_Object(aTHX_ sv);
81     case MOUSE_TC_CLASS_NAME: return mouse_tc_ClassName(aTHX_ sv);
82     case MOUSE_TC_ROLE_NAME:  return mouse_tc_RoleName(aTHX_ sv);
83     default:
84         NOOP;
85     }
86
87     croak("Mouse-panic: unrecognized type constraint id: %d", (int)tc);
88     return FALSE; /* not reached */
89 }
90
91
92 /*
93     The following type check functions return an integer, not a bool, to keep them simple,
94     so if you assign these return value to bool variable, you must use "expr ? TRUE : FALSE".
95 */
96
97 int
98 mouse_tc_Any(pTHX_ SV* const sv PERL_UNUSED_DECL) {
99     assert(sv);
100     return TRUE;
101 }
102
103 int
104 mouse_tc_Bool(pTHX_ SV* const sv) {
105     assert(sv);
106
107     if(SvTRUE(sv)){
108         if(SvIOKp(sv)){
109             return SvIVX(sv) == 1;
110         }
111         else if(SvNOKp(sv)){
112             return SvNVX(sv) == 1.0;
113         }
114         else if(SvPOKp(sv)){ /* "1" */
115             return SvCUR(sv) == 1 && SvPVX(sv)[0] == '1';
116         }
117         else{
118             return FALSE;
119         }
120     }
121     else{
122         /* false must be boolean */
123         return TRUE;
124     }
125 }
126
127 int
128 mouse_tc_Undef(pTHX_ SV* const sv) {
129     assert(sv);
130     return !SvOK(sv);
131 }
132
133 int
134 mouse_tc_Defined(pTHX_ SV* const sv) {
135     assert(sv);
136     return SvOK(sv);
137 }
138
139 int
140 mouse_tc_Value(pTHX_ SV* const sv) {
141     assert(sv);
142     return SvOK(sv) && !SvROK(sv);
143 }
144
145 int
146 mouse_tc_Num(pTHX_ SV* const sv) {
147     assert(sv);
148     return LooksLikeNumber(sv);
149 }
150
151 int
152 mouse_tc_Int(pTHX_ SV* const sv) {
153     assert(sv);
154     if(SvIOKp(sv)){
155         return TRUE;
156     }
157     else if(SvNOKp(sv)){
158         NV const nv = SvNVX(sv);
159         return nv > 0 ? (nv == (NV)(UV)nv) : (nv == (NV)(IV)nv);
160     }
161     else if(SvPOKp(sv)){
162         int const num_type = grok_number(SvPVX(sv), SvCUR(sv), NULL);
163         if(num_type){
164             return !(num_type & IS_NUMBER_NOT_INT);
165         }
166     }
167     return FALSE;
168 }
169
170 int
171 mouse_tc_Str(pTHX_ SV* const sv) {
172     assert(sv);
173     return SvOK(sv) && !SvROK(sv) && !isGV(sv);
174 }
175
176 int
177 mouse_tc_ClassName(pTHX_ SV* const sv){ 
178     assert(sv);
179     return is_class_loaded(sv);
180 }
181
182 int
183 mouse_tc_RoleName(pTHX_ SV* const sv) {
184     assert(sv);
185     if(is_class_loaded(sv)){
186         int ok;
187         SV* meta;
188         dSP;
189
190         ENTER;
191         SAVETMPS;
192
193         PUSHMARK(SP);
194         XPUSHs(sv);
195         PUTBACK;
196         call_pv("Mouse::Util::get_metaclass_by_name", G_SCALAR);
197         SPAGAIN;
198         meta = POPs;
199         PUTBACK;
200
201         ok =  is_an_instance_of("Mouse::Meta::Role", meta);
202
203         FREETMPS;
204         LEAVE;
205
206         return ok;
207     }
208     return FALSE;
209 }
210
211 int
212 mouse_tc_Ref(pTHX_ SV* const sv) {
213     assert(sv);
214     return SvROK(sv);
215 }
216
217 int
218 mouse_tc_ScalarRef(pTHX_ SV* const sv) {
219     assert(sv);
220     return SvROK(sv) && !SvOBJECT(SvRV(sv)) && (SvTYPE(SvRV(sv)) <= SVt_PVLV && !isGV(SvRV(sv)));
221 }
222
223 int
224 mouse_tc_ArrayRef(pTHX_ SV* const sv) {
225     assert(sv);
226     return SvROK(sv) && !SvOBJECT(SvRV(sv)) && SvTYPE(SvRV(sv)) == SVt_PVAV;
227 }
228
229 int
230 mouse_tc_HashRef(pTHX_ SV* const sv) {
231     assert(sv);
232     return SvROK(sv) && !SvOBJECT(SvRV(sv)) && SvTYPE(SvRV(sv)) == SVt_PVHV;
233 }
234
235 int
236 mouse_tc_CodeRef(pTHX_ SV* const sv) {
237     assert(sv);
238     return SvROK(sv)  && !SvOBJECT(SvRV(sv))&& SvTYPE(SvRV(sv)) == SVt_PVCV;
239 }
240
241 int
242 mouse_tc_RegexpRef(pTHX_ SV* const sv) {
243     assert(sv);
244     return SvRXOK(sv);
245 }
246
247 int
248 mouse_tc_GlobRef(pTHX_ SV* const sv) {
249     assert(sv);
250     return SvROK(sv) && !SvOBJECT(SvRV(sv)) && isGV(SvRV(sv));
251 }
252
253 int
254 mouse_tc_FileHandle(pTHX_ SV* const sv) {
255     GV* gv;
256     assert(sv);
257
258     /* see pp_fileno() in pp_sys.c and Scalar::Util::openhandle() */
259
260     gv = (GV*)(SvROK(sv) ? SvRV(sv) : sv);
261     if(isGV(gv) || SvTYPE(gv) == SVt_PVIO){
262         IO* const io = isGV(gv) ? GvIO(gv) : (IO*)gv;
263
264         if(io && ( IoIFP(io) || SvTIED_mg((SV*)io, PERL_MAGIC_tiedscalar) )){
265             return TRUE;
266         }
267     }
268
269     return is_an_instance_of("IO::Handle", sv);
270 }
271
272 int
273 mouse_tc_Object(pTHX_ SV* const sv) {
274     assert(sv);
275     return SvROK(sv) && SvOBJECT(SvRV(sv)) && !SvRXOK(sv);
276 }
277
278 /* Parameterized type constraints */
279
280 static int
281 mouse_parameterized_ArrayRef(pTHX_ SV* const param, SV* const sv) {
282     if(mouse_tc_ArrayRef(aTHX_ sv)){
283         AV* const av  = (AV*)SvRV(sv);
284         I32 const len = av_len(av) + 1;
285         I32 i;
286         for(i = 0; i < len; i++){
287             SV* const value = *av_fetch(av, i, TRUE);
288             SvGETMAGIC(value);
289             if(!mouse_tc_check(aTHX_ param, value)){
290                 return FALSE;
291             }
292         }
293         return TRUE;
294     }
295     return FALSE;
296 }
297
298 static int
299 mouse_parameterized_HashRef(pTHX_ SV* const param, SV* const sv) {
300     if(mouse_tc_HashRef(aTHX_ sv)){
301         HV* const hv  = (HV*)SvRV(sv);
302         HE* he;
303
304         hv_iterinit(hv);
305         while((he = hv_iternext(hv))){
306             SV* const value = hv_iterval(hv, he);
307             SvGETMAGIC(value);
308             if(!mouse_tc_check(aTHX_ param, value)){
309                 return FALSE;
310             }
311         }
312         return TRUE;
313     }
314     return FALSE;
315 }
316
317 static int
318 mouse_parameterized_Maybe(pTHX_ SV* const param, SV* const sv) {
319     if(SvOK(sv)){
320         return mouse_tc_check(aTHX_ param, sv);
321     }
322     return TRUE;
323 }
324
325 static int
326 mouse_types_union_check(pTHX_ AV* const types, SV* const sv) {
327     I32 const len = AvFILLp(types) + 1;
328     I32 i;
329
330     for(i = 0; i < len; i++){
331         if(mouse_tc_check(aTHX_ AvARRAY(types)[i], sv)){
332             return TRUE;
333         }
334     }
335
336     return FALSE;
337 }
338
339 static int
340 mouse_types_check(pTHX_ AV* const types, SV* const sv) {
341     I32 const len = AvFILLp(types) + 1;
342     I32 i;
343
344     ENTER;
345     SAVE_DEFSV;
346     DEFSV_set(sv);
347
348     for(i = 0; i < len; i++){
349         if(!mouse_tc_check(aTHX_ AvARRAY(types)[i], sv)){
350             LEAVE;
351             return FALSE;
352         }
353     }
354
355     LEAVE;
356
357     return TRUE;
358 }
359
360 /*
361  *  This class_type generator is taken from Scalar::Util::Instance
362  */
363
364 #define MY_CXT_KEY "Mouse::Util::TypeConstraints::_guts" XS_VERSION
365 typedef struct sui_cxt{
366     GV* universal_isa;
367 } my_cxt_t;
368 START_MY_CXT
369
370 #define MG_klass_stash(mg) ((HV*)(mg)->mg_obj)
371 #define MG_klass_pv(mg)    ((mg)->mg_ptr)
372 #define MG_klass_len(mg)   ((mg)->mg_len)
373
374 static const char*
375 mouse_canonicalize_package_name(const char* name){
376
377     /* "::Foo" -> "Foo" */
378     if(name[0] == ':' && name[1] == ':'){
379         name += 2;
380     }
381
382     /* "main::main::main::Foo" -> "Foo" */
383     while(strnEQ(name, "main::", sizeof("main::")-1)){
384         name += sizeof("main::")-1;
385     }
386
387     return name;
388 }
389
390 static int
391 mouse_lookup_isa(pTHX_ HV* const instance_stash, const char* const klass_pv){
392     AV*  const linearized_isa = mro_get_linear_isa(instance_stash);
393     SV**       svp            = AvARRAY(linearized_isa);
394     SV** const end            = svp + AvFILLp(linearized_isa) + 1;
395
396     while(svp != end){
397         assert(SvPVX(*svp));
398         if(strEQ(klass_pv, mouse_canonicalize_package_name(SvPVX(*svp)))){
399             return TRUE;
400         }
401         svp++;
402     }
403     return FALSE;
404 }
405
406 int
407 mouse_is_an_instance_of(pTHX_ HV* const stash, SV* const instance){
408     assert(stash);
409     assert(SvTYPE(stash) == SVt_PVHV);
410
411     if(IsObject(instance)){
412         dMY_CXT;
413         HV* const instance_stash = SvSTASH(SvRV(instance));
414         GV* const instance_isa   = gv_fetchmeth_autoload(instance_stash, "isa", sizeof("isa")-1, 0);
415
416         /* the instance has no own isa method */
417         if(instance_isa == NULL || GvCV(instance_isa) == GvCV(MY_CXT.universal_isa)){
418             return stash == instance_stash
419                 || mouse_lookup_isa(aTHX_ instance_stash, HvNAME_get(stash));
420         }
421         /* the instance has its own isa method */
422         else {
423             int retval;
424             dSP;
425
426             ENTER;
427             SAVETMPS;
428
429             PUSHMARK(SP);
430             EXTEND(SP, 2);
431             PUSHs(instance);
432             mPUSHp(HvNAME_get(stash), HvNAMELEN_get(stash));
433             PUTBACK;
434
435             call_sv((SV*)instance_isa, G_SCALAR);
436
437             SPAGAIN;
438
439             retval = SvTRUEx(POPs);
440
441             PUTBACK;
442
443             FREETMPS;
444             LEAVE;
445
446             return retval;
447         }
448     }
449     return FALSE;
450 }
451
452 static int
453 mouse_is_an_instance_of_universal(pTHX_ SV* const data, SV* const sv){
454     PERL_UNUSED_ARG(data);
455     return SvROK(sv) && SvOBJECT(SvRV(sv));
456 }
457
458 static MGVTBL mouse_util_type_constraints_vtbl; /* not used, only for identity */
459
460 static CV*
461 mouse_tc_parameterize(pTHX_ const char* const name, check_fptr_t const fptr, SV* const param) {
462     CV* xsub;
463
464     xsub = newXS(name, XS_Mouse_parameterized_check, __FILE__);
465     CvXSUBANY(xsub).any_ptr = sv_magicext(
466         (SV*)xsub,
467         param,       /* mg_obj: refcnt will be increased */
468         PERL_MAGIC_ext,
469         &mouse_util_type_constraints_vtbl,
470         (void*)fptr, /* mg_ptr */
471         0            /* mg_len: 0 for static data */
472     );
473
474     if(!name){
475         sv_2mortal((SV*)xsub);
476     }
477
478     return xsub;
479 }
480
481 CV*
482 mouse_generate_isa_predicate_for(pTHX_ SV* const klass, const char* const predicate_name){
483     STRLEN klass_len;
484     const char* klass_pv = SvPV_const(klass, klass_len);
485     SV*   param;
486     void* fptr;
487
488     klass_pv = mouse_canonicalize_package_name(klass_pv);
489
490     if(strNE(klass_pv, "UNIVERSAL")){
491         param = (SV*)gv_stashpvn(klass_pv, klass_len, GV_ADD);
492         fptr = (void*)mouse_is_an_instance_of;
493
494     }
495     else{
496         param = NULL;
497         fptr = (void*)mouse_is_an_instance_of_universal;
498     }
499
500     return mouse_tc_parameterize(aTHX_ predicate_name, fptr, param);
501 }
502
503 XS(XS_Mouse_parameterized_check) {
504     dVAR;
505     dXSARGS;
506     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
507
508     if(items < 1){
509         croak("Too few arguments for parameterized check functions");
510     }
511
512     SvGETMAGIC( ST(0) );
513     ST(0) = boolSV( CALL_FPTR((check_fptr_t)mg->mg_ptr)(aTHX_ mg->mg_obj, ST(0)) );
514     XSRETURN(1);
515 }
516
517 static void
518 setup_my_cxt(pTHX_ pMY_CXT){
519     MY_CXT.universal_isa = gv_fetchpvs("UNIVERSAL::isa", GV_ADD, SVt_PVCV);
520     SvREFCNT_inc_simple_void_NN(MY_CXT.universal_isa);
521 }
522
523 MODULE = Mouse::Util::TypeConstraints    PACKAGE = Mouse::Util::TypeConstraints
524
525 PROTOTYPES:   DISABLE
526 VERSIONCHECK: DISABLE
527
528 BOOT:
529 {
530     MY_CXT_INIT;
531     setup_my_cxt(aTHX_ aMY_CXT);
532 }
533
534 #ifdef USE_ITHREADS
535
536 void
537 CLONE(...)
538 CODE:
539 {
540     MY_CXT_CLONE;
541     setup_my_cxt(aTHX_ aMY_CXT);
542     PERL_UNUSED_VAR(items);
543 }
544
545 #endif /* !USE_ITHREADS */
546
547 void
548 Item(SV* sv = &PL_sv_undef)
549 ALIAS:
550     Any        = MOUSE_TC_ANY
551     Item       = MOUSE_TC_ITEM
552     Undef      = MOUSE_TC_UNDEF
553     Defined    = MOUSE_TC_DEFINED
554     Bool       = MOUSE_TC_BOOL
555     Value      = MOUSE_TC_VALUE
556     Ref        = MOUSE_TC_REF
557     Str        = MOUSE_TC_STR
558     Num        = MOUSE_TC_NUM
559     Int        = MOUSE_TC_INT
560     ScalarRef  = MOUSE_TC_SCALAR_REF
561     ArrayRef   = MOUSE_TC_ARRAY_REF
562     HashRef    = MOUSE_TC_HASH_REF
563     CodeRef    = MOUSE_TC_CODE_REF
564     GlobRef    = MOUSE_TC_GLOB_REF
565     FileHandle = MOUSE_TC_FILEHANDLE
566     RegexpRef  = MOUSE_TC_REGEXP_REF
567     Object     = MOUSE_TC_OBJECT
568     ClassName  = MOUSE_TC_CLASS_NAME
569     RoleName   = MOUSE_TC_ROLE_NAME
570 CODE:
571     SvGETMAGIC(sv);
572     ST(0) = boolSV( mouse_builtin_tc_check(aTHX_ ix, sv) );
573     XSRETURN(1);
574
575
576 CV*
577 _parameterize_ArrayRef_for(SV* param)
578 ALIAS:
579     _parameterize_ArrayRef_for = MOUSE_TC_ARRAY_REF
580     _parameterize_HashRef_for  = MOUSE_TC_HASH_REF
581     _parameterize_Maybe_for    = MOUSE_TC_MAYBE
582 CODE:
583 {
584     check_fptr_t fptr;
585     SV* const tc_code = mcall0s(param, "_compiled_type_constraint");
586     if(!(SvROK(tc_code) && SvTYPE(SvRV(tc_code)) == SVt_PVCV)){
587         croak("_compiled_type_constraint didn't return a CODE reference");
588     }
589
590     switch(ix){
591     case MOUSE_TC_ARRAY_REF:
592         fptr = mouse_parameterized_ArrayRef;
593         break;
594     case MOUSE_TC_HASH_REF:
595         fptr = mouse_parameterized_HashRef;
596         break;
597     default: /* Maybe type */
598         fptr = mouse_parameterized_Maybe;
599     }
600     RETVAL = mouse_tc_parameterize(aTHX_ NULL, fptr, tc_code);
601 }
602 OUTPUT:
603     RETVAL
604
605 MODULE = Mouse::Util::TypeConstraints    PACKAGE = Mouse::Meta::TypeConstraint
606
607 void
608 compile_type_constraint(SV* self)
609 CODE:
610 {
611     AV* const checks = newAV();
612     SV* check; /* check function */
613     SV* parent;
614     SV* types_ref;
615
616     sv_2mortal((SV*)checks);
617
618     for(parent = get_slots(self, "parent"); parent; parent = get_slots(parent, "parent")){
619         check = get_slots(parent, "hand_optimized_type_constraint");
620         if(check && SvOK(check)){
621             if(!mouse_tc_CodeRef(aTHX_ check)){
622                 croak("Not a CODE reference");
623             }
624             av_unshift(checks, 1);
625             av_store(checks, 0, newSVsv(check));
626             break; /* a hand optimized constraint must include all the parent */
627         }
628
629         check = get_slots(parent, "constraint");
630         if(check && SvOK(check)){
631             if(!mouse_tc_CodeRef(aTHX_ check)){
632                 croak("Not a CODE reference");
633             }
634             av_unshift(checks, 1);
635             av_store(checks, 0, newSVsv(check));
636         }
637     }
638
639     check = get_slots(self, "constraint");
640     if(check && SvOK(check)){
641         if(!mouse_tc_CodeRef(aTHX_ check)){
642             croak("Not a CODE reference");
643         }
644         av_push(checks, newSVsv(check));
645     }
646
647     types_ref = get_slots(self, "type_constraints");
648     if(types_ref && SvOK(types_ref)){ /* union type */
649         AV* types;
650         AV* union_checks;
651         CV* union_check;
652         I32 len;
653         I32 i;
654
655         if(!mouse_tc_ArrayRef(aTHX_ types_ref)){
656             croak("Not an ARRAY reference");
657         }
658         types = (AV*)SvRV(types_ref);
659         len = av_len(types) + 1;
660
661         union_checks = newAV();
662         sv_2mortal((SV*)union_checks);
663
664         for(i = 0; i < len; i++){
665             SV* const tc = *av_fetch(types, i, TRUE);
666             SV* const c  = get_slots(tc, "compiled_type_constraint");
667             if(!(c && mouse_tc_CodeRef(aTHX_ c))){
668                 sv_dump(self);
669                 croak("'%"SVf"' has no compiled type constraint", self);
670             }
671             av_push(union_checks, newSVsv(c));
672         }
673
674         union_check = mouse_tc_parameterize(aTHX_ NULL, (check_fptr_t)mouse_types_union_check, (SV*)union_checks);
675         av_push(checks, newRV_inc((SV*)union_check));
676     }
677
678     if(AvFILLp(checks) < 0){
679         check = newRV_inc((SV*)get_cv("Mouse::Util::TypeConstraints::Any", TRUE));
680     }
681     else{
682         check = newRV_inc((SV*)mouse_tc_parameterize(aTHX_ NULL, (check_fptr_t)mouse_types_check, (SV*)checks));
683     }
684     set_slots(self, "compiled_type_constraint", check);
685 }
686