Fix clone_object() to ignore hehaviours without init args
[gitmo/Mouse.git] / xs-src / MouseAccessor.xs
1 #include "mouse.h"
2
3 #define CHECK_INSTANCE(instance) STMT_START{                           \
4         if(!(SvROK(instance) && SvTYPE(SvRV(instance)) == SVt_PVHV)){  \
5             croak("Invalid object instance");                          \
6         }                                                              \
7     } STMT_END
8
9
10 #define MOUSE_mg_attribute(mg) MOUSE_xa_attribute(MOUSE_mg_xa(mg))
11
12 static MGVTBL mouse_accessor_vtbl; /* MAGIC identity */
13
14 #define dMOUSE_self  SV* const self = mouse_accessor_get_self(aTHX_ ax, items, cv)
15
16 static inline SV*
17 mouse_accessor_get_self(pTHX_ I32 const ax, I32 const items, CV* const cv) {
18     if(items < 1){
19         croak("Too few arguments for %s", GvNAME(CvGV(cv)));
20     }
21
22     /* NOTE: If self has GETMAGIC, $self->accessor will invoke GETMAGIC
23      *       before calling methods, so SvGETMAGIC(self) is not necessarily needed here.
24      */
25
26     return ST(0);
27 }
28
29
30 CV*
31 mouse_accessor_generate(pTHX_ SV* const attr, XSUBADDR_t const accessor_impl){
32     AV* const xa = mouse_get_xa(aTHX_ attr);
33     CV* xsub;
34     MAGIC* mg;
35
36     xsub = newXS(NULL, accessor_impl, __FILE__);
37     sv_2mortal((SV*)xsub);
38
39     mg = sv_magicext((SV*)xsub, MOUSE_xa_slot(xa), PERL_MAGIC_ext, &mouse_accessor_vtbl, (char*)xa, HEf_SVKEY);
40
41     MOUSE_mg_flags(mg) = (U16)MOUSE_xa_flags(xa);
42
43     /* NOTE:
44      * although we use MAGIC for gc, we also store mg to CvXSUBANY for efficiency (gfx)
45      */
46     CvXSUBANY(xsub).any_ptr = (void*)mg;
47
48     return xsub;
49 }
50
51
52 #define PUSH_VALUE(value, flags) STMT_START { \
53         if((flags) & MOUSEf_ATTR_SHOULD_AUTO_DEREF && GIMME_V == G_ARRAY){ \
54             mouse_push_values(aTHX_ value, (flags));                       \
55         }                                                                  \
56         else{                                                              \
57             dSP;                                                           \
58             XPUSHs(value ? value : &PL_sv_undef);                          \
59             PUTBACK;                                                       \
60         }                                                                  \
61     } STMT_END                                                             \
62
63 /* pushes return values, does auto-deref if needed */
64 static void
65 mouse_push_values(pTHX_ SV* const value, U16 const flags){
66     dSP;
67
68     assert( flags & MOUSEf_ATTR_SHOULD_AUTO_DEREF && GIMME_V == G_ARRAY );
69
70     if(!(value && SvOK(value))){
71         return;
72     }
73
74     if(flags & MOUSEf_TC_IS_ARRAYREF){
75         AV* av;
76         I32 len;
77         I32 i;
78
79         if(!IsArrayRef(value)){
80             croak("Mouse-panic: Not an ARRAY reference");
81         }
82
83         av  = (AV*)SvRV(value);
84         len = av_len(av) + 1;
85         EXTEND(SP, len);
86         for(i = 0; i < len; i++){
87             SV** const svp = av_fetch(av, i, FALSE);
88             PUSHs(svp ? *svp : &PL_sv_undef);
89         }
90     }
91     else{
92         HV* hv;
93         HE* he;
94
95         assert(flags & MOUSEf_TC_IS_HASHREF);
96
97         if(!IsHashRef(value)){
98             croak("Mouse-panic: Not a HASH reference");
99         }
100
101         hv = (HV*)SvRV(value);
102         hv_iterinit(hv);
103         while((he = hv_iternext(hv))){
104             EXTEND(SP, 2);
105             PUSHs(hv_iterkeysv(he));
106             PUSHs(hv_iterval(hv, he));
107         }
108     }
109
110     PUTBACK;
111 }
112
113 static void
114 mouse_attr_get(pTHX_ SV* const self, MAGIC* const mg){
115     U16 const flags = MOUSE_mg_flags(mg);
116     SV* value;
117
118     value = get_slot(self, MOUSE_mg_slot(mg));
119
120     /* check_lazy */
121     if( !value && flags & MOUSEf_ATTR_IS_LAZY ){
122         value = mouse_xa_set_default(aTHX_ MOUSE_mg_xa(mg), self);
123     }
124
125     PUSH_VALUE(value, flags);
126 }
127
128 static void
129 mouse_attr_set(pTHX_ SV* const self, MAGIC* const mg, SV* value){
130     U16 const flags = MOUSE_mg_flags(mg);
131     SV* const slot  = MOUSE_mg_slot(mg);
132
133     if(flags & MOUSEf_ATTR_HAS_TC){
134         value = mouse_xa_apply_type_constraint(aTHX_ MOUSE_mg_xa(mg), value, flags);
135     }
136
137     value = set_slot(self, slot, value);
138
139     if(flags & MOUSEf_ATTR_IS_WEAK_REF){
140         weaken_slot(self, slot);
141     }
142
143     if(flags & MOUSEf_ATTR_HAS_TRIGGER){
144         SV* const trigger = mcall0s(MOUSE_mg_attribute(mg), "trigger");
145         dSP;
146
147         /* NOTE: triggers can remove value, so
148                  value must be copied here,
149                  revealed by Net::Google::DataAPI (DANJOU).
150          */
151         value = sv_mortalcopy(value);
152
153         PUSHMARK(SP);
154         EXTEND(SP, 2);
155         PUSHs(self);
156         PUSHs(value);
157
158         PUTBACK;
159         call_sv_safe(trigger, G_VOID | G_DISCARD);
160         /* need not SPAGAIN */
161
162         assert(SvTYPE(value) != SVTYPEMASK);
163     }
164
165     PUSH_VALUE(value, flags);
166 }
167
168 XS(XS_Mouse_accessor)
169 {
170     dVAR; dXSARGS;
171     dMOUSE_self;
172     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
173
174     SP -= items; /* PPCODE */
175     PUTBACK;
176
177     if(items == 1){ /* reader */
178         mouse_attr_get(aTHX_ self, mg);
179     }
180     else if (items == 2){ /* writer */
181         mouse_attr_set(aTHX_ self, mg, ST(1));
182     }
183     else{
184         mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
185             "Expected exactly one or two argument for an accessor");
186     }
187 }
188
189
190 XS(XS_Mouse_reader)
191 {
192     dVAR; dXSARGS;
193     dMOUSE_self;
194     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
195
196     if (items != 1) {
197         mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
198             "Cannot assign a value to a read-only accessor");
199     }
200
201     SP -= items; /* PPCODE */
202     PUTBACK;
203
204     mouse_attr_get(aTHX_ self, mg);
205 }
206
207 XS(XS_Mouse_writer)
208 {
209     dVAR; dXSARGS;
210     dMOUSE_self;
211     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
212
213     if (items != 2) {
214         mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
215             "Too few arguments for a write-only accessor");
216     }
217
218     SP -= items; /* PPCODE */
219     PUTBACK;
220
221     mouse_attr_set(aTHX_ self, mg, ST(1));
222 }
223
224 /* simple accessors */
225
226 /*
227 static MAGIC*
228 mouse_accessor_get_mg(pTHX_ CV* const xsub){
229     return moose_mg_find(aTHX_ (SV*)xsub, &mouse_simple_accessor_vtbl, MOOSEf_DIE_ON_FAIL);
230 }
231 */
232
233 CV*
234 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){
235     CV* const xsub = newXS((char*)fq_name, accessor_impl, __FILE__);
236     SV* const slot = newSVpvn_share(key, keylen, 0U);
237     MAGIC* mg;
238
239     if(!fq_name){
240         /* anonymous xsubs need sv_2mortal */
241         sv_2mortal((SV*)xsub);
242     }
243
244     mg = sv_magicext((SV*)xsub, slot, PERL_MAGIC_ext, &mouse_accessor_vtbl, (char*)dptr, dlen);
245     SvREFCNT_dec(slot); /* sv_magicext() increases refcnt in mg_obj */
246     if(dlen == HEf_SVKEY){
247         SvREFCNT_dec(dptr);
248     }
249
250     /* NOTE:
251      * although we use MAGIC for gc, we also store mg to CvXSUBANY for efficiency (gfx)
252      */
253     CvXSUBANY(xsub).any_ptr = (void*)mg;
254
255     return xsub;
256 }
257
258 XS(XS_Mouse_simple_reader)
259 {
260     dVAR; dXSARGS;
261     dMOUSE_self;
262     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
263     SV* value;
264
265     if (items != 1) {
266         croak("Expected exactly one argument for a reader for '%"SVf"'", MOUSE_mg_slot(mg));
267     }
268
269     value = get_slot(self, MOUSE_mg_slot(mg));
270     if(!value) {
271         if(MOUSE_mg_ptr(mg)){
272             /* the default value must be a SV */
273             assert(MOUSE_mg_len(mg) == HEf_SVKEY);
274             value = (SV*)MOUSE_mg_ptr(mg);
275         }
276         else{
277             value = &PL_sv_undef;
278         }
279     }
280
281     ST(0) = value;
282     XSRETURN(1);
283 }
284
285
286 XS(XS_Mouse_simple_writer)
287 {
288     dVAR; dXSARGS;
289     dMOUSE_self;
290     SV* const slot = MOUSE_mg_slot((MAGIC*)XSANY.any_ptr);
291
292     if (items != 2) {
293         croak("Expected exactly two argument for a writer for '%"SVf"'", slot);
294     }
295
296     ST(0) = set_slot(self, slot, ST(1));
297     XSRETURN(1);
298 }
299
300 XS(XS_Mouse_simple_clearer)
301 {
302     dVAR; dXSARGS;
303     dMOUSE_self;
304     SV* const slot = MOUSE_mg_slot((MAGIC*)XSANY.any_ptr);
305     SV* value;
306
307     if (items != 1) {
308         croak("Expected exactly one argument for a clearer for '%"SVf"'", slot);
309     }
310
311     value = delete_slot(self, slot);
312     ST(0) = value ? value : &PL_sv_undef;
313     XSRETURN(1);
314 }
315
316 XS(XS_Mouse_simple_predicate)
317 {
318     dVAR; dXSARGS;
319     dMOUSE_self;
320     SV* const slot = MOUSE_mg_slot((MAGIC*)XSANY.any_ptr);
321
322     if (items != 1) {
323         croak("Expected exactly one argument for a predicate for '%"SVf"'", slot);
324     }
325
326     ST(0) = boolSV( has_slot(self, slot) );
327     XSRETURN(1);
328 }
329
330 /* simple instance slot accessor (or Mouse::Meta::Instance) */
331
332 SV*
333 mouse_instance_create(pTHX_ HV* const stash) {
334     assert(stash);
335     assert(SvTYPE(stash) == SVt_PVHV);
336     return sv_bless( newRV_noinc((SV*)newHV()), stash );
337 }
338
339 SV*
340 mouse_instance_clone(pTHX_ SV* const instance) {
341     HV* proto;
342     assert(instance);
343
344     CHECK_INSTANCE(instance);
345     proto = newHVhv((HV*)SvRV(instance));
346     return sv_bless( newRV_noinc((SV*)proto), SvSTASH(SvRV(instance)) );
347 }
348
349 bool
350 mouse_instance_has_slot(pTHX_ SV* const instance, SV* const slot) {
351     assert(instance);
352     assert(slot);
353     CHECK_INSTANCE(instance);
354     return hv_exists_ent((HV*)SvRV(instance), slot, 0U);
355 }
356
357 SV*
358 mouse_instance_get_slot(pTHX_ SV* const instance, SV* const slot) {
359     HE* he;
360     assert(instance);
361     assert(slot);
362     CHECK_INSTANCE(instance);
363     he = hv_fetch_ent((HV*)SvRV(instance), slot, FALSE, 0U);
364     return he ? HeVAL(he) : NULL;
365 }
366
367 SV*
368 mouse_instance_set_slot(pTHX_ SV* const instance, SV* const slot, SV* const value) {
369     HE* he;
370     SV* sv;
371     assert(instance);
372     assert(slot);
373     assert(value);
374     CHECK_INSTANCE(instance);
375     he = hv_fetch_ent((HV*)SvRV(instance), slot, TRUE, 0U);
376     sv = HeVAL(he);
377     sv_setsv(sv, value);
378     SvSETMAGIC(sv);
379     return sv;
380 }
381
382 SV*
383 mouse_instance_delete_slot(pTHX_ SV* const instance, SV* const slot) {
384     assert(instance);
385     assert(slot);
386     CHECK_INSTANCE(instance);
387     return hv_delete_ent((HV*)SvRV(instance), slot, 0, 0U);
388 }
389
390 void
391 mouse_instance_weaken_slot(pTHX_ SV* const instance, SV* const slot) {
392     HE* he;
393     assert(instance);
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