Move overload stuff to 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");
187     }
188 }
189
190
191 XS(XS_Mouse_reader)
192 {
193     dVAR; dXSARGS;
194     dMOUSE_self;
195     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
196
197     if (items != 1) {
198         mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
199             "Cannot assign a value to a read-only accessor");
200     }
201
202     SP -= items; /* PPCODE */
203     PUTBACK;
204
205     mouse_attr_get(aTHX_ self, mg);
206 }
207
208 XS(XS_Mouse_writer)
209 {
210     dVAR; dXSARGS;
211     dMOUSE_self;
212     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
213
214     if (items != 2) {
215         mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
216             "Too few arguments for a write-only accessor");
217     }
218
219     SP -= items; /* PPCODE */
220     PUTBACK;
221
222     mouse_attr_set(aTHX_ self, mg, ST(1));
223 }
224
225 /* simple accessors */
226
227 /*
228 static MAGIC*
229 mouse_accessor_get_mg(pTHX_ CV* const xsub){
230     return moose_mg_find(aTHX_ (SV*)xsub, &mouse_simple_accessor_vtbl, MOOSEf_DIE_ON_FAIL);
231 }
232 */
233
234 CV*
235 mouse_simple_accessor_generate(pTHX_ const char* const fq_name, const char* const key, I32 const keylen, XSUBADDR_t const accessor_impl, void* const dptr, I32 const dlen){
236     CV* const xsub = newXS((char*)fq_name, accessor_impl, __FILE__);
237     SV* const slot = newSVpvn_share(key, keylen, 0U);
238     MAGIC* mg;
239
240     if(!fq_name){
241         /* anonymous xsubs need sv_2mortal */
242         sv_2mortal((SV*)xsub);
243     }
244
245     mg = sv_magicext((SV*)xsub, slot, PERL_MAGIC_ext, &mouse_accessor_vtbl, (char*)dptr, dlen);
246     SvREFCNT_dec(slot); /* sv_magicext() increases refcnt in mg_obj */
247     if(dlen == HEf_SVKEY){
248         SvREFCNT_dec(dptr);
249     }
250
251     /* NOTE:
252      * although we use MAGIC for gc, we also store mg to CvXSUBANY for efficiency (gfx)
253      */
254     CvXSUBANY(xsub).any_ptr = (void*)mg;
255
256     return xsub;
257 }
258
259 XS(XS_Mouse_simple_reader)
260 {
261     dVAR; dXSARGS;
262     dMOUSE_self;
263     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
264     SV* value;
265
266     if (items != 1) {
267         croak("Expected exactly one argument for a reader for '%"SVf"'", MOUSE_mg_slot(mg));
268     }
269
270     value = get_slot(self, MOUSE_mg_slot(mg));
271     if(!value) {
272         if(MOUSE_mg_ptr(mg)){
273             /* the default value must be a SV */
274             assert(MOUSE_mg_len(mg) == HEf_SVKEY);
275             value = (SV*)MOUSE_mg_ptr(mg);
276         }
277         else{
278             value = &PL_sv_undef;
279         }
280     }
281
282     ST(0) = value;
283     XSRETURN(1);
284 }
285
286
287 XS(XS_Mouse_simple_writer)
288 {
289     dVAR; dXSARGS;
290     dMOUSE_self;
291     SV* const slot = MOUSE_mg_slot((MAGIC*)XSANY.any_ptr);
292
293     if (items != 2) {
294         croak("Expected exactly two argument for a writer for '%"SVf"'", slot);
295     }
296
297     ST(0) = set_slot(self, slot, ST(1));
298     XSRETURN(1);
299 }
300
301 XS(XS_Mouse_simple_clearer)
302 {
303     dVAR; dXSARGS;
304     dMOUSE_self;
305     SV* const slot = MOUSE_mg_slot((MAGIC*)XSANY.any_ptr);
306     SV* value;
307
308     if (items != 1) {
309         croak("Expected exactly one argument for a clearer for '%"SVf"'", slot);
310     }
311
312     value = delete_slot(self, slot);
313     ST(0) = value ? value : &PL_sv_undef;
314     XSRETURN(1);
315 }
316
317 XS(XS_Mouse_simple_predicate)
318 {
319     dVAR; dXSARGS;
320     dMOUSE_self;
321     SV* const slot = MOUSE_mg_slot((MAGIC*)XSANY.any_ptr);
322
323     if (items != 1) {
324         croak("Expected exactly one argument for a predicate for '%"SVf"'", slot);
325     }
326
327     ST(0) = boolSV( has_slot(self, slot) );
328     XSRETURN(1);
329 }
330
331 /* simple instance slot accessor (or Mouse::Meta::Instance) */
332
333 SV*
334 mouse_instance_create(pTHX_ HV* const stash) {
335     SV* instance;
336     assert(stash);
337     assert(SvTYPE(stash) == SVt_PVHV);
338     instance = sv_bless( newRV_noinc((SV*)newHV()), stash );
339     return sv_2mortal(instance);
340 }
341
342 SV*
343 mouse_instance_clone(pTHX_ SV* const instance) {
344     SV* proto;
345     CHECK_INSTANCE(instance);
346     assert(SvOBJECT(SvRV(instance)));
347
348     proto = newRV_noinc((SV*)newHVhv((HV*)SvRV(instance)));
349     sv_bless(proto, SvSTASH(SvRV(instance)));
350     return sv_2mortal(proto);
351 }
352
353 bool
354 mouse_instance_has_slot(pTHX_ SV* const instance, SV* const slot) {
355     assert(slot);
356     CHECK_INSTANCE(instance);
357     return hv_exists_ent((HV*)SvRV(instance), slot, 0U);
358 }
359
360 SV*
361 mouse_instance_get_slot(pTHX_ SV* const instance, SV* const slot) {
362     HE* he;
363     assert(slot);
364     CHECK_INSTANCE(instance);
365     he = hv_fetch_ent((HV*)SvRV(instance), slot, FALSE, 0U);
366     return he ? HeVAL(he) : NULL;
367 }
368
369 SV*
370 mouse_instance_set_slot(pTHX_ SV* const instance, SV* const slot, SV* const value) {
371     HE* he;
372     SV* sv;
373     assert(slot);
374     assert(value);
375     CHECK_INSTANCE(instance);
376     he = hv_fetch_ent((HV*)SvRV(instance), slot, TRUE, 0U);
377     sv = HeVAL(he);
378     sv_setsv(sv, value);
379     SvSETMAGIC(sv);
380     return sv;
381 }
382
383 SV*
384 mouse_instance_delete_slot(pTHX_ SV* const instance, SV* const slot) {
385     assert(instance);
386     assert(slot);
387     CHECK_INSTANCE(instance);
388     return hv_delete_ent((HV*)SvRV(instance), slot, 0, 0U);
389 }
390
391 void
392 mouse_instance_weaken_slot(pTHX_ SV* const instance, SV* const slot) {
393     HE* he;
394     assert(slot);
395     CHECK_INSTANCE(instance);
396     he = hv_fetch_ent((HV*)SvRV(instance), slot, FALSE, 0U);
397     if(he){
398         sv_rvweaken(HeVAL(he));
399     }
400 }
401
402 MODULE = Mouse::Meta::Method::Accessor::XS  PACKAGE = Mouse::Meta::Method::Accessor::XS
403
404 PROTOTYPES:   DISABLE
405 VERSIONCHECK: DISABLE
406
407 CV*
408 _generate_accessor(klass, SV* attr, metaclass)
409 CODE:
410 {
411     RETVAL = mouse_accessor_generate(aTHX_ attr, XS_Mouse_accessor);
412 }
413 OUTPUT:
414     RETVAL
415
416 CV*
417 _generate_reader(klass, SV* attr, metaclass)
418 CODE:
419 {
420     RETVAL = mouse_accessor_generate(aTHX_ attr, XS_Mouse_reader);
421 }
422 OUTPUT:
423     RETVAL
424
425 CV*
426 _generate_writer(klass, SV* attr, metaclass)
427 CODE:
428 {
429     RETVAL = mouse_accessor_generate(aTHX_ attr, XS_Mouse_writer);
430 }
431 OUTPUT:
432     RETVAL
433
434 CV*
435 _generate_clearer(klass, SV* attr, metaclass)
436 CODE:
437 {
438     SV* const slot = mcall0(attr, mouse_name);
439     STRLEN len;
440     const char* const pv = SvPV_const(slot, len);
441     RETVAL = mouse_simple_accessor_generate(aTHX_ NULL, pv, len, XS_Mouse_simple_clearer, NULL, 0);
442 }
443 OUTPUT:
444     RETVAL
445
446 CV*
447 _generate_predicate(klass, SV* attr, metaclass)
448 CODE:
449 {
450     SV* const slot = mcall0(attr, mouse_name);
451     STRLEN len;
452     const char* const pv = SvPV_const(slot, len);
453     RETVAL = mouse_simple_accessor_generate(aTHX_ NULL, pv, len, XS_Mouse_simple_predicate, NULL, 0);
454 }
455 OUTPUT:
456     RETVAL
457