92fa6769cb7f45a811b7a42d576d43d47daa4c7b
[gitmo/Mouse.git] / xs-src / MouseUtil.xs
1 #include "mouse.h"
2
3 #define MY_CXT_KEY "Mouse::Util::_guts" XS_VERSION
4 typedef struct {
5     HV* metas;
6 } my_cxt_t;
7 START_MY_CXT
8
9 #define ISA_CACHE "::LINEALIZED_ISA_CACHE::"
10
11 #ifdef no_mro_get_linear_isa
12 AV*
13 mouse_mro_get_linear_isa(pTHX_ HV* const stash){
14     GV* const cachegv = *(GV**)hv_fetchs(stash, ISA_CACHE, TRUE);
15     AV* isa;
16     SV* gen;
17     CV* get_linear_isa;
18
19     if(!isGV(cachegv))
20         gv_init(cachegv, stash, ISA_CACHE, sizeof(ISA_CACHE)-1, TRUE);
21
22     isa = GvAVn(cachegv);
23     gen = GvSVn(cachegv);
24
25
26     if(SvIOK(gen) && SvIVX(gen) == (IV)mro_get_pkg_gen(stash)){
27         return isa; /* returns the cache if available */
28     }
29     else{
30         SvREFCNT_dec(isa);
31         GvAV(cachegv) = isa = newAV();
32     }
33
34     get_linear_isa = get_cv("Mouse::Util::get_linear_isa", TRUE);
35
36     {
37         SV* avref;
38         dSP;
39
40         ENTER;
41         SAVETMPS;
42
43         PUSHMARK(SP);
44         mXPUSHp(HvNAME_get(stash), HvNAMELEN_get(stash));
45         PUTBACK;
46
47         call_sv((SV*)get_linear_isa, G_SCALAR);
48
49         SPAGAIN;
50         avref = POPs;
51         PUTBACK;
52
53         if(IsArrayRef(avref)){
54             AV* const av  = (AV*)SvRV(avref);
55             I32 const len = AvFILLp(av) + 1;
56             I32 i;
57
58             for(i = 0; i < len; i++){
59                 HV* const stash = gv_stashsv(AvARRAY(av)[i], FALSE);
60                 if(stash)
61                     av_push(isa, newSVpv(HvNAME(stash), 0));
62             }
63             SvREADONLY_on(isa);
64         }
65         else{
66             Perl_croak(aTHX_ "Mouse:Util::get_linear_isa() didn't return an ARRAY reference");
67         }
68
69         FREETMPS;
70         LEAVE;
71     }
72
73     sv_setiv(gen, (IV)mro_get_pkg_gen(stash));
74     return isa;
75 }
76 #endif /* !no_mor_get_linear_isa */
77
78 #ifdef DEBUGGING
79 SV**
80 mouse_av_at_safe(pTHX_ AV* const av, I32 const ix){
81     assert(av);
82     assert(SvTYPE(av) == SVt_PVAV);
83     assert(AvMAX(av) >= ix);
84     return &AvARRAY(av)[ix];
85 }
86 #endif
87
88 void
89 mouse_throw_error(SV* const metaobject, SV* const data /* not used */, const char* const fmt, ...){
90     dTHX;
91     va_list args;
92     SV* message;
93
94     assert(metaobject);
95     assert(fmt);
96
97     va_start(args, fmt);
98     message = vnewSVpvf(fmt, &args);
99     va_end(args);
100
101     {
102         dSP;
103         PUSHMARK(SP);
104         EXTEND(SP, 6);
105
106         PUSHs(metaobject);
107         mPUSHs(message);
108
109         if(data){ /* extra arg, might be useful for debugging */
110             mPUSHs(newSVpvs("data"));
111             PUSHs(data);
112             mPUSHs(newSVpvs("depth"));
113             mPUSHi(-1);
114         }
115
116         PUTBACK;
117
118         call_method("throw_error", G_VOID);
119         croak("throw_error() did not throw the error (%"SVf")", message);
120     }
121 }
122
123 bool
124 mouse_is_class_loaded(pTHX_ SV * const klass){
125     HV *stash;
126     GV** gvp;
127     HE* he;
128
129     if (!(SvPOKp(klass) && SvCUR(klass))) { /* XXX: SvPOK does not work with magical scalars */
130         return FALSE;
131     }
132
133     stash = gv_stashsv(klass, FALSE);
134     if (!stash) {
135         return FALSE;
136     }
137
138     if (( gvp = (GV**)hv_fetchs(stash, "VERSION", FALSE) )) {
139         if(isGV(*gvp) && GvSV(*gvp) && SvOK(GvSV(*gvp))){
140             return TRUE;
141         }
142     }
143
144     if (( gvp = (GV**)hv_fetchs(stash, "ISA", FALSE) )) {
145         if(isGV(*gvp) && GvAV(*gvp) && av_len(GvAV(*gvp)) != -1){
146             return TRUE;
147         }
148     }
149
150     hv_iterinit(stash);
151     while(( he = hv_iternext(stash) )){
152         GV* const gv = (GV*)HeVAL(he);
153
154         if(isGV(gv)){
155             if(GvCVu(gv)){
156                 return TRUE;
157             }
158         }
159         else if(SvOK(gv)){
160             return TRUE;
161         }
162     }
163     return FALSE;
164 }
165
166
167 SV*
168 mouse_call0 (pTHX_ SV* const self, SV* const method) {
169     dSP;
170     SV *ret;
171
172     PUSHMARK(SP);
173     XPUSHs(self);
174     PUTBACK;
175
176     call_sv(method, G_SCALAR | G_METHOD);
177
178     SPAGAIN;
179     ret = POPs;
180     PUTBACK;
181
182     return ret;
183 }
184
185 SV*
186 mouse_call1 (pTHX_ SV* const self, SV* const method, SV* const arg1) {
187     dSP;
188     SV *ret;
189
190     PUSHMARK(SP);
191     EXTEND(SP, 2);
192     PUSHs(self);
193     PUSHs(arg1);
194     PUTBACK;
195
196     call_sv(method, G_SCALAR | G_METHOD);
197
198     SPAGAIN;
199     ret = POPs;
200     PUTBACK;
201
202     return ret;
203 }
204
205 int
206 mouse_predicate_call(pTHX_ SV* const self, SV* const method) {
207     return sv_true( mcall0(self, method) );
208 }
209
210 SV*
211 mouse_get_metaclass(pTHX_ SV* metaclass_name){
212     dMY_CXT;
213     HE* he;
214
215     assert(metaclass_name);
216     assert(MY_CXT.metas);
217
218     if(IsObject(metaclass_name)){
219         HV* const stash = SvSTASH(SvRV(metaclass_name));
220
221         metaclass_name = newSVpvn_share(HvNAME_get(stash), HvNAMELEN_get(stash), 0U);
222         sv_2mortal(metaclass_name);
223     }
224
225     he = hv_fetch_ent(MY_CXT.metas, metaclass_name, FALSE, 0U);
226
227     return he ? HeVAL(he) : &PL_sv_undef;
228 }
229
230 MAGIC*
231 mouse_mg_find(pTHX_ SV* const sv, const MGVTBL* const vtbl, I32 const flags){
232     MAGIC* mg;
233
234     assert(sv != NULL);
235     for(mg = SvMAGIC(sv); mg; mg = mg->mg_moremagic){
236         if(mg->mg_virtual == vtbl){
237             return mg;
238         }
239     }
240
241     if(flags & MOUSEf_DIE_ON_FAIL){
242         croak("mouse_mg_find: no MAGIC found for %"SVf, sv_2mortal(newRV_inc(sv)));
243     }
244     return NULL;
245 }
246
247 GV*
248 mouse_stash_fetch(pTHX_ HV* const stash, const char* const name, I32 const namelen, I32 const create) {
249     GV** const gvp = (GV**)hv_fetch(stash, name, namelen, create);
250
251     if(gvp){
252         if(!isGV(*gvp)){
253             gv_init(*gvp, stash, name, namelen, GV_ADDMULTI);
254         }
255         return *gvp;
256     }
257     else{
258         return NULL;
259     }
260 }
261
262 MODULE = Mouse::Util  PACKAGE = Mouse::Util
263
264 PROTOTYPES:   DISABLE
265 VERSIONCHECK: DISABLE
266
267 BOOT:
268 {
269     MY_CXT_INIT;
270     MY_CXT.metas = NULL;
271 }
272
273 void
274 __register_metaclass_storage(HV* metas, bool cloning)
275 CODE:
276 {
277     if(cloning){
278         MY_CXT_CLONE;
279         MY_CXT.metas = NULL;
280     }
281     {
282         dMY_CXT;
283         if(MY_CXT.metas) croak("Cannot set metaclass storage more than once");
284         MY_CXT.metas = metas;
285         SvREFCNT_inc_simple_void_NN(metas);
286     }
287 }
288
289 bool
290 is_valid_class_name(SV* sv)
291 CODE:
292 {
293     SvGETMAGIC(sv);
294     if(SvPOKp(sv) && SvCUR(sv) > 0){
295         UV i;
296         RETVAL = TRUE;
297         for(i = 0; i < SvCUR(sv); i++){
298             char const c = SvPVX(sv)[i];
299             if(!(isALNUM(c) || c == ':')){
300                 RETVAL = FALSE;
301                 break;
302             }
303         }
304     }
305     else{
306         RETVAL = SvNIOKp(sv) ? TRUE : FALSE;
307     }
308 }
309 OUTPUT:
310     RETVAL
311
312 bool
313 is_class_loaded(SV* sv)
314
315 void
316 get_code_info(CV* code)
317 PREINIT:
318     GV* gv;
319     HV* stash;
320 PPCODE:
321     if((gv = CvGV(code)) && isGV(gv) && (stash = GvSTASH(gv))){
322         EXTEND(SP, 2);
323         mPUSHs(newSVpvn_share(HvNAME_get(stash), HvNAMELEN_get(stash), 0U));
324         mPUSHs(newSVpvn_share(GvNAME_get(gv), GvNAMELEN_get(gv), 0U));
325     }
326
327 SV*
328 get_code_package(CV* code)
329 PREINIT:
330     HV* stash;
331 CODE:
332     if(CvGV(code) && isGV(CvGV(code)) && (stash = GvSTASH(CvGV(code)))){
333         RETVAL = newSVpvn_share(HvNAME_get(stash), HvNAMELEN_get(stash), 0U);
334     }
335     else{
336         RETVAL = &PL_sv_no;
337     }
338 OUTPUT:
339     RETVAL
340
341 CV*
342 get_code_ref(SV* package, SV* name)
343 CODE:
344 {
345     HV* stash;
346     STRLEN name_len;
347     const char* name_pv;
348     GV* gv;
349
350     if(!SvOK(package)){
351         croak("You must define a package name");
352     }
353     if(!SvOK(name)){
354         croak("You must define a subroutine name");
355     }
356
357     stash = gv_stashsv(package, FALSE);
358     if(!stash){
359         XSRETURN_UNDEF;
360     }
361
362     name_pv = SvPV_const(name, name_len);
363     gv = stash_fetch(stash, name_pv, name_len, FALSE);
364     RETVAL = gv ? GvCVu(gv) : NULL;
365
366     if(!RETVAL){
367         XSRETURN_UNDEF;
368     }
369 }
370 OUTPUT:
371     RETVAL
372
373 void
374 generate_isa_predicate_for(SV* arg, SV* predicate_name = NULL)
375 ALIAS:
376     generate_isa_predicate_for = 0
377     generate_can_predicate_for = 1
378 PPCODE:
379 {
380     const char* name_pv = NULL;
381     CV* xsub;
382
383     SvGETMAGIC(arg);
384
385     if(!SvOK(arg)){
386         croak("You must define %s", ix == 0 ? "a class name" : "method names");
387     }
388
389     if(predicate_name){
390         SvGETMAGIC(predicate_name);
391         if(!SvOK(predicate_name)){
392             croak("You must define %s", "a predicate name");
393         }
394         name_pv = SvPV_nolen_const(predicate_name);
395     }
396
397     if(ix == 0){
398         xsub = mouse_generate_isa_predicate_for(aTHX_ arg, name_pv);
399     }
400     else{
401         xsub = mouse_generate_can_predicate_for(aTHX_ arg, name_pv);
402     }
403
404     if(predicate_name == NULL){ /* anonymous predicate */
405         mXPUSHs( newRV_inc((SV*)xsub) );
406     }
407 }