]> git.vpit.fr Git - perl/modules/re-engine-Plugin.git/blob - Plugin.xs
Switch to qw<>
[perl/modules/re-engine-Plugin.git] / Plugin.xs
1 /* This file is part of the re::engine::Plugin Perl module.
2  * See http://search.cpan.org/dist/re-engine-Plugin/ */
3
4 #define PERL_NO_GET_CONTEXT
5 #include "EXTERN.h"
6 #include "perl.h"
7 #include "XSUB.h"
8
9 #include "Plugin.h"
10
11 #define __PACKAGE__     "re::engine::Plugin"
12 #define __PACKAGE_LEN__ (sizeof(__PACKAGE__)-1)
13
14 #define REP_HAS_PERL(R, V, S) (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
15
16 #ifndef REP_WORKAROUND_REQUIRE_PROPAGATION
17 # define REP_WORKAROUND_REQUIRE_PROPAGATION !REP_HAS_PERL(5, 10, 1)
18 #endif
19
20 /* ... Thread safety and multiplicity ...................................... */
21
22 #ifndef REP_MULTIPLICITY
23 # if defined(MULTIPLICITY) || defined(PERL_IMPLICIT_CONTEXT)
24 #  define REP_MULTIPLICITY 1
25 # else
26 #  define REP_MULTIPLICITY 0
27 # endif
28 #endif
29 #if REP_MULTIPLICITY && !defined(tTHX)
30 # define tTHX PerlInterpreter*
31 #endif
32
33 #if REP_MULTIPLICITY && defined(USE_ITHREADS) && defined(dMY_CXT) && defined(MY_CXT) && defined(START_MY_CXT) && defined(MY_CXT_INIT) && (defined(MY_CXT_CLONE) || defined(dMY_CXT_SV))
34 # define REP_THREADSAFE 1
35 # ifndef MY_CXT_CLONE
36 #  define MY_CXT_CLONE \
37     dMY_CXT_SV;                                                      \
38     my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1)); \
39     Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t); \
40     sv_setuv(my_cxt_sv, PTR2UV(my_cxtp))
41 # endif
42 #else
43 # define REP_THREADSAFE 0
44 # undef  dMY_CXT
45 # define dMY_CXT      dNOOP
46 # undef  MY_CXT
47 # define MY_CXT       rep_globaldata
48 # undef  START_MY_CXT
49 # define START_MY_CXT STATIC my_cxt_t MY_CXT;
50 # undef  MY_CXT_INIT
51 # define MY_CXT_INIT  NOOP
52 # undef  MY_CXT_CLONE
53 # define MY_CXT_CLONE NOOP
54 #endif
55
56 /* --- Helpers ------------------------------------------------------------- */
57
58 /* ... Thread-safe hints ................................................... */
59
60 typedef struct {
61  SV *comp;
62  SV *exec;
63 #if REP_WORKAROUND_REQUIRE_PROPAGATION
64  IV  require_tag;
65 #endif
66 } rep_hint_t;
67
68 #if REP_THREADSAFE
69
70 #define PTABLE_VAL_FREE(V) { \
71  rep_hint_t *h = (V);        \
72  SvREFCNT_dec(h->comp);      \
73  SvREFCNT_dec(h->exec);      \
74  PerlMemShared_free(h);      \
75 }
76
77 #define pPTBL  pTHX
78 #define pPTBL_ pTHX_
79 #define aPTBL  aTHX
80 #define aPTBL_ aTHX_
81
82 #include "ptable.h"
83
84 #define ptable_store(T, K, V) ptable_store(aTHX_ (T), (K), (V))
85 #define ptable_free(T)        ptable_free(aTHX_ (T))
86
87 #define MY_CXT_KEY __PACKAGE__ "::_guts" XS_VERSION
88
89 typedef struct {
90  ptable *tbl;
91  tTHX    owner;
92 } my_cxt_t;
93
94 START_MY_CXT
95
96 STATIC SV *rep_clone(pTHX_ SV *sv, tTHX owner) {
97 #define rep_clone(S, O) rep_clone(aTHX_ (S), (O))
98  CLONE_PARAMS  param;
99  AV           *stashes = NULL;
100  SV           *dupsv;
101
102  if (SvTYPE(sv) == SVt_PVHV && HvNAME_get(sv))
103   stashes = newAV();
104
105  param.stashes    = stashes;
106  param.flags      = 0;
107  param.proto_perl = owner;
108
109  dupsv = sv_dup(sv, &param);
110
111  if (stashes) {
112   av_undef(stashes);
113   SvREFCNT_dec(stashes);
114  }
115
116  return SvREFCNT_inc_simple(dupsv);
117 }
118
119 STATIC void rep_ptable_clone(pTHX_ ptable_ent *ent, void *ud_) {
120  my_cxt_t   *ud = ud_;
121  rep_hint_t *h1 = ent->val;
122  rep_hint_t *h2;
123
124  if (ud->owner == aTHX)
125   return;
126
127  h2              = PerlMemShared_malloc(sizeof *h2);
128  h2->comp        = rep_clone(h1->comp, ud->owner);
129  SvREFCNT_inc_simple_void(h2->comp);
130  h2->exec        = rep_clone(h1->exec, ud->owner);
131  SvREFCNT_inc_simple_void(h2->exec);
132 #if REP_WORKAROUND_REQUIRE_PROPAGATION
133  h2->require_tag = PTR2IV(rep_clone(INT2PTR(SV *, h1->require_tag), ud->owner));
134 #endif
135
136  ptable_store(ud->tbl, ent->key, h2);
137 }
138
139 #include "reap.h"
140
141 STATIC void rep_thread_cleanup(pTHX_ void *ud) {
142  dMY_CXT;
143
144  ptable_free(MY_CXT.tbl);
145 }
146
147 #endif /* REP_THREADSAFE */
148
149 STATIC SV *rep_validate_callback(SV *code) {
150  if (!SvROK(code))
151   return NULL;
152
153  code = SvRV(code);
154  if (SvTYPE(code) < SVt_PVCV)
155   return NULL;
156
157  return SvREFCNT_inc_simple_NN(code);
158 }
159
160 #if REP_WORKAROUND_REQUIRE_PROPAGATION
161 STATIC IV rep_require_tag(pTHX) {
162 #define rep_require_tag() rep_require_tag(aTHX)
163  const CV *cv, *outside;
164
165  cv = PL_compcv;
166
167  if (!cv) {
168   /* If for some reason the pragma is operational at run-time, try to discover
169    * the current cv in use. */
170   const PERL_SI *si;
171
172   for (si = PL_curstackinfo; si; si = si->si_prev) {
173    I32 cxix;
174
175    for (cxix = si->si_cxix; cxix >= 0; --cxix) {
176     const PERL_CONTEXT *cx = si->si_cxstack + cxix;
177
178     switch (CxTYPE(cx)) {
179      case CXt_SUB:
180      case CXt_FORMAT:
181       /* The propagation workaround is only needed up to 5.10.0 and at that
182        * time format and sub contexts were still identical. And even later the
183        * cv members offsets should have been kept the same. */
184       cv = cx->blk_sub.cv;
185       goto get_enclosing_cv;
186      case CXt_EVAL:
187       cv = cx->blk_eval.cv;
188       goto get_enclosing_cv;
189      default:
190       break;
191     }
192    }
193   }
194
195   cv = PL_main_cv;
196  }
197
198 get_enclosing_cv:
199  for (outside = CvOUTSIDE(cv); outside; outside = CvOUTSIDE(cv))
200   cv = outside;
201
202  return PTR2IV(cv);
203 }
204 #endif /* REP_WORKAROUND_REQUIRE_PROPAGATION */
205
206 STATIC SV *rep_tag(pTHX_ SV *comp, SV *exec) {
207 #define rep_tag(C, E) rep_tag(aTHX_ (C), (E))
208  rep_hint_t *h;
209  dMY_CXT;
210
211  h              = PerlMemShared_malloc(sizeof *h);
212  h->comp        = rep_validate_callback(comp);
213  h->exec        = rep_validate_callback(exec);
214 #if REP_WORKAROUND_REQUIRE_PROPAGATION
215  h->require_tag = rep_require_tag();
216 #endif /* REP_WORKAROUND_REQUIRE_PROPAGATION */
217
218 #if REP_THREADSAFE
219  {
220   dMY_CXT;
221   /* We only need for the key to be an unique tag for looking up the value later
222    * Allocated memory provides convenient unique identifiers, so that's why we
223    * use the hint as the key itself. */
224   ptable_store(MY_CXT.tbl, h, h);
225  }
226 #endif /* REP_THREADSAFE */
227
228  return newSViv(PTR2IV(h));
229 }
230
231 STATIC const rep_hint_t *rep_detag(pTHX_ const SV *hint) {
232 #define rep_detag(H) rep_detag(aTHX_ (H))
233  rep_hint_t *h;
234
235  if (!(hint && SvIOK(hint)))
236   return NULL;
237
238  h = INT2PTR(rep_hint_t *, SvIVX(hint));
239 #if REP_THREADSAFE
240  {
241   dMY_CXT;
242   h = ptable_fetch(MY_CXT.tbl, h);
243  }
244 #endif /* REP_THREADSAFE */
245
246 #if REP_WORKAROUND_REQUIRE_PROPAGATION
247  if (rep_require_tag() != h->require_tag)
248   return NULL;
249 #endif /* REP_WORKAROUND_REQUIRE_PROPAGATION */
250
251  return h;
252 }
253
254 STATIC U32 rep_hash = 0;
255
256 STATIC const rep_hint_t *rep_hint(pTHX) {
257 #define rep_hint() rep_hint(aTHX)
258  SV *hint;
259
260 #ifdef cop_hints_fetch_pvn
261  hint = cop_hints_fetch_pvn(PL_curcop,
262                             __PACKAGE__, __PACKAGE_LEN__, rep_hash, 0);
263 #else
264  /* We already require 5.9.5 for the regexp engine API. */
265  hint = Perl_refcounted_he_fetch(aTHX_ PL_curcop->cop_hints_hash,
266                                        NULL,
267                                        __PACKAGE__, __PACKAGE_LEN__,
268                                        0,
269                                        rep_hash);
270 #endif
271
272  return rep_detag(hint);
273 }
274
275 REGEXP *
276 #if PERL_VERSION <= 10
277 Plugin_comp(pTHX_ const SV * const pattern, const U32 flags)
278 #else
279 Plugin_comp(pTHX_ SV * const pattern, U32 flags)
280 #endif
281 {
282     dSP;
283     struct regexp * rx;
284     REGEXP *RX;
285
286     re__engine__Plugin re;
287     const rep_hint_t *h;
288
289     STRLEN plen;
290     char *pbuf;
291
292     SV *obj;
293
294     h = rep_hint();
295     if (!h) /* This looks like a pragma leak. Apply the default behaviour */
296         return re_compile(pattern, flags);
297
298     /* exp/xend version of the pattern & length */
299     pbuf = SvPV((SV*)pattern, plen);
300
301     /* Our blessed object */
302     obj = newSV(0);
303     SvREFCNT_inc_simple_void_NN(obj);
304     Newxz(re, 1, struct replug);
305     sv_setref_pv(obj, "re::engine::Plugin", (void*)re);
306
307     newREGEXP(RX);
308     rx = rxREGEXP(RX);
309
310     re->rx = rx;                   /* Make the rx accessible from self->rx */
311     rx->intflags = flags;          /* Flags for internal use */
312     rx->extflags = flags;          /* Flags for perl to use */
313     rx->engine = RE_ENGINE_PLUGIN; /* Compile to use this engine */
314
315 #if PERL_VERSION <= 10
316     rx->refcnt = 1;                /* Refcount so we won't be destroyed */
317
318     /* Precompiled pattern for pp_regcomp to use */
319     rx->prelen = plen;
320     rx->precomp = savepvn(pbuf, rx->prelen);
321
322     /* Set up qr// stringification to be equivalent to the supplied
323      * pattern, this should be done via overload eventually.
324      */
325     rx->wraplen = rx->prelen;
326     Newx(rx->wrapped, rx->wraplen, char);
327     Copy(rx->precomp, rx->wrapped, rx->wraplen, char);
328 #endif
329
330     /* Store our private object */
331     rx->pprivate = obj;
332
333     /* Store the pattern for ->pattern */
334     re->pattern = (SV*)pattern;
335     SvREFCNT_inc_simple_void(re->pattern);
336
337     /* If there's an exec callback, store it into the private object so
338      * that it will be the one to be called, even if the engine changes
339      * in between */
340     if (h->exec) {
341         re->cb_exec = h->exec;
342         SvREFCNT_inc_simple_void_NN(h->exec);
343     }
344
345     re->cb_num_capture_buff_FETCH  = NULL;
346     re->cb_num_capture_buff_STORE  = NULL;
347     re->cb_num_capture_buff_LENGTH = NULL;
348
349     /* Call our callback function if one was defined, if not we've
350      * already set up all the stuff we're going to to need for
351      * subsequent exec and other calls */
352     if (h->comp) {
353         ENTER;    
354         SAVETMPS;
355    
356         PUSHMARK(SP);
357         XPUSHs(obj);
358         PUTBACK;
359
360         call_sv(h->comp, G_DISCARD);
361
362         FREETMPS;
363         LEAVE;
364     }
365
366     /* If any of the comp-time accessors were called we'll have to
367      * update the regexp struct with the new info.
368      */
369
370     Newxz(rx->offs, rx->nparens + 1, regexp_paren_pair);
371
372     return RX;
373 }
374
375 I32
376 Plugin_exec(pTHX_ REGEXP * const RX, char *stringarg, char *strend,
377             char *strbeg, I32 minend, SV *sv, void *data, U32 flags)
378 {
379     dSP;
380     I32 matched;
381     struct regexp *rx = rxREGEXP(RX);
382     GET_SELF_FROM_PPRIVATE(rx->pprivate);
383
384     if (self->cb_exec) {
385         SV *ret;
386
387         /* Store the current str for ->str */
388         SvREFCNT_dec(self->str);
389         self->str = sv;
390         SvREFCNT_inc_simple_void(self->str);
391
392         ENTER;
393         SAVETMPS;
394    
395         PUSHMARK(SP);
396         XPUSHs(rx->pprivate);
397         XPUSHs(sv);
398         PUTBACK;
399
400         call_sv(self->cb_exec, G_SCALAR);
401  
402         SPAGAIN;
403
404         ret = POPs;
405         if (SvTRUE(ret))
406             matched = 1;
407         else
408             matched = 0;
409
410         PUTBACK;
411         FREETMPS;
412         LEAVE;
413     } else {
414         matched = 0;
415     }
416
417     return matched;
418 }
419
420 char *
421 Plugin_intuit(pTHX_ REGEXP * const RX, SV *sv, char *strpos,
422                      char *strend, U32 flags, re_scream_pos_data *data)
423 {
424     PERL_UNUSED_ARG(RX);
425     PERL_UNUSED_ARG(sv);
426     PERL_UNUSED_ARG(strpos);
427     PERL_UNUSED_ARG(strend);
428     PERL_UNUSED_ARG(flags);
429     PERL_UNUSED_ARG(data);
430     return NULL;
431 }
432
433 SV *
434 Plugin_checkstr(pTHX_ REGEXP * const RX)
435 {
436     PERL_UNUSED_ARG(RX);
437     return NULL;
438 }
439
440 void
441 Plugin_free(pTHX_ REGEXP * const RX)
442 {
443     struct regexp *rx = rxREGEXP(RX);
444     GET_SELF_FROM_PPRIVATE(rx->pprivate);
445
446     SvREFCNT_dec(self->pattern);
447     SvREFCNT_dec(self->str);
448
449     SvREFCNT_dec(self->cb_exec);
450
451     SvREFCNT_dec(self->cb_num_capture_buff_FETCH);
452     SvREFCNT_dec(self->cb_num_capture_buff_STORE);
453     SvREFCNT_dec(self->cb_num_capture_buff_LENGTH);
454
455     self->rx = NULL;
456     Safefree(self);
457
458 /*
459     dSP;
460     SV * callback;
461
462     callback = self->cb_free;
463
464     if (callback) {
465         ENTER;
466         SAVETMPS;
467    
468         PUSHMARK(SP);
469         XPUSHs(rx->pprivate);
470         PUTBACK;
471
472         call_sv(callback, G_DISCARD);
473
474         PUTBACK;
475         FREETMPS;
476         LEAVE;
477     }
478     return;
479 */
480 }
481
482 void *
483 Plugin_dupe(pTHX_ REGEXP * const RX, CLONE_PARAMS *param)
484 {
485     struct regexp *rx = rxREGEXP(RX);
486     Perl_croak(aTHX_ "dupe not supported yet");
487     return rx->pprivate;
488 }
489
490
491 void
492 Plugin_numbered_buff_FETCH(pTHX_ REGEXP * const RX, const I32 paren,
493                            SV * const sv)
494 {
495     dSP;
496     I32 items;
497     SV * callback;
498     struct regexp *rx = rxREGEXP(RX);
499     GET_SELF_FROM_PPRIVATE(rx->pprivate);
500
501     callback = self->cb_num_capture_buff_FETCH;
502
503     if (callback) {
504         ENTER;
505         SAVETMPS;
506    
507         PUSHMARK(SP);
508         XPUSHs(rx->pprivate);
509         XPUSHs(sv_2mortal(newSViv(paren)));
510         PUTBACK;
511
512         items = call_sv(callback, G_SCALAR);
513         
514         if (items == 1) {
515             SV *ret;
516
517             SPAGAIN;
518             ret = POPs;
519             sv_setsv(sv, ret);
520         } else {
521             sv_setsv(sv, &PL_sv_undef);
522         }
523
524         PUTBACK;
525         FREETMPS;
526         LEAVE;
527     } else {
528         sv_setsv(sv, &PL_sv_undef);
529     }
530 }
531
532 void
533 Plugin_numbered_buff_STORE(pTHX_ REGEXP * const RX, const I32 paren,
534                            SV const * const value)
535 {
536     dSP;
537     SV * callback;
538     struct regexp *rx = rxREGEXP(RX);
539     GET_SELF_FROM_PPRIVATE(rx->pprivate);
540
541     callback = self->cb_num_capture_buff_STORE;
542
543     if (callback) {
544         ENTER;
545         SAVETMPS;
546    
547         PUSHMARK(SP);
548         XPUSHs(rx->pprivate);
549         XPUSHs(sv_2mortal(newSViv(paren)));
550         XPUSHs((SV *) value);
551         PUTBACK;
552
553         call_sv(callback, G_DISCARD);
554
555         PUTBACK;
556         FREETMPS;
557         LEAVE;
558     }
559 }
560
561 I32
562 Plugin_numbered_buff_LENGTH(pTHX_ REGEXP * const RX, const SV * const sv,
563                               const I32 paren)
564 {
565     dSP;
566     SV * callback;
567     struct regexp *rx = rxREGEXP(RX);
568     GET_SELF_FROM_PPRIVATE(rx->pprivate);
569
570     callback = self->cb_num_capture_buff_LENGTH;
571
572     if (callback) {
573         IV ret;
574
575         ENTER;
576         SAVETMPS;
577    
578         PUSHMARK(SP);
579         XPUSHs(rx->pprivate);
580         XPUSHs(sv_2mortal(newSViv(paren)));
581         PUTBACK;
582
583         call_sv(callback, G_SCALAR);
584
585         SPAGAIN;
586
587         ret = POPi;
588
589         PUTBACK;
590         FREETMPS;
591         LEAVE;
592
593         return (I32)ret;
594     } else {
595         /* TODO: call FETCH and get the length on that value */
596         return 0;
597     }
598 }
599
600
601 SV*
602 Plugin_named_buff (pTHX_ REGEXP * const RX, SV * const key, SV * const value,
603                    const U32 flags)
604 {
605     return NULL;
606 }
607
608 SV*
609 Plugin_named_buff_iter (pTHX_ REGEXP * const RX, const SV * const lastkey,
610                         const U32 flags)
611 {
612     return NULL;
613 }
614
615 SV*
616 Plugin_package(pTHX_ REGEXP * const RX)
617 {
618     PERL_UNUSED_ARG(RX);
619     return newSVpvs("re::engine::Plugin");
620 }
621
622 #if REP_THREADSAFE
623
624 STATIC U32 rep_initialized = 0;
625
626 STATIC void rep_teardown(pTHX_ void *root) {
627  if (!rep_initialized || aTHX != root)
628   return;
629
630  {
631   dMY_CXT;
632   ptable_free(MY_CXT.tbl);
633  }
634
635  rep_initialized = 0;
636 }
637
638 STATIC void rep_setup(pTHX) {
639 #define rep_setup() rep_setup(aTHX)
640  if (rep_initialized)
641   return;
642
643  {
644   MY_CXT_INIT;
645   MY_CXT.tbl   = ptable_new();
646   MY_CXT.owner = aTHX;
647  }
648
649  call_atexit(rep_teardown, aTHX);
650
651  rep_initialized = 1;
652 }
653
654 #else  /*  REP_THREADSAFE */
655
656 #define rep_setup()
657
658 #endif /* !REP_THREADSAFE */
659
660 STATIC U32 rep_booted = 0;
661
662 /* --- XS ------------------------------------------------------------------ */
663
664 MODULE = re::engine::Plugin       PACKAGE = re::engine::Plugin
665
666 PROTOTYPES: DISABLE
667
668 BOOT:
669 {
670     if (!rep_booted++) {
671         PERL_HASH(rep_hash, __PACKAGE__, __PACKAGE_LEN__);
672     }
673
674     rep_setup();
675 }
676
677 #if REP_THREADSAFE
678
679 void
680 CLONE(...)
681 PREINIT:
682     ptable *t;
683 PPCODE:
684     {
685         my_cxt_t ud;
686         dMY_CXT;
687         ud.tbl   = t = ptable_new();
688         ud.owner = MY_CXT.owner;
689         ptable_walk(MY_CXT.tbl, rep_ptable_clone, &ud);
690     }
691     {
692         MY_CXT_CLONE;
693         MY_CXT.tbl   = t;
694         MY_CXT.owner = aTHX;
695     }
696     reap(3, rep_thread_cleanup, NULL);
697     XSRETURN(0);
698
699 #endif /* REP_THREADSAFE */
700
701 void
702 pattern(re::engine::Plugin self, ...)
703 PPCODE:
704     XPUSHs(self->pattern);
705
706 void
707 str(re::engine::Plugin self, ...)
708 PPCODE:
709     XPUSHs(self->str);
710
711 void
712 mod(re::engine::Plugin self)
713 PREINIT:
714     U32 flags;
715     char mods[5 + 1];
716     int n = 0, i;
717 PPCODE:
718     flags = self->rx->intflags;
719     if (flags & PMf_FOLD)         /* /i */
720         mods[n++] = 'i';
721     if (flags & PMf_MULTILINE)    /* /m */
722         mods[n++] = 'm';
723     if (flags & PMf_SINGLELINE)   /* /s */
724         mods[n++] = 's';
725     if (flags & PMf_EXTENDED)     /* /x */
726         mods[n++] = 'x';
727     if (flags & RXf_PMf_KEEPCOPY) /* /p */
728         mods[n++] = 'p';
729     mods[n] = '\0';
730     EXTEND(SP, 2 * n);
731     for (i = 0; i < n; ++i) {
732         mPUSHp(mods + i, 1);
733         PUSHs(&PL_sv_yes);
734     }
735     XSRETURN(2 * n);
736
737 void
738 stash(re::engine::Plugin self, ...)
739 PPCODE:
740     if (items > 1) {
741         SvREFCNT_dec(self->stash);
742         self->stash = ST(1);
743         SvREFCNT_inc_simple_void(self->stash);
744         XSRETURN_EMPTY;
745     } else {
746         XPUSHs(self->stash);
747     }
748
749 void
750 minlen(re::engine::Plugin self, ...)
751 PPCODE:
752     if (items > 1) {
753         self->rx->minlen = (I32)SvIV(ST(1));
754         XSRETURN_EMPTY;
755     } else {
756         if (self->rx->minlen) {
757             XPUSHs(sv_2mortal(newSViv(self->rx->minlen)));
758         } else {
759             XPUSHs(sv_2mortal(&PL_sv_undef));
760         }
761     }
762
763 void
764 gofs(re::engine::Plugin self, ...)
765 PPCODE:
766     if (items > 1) {
767         self->rx->gofs = (U32)SvIV(ST(1));
768         XSRETURN_EMPTY;
769     } else {
770         if (self->rx->gofs) {
771             XPUSHs(sv_2mortal(newSVuv(self->rx->gofs)));
772         } else {
773             XPUSHs(sv_2mortal(&PL_sv_undef));
774         }
775     }
776
777 void
778 nparens(re::engine::Plugin self, ...)
779 PPCODE:
780     if (items > 1) {
781         self->rx->nparens = (U32)SvIV(ST(1));
782         XSRETURN_EMPTY;
783     } else {
784         if (self->rx->nparens) {
785             XPUSHs(sv_2mortal(newSVuv(self->rx->nparens)));
786         } else {
787             XPUSHs(sv_2mortal(&PL_sv_undef));
788         }
789     }
790
791 void
792 _exec(re::engine::Plugin self, ...)
793 PPCODE:
794     if (items > 1) {
795         SvREFCNT_dec(self->cb_exec);
796         self->cb_exec = ST(1);
797         SvREFCNT_inc_simple_void(self->cb_exec);
798     }
799
800 void
801 _num_capture_buff_FETCH(re::engine::Plugin self, ...)
802 PPCODE:
803     if (items > 1) {
804         SvREFCNT_dec(self->cb_num_capture_buff_FETCH);
805         self->cb_num_capture_buff_FETCH = ST(1);
806         SvREFCNT_inc_simple_void(self->cb_num_capture_buff_FETCH);
807     }
808
809 void
810 _num_capture_buff_STORE(re::engine::Plugin self, ...)
811 PPCODE:
812     if (items > 1) {
813         SvREFCNT_dec(self->cb_num_capture_buff_STORE);
814         self->cb_num_capture_buff_STORE = ST(1);
815         SvREFCNT_inc_simple_void(self->cb_num_capture_buff_STORE);
816     }
817
818 void
819 _num_capture_buff_LENGTH(re::engine::Plugin self, ...)
820 PPCODE:
821     if (items > 1) {
822         SvREFCNT_dec(self->cb_num_capture_buff_LENGTH);
823         self->cb_num_capture_buff_LENGTH = ST(1);
824         SvREFCNT_inc_simple_void(self->cb_num_capture_buff_LENGTH);
825     }
826
827 SV *
828 _tag(SV *comp, SV *exec)
829 CODE:
830     RETVAL = rep_tag(comp, exec);
831 OUTPUT:
832     RETVAL
833
834 void
835 ENGINE()
836 PPCODE:
837     XPUSHs(sv_2mortal(newSViv(PTR2IV(&engine_plugin))));