Rename a file
[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;
138 int ok;
139
140 if(flags & MOUSEf_ATTR_SHOULD_COERCE){
141 value = mcall1s(tc, "coerce", value);
142 }
143
144 if(!SvOK(MOUSE_xa_tc_code(xa))){
145 XS(XS_Mouse__Util__TypeConstraints_Item); /* prototype defined in Mouse.xs */
146
147 tc_code = mcall0s(tc, "_compiled_type_constraint");
148
149 if(SvROK(tc_code) && SvTYPE(SvRV(tc_code))
150 && CvXSUB((CV*)SvRV(tc_code)) == XS_Mouse__Util__TypeConstraints_Item){
151 /* built-in type constraints */
152 mouse_tc const id = CvXSUBANY((CV*)SvRV(tc_code)).any_i32;
153 av_store(xa, MOUSE_XA_TC_CODE, newSViv(id));
154 }
155 else{
156 av_store(xa, MOUSE_XA_TC_CODE, newSVsv(tc_code));
157 }
158 }
159 else{
160 tc_code = MOUSE_xa_tc_code(xa);
161 }
162
163 if(SvIOK(tc_code)){ /* built-in type constraints */
164 ok = mouse_tc_check(aTHX_ SvIVX(tc_code), value);
165 }
166 else {
167 dSP;
168
169 ENTER;
170 SAVETMPS;
171
172 PUSHMARK(SP);
173 XPUSHs(value);
174 PUTBACK;
175
176 call_sv(tc_code, G_SCALAR);
177
178 SPAGAIN;
179 ok = SvTRUEx(POPs);
180 PUTBACK;
181
182 FREETMPS;
183 LEAVE;
184 }
185
186 if(!ok){
187 mouse_throw_error(MOUSE_xa_attribute(xa), value,
188 "Attribute (%"SVf") does not pass the type constraint because: %"SVf,
189 mcall0s(MOUSE_xa_attribute(xa), "name"),
190 mcall1s(tc, "get_message", value));
191 }
192
193 return value;
194}
195
196
197/* pushes return values, does auto-deref if needed */
198static void
199mouse_push_values(pTHX_ AV* const xa, SV* const value, U16 const flags){
200 dSP;
201 PERL_UNUSED_ARG(xa);
202
203 if(flags & MOUSEf_ATTR_SHOULD_AUTO_DEREF && GIMME_V == G_ARRAY){
204 if(!(value && SvOK(value))){
205 return;
206 }
207
208 if(flags & MOUSEf_TC_IS_ARRAYREF){
209 AV* const av = (AV*)SvRV(value);
210 I32 len;
211 I32 i;
212
213 if(SvTYPE(av) != SVt_PVAV){
214 croak("Mouse-panic: Not an ARRAY reference");
215 }
216
217 len = av_len(av) + 1;
218 EXTEND(SP, len);
219 for(i = 0; i < len; i++){
220 SV** const svp = av_fetch(av, i, FALSE);
221 PUSHs(svp ? *svp : &PL_sv_undef);
222 }
223 }
224 else if(flags & MOUSEf_TC_IS_HASHREF){
225 HV* const hv = (HV*)SvRV(value);
226 HE* he;
227
228 if(SvTYPE(hv) != SVt_PVHV){
229 croak("Mouse-panic: Not a HASH reference");
230 }
231
232 hv_iterinit(hv);
233 while((he = hv_iternext(hv))){
234 EXTEND(SP, 2);
235 PUSHs(hv_iterkeysv(he));
236 PUSHs(hv_iterval(hv, he));
237 }
238 }
239 }
240 else{
241 XPUSHs(value ? value : &PL_sv_undef);
242 }
243
244 PUTBACK;
245}
246
247static void
248mouse_attr_get(pTHX_ SV* const self, MAGIC* const mg){
249 AV* const xa = MOUSE_mg_xa(mg);
250 U16 const flags = MOUSE_mg_flags(mg);
251 SV* const slot = MOUSE_mg_slot(mg);
252 SV* value;
253
254 value = mouse_instance_get_slot(aTHX_ self, slot);
255
256 /* check_lazy */
257 if( !value && flags & MOUSEf_ATTR_IS_LAZY ){
258 SV* const attr = MOUSE_xa_attribute(xa);
259 /* get default value by $attr->default or $attr->builder */
260 if(flags & MOUSEf_ATTR_HAS_DEFAULT){
261 value = mcall0s(attr, "default");
262
263 if(SvROK(value) && SvTYPE(SvRV(value)) == SVt_PVCV){
264 value = mcall0(self, value);
265 }
266 }
267 else if(flags & MOUSEf_ATTR_HAS_BUILDER){
268 SV* const builder = mcall0s(attr, "builder");
269 value = mcall0(self, builder);
270 }
271
272 if(!value){
273 value = sv_newmortal();
274 }
275
276 /* apply coerce and type constraint */
277 if(flags & MOUSEf_ATTR_HAS_TC){
278 value = mouse_apply_type_constraint(aTHX_ xa, value, flags);
279 }
280
281 /* store value to slot */
282 value = mouse_instance_set_slot(aTHX_ self, slot, value);
283 }
284
285 mouse_push_values(aTHX_ xa, value, flags);
286}
287
288static void
289mouse_attr_set(pTHX_ SV* const self, MAGIC* const mg, SV* value){
290 AV* const xa = MOUSE_mg_xa(mg);
291 U16 const flags = MOUSE_mg_flags(mg);
292 SV* const slot = MOUSE_mg_slot(mg);
293
294 if(flags & MOUSEf_ATTR_HAS_TC){
295 value = mouse_apply_type_constraint(aTHX_ xa, value, flags);
296 }
297
298 mouse_instance_set_slot(aTHX_ self, slot, value);
299
300 if(flags & MOUSEf_ATTR_IS_WEAK_REF){
301 mouse_instance_weaken_slot(aTHX_ self, slot);
302 }
303
304 if(flags & MOUSEf_ATTR_HAS_TRIGGER){
305 SV* const trigger = mcall0s(MOUSE_xa_attribute(xa), "trigger");
306 dSP;
307
308 PUSHMARK(SP);
309 EXTEND(SP, 2);
310 PUSHs(self);
311 PUSHs(value);
312
313 PUTBACK;
314 call_sv(trigger, G_VOID | G_DISCARD);
315 /* need not SPAGAIN */
316 }
317
318 mouse_push_values(aTHX_ xa, value, flags);
319}
320
321XS(mouse_xs_accessor)
322{
323 dVAR; dXSARGS;
324 dMOUSE_self;
325 MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
326
327 SP -= items; /* PPCODE */
328 PUTBACK;
329
330 if(items == 1){ /* reader */
331 mouse_attr_get(aTHX_ self, mg);
332 }
333 else if (items == 2){ /* writer */
334 mouse_attr_set(aTHX_ self, mg, ST(1));
335 }
336 else{
337 mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
338 "Expected exactly one or two argument for an accessor");
339 }
340}
341
342
343XS(mouse_xs_reader)
344{
345 dVAR; dXSARGS;
346 dMOUSE_self;
347 MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
348
349 if (items != 1) {
350 mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
351 "Cannot assign a value to a read-only accessor");
352 }
353
354 SP -= items; /* PPCODE */
355 PUTBACK;
356
357 mouse_attr_get(aTHX_ self, mg);
358}
359
360XS(mouse_xs_writer)
361{
362 dVAR; dXSARGS;
363 dMOUSE_self;
364 MAGIC* const mg = (MAGIC*)XSANY.any_ptr;
365
366 if (items != 2) {
367 mouse_throw_error(MOUSE_mg_attribute(mg), NULL,
368 "Too few arguments for a write-only accessor");
369 }
370
371 SP -= items; /* PPCODE */
372 PUTBACK;
373
374 mouse_attr_set(aTHX_ self, mg, ST(1));
375}