No meta programming in Mouse/Meta/Role.pm
[gitmo/Mouse.git] / xs-src / mouse_accessor.xs
CommitLineData
93540011 1#include "mouse.h"
2
3/* Moose XS Attribute object */
4enum 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
18enum 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
44static MGVTBL mouse_accessor_vtbl; /* MAGIC identity */
45
46CV*
47mouse_instantiate_xs_accessor(pTHX_ SV* const attr, XSUBADDR_t const accessor_impl){
48 SV* const slot = mcall0s(attr, "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 mcall0s(tc, "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
134static SV*
135mouse_apply_type_constraint(pTHX_ AV* const xa, SV* value, U16 const flags){
136 SV* const tc = MOUSE_xa_tc(xa);
137 SV* tc_code;
93540011 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
1d5ecd5f 162 if(!mouse_tc_check(aTHX_ tc_code, value)){
93540011 163 mouse_throw_error(MOUSE_xa_attribute(xa), value,
164 "Attribute (%"SVf") does not pass the type constraint because: %"SVf,
165 mcall0s(MOUSE_xa_attribute(xa), "name"),
166 mcall1s(tc, "get_message", value));
167 }
168
169 return value;
170}
171
172
173/* pushes return values, does auto-deref if needed */
174static void
c0c20aa6 175mouse_push_values(pTHX_ SV* const value, U16 const flags){
93540011 176 dSP;
93540011 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
222static void
223mouse_attr_get(pTHX_ SV* const self, MAGIC* const mg){
93540011 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 ){
c0c20aa6 232 AV* const xa = MOUSE_mg_xa(mg);
93540011 233 SV* const attr = MOUSE_xa_attribute(xa);
c0c20aa6 234
93540011 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
c0c20aa6 261 mouse_push_values(aTHX_ value, flags);
93540011 262}
263
264static void
265mouse_attr_set(pTHX_ SV* const self, MAGIC* const mg, SV* value){
93540011 266 U16 const flags = MOUSE_mg_flags(mg);
267 SV* const slot = MOUSE_mg_slot(mg);
268
269 if(flags & MOUSEf_ATTR_HAS_TC){
c0c20aa6 270 value = mouse_apply_type_constraint(aTHX_ MOUSE_mg_xa(mg), value, flags);
93540011 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){
c0c20aa6 280 SV* const trigger = mcall0s(MOUSE_mg_attribute(mg), "trigger");
93540011 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
c0c20aa6 293 mouse_push_values(aTHX_ value, flags);
93540011 294}
295
296XS(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
318XS(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
335XS(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}