Commit | Line | Data |
93540011 |
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 = 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 | |
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; |
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 */ |
174 | static void |
c0c20aa6 |
175 | mouse_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 | |
222 | static void |
223 | mouse_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 | |
264 | static void |
265 | mouse_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 | |
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 | } |