Use bit-or, not simple assignment
[gitmo/Mouse.git] / xs-src / mouse_accessor.xs
1 #include "mouse.h"
2
3 /* Moose XS Attribute object */
4 enum mouse_xa_ix_t{
5     MOUSE_XA_ATTRIBUTE,
6     MOUSE_XA_TC,
7     MOUSE_XA_TC_CODE,
8
9     MOUSE_XA_last
10 };
11
12 #define MOUSE_xa_attribute(m) MOUSE_av_at(m, MOUSE_XA_ATTRIBUTE)
13 #define MOUSE_xa_tc(m)        MOUSE_av_at(m, MOUSE_XA_TC)
14 #define MOUSE_xa_tc_code(m)   MOUSE_av_at(m, MOUSE_XA_TC_CODE)
15
16 #define MOUSE_mg_attribute(mg) MOUSE_xa_attribute(MOUSE_mg_xa(mg))
17
18 enum mouse_xa_flags_t{
19     MOUSEf_ATTR_HAS_TC          = 0x0001,
20     MOUSEf_ATTR_HAS_DEFAULT     = 0x0002,
21     MOUSEf_ATTR_HAS_BUILDER     = 0x0004,
22     MOUSEf_ATTR_HAS_INITIALIZER = 0x0008, /* not used in Mouse */
23     MOUSEf_ATTR_HAS_TRIGGER     = 0x0010,
24
25     MOUSEf_ATTR_IS_LAZY         = 0x0020,
26     MOUSEf_ATTR_IS_WEAK_REF     = 0x0040,
27     MOUSEf_ATTR_IS_REQUIRED     = 0x0080,
28
29     MOUSEf_ATTR_SHOULD_COERCE   = 0x0100,
30
31     MOUSEf_ATTR_SHOULD_AUTO_DEREF
32                                 = 0x0200,
33     MOUSEf_TC_IS_ARRAYREF       = 0x0400,
34     MOUSEf_TC_IS_HASHREF        = 0x0800,
35
36     MOUSEf_OTHER1               = 0x1000,
37     MOUSEf_OTHER2               = 0x2000,
38     MOUSEf_OTHER3               = 0x4000,
39     MOUSEf_OTHER4               = 0x8000,
40
41     MOUSEf_MOUSE_MASK           = 0xFFFF /* not used */
42 };
43
44 static MGVTBL mouse_accessor_vtbl; /* MAGIC identity */
45
46 CV*
47 mouse_instantiate_xs_accessor(pTHX_ SV* const attr, XSUBADDR_t const accessor_impl){
48     SV* const slot = mcall0(attr,  mouse_name);
49     AV* const xa = newAV();
50     CV* xsub;
51     MAGIC* mg;
52     U16 flags = 0;
53
54     sv_2mortal((SV*)xa);
55
56     xsub = newXS(NULL, accessor_impl, __FILE__);
57     sv_2mortal((SV*)xsub);
58
59     mg = sv_magicext((SV*)xsub, slot, PERL_MAGIC_ext, &mouse_accessor_vtbl, (char*)xa, HEf_SVKEY);
60
61     /* NOTE:
62      * although we use MAGIC for gc, we also store mg to CvXSUBANY for efficiency (gfx)
63      */
64     CvXSUBANY(xsub).any_ptr = (void*)mg;
65
66     av_extend(xa, MOUSE_XA_last - 1);
67
68     av_store(xa, MOUSE_XA_ATTRIBUTE, newSVsv(attr));
69
70     /* prepare attribute status */
71     /* XXX: making it lazy is a good way? */
72
73     if(SvTRUEx(mcall0s(attr, "has_type_constraint"))){
74         SV* tc;
75         flags |= MOUSEf_ATTR_HAS_TC;
76
77         ENTER;
78         SAVETMPS;
79
80         tc = mcall0s(attr, "type_constraint");
81         av_store(xa, MOUSE_XA_TC, newSVsv(tc));
82
83         if(SvTRUEx(mcall0s(attr, "should_auto_deref"))){
84             flags |= MOUSEf_ATTR_SHOULD_AUTO_DEREF;
85             if( SvTRUEx(mcall1s(tc, "is_a_type_of", newSVpvs_flags("ArrayRef", SVs_TEMP))) ){
86                 flags |= MOUSEf_TC_IS_ARRAYREF;
87             }
88             else if( SvTRUEx(mcall1s(tc, "is_a_type_of", newSVpvs_flags("HashRef", SVs_TEMP))) ){
89                 flags |= MOUSEf_TC_IS_HASHREF;
90             }
91             else{
92                 mouse_throw_error(attr, tc,
93                     "Can not auto de-reference the type constraint '%"SVf"'",
94                         mcall0(tc, mouse_name));
95             }
96         }
97
98         if(SvTRUEx(mcall0s(attr, "should_coerce"))){
99             flags |= MOUSEf_ATTR_SHOULD_COERCE;
100         }
101
102         FREETMPS;
103         LEAVE;
104     }
105
106     if(SvTRUEx(mcall0s(attr, "has_trigger"))){
107         flags |= MOUSEf_ATTR_HAS_TRIGGER;
108     }
109
110     if(SvTRUEx(mcall0s(attr, "is_lazy"))){
111         flags |= MOUSEf_ATTR_IS_LAZY;
112
113         if(SvTRUEx(mcall0s(attr, "has_builder"))){
114             flags |= MOUSEf_ATTR_HAS_BUILDER;
115         }
116         else if(SvTRUEx(mcall0s(attr, "has_default"))){
117             flags |= MOUSEf_ATTR_HAS_DEFAULT;
118         }
119     }
120
121     if(SvTRUEx(mcall0s(attr, "is_weak_ref"))){
122         flags |= MOUSEf_ATTR_IS_WEAK_REF;
123     }
124
125     if(SvTRUEx(mcall0s(attr, "is_required"))){
126         flags |= MOUSEf_ATTR_IS_REQUIRED;
127     }
128
129     MOUSE_mg_flags(mg) = flags;
130
131     return xsub;
132 }
133
134 static SV*
135 mouse_apply_type_constraint(pTHX_ AV* const xa, SV* value, U16 const flags){
136     SV* const tc = MOUSE_xa_tc(xa);
137     SV* tc_code;
138
139     if(flags & MOUSEf_ATTR_SHOULD_COERCE){
140           value = mcall1s(tc, "coerce", value);
141     }
142
143     if(!SvOK(MOUSE_xa_tc_code(xa))){
144         XS(XS_Mouse__Util__TypeConstraints_Item); /* prototype defined in Mouse.xs */
145
146         tc_code = mcall0s(tc, "_compiled_type_constraint");
147
148         if(SvROK(tc_code) && SvTYPE(SvRV(tc_code))
149             && CvXSUB((CV*)SvRV(tc_code)) == XS_Mouse__Util__TypeConstraints_Item){
150             /* built-in type constraints */
151             mouse_tc const id = CvXSUBANY((CV*)SvRV(tc_code)).any_i32;
152             av_store(xa, MOUSE_XA_TC_CODE, newSViv(id));
153         }
154         else{
155             av_store(xa, MOUSE_XA_TC_CODE, newSVsv(tc_code));
156         }
157     }
158     else{
159         tc_code = MOUSE_xa_tc_code(xa);
160     }
161
162     if(!mouse_tc_check(aTHX_ tc_code, value)){
163         mouse_throw_error(MOUSE_xa_attribute(xa), value,
164             "Attribute (%"SVf") does not pass the type constraint because: %"SVf,
165                 mcall0(MOUSE_xa_attribute(xa), mouse_name),
166                 mcall1s(tc, "get_message", value));
167     }
168
169     return value;
170 }
171
172
173 /* pushes return values, does auto-deref if needed */
174 static void
175 mouse_push_values(pTHX_ SV* const value, U16 const flags){
176     dSP;
177
178     if(flags & MOUSEf_ATTR_SHOULD_AUTO_DEREF && GIMME_V == G_ARRAY){
179         if(!(value && SvOK(value))){
180             return;
181         }
182
183         if(flags & MOUSEf_TC_IS_ARRAYREF){
184             AV* const av = (AV*)SvRV(value);
185             I32 len;
186             I32 i;
187
188             if(SvTYPE(av) != SVt_PVAV){
189                 croak("Mouse-panic: Not an ARRAY reference");
190             }
191
192             len = av_len(av) + 1;
193             EXTEND(SP, len);
194             for(i = 0; i < len; i++){
195                 SV** const svp = av_fetch(av, i, FALSE);
196                 PUSHs(svp ? *svp : &PL_sv_undef);
197             }
198         }
199         else if(flags & MOUSEf_TC_IS_HASHREF){
200             HV* const hv = (HV*)SvRV(value);
201             HE* he;
202
203             if(SvTYPE(hv) != SVt_PVHV){
204                 croak("Mouse-panic: Not a HASH reference");
205             }
206
207             hv_iterinit(hv);
208             while((he = hv_iternext(hv))){
209                 EXTEND(SP, 2);
210                 PUSHs(hv_iterkeysv(he));
211                 PUSHs(hv_iterval(hv, he));
212             }
213         }
214     }
215     else{
216         XPUSHs(value ? value : &PL_sv_undef);
217     }
218
219     PUTBACK;
220 }
221
222 static void
223 mouse_attr_get(pTHX_ SV* const self, MAGIC* const mg){
224     U16 const flags = MOUSE_mg_flags(mg);
225     SV* const slot  = MOUSE_mg_slot(mg);
226     SV* value;
227
228     value = mouse_instance_get_slot(aTHX_ self, slot);
229
230     /* check_lazy */
231     if( !value && flags & MOUSEf_ATTR_IS_LAZY ){
232         AV* const xa    = MOUSE_mg_xa(mg);
233         SV* const attr = MOUSE_xa_attribute(xa);
234
235         /* get default value by $attr->default or $attr->builder */
236         if(flags & MOUSEf_ATTR_HAS_DEFAULT){
237             value = mcall0s(attr, "default");
238
239             if(SvROK(value) && SvTYPE(SvRV(value)) == SVt_PVCV){
240                 value = mcall0(self, value);
241             }
242         }
243         else if(flags & MOUSEf_ATTR_HAS_BUILDER){
244             SV* const builder = mcall0s(attr, "builder");
245             value = mcall0(self, builder);
246         }
247
248         if(!value){
249             value = sv_newmortal();
250         }
251
252         /* apply coerce and type constraint */
253         if(flags & MOUSEf_ATTR_HAS_TC){
254             value = mouse_apply_type_constraint(aTHX_ xa, value, flags);
255         }
256
257         /* store value to slot */
258         value = mouse_instance_set_slot(aTHX_ self, slot, value);
259     }
260
261     mouse_push_values(aTHX_ value, flags);
262 }
263
264 static void
265 mouse_attr_set(pTHX_ SV* const self, MAGIC* const mg, SV* value){
266     U16 const flags = MOUSE_mg_flags(mg);
267     SV* const slot  = MOUSE_mg_slot(mg);
268
269     if(flags & MOUSEf_ATTR_HAS_TC){
270         value = mouse_apply_type_constraint(aTHX_ MOUSE_mg_xa(mg), value, flags);
271     }
272
273     mouse_instance_set_slot(aTHX_ self, slot, value);
274
275     if(flags & MOUSEf_ATTR_IS_WEAK_REF){
276         mouse_instance_weaken_slot(aTHX_ self, slot);
277     }
278
279     if(flags & MOUSEf_ATTR_HAS_TRIGGER){
280         SV* const trigger = mcall0s(MOUSE_mg_attribute(mg), "trigger");
281         dSP;
282
283         PUSHMARK(SP);
284         EXTEND(SP, 2);
285         PUSHs(self);
286         PUSHs(value);
287
288         PUTBACK;
289         call_sv(trigger, G_VOID | G_DISCARD);
290         /* need not SPAGAIN */
291     }
292
293     mouse_push_values(aTHX_ value, flags);
294 }
295
296 XS(mouse_xs_accessor)
297 {
298     dVAR; dXSARGS;
299     dMOUSE_self;
300     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
301
302     SP -= items; /* PPCODE */
303     PUTBACK;
304
305     if(items == 1){ /* reader */
306         mouse_attr_get(aTHX_ self, mg);
307     }
308     else if (items == 2){ /* writer */
309         mouse_attr_set(aTHX_ self, mg, ST(1));
310     }
311     else{
312         mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
313             "Expected exactly one or two argument for an accessor");
314     }
315 }
316
317
318 XS(mouse_xs_reader)
319 {
320     dVAR; dXSARGS;
321     dMOUSE_self;
322     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
323
324     if (items != 1) {
325         mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
326             "Cannot assign a value to a read-only accessor");
327     }
328
329     SP -= items; /* PPCODE */
330     PUTBACK;
331
332     mouse_attr_get(aTHX_ self, mg);
333 }
334
335 XS(mouse_xs_writer)
336 {
337     dVAR; dXSARGS;
338     dMOUSE_self;
339     MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
340
341     if (items != 2) {
342         mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
343             "Too few arguments for a write-only accessor");
344     }
345
346     SP -= items; /* PPCODE */
347     PUTBACK;
348
349     mouse_attr_set(aTHX_ self, mg, ST(1));
350 }