0d0d44818f14de2da0e7a7c43041c26748b380b8
[gitmo/Mouse.git] / xs-src / MouseAccessor.xs
1 #include "mouse.h"
2
3 #define CHECK_INSTANCE(instance) STMT_START{                           \
4         assert(instance);                                              \
5         if(!(SvROK(instance) && SvTYPE(SvRV(instance)) == SVt_PVHV)){  \
6             croak("Invalid object instance: '%"SVf"'", instance);      \
7         }                                                              \
8     } STMT_END
9
10
11 #define MOUSE_mg_attribute(mg) MOUSE_xa_attribute(MOUSE_mg_xa(mg))
12
13 static MGVTBL mouse_accessor_vtbl; /* MAGIC identity */
14
15 #define dMOUSE_self  SV* const self = mouse_accessor_get_self(aTHX_ ax, items, cv)
16
17 STATIC_INLINE SV*
18 mouse_accessor_get_self(pTHX_ I32 const ax, I32 const items, CV* const cv) {
19     if(items < 1){
20         croak("Too few arguments for %s", GvNAME(CvGV(cv)));
21     }
22
23     /* NOTE: If self has GETMAGIC, $self->accessor will invoke GETMAGIC
24      *       before calling methods, so SvGETMAGIC(self) is not necessarily needed here.
25      */
26
27     return ST(0);
28 }
29
30
31 CV*
32 mouse_accessor_generate(pTHX_ SV* const attr, XSUBADDR_t const accessor_impl){
33     AV* const xa = mouse_get_xa(aTHX_ attr);
34     CV* xsub;
35     MAGIC* mg;
36
37     xsub = newXS(NULL, accessor_impl, __FILE__);
38     sv_2mortal((SV*)xsub);
39
40     mg = sv_magicext((SV*)xsub, MOUSE_xa_slot(xa), PERL_MAGIC_ext, &mouse_accessor_vtbl, (char*)xa, HEf_SVKEY);
41
42     MOUSE_mg_flags(mg) = (U16)MOUSE_xa_flags(xa);
43
44     /* NOTE:
45      * although we use MAGIC for gc, we also store mg to CvXSUBANY for efficiency (gfx)
46      */
47     CvXSUBANY(xsub).any_ptr = (void*)mg;
48
49     return xsub;
50 }
51
52
53 #define PUSH_VALUE(value, flags) STMT_START { \
54         if((flags) & MOUSEf_ATTR_SHOULD_AUTO_DEREF && GIMME_V == G_ARRAY){ \
55             mouse_push_values(aTHX_ value, (flags));                       \
56         }                                                                  \
57         else{                                                              \
58             dSP;                                                           \
59             XPUSHs(value ? value : &PL_sv_undef);                          \
60             PUTBACK;                                                       \
61         }                                                                  \
62     } STMT_END                                                             \
63
64 /* pushes return values, does auto-deref if needed */
65 static void
66 mouse_push_values(pTHX_ SV* const value, U16 const flags){
67     dSP;
68
69     assert( flags & MOUSEf_ATTR_SHOULD_AUTO_DEREF && GIMME_V == G_ARRAY );
70
71     if(!(value && SvOK(value))){
72         return;
73     }
74
75     if(flags & MOUSEf_TC_IS_ARRAYREF){
76         AV* av;
77         I32 len;
78         I32 i;
79
80         if(!IsArrayRef(value)){
81             croak("Mouse-panic: Not an ARRAY reference");
82         }
83
84         av  = (AV*)SvRV(value);
85         len = av_len(av) + 1;
86         EXTEND(SP, len);
87         for(i = 0; i < len; i++){
88             SV** const svp = av_fetch(av, i, FALSE);
89             PUSHs(svp ? *svp : &PL_sv_undef);
90         }
91     }
92     else{
93         HV* hv;
94         HE* he;
95
96         assert(flags & MOUSEf_TC_IS_HASHREF);
97
98         if(!IsHashRef(value)){
99             croak("Mouse-panic: Not a HASH reference");
100         }
101
102         hv = (HV*)SvRV(value);
103         hv_iterinit(hv);
104         while((he = hv_iternext(hv))){
105             EXTEND(SP, 2);
106             PUSHs(hv_iterkeysv(he));
107             PUSHs(hv_iterval(hv, he));
108         }
109     }
110
111     PUTBACK;
112 }
113
114 static void
115 mouse_attr_get(pTHX_ SV* const self, MAGIC* const mg){
116     U16 const flags = MOUSE_mg_flags(mg);
117     SV* value;
118
119     value = get_slot(self, MOUSE_mg_slot(mg));
120
121     /* check_lazy */
122     if( !value && flags & MOUSEf_ATTR_IS_LAZY ){
123         value = mouse_xa_set_default(aTHX_ MOUSE_mg_xa(mg), self);
124     }
125
126     PUSH_VALUE(value, flags);
127 }
128
129 static void
130 mouse_attr_set(pTHX_ SV* const self, MAGIC* const mg, SV* value){
131     U16 const flags = MOUSE_mg_flags(mg);
132     SV* const slot  = MOUSE_mg_slot(mg);
133
134     if(flags & MOUSEf_ATTR_HAS_TC){
135         value = mouse_xa_apply_type_constraint(aTHX_ MOUSE_mg_xa(mg), value, flags);
136     }
137
138     value = set_slot(self, slot, value);
139
140     if(flags & MOUSEf_ATTR_IS_WEAK_REF){
141         weaken_slot(self, slot);
142     }
143
144     if(flags & MOUSEf_ATTR_HAS_TRIGGER){
145         SV* const trigger = mcall0s(MOUSE_mg_attribute(mg), "trigger");
146         dSP;
147
148         /* NOTE: triggers can remove value, so
149                  value must be copied here,
150                  revealed by Net::Google::DataAPI (DANJOU).
151          */
152         value = sv_mortalcopy(value);
153
154         PUSHMARK(SP);
155         EXTEND(SP, 2);
156         PUSHs(self);
157         PUSHs(value);
158
159         PUTBACK;
160         call_sv_safe(trigger, G_VOID | G_DISCARD);
161         /* need not SPAGAIN */
162
163         assert(SvTYPE(value) != SVTYPEMASK);
164     }
165
166     PUSH_VALUE(value, flags);
167 }
168
169 XS(XS_Mouse_accessor)
170 {
171     dVAR; dXSARGS;
172     dMOUSE_self;
173     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
174
175     SP -= items; /* PPCODE */
176     PUTBACK;
177
178     if(items == 1){ /* reader */
179         mouse_attr_get(aTHX_ self, mg);
180     }
181     else if (items == 2){ /* writer */
182         mouse_attr_set(aTHX_ self, mg, ST(1));
183     }
184     else{
185         mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
186             "Expected exactly one or two argument for an accessor of %"SVf,
187             MOUSE_mg_slot(mg));
188     }
189 }
190
191
192 XS(XS_Mouse_reader)
193 {
194     dVAR; dXSARGS;
195     dMOUSE_self;
196     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
197
198     if (items != 1) {
199         mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
200             "Cannot assign a value to a read-only accessor of %"SVf,
201             MOUSE_mg_slot(mg));
202     }
203
204     SP -= items; /* PPCODE */
205     PUTBACK;
206
207     mouse_attr_get(aTHX_ self, mg);
208 }
209
210 XS(XS_Mouse_writer)
211 {
212     dVAR; dXSARGS;
213     dMOUSE_self;
214     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
215
216     if (items != 2) {
217         mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
218             "Too few arguments for a write-only accessor of %"SVf,
219             MOUSE_mg_slot(mg));
220     }
221
222     SP -= items; /* PPCODE */
223     PUTBACK;
224
225     mouse_attr_set(aTHX_ self, mg, ST(1));
226 }
227
228 /* simple accessors */
229
230 /*
231 static MAGIC*
232 mouse_accessor_get_mg(pTHX_ CV* const xsub){
233     return moose_mg_find(aTHX_ (SV*)xsub, &mouse_simple_accessor_vtbl, MOOSEf_DIE_ON_FAIL);
234 }
235 */
236
237 CV*
238 mouse_simple_accessor_generate(pTHX_
239     const char* const fq_name, const char* const key, I32 const keylen,
240     XSUBADDR_t const accessor_impl, void* const dptr, I32 const dlen) {
241     CV* const xsub = newXS((char*)fq_name, accessor_impl, __FILE__);
242     SV* const slot = newSVpvn_share(key, keylen, 0U);
243     MAGIC* mg;
244
245     if(!fq_name){
246         /* anonymous xsubs need sv_2mortal */
247         sv_2mortal((SV*)xsub);
248     }
249
250     mg = sv_magicext((SV*)xsub, slot, PERL_MAGIC_ext, &mouse_accessor_vtbl, (char*)dptr, dlen);
251     SvREFCNT_dec(slot); /* sv_magicext() increases refcnt in mg_obj */
252     if(dlen == HEf_SVKEY){
253         SvREFCNT_dec(dptr);
254     }
255
256     /* NOTE:
257      * although we use MAGIC for gc, we also store mg to CvXSUBANY for efficiency (gfx)
258      */
259     CvXSUBANY(xsub).any_ptr = (void*)mg;
260
261     return xsub;
262 }
263
264 XS(XS_Mouse_simple_reader)
265 {
266     dVAR; dXSARGS;
267     dMOUSE_self;
268     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
269     SV* value;
270
271     if (items != 1) {
272         croak("Expected exactly one argument for a reader of %"SVf,
273             MOUSE_mg_slot(mg));
274     }
275
276     value = get_slot(self, MOUSE_mg_slot(mg));
277     if(!value) {
278         if(MOUSE_mg_ptr(mg)){
279             /* the default value must be a SV */
280             assert(MOUSE_mg_len(mg) == HEf_SVKEY);
281             value = (SV*)MOUSE_mg_ptr(mg);
282         }
283         else{
284             value = &PL_sv_undef;
285         }
286     }
287
288     ST(0) = value;
289     XSRETURN(1);
290 }
291
292
293 XS(XS_Mouse_simple_writer)
294 {
295     dVAR; dXSARGS;
296     dMOUSE_self;
297     SV* const slot = MOUSE_mg_slot((MAGIC*)XSANY.any_ptr);
298
299     if (items != 2) {
300         croak("Expected exactly two argument for a writer of %"SVf,
301             slot);
302     }
303
304     ST(0) = set_slot(self, slot, ST(1));
305     XSRETURN(1);
306 }
307
308 XS(XS_Mouse_simple_clearer)
309 {
310     dVAR; dXSARGS;
311     dMOUSE_self;
312     SV* const slot = MOUSE_mg_slot((MAGIC*)XSANY.any_ptr);
313     SV* value;
314
315     if (items != 1) {
316         croak("Expected exactly one argument for a clearer of %"SVf,
317             slot);
318     }
319
320     value = delete_slot(self, slot);
321     ST(0) = value ? value : &PL_sv_undef;
322     XSRETURN(1);
323 }
324
325 XS(XS_Mouse_simple_predicate)
326 {
327     dVAR; dXSARGS;
328     dMOUSE_self;
329     SV* const slot = MOUSE_mg_slot((MAGIC*)XSANY.any_ptr);
330
331     if (items != 1) {
332         croak("Expected exactly one argument for a predicate of %"SVf, slot);
333     }
334
335     ST(0) = boolSV( has_slot(self, slot) );
336     XSRETURN(1);
337 }
338
339 /* Class::Data::Inheritable-like class accessor */
340 XS(XS_Mouse_inheritable_class_accessor) {
341     dVAR; dXSARGS;
342     dMOUSE_self;
343     SV* const slot = MOUSE_mg_slot((MAGIC*)XSANY.any_ptr);
344     SV* value;
345     SV* stash_ref;
346     HV* stash;
347
348     if(items == 1){ /* reader */
349         value = NULL;
350     }
351     else if (items == 2){ /* writer */
352         value = ST(1);
353     }
354     else{
355         croak("Expected exactly one or two argument for a class data accessor"
356             "of %"SVf, slot);
357         value = NULL; /* -Wuninitialized */
358     }
359
360     stash_ref= mcall0(self, mouse_namespace);
361     if(!(SvROK(stash_ref) && SvTYPE(SvRV(stash_ref)) == SVt_PVHV)) {
362         croak("namespace() didn't return a HASH reference");
363     }
364     stash = (HV*)SvRV(stash_ref);
365
366     if(!value) { /* reader */
367         value = get_slot(self, slot);
368         if(!value) {
369             AV* const isa   = mro_get_linear_isa(stash);
370             I32 const len   = av_len(isa) + 1;
371             I32 i;
372             for(i = 1; i < len; i++) {
373                 SV* const klass = MOUSE_av_at(isa, i);
374                 SV* const meta  = get_metaclass(klass);
375                 if(!SvOK(meta)){
376                     continue; /* skip non-Mouse classes */
377                 }
378                 value = get_slot(meta, slot);
379                 if(value) {
380                     break;
381                 }
382             }
383             if(!value) {
384                 value = &PL_sv_undef;
385             }
386         }
387     }
388     else { /* writer */
389         set_slot(self, slot, value);
390         mro_method_changed_in(stash);
391     }
392
393     ST(0) = value;
394     XSRETURN(1);
395 }
396
397 /* simple instance slot accessor (or Mouse::Meta::Instance) */
398
399 SV*
400 mouse_instance_create(pTHX_ HV* const stash) {
401     SV* instance;
402     assert(stash);
403     assert(SvTYPE(stash) == SVt_PVHV);
404     instance = sv_bless( newRV_noinc((SV*)newHV()), stash );
405     return sv_2mortal(instance);
406 }
407
408 SV*
409 mouse_instance_clone(pTHX_ SV* const instance) {
410     SV* proto;
411     CHECK_INSTANCE(instance);
412     assert(SvOBJECT(SvRV(instance)));
413
414     proto = newRV_noinc((SV*)newHVhv((HV*)SvRV(instance)));
415     sv_bless(proto, SvSTASH(SvRV(instance)));
416     return sv_2mortal(proto);
417 }
418
419 bool
420 mouse_instance_has_slot(pTHX_ SV* const instance, SV* const slot) {
421     assert(slot);
422     CHECK_INSTANCE(instance);
423     return hv_exists_ent((HV*)SvRV(instance), slot, 0U);
424 }
425
426 SV*
427 mouse_instance_get_slot(pTHX_ SV* const instance, SV* const slot) {
428     HE* he;
429     assert(slot);
430     CHECK_INSTANCE(instance);
431     he = hv_fetch_ent((HV*)SvRV(instance), slot, FALSE, 0U);
432     return he ? HeVAL(he) : NULL;
433 }
434
435 SV*
436 mouse_instance_set_slot(pTHX_ SV* const instance, SV* const slot, SV* const value) {
437     HE* he;
438     SV* sv;
439     assert(slot);
440     assert(value);
441     CHECK_INSTANCE(instance);
442     he = hv_fetch_ent((HV*)SvRV(instance), slot, TRUE, 0U);
443     sv = HeVAL(he);
444     sv_setsv(sv, value);
445     SvSETMAGIC(sv);
446     return sv;
447 }
448
449 SV*
450 mouse_instance_delete_slot(pTHX_ SV* const instance, SV* const slot) {
451     assert(instance);
452     assert(slot);
453     CHECK_INSTANCE(instance);
454     return hv_delete_ent((HV*)SvRV(instance), slot, 0, 0U);
455 }
456
457 void
458 mouse_instance_weaken_slot(pTHX_ SV* const instance, SV* const slot) {
459     HE* he;
460     assert(slot);
461     CHECK_INSTANCE(instance);
462     he = hv_fetch_ent((HV*)SvRV(instance), slot, FALSE, 0U);
463     if(he){
464         sv_rvweaken(HeVAL(he));
465     }
466 }
467
468 MODULE = Mouse::Meta::Method::Accessor::XS  PACKAGE = Mouse::Meta::Method::Accessor::XS
469
470 PROTOTYPES:   DISABLE
471 VERSIONCHECK: DISABLE
472
473 CV*
474 _generate_accessor(klass, SV* attr, metaclass)
475 CODE:
476 {
477     RETVAL = mouse_accessor_generate(aTHX_ attr, XS_Mouse_accessor);
478 }
479 OUTPUT:
480     RETVAL
481
482 CV*
483 _generate_reader(klass, SV* attr, metaclass)
484 CODE:
485 {
486     RETVAL = mouse_accessor_generate(aTHX_ attr, XS_Mouse_reader);
487 }
488 OUTPUT:
489     RETVAL
490
491 CV*
492 _generate_writer(klass, SV* attr, metaclass)
493 CODE:
494 {
495     RETVAL = mouse_accessor_generate(aTHX_ attr, XS_Mouse_writer);
496 }
497 OUTPUT:
498     RETVAL
499
500 CV*
501 _generate_clearer(klass, SV* attr, metaclass)
502 CODE:
503 {
504     SV* const slot = mcall0(attr, mouse_name);
505     STRLEN len;
506     const char* const pv = SvPV_const(slot, len);
507     RETVAL = mouse_simple_accessor_generate(aTHX_ NULL, pv, len, XS_Mouse_simple_clearer, NULL, 0);
508 }
509 OUTPUT:
510     RETVAL
511
512 CV*
513 _generate_predicate(klass, SV* attr, metaclass)
514 CODE:
515 {
516     SV* const slot = mcall0(attr, mouse_name);
517     STRLEN len;
518     const char* const pv = SvPV_const(slot, len);
519     RETVAL = mouse_simple_accessor_generate(aTHX_ NULL, pv, len, XS_Mouse_simple_predicate, NULL, 0);
520 }
521 OUTPUT:
522     RETVAL
523