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