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