Cleanup XS
[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     HV* stash;
346
347     if(items == 1){ /* reader */
348         value = NULL;
349     }
350     else if (items == 2){ /* writer */
351         value = ST(1);
352     }
353     else{
354         croak("Expected exactly one or two argument for a class data accessor"
355             "of %"SVf, slot);
356         value = NULL; /* -Wuninitialized */
357     }
358
359     stash = mouse_get_namespace(aTHX_ self);
360
361     if(!value) { /* reader */
362         value = get_slot(self, slot);
363         if(!value) {
364             AV* const isa   = mro_get_linear_isa(stash);
365             I32 const len   = av_len(isa) + 1;
366             I32 i;
367             for(i = 1; i < len; i++) {
368                 SV* const klass = MOUSE_av_at(isa, i);
369                 SV* const meta  = get_metaclass(klass);
370                 if(!SvOK(meta)){
371                     continue; /* skip non-Mouse classes */
372                 }
373                 value = get_slot(meta, slot);
374                 if(value) {
375                     break;
376                 }
377             }
378             if(!value) {
379                 value = &PL_sv_undef;
380             }
381         }
382     }
383     else { /* writer */
384         set_slot(self, slot, value);
385         mro_method_changed_in(stash);
386     }
387
388     ST(0) = value;
389     XSRETURN(1);
390 }
391
392 /* simple instance slot accessor (or Mouse::Meta::Instance) */
393
394 SV*
395 mouse_instance_create(pTHX_ HV* const stash) {
396     SV* instance;
397     assert(stash);
398     assert(SvTYPE(stash) == SVt_PVHV);
399     instance = sv_bless( newRV_noinc((SV*)newHV()), stash );
400     return sv_2mortal(instance);
401 }
402
403 SV*
404 mouse_instance_clone(pTHX_ SV* const instance) {
405     SV* proto;
406     CHECK_INSTANCE(instance);
407     assert(SvOBJECT(SvRV(instance)));
408
409     proto = newRV_noinc((SV*)newHVhv((HV*)SvRV(instance)));
410     sv_bless(proto, SvSTASH(SvRV(instance)));
411     return sv_2mortal(proto);
412 }
413
414 bool
415 mouse_instance_has_slot(pTHX_ SV* const instance, SV* const slot) {
416     assert(slot);
417     CHECK_INSTANCE(instance);
418     return hv_exists_ent((HV*)SvRV(instance), slot, 0U);
419 }
420
421 SV*
422 mouse_instance_get_slot(pTHX_ SV* const instance, SV* const slot) {
423     HE* he;
424     assert(slot);
425     CHECK_INSTANCE(instance);
426     he = hv_fetch_ent((HV*)SvRV(instance), slot, FALSE, 0U);
427     return he ? HeVAL(he) : NULL;
428 }
429
430 SV*
431 mouse_instance_set_slot(pTHX_ SV* const instance, SV* const slot, SV* const value) {
432     HE* he;
433     SV* sv;
434     assert(slot);
435     assert(value);
436     CHECK_INSTANCE(instance);
437     he = hv_fetch_ent((HV*)SvRV(instance), slot, TRUE, 0U);
438     sv = HeVAL(he);
439     sv_setsv(sv, value);
440     SvSETMAGIC(sv);
441     return sv;
442 }
443
444 SV*
445 mouse_instance_delete_slot(pTHX_ SV* const instance, SV* const slot) {
446     assert(instance);
447     assert(slot);
448     CHECK_INSTANCE(instance);
449     return hv_delete_ent((HV*)SvRV(instance), slot, 0, 0U);
450 }
451
452 void
453 mouse_instance_weaken_slot(pTHX_ SV* const instance, SV* const slot) {
454     HE* he;
455     assert(slot);
456     CHECK_INSTANCE(instance);
457     he = hv_fetch_ent((HV*)SvRV(instance), slot, FALSE, 0U);
458     if(he){
459         sv_rvweaken(HeVAL(he));
460     }
461 }
462
463 MODULE = Mouse::Meta::Method::Accessor::XS  PACKAGE = Mouse::Meta::Method::Accessor::XS
464
465 PROTOTYPES:   DISABLE
466 VERSIONCHECK: DISABLE
467
468 CV*
469 _generate_accessor(klass, SV* attr, metaclass)
470 CODE:
471 {
472     RETVAL = mouse_accessor_generate(aTHX_ attr, XS_Mouse_accessor);
473 }
474 OUTPUT:
475     RETVAL
476
477 CV*
478 _generate_reader(klass, SV* attr, metaclass)
479 CODE:
480 {
481     RETVAL = mouse_accessor_generate(aTHX_ attr, XS_Mouse_reader);
482 }
483 OUTPUT:
484     RETVAL
485
486 CV*
487 _generate_writer(klass, SV* attr, metaclass)
488 CODE:
489 {
490     RETVAL = mouse_accessor_generate(aTHX_ attr, XS_Mouse_writer);
491 }
492 OUTPUT:
493     RETVAL
494
495 CV*
496 _generate_clearer(klass, SV* attr, metaclass)
497 CODE:
498 {
499     SV* const slot = mcall0(attr, mouse_name);
500     STRLEN len;
501     const char* const pv = SvPV_const(slot, len);
502     RETVAL = mouse_simple_accessor_generate(aTHX_ NULL, pv, len, XS_Mouse_simple_clearer, NULL, 0);
503 }
504 OUTPUT:
505     RETVAL
506
507 CV*
508 _generate_predicate(klass, SV* attr, metaclass)
509 CODE:
510 {
511     SV* const slot = mcall0(attr, mouse_name);
512     STRLEN len;
513     const char* const pv = SvPV_const(slot, len);
514     RETVAL = mouse_simple_accessor_generate(aTHX_ NULL, pv, len, XS_Mouse_simple_predicate, NULL, 0);
515 }
516 OUTPUT:
517     RETVAL
518