]> git.vpit.fr Git - perl/modules/Variable-Magic.git/blob - Magic.xs
Importing Variable-Magic-0.05.tar.gz
[perl/modules/Variable-Magic.git] / Magic.xs
1 /* This file is part of the Variable::Magic Perl module.
2  * See http://search.cpan.org/dist/Variable-Magic/ */
3
4 #include <stdlib.h> /* rand(), RAND_MAX */
5 #include <stdio.h>  /* sprintf() */
6
7 #define PERL_NO_GET_CONTEXT
8 #include "EXTERN.h"
9 #include "perl.h"
10 #include "XSUB.h"
11
12 #define __PACKAGE__ "Variable::Magic"
13
14 #define R(S) fprintf(stderr, "R(" #S ") = %d\n", SvREFCNT(S))
15
16 /* --- Compatibility ------------------------------------------------------- */
17
18 #ifndef Newx
19 # define Newx(v, n, c) New(0, v, n, c)
20 #endif
21
22 #ifndef SvMAGIC_set
23 # define SvMAGIC_set(sv, val) (SvMAGIC(sv) = (val))
24 #endif
25
26 #ifndef dMY_CXT
27 # define MY_CXT vmg_globaldata
28 # define dMY_CXT
29 # define START_MY_CXT STATIC my_cxt_t MY_CXT;
30 # define MY_CXT_INIT
31 #endif
32
33 /* --- Context-safe global data -------------------------------------------- */
34
35 #define MY_CXT_KEY __PACKAGE__ "::_guts" XS_VERSION
36
37 typedef struct {
38  HV *wizz;
39  U16 count;
40 } my_cxt_t;
41
42 START_MY_CXT
43
44 /* --- Signatures ---------------------------------------------------------- */
45
46 #define SIG_MIN ((U16) (1u << 8))
47 #define SIG_MAX ((U16) (1u << 16 - 1))
48 #define SIG_NBR (SIG_MAX - SIG_MIN + 1)
49 #define SIG_WIZ ((U16) (1u << 8 - 1))
50
51 /* ... Generate signatures ................................................. */
52
53 STATIC U16 vmg_gensig(pTHX) {
54 #define vmg_gensig() vmg_gensig(aTHX)
55  U16 sig;
56  char buf[8];
57  dMY_CXT;
58
59  do {
60   double u = rand() / (RAND_MAX + 1.0);
61   sig = SIG_NBR * u + SIG_MIN;
62  } while (hv_exists(MY_CXT.wizz, buf, sprintf(buf, "%u", sig)));
63
64  return sig;
65 }
66
67 /* --- MGWIZ structure ----------------------------------------------------- */
68
69 typedef struct {
70  MGVTBL *vtbl;
71  U16 sig;
72  SV *cb_get, *cb_set, *cb_len, *cb_clear, *cb_free, *cb_data;
73 } MGWIZ;
74
75 #define MGWIZ2SV(W) (newSVuv(PTR2UV(W)))
76 #define SV2MGWIZ(S) (INT2PTR(MGWIZ*, SvUVX((SV *) (S))))
77
78 /* ... Construct private data .............................................. */
79
80 STATIC SV *vmg_data_new(pTHX_ SV *ctor, SV *sv, AV *args) {
81 #define vmg_data_new(C, S, A) vmg_data_new(aTHX_ (C), (S), (A))
82  SV *nsv;
83
84  dSP;
85  int count;
86
87  ENTER;
88  SAVETMPS;
89
90  PUSHMARK(SP);
91  XPUSHs(sv_2mortal(newRV_inc(sv)));
92  if (args != NULL) {
93   I32 i, alen = av_len(args);
94   for (i = 0; i < alen; ++i) { XPUSHs(*av_fetch(args, i, 0)); }
95  }
96  PUTBACK;
97
98  count = call_sv(ctor, G_SCALAR);
99
100  SPAGAIN;
101
102  if (count != 1) { croak("Callback needs to return 1 scalar\n"); }
103  nsv = POPs;
104  SvREFCNT_inc(nsv); /* Or it will be destroyed in FREETMPS */
105
106  PUTBACK;
107
108  FREETMPS;
109  LEAVE;
110
111  return nsv;
112 }
113
114 STATIC SV *vmg_data_get(SV *sv, U16 sig) {
115  MAGIC *mg, *moremagic;
116  MGWIZ *w;
117
118  if (SvTYPE(sv) >= SVt_PVMG) {
119   for (mg = SvMAGIC(sv); mg; mg = moremagic) {
120    moremagic = mg->mg_moremagic;
121    if ((mg->mg_type == PERL_MAGIC_ext) && (mg->mg_private == sig)) { break; }
122   }
123   if (mg) { return mg->mg_obj; }
124  }
125
126  return NULL;
127
128
129 /* ... Magic cast/dispell .................................................. */
130
131 STATIC UV vmg_cast(pTHX_ SV *sv, SV *wiz, AV *args) {
132 #define vmg_cast(S, W, A) vmg_cast(aTHX_ (S), (W), (A))
133  MAGIC *mg = NULL, *moremagic = NULL;
134  MGWIZ *w;
135  SV *data;
136
137  w = SV2MGWIZ(wiz);
138
139  if (SvTYPE(sv) >= SVt_PVMG) {
140   for (mg = SvMAGIC(sv); mg; mg = moremagic) {
141    moremagic = mg->mg_moremagic;
142    if ((mg->mg_type == PERL_MAGIC_ext) && (mg->mg_private == w->sig)) { break; }
143   }
144   if (mg) { return 1; }
145  }
146
147  data = (w->cb_data) ? vmg_data_new(w->cb_data, sv, args) : NULL;
148  mg = sv_magicext(sv, data, PERL_MAGIC_ext, w->vtbl,
149                             (const char *) wiz, HEf_SVKEY);
150  mg->mg_private = w->sig;
151
152  return 1;
153 }
154
155 STATIC UV vmg_dispell(pTHX_ SV *sv, U16 sig) {
156 #define vmg_dispell(S, Z) vmg_dispell(aTHX_ (S), (Z))
157  MAGIC *mg, *prevmagic, *moremagic = NULL;
158  MGWIZ *w;
159
160  if (SvTYPE(sv) < SVt_PVMG) { return 0; }
161
162  for (prevmagic = NULL, mg = SvMAGIC(sv); mg; prevmagic = mg, mg = moremagic) {
163   moremagic = mg->mg_moremagic;
164   if ((mg->mg_type == PERL_MAGIC_ext) && (mg->mg_private == sig)) { break; }
165  }
166  if (!mg) { return 0; }
167
168  if (prevmagic) {
169   prevmagic->mg_moremagic = moremagic;
170  } else {
171   SvMAGIC_set(sv, moremagic);
172  }
173  mg->mg_moremagic = NULL;
174
175  if (mg->mg_obj != sv) { SvREFCNT_dec(mg->mg_obj); } /* Destroy private data */
176  SvREFCNT_dec((SV *) mg->mg_ptr); /* Unreference the wizard */
177  Safefree(mg);
178
179  return 1;
180 }
181
182 /* ... svt callbacks ....................................................... */
183
184 STATIC int vmg_cb_call(pTHX_ SV *cb, SV *sv, SV *data) {
185 #define vmg_cb_call(I, S, D) vmg_cb_call(aTHX_ (I), (S), (D))
186  int ret;
187
188  dSP;
189  int count;
190
191  ENTER;
192  SAVETMPS;
193
194  PUSHMARK(SP);
195  XPUSHs(sv_2mortal(newRV_inc(sv)));
196  if (data) { XPUSHs(data); }
197  PUTBACK;
198
199  count = call_sv(cb, G_SCALAR);
200
201  SPAGAIN;
202
203  if (count != 1) { croak("Callback needs to return 1 scalar\n"); }
204  ret = POPi;
205
206  PUTBACK;
207
208  FREETMPS;
209  LEAVE;
210
211  return ret;
212 }
213
214 STATIC int vmg_svt_get(pTHX_ SV *sv, MAGIC *mg) {
215  return vmg_cb_call(SV2MGWIZ(mg->mg_ptr)->cb_get, sv, mg->mg_obj);
216 }
217
218 STATIC int vmg_svt_set(pTHX_ SV *sv, MAGIC *mg) {
219  return vmg_cb_call(SV2MGWIZ(mg->mg_ptr)->cb_set, sv, mg->mg_obj);
220 }
221
222 STATIC U32 vmg_svt_len(pTHX_ SV *sv, MAGIC *mg) {
223  U32 ret;
224
225  dSP;
226  int count;
227
228  ENTER;
229  SAVETMPS;
230
231  PUSHMARK(SP);
232  XPUSHs(sv_2mortal(newRV_inc(sv)));
233  XPUSHs((mg->mg_obj) ? (mg->mg_obj) : &PL_sv_undef);
234  if (SvTYPE(sv) == SVt_PVAV) {
235   XPUSHs(sv_2mortal(newSViv(av_len((AV *) sv) + 1)));
236  }
237  PUTBACK;
238
239  count = call_sv(SV2MGWIZ(mg->mg_ptr)->cb_len, G_SCALAR);
240
241  SPAGAIN;
242
243  if (count != 1) { croak("Callback needs to return 1 scalar\n"); }
244  ret = POPi;
245
246  PUTBACK;
247
248  FREETMPS;
249  LEAVE;
250
251  return ret - 1;
252 }
253
254 STATIC int vmg_svt_clear(pTHX_ SV *sv, MAGIC *mg) {
255  return vmg_cb_call(SV2MGWIZ(mg->mg_ptr)->cb_clear, sv, mg->mg_obj);
256 }
257
258 STATIC int vmg_svt_free(pTHX_ SV *sv, MAGIC *mg) {
259  /* So that it can survive tmp cleanup in vmg_cb_call */
260  SvREFCNT_inc(sv);
261  /* Perl_mg_free will get rid of the magic and decrement mg->mg_obj and
262   * mg->mg_ptr reference count */
263  return vmg_cb_call(SV2MGWIZ(mg->mg_ptr)->cb_free, sv, mg->mg_obj);
264 }
265
266 /* ... Wizard destructor ................................................... */
267
268 STATIC int vmg_wizard_free(pTHX_ SV *wiz, MAGIC *mg) {
269  char buf[8];
270  MGWIZ *w;
271  dMY_CXT;
272
273  w = SV2MGWIZ(wiz);
274
275  SvREFCNT_inc(wiz); /* Fake survival - it's gonna be deleted anyway */
276 #if PERL_API_REVISION > 5 || (PERL_API_REVISION == 5 && (PERL_API_VERSION > 9 || (PERL_API_VERSION == 9 && PERL_API_SUBVERSION >= 5)))
277  SvREFCNT_inc(wiz); /* One more push */
278 #endif
279  if (hv_delete(MY_CXT.wizz, buf, sprintf(buf, "%u", w->sig), 0)) {
280   --MY_CXT.count;
281  }
282
283  if (w->cb_get   != NULL) { SvREFCNT_dec(SvRV(w->cb_get)); }
284  if (w->cb_set   != NULL) { SvREFCNT_dec(SvRV(w->cb_set)); }
285  if (w->cb_len   != NULL) { SvREFCNT_dec(SvRV(w->cb_len)); }
286  if (w->cb_clear != NULL) { SvREFCNT_dec(SvRV(w->cb_clear)); }
287  if (w->cb_free  != NULL) { SvREFCNT_dec(SvRV(w->cb_free)); }
288  if (w->cb_data  != NULL) { SvREFCNT_dec(SvRV(w->cb_data)); }
289  Safefree(w->vtbl);
290  Safefree(w);
291
292  return 0;
293 }
294
295 STATIC MGVTBL vmg_wizard_vtbl = {
296  NULL,            /* get */
297  NULL,            /* set */
298  NULL,            /* len */
299  NULL,            /* clear */
300  vmg_wizard_free, /* free */
301 #ifdef MGf_COPY
302  NULL,            /* copy */
303 #endif /* MGf_COPY */
304 #ifdef MGf_DUP
305  NULL,            /* dup */
306 #endif /* MGf_DUP */
307 };
308
309 STATIC const char vmg_invalid_wiz[]    = "Invalid wizard object";
310 STATIC const char vmg_invalid_sv[]     = "Invalid variable";
311 STATIC const char vmg_invalid_sig[]    = "Invalid numeric signature";
312 STATIC const char vmg_toomanysigs[]    = "Too many magic signatures used";
313 STATIC const char vmg_argstorefailed[] = "Error while storing arguments";
314
315 STATIC U16 vmg_sv2sig(pTHX_ SV *sv) {
316 #define vmg_sv2sig(S) vmg_sv2sig(aTHX_ (S))
317  U16 sig;
318
319  if (SvIOK(sv)) {
320   sig = SvUVX(sv);
321  } else if (SvNOK(sv)) {
322   sig = SvNVX(sv);
323  } else if ((SvPOK(sv) && grok_number(SvPVX(sv), SvCUR(sv), NULL))) {
324   sig = SvUV(sv);
325  } else {
326   croak(vmg_invalid_sig);
327  }
328  if (sig < SIG_MIN) { sig += SIG_MIN; }
329  if (sig > SIG_MAX) { sig %= SIG_MAX + 1; }
330
331  return sig;
332 }
333
334 /* --- XS ------------------------------------------------------------------ */
335
336 MODULE = Variable::Magic            PACKAGE = Variable::Magic
337
338 PROTOTYPES: ENABLE
339
340 BOOT:
341 {
342  HV *stash;
343  MY_CXT_INIT;
344  MY_CXT.wizz = newHV();
345  MY_CXT.count = 0;
346  stash = gv_stashpv(__PACKAGE__, 1);
347  newCONSTSUB(stash, "SIG_MIN",  newSVuv(SIG_MIN));
348  newCONSTSUB(stash, "SIG_MAX",  newSVuv(SIG_MAX));
349  newCONSTSUB(stash, "SIG_NBR",  newSVuv(SIG_NBR));
350 /*
351  newCONSTSUB(stash, "MGf_COPY", newSVuv(MGf_COPY));
352  newCONSTSUB(stash, "MGf_DUP",  newSVuv(MGf_DUP));
353 */
354 }
355
356 SV *_wizard(SV *svsig, SV *cb_get, SV *cb_set, SV *cb_len, SV *cb_clear, SV *cb_free, SV *cb_data)
357 PROTOTYPE: $&&&&&&
358 PREINIT:
359  U16 sig;
360  char buf[8];
361  MGWIZ *w;
362  MGVTBL *t;
363  MAGIC *mg;
364  SV *sv;
365 CODE:
366  dMY_CXT;
367  if (SvOK(svsig)) {
368   SV **old;
369   sig = vmg_sv2sig(svsig);
370   if (old = hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0)) {
371    ST(0) = sv_2mortal(newRV_inc(*old));
372    XSRETURN(1);
373   }
374  } else {
375   if (MY_CXT.count >= SIG_NBR) { croak(vmg_toomanysigs); }
376   sig = vmg_gensig();
377  }
378  
379  Newx(t, 1, MGVTBL);
380  t->svt_get   = (SvOK(cb_get))   ? vmg_svt_get   : NULL;
381  t->svt_set   = (SvOK(cb_set))   ? vmg_svt_set   : NULL;
382  t->svt_len   = (SvOK(cb_len))   ? vmg_svt_len   : NULL;
383  t->svt_clear = (SvOK(cb_clear)) ? vmg_svt_clear : NULL;
384  t->svt_free  = (SvOK(cb_free))  ? vmg_svt_free  : NULL;
385 #ifdef MGf_COPY
386  t->svt_copy  = NULL;
387 #endif /* MGf_COPY */
388 #ifdef MGf_DUP
389  t->svt_dup   = NULL;
390 #endif /* MGf_DUP */
391
392  Newx(w, 1, MGWIZ);
393  w->vtbl = t;
394  w->sig  = sig;
395  w->cb_get   = (SvROK(cb_get))   ? newRV_inc(SvRV(cb_get))   : NULL;
396  w->cb_set   = (SvROK(cb_set))   ? newRV_inc(SvRV(cb_set))   : NULL;
397  w->cb_len   = (SvROK(cb_len))   ? newRV_inc(SvRV(cb_len))   : NULL;
398  w->cb_clear = (SvROK(cb_clear)) ? newRV_inc(SvRV(cb_clear)) : NULL;
399  w->cb_free  = (SvROK(cb_free))  ? newRV_inc(SvRV(cb_free))  : NULL;
400  w->cb_data  = (SvROK(cb_data))  ? newRV_inc(SvRV(cb_data))  : NULL;
401
402  sv = MGWIZ2SV(w);
403  mg = sv_magicext(sv, NULL, PERL_MAGIC_ext, &vmg_wizard_vtbl, NULL, -1);
404  mg->mg_private = SIG_WIZ;
405
406  hv_store(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), sv, 0);
407  ++MY_CXT.count;
408  
409  RETVAL = newRV_noinc(sv);
410 OUTPUT:
411  RETVAL
412
413 SV *gensig()
414 PROTOTYPE:
415 CODE:
416  dMY_CXT;
417  if (MY_CXT.count >= SIG_NBR) { croak(vmg_toomanysigs); }
418  RETVAL = newSVuv(vmg_gensig());
419 OUTPUT:
420  RETVAL
421
422 SV *getsig(SV *wiz)
423 PROTOTYPE: $
424 CODE:
425  if (!SvROK(wiz)) { croak(vmg_invalid_wiz); }
426  RETVAL = newSVuv(SV2MGWIZ(SvRV(wiz))->sig);
427 OUTPUT:
428  RETVAL
429
430 SV *cast(SV *sv, SV *wiz, ...)
431 PROTOTYPE: \[$@%&*]$@
432 PREINIT:
433  AV *args = NULL;
434  SV *ret;
435 CODE:
436  dMY_CXT;
437  if (SvROK(wiz)) {
438   wiz = SvRV(wiz);
439  } else if (SvOK(wiz)) {
440   char buf[8];
441   SV **old;
442   U16 sig = vmg_sv2sig(wiz);
443   if (old = hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0)) {
444    wiz = *old;
445   } else {
446    XSRETURN_UNDEF;
447   }
448  } else {
449   croak(vmg_invalid_sig);
450  }
451  if (items > 2) {
452   I32 i;
453   args = newAV();
454   av_fill(args, items - 2);
455   for (i = 2; i < items; ++i) {
456    SV *arg = ST(i);
457    SvREFCNT_inc(arg);
458    if (av_store(args, i - 2, arg) == NULL) { croak(vmg_argstorefailed); }
459   }
460  }
461  ret = newSVuv(vmg_cast(SvRV(sv), wiz, args));
462  SvREFCNT_dec(args);
463  RETVAL = ret;
464 OUTPUT:
465  RETVAL
466
467 SV *getdata(SV *sv, SV *wiz)
468 PROTOTYPE: \[$@%&*]$
469 PREINIT:
470  SV *data;
471  U16 sig;
472 CODE:
473  dMY_CXT;
474  if (SvROK(wiz)) {
475   sig = SV2MGWIZ(SvRV(wiz))->sig;
476  } else if (SvOK(wiz)) {
477   char buf[8];
478   sig = vmg_sv2sig(wiz);
479   if (!hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0)) {
480    XSRETURN_UNDEF;
481   }
482  } else {
483   croak(vmg_invalid_wiz);
484  }
485  data = vmg_data_get(SvRV(sv), sig);
486  if (!data) { XSRETURN_UNDEF; }
487  ST(0) = newSVsv(data);
488  XSRETURN(1);
489
490 SV *dispell(SV *sv, SV *wiz)
491 PROTOTYPE: \[$@%&*]$
492 PREINIT:
493  U16 sig;
494 CODE:
495  dMY_CXT;
496  if (SvROK(wiz)) {
497   sig = SV2MGWIZ(SvRV(wiz))->sig;
498  } else if (SvOK(wiz)) {
499   char buf[8];
500   sig = vmg_sv2sig(wiz);
501   if (!hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0)) {
502    XSRETURN_UNDEF;
503   }
504  } else {
505   croak(vmg_invalid_wiz);
506  }
507  RETVAL = newSVuv(vmg_dispell(SvRV(sv), sig));
508 OUTPUT:
509  RETVAL