File Coverage

lib/XS/Parse/Sublike.xs
Criterion Covered Total %
statement 203 241 84.2
branch 164 222 73.8
condition n/a
subroutine n/a
pod n/a
total 367 463 79.2


line stmt bran cond sub pod time code
1             /* You may distribute under the terms of either the GNU General Public License
2             * or the Artistic License (the same terms as Perl itself)
3             *
4             * (C) Paul Evans, 2019-2023 -- leonerd@leonerd.org.uk
5             */
6              
7             #include "EXTERN.h"
8             #include "perl.h"
9             #include "XSUB.h"
10              
11             /* We need to be able to see FEATURE_*_IS_ENABLED */
12             #define PERL_EXT
13             #include "feature.h"
14              
15             #include "XSParseSublike.h"
16              
17             #define HAVE_PERL_VERSION(R, V, S) \
18             (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
19              
20             #if HAVE_PERL_VERSION(5, 37, 10)
21             /* feature 'class' first became available in 5.37.9 but it wasn't until
22             * 5.37.10 that we could pass CVf_IsMETHOD to start_subparse()
23             */
24             # define HAVE_FEATURE_CLASS
25             #endif
26              
27             /* We always need this included to get the struct and function definitions
28             * visible, even though we won't be calling it
29             */
30             #include "parse_subsignature_ex.h"
31              
32             #if HAVE_PERL_VERSION(5, 26, 0)
33             # include "make_argcheck_aux.c.inc"
34              
35             # if !HAVE_PERL_VERSION(5, 31, 3)
36             # define parse_subsignature(flags) parse_subsignature_ex(0) /* ignore core flags as there are none */
37             # endif
38              
39             # define HAVE_PARSE_SUBSIGNATURE
40             #endif
41              
42             #if !HAVE_PERL_VERSION(5, 22, 0)
43             # include "block_start.c.inc"
44             # include "block_end.c.inc"
45             #endif
46              
47             #ifndef wrap_keyword_plugin
48             # include "wrap_keyword_plugin.c.inc"
49             #endif
50              
51             #include "lexer-additions.c.inc"
52              
53             #define QUOTED_PVNf "\"%.*s\"%s"
54             #define QUOTED_PVNfARG(pv,len) ((len) <= 255 ? (int)(len) : 255), (pv), ((len) <= 255 ? "" : "...")
55              
56             struct HooksAndData {
57             const struct XSParseSublikeHooks *hooks;
58             void *data;
59             };
60              
61             #define FOREACH_HOOKS_FORWARD \
62             for(hooki = 0; \
63             (hooki < nhooks) && (hooks = hooksanddata[hooki].hooks, hookdata = hooksanddata[hooki].data), (hooki < nhooks); \
64             hooki++)
65              
66             #define FOREACH_HOOKS_REVERSE \
67             for(hooki = nhooks - 1; \
68             (hooki >= 0) && (hooks = hooksanddata[hooki].hooks, hookdata = hooksanddata[hooki].data), (hooki >= 0); \
69             hooki--)
70              
71             /* Non-documented internal flags we use for our own purposes */
72             enum {
73             XS_PARSE_SUBLIKE_ACTION_CVf_IsMETHOD = (1<<31), /* do we set CVf_IsMETHOD? */
74             };
75              
76 49           static int parse(pTHX_
77             struct HooksAndData hooksanddata[],
78             size_t nhooks,
79             OP **op_ptr)
80             {
81 49           struct XSParseSublikeContext ctx = { 0 };
82              
83             IV hooki;
84             const struct XSParseSublikeHooks *hooks;
85             void *hookdata;
86              
87             U8 require_parts = 0, skip_parts = 0;
88             bool have_dynamic_actions = FALSE;
89              
90 49           ENTER_with_name("parse_sublike");
91             /* From here onwards any `return` must be prefixed by LEAVE_with_name() */
92 49           U32 was_scopestack_ix = PL_scopestack_ix;
93              
94 49           ctx.moddata = newHV();
95 49           SAVEFREESV(ctx.moddata);
96              
97 100 100         FOREACH_HOOKS_FORWARD {
    100          
98 51           require_parts |= hooks->require_parts;
99 51           skip_parts |= hooks->skip_parts;
100 51 100         if(!(hooks->flags & XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL))
101 50           require_parts |= XS_PARSE_SUBLIKE_PART_BODY;
102 51 100         if(hooks->flags & XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS)
103             have_dynamic_actions = TRUE;
104             }
105              
106 49 100         if(!(skip_parts & XS_PARSE_SUBLIKE_PART_NAME)) {
107 48           ctx.name = lex_scan_ident();
108 48           lex_read_space(0);
109             }
110 49 100         if((require_parts & XS_PARSE_SUBLIKE_PART_NAME) && !ctx.name)
    100          
111 1           croak("Expected name for sub-like construction");
112              
113             /* Initial idea of actions are determined by whether we have a name */
114 48 100         ctx.actions = ctx.name
115             ? /* named */ XS_PARSE_SUBLIKE_ACTION_SET_CVNAME|XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL
116             : /* anon */ XS_PARSE_SUBLIKE_ACTION_CVf_ANON|XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE|XS_PARSE_SUBLIKE_ACTION_RET_EXPR;
117              
118 98 100         FOREACH_HOOKS_FORWARD {
    100          
119 50 100         if(hooks->pre_subparse)
120 13           (*hooks->pre_subparse)(aTHX_ &ctx, hookdata);
121             }
122              
123             #ifdef DEBUGGING
124             if(PL_scopestack_ix != was_scopestack_ix)
125             croak("ARGH: pre_subparse broke the scopestack (was %d, now %d)\n",
126             was_scopestack_ix, PL_scopestack_ix);
127             #endif
128              
129 48 100         if(!have_dynamic_actions) {
130 45 100         if(ctx.name)
131 36           ctx.actions &= ~XS_PARSE_SUBLIKE_ACTION_CVf_ANON;
132             else
133 9           ctx.actions |= XS_PARSE_SUBLIKE_ACTION_CVf_ANON;
134             }
135              
136             int subparse_flags = 0;
137 48 100         if(ctx.actions & XS_PARSE_SUBLIKE_ACTION_CVf_ANON)
138             subparse_flags |= CVf_ANON;
139             #ifdef HAVE_FEATURE_CLASS
140             if(ctx.actions & XS_PARSE_SUBLIKE_ACTION_CVf_IsMETHOD)
141             subparse_flags |= CVf_IsMETHOD;
142             #endif
143              
144 48           I32 floor_ix = start_subparse(FALSE, subparse_flags);
145 48           SAVEFREESV(PL_compcv);
146              
147 48 100         if(!(skip_parts & XS_PARSE_SUBLIKE_PART_ATTRS) && (lex_peek_unichar(0) == ':')) {
    100          
148 4           lex_read_unichar(0);
149 4           lex_read_space(0);
150              
151 8           ctx.attrs = newLISTOP(OP_LIST, 0, NULL, NULL);
152              
153             while(1) {
154 8           SV *attr = newSV(0);
155 8           SV *val = newSV(0);
156 8 100         if(!lex_scan_attrval_into(attr, val))
157             break;
158 4           lex_read_space(0);
159 4 50         if(lex_peek_unichar(0) == ':') {
160 0           lex_read_unichar(0);
161 4           lex_read_space(0);
162             }
163              
164             bool handled = FALSE;
165              
166 8 100         FOREACH_HOOKS_FORWARD {
    100          
167 4 100         if((hooks->flags & XS_PARSE_SUBLIKE_FLAG_FILTERATTRS) && (hooks->filter_attr))
    50          
168 1           handled |= (*hooks->filter_attr)(aTHX_ &ctx, attr, val, hookdata);
169             }
170              
171 4 100         if(handled) {
172             SvREFCNT_dec(attr);
173             SvREFCNT_dec(val);
174 1           continue;
175             }
176              
177 3 50         if(strEQ(SvPVX(attr), "lvalue")) {
178 0           CvLVALUE_on(PL_compcv);
179 0           continue;
180             }
181              
182 3 100         if(SvPOK(val))
183 1           sv_catpvf(attr, "(%" SVf ")", val);
184             SvREFCNT_dec(val);
185              
186 3           ctx.attrs = op_append_elem(OP_LIST, ctx.attrs, newSVOP(OP_CONST, 0, attr));
187             }
188             }
189              
190 48           PL_hints |= HINT_LOCALIZE_HH;
191 48           I32 save_ix = block_start(TRUE);
192              
193 98 100         FOREACH_HOOKS_FORWARD {
    100          
194 50 100         if(hooks->post_blockstart)
195 10           (*hooks->post_blockstart)(aTHX_ &ctx, hookdata);
196             }
197              
198             #ifdef DEBUGGING
199             if(PL_scopestack_ix != was_scopestack_ix)
200             croak("ARGH: post_blockstart broke the scopestack (was %d, now %d)\n",
201             was_scopestack_ix, PL_scopestack_ix);
202             #endif
203              
204             #ifdef HAVE_PARSE_SUBSIGNATURE
205             OP *sigop = NULL;
206 48 100         if(!(skip_parts & XS_PARSE_SUBLIKE_PART_SIGNATURE) && (lex_peek_unichar(0) == '(')) {
    100          
207 21           lex_read_unichar(0);
208 21           lex_read_space(0);
209              
210 21 50         if(require_parts & XS_PARSE_SUBLIKE_PART_SIGNATURE) {
211             #if HAVE_PERL_VERSION(5, 32, 0)
212             SAVEI32(PL_compiling.cop_features);
213             PL_compiling.cop_features |= FEATURE_SIGNATURES_BIT;
214             #else
215             /* So far this is only used by the "method" keyword hack for perl 5.38
216             * onwards so this doesn't technically matter. Yet...
217             */
218 0           croak("TODO: import_pragma(\"feature\", \"signatures\")");
219             #endif
220             }
221              
222             #if HAVE_PERL_VERSION(5, 31, 3)
223             /* core's parse_subsignature doesn't seem able to handle empty sigs
224             * RT132284
225             * https://github.com/Perl/perl5/issues/17689
226             */
227             if(lex_peek_unichar(0) == ')') {
228             /* Inject an empty OP_ARGCHECK much as core would do if it encountered
229             * an empty signature */
230             UNOP_AUX_item *aux = make_argcheck_aux(0, 0, 0);
231              
232             sigop = op_prepend_elem(OP_LINESEQ, newSTATEOP(0, NULL, NULL),
233             newUNOP_AUX(OP_ARGCHECK, 0, NULL, aux));
234              
235             /* a nextstate at the end handles context correctly for an empty
236             * sub body */
237             sigop = op_append_elem(OP_LINESEQ, sigop, newSTATEOP(0, NULL, NULL));
238              
239             #if HAVE_PERL_VERSION(5,31,5)
240             /* wrap the list of arg ops in a NULL aux op. This serves two
241             * purposes. First, it makes the arg list a separate subtree
242             * from the body of the sub, and secondly the null op may in
243             * future be upgraded to an OP_SIGNATURE when implemented. For
244             * now leave it as ex-argcheck
245             */
246             sigop = newUNOP_AUX(OP_ARGCHECK, 0, sigop, NULL);
247             op_null(sigop);
248             #endif
249             }
250             else
251             #endif
252             {
253             U32 flags = 0;
254 42 100         FOREACH_HOOKS_FORWARD {
    100          
255 21 100         if(hooks->flags & XS_PARSE_SUBLIKE_FLAG_SIGNATURE_NAMED_PARAMS)
256 9           flags |= PARSE_SUBSIGNATURE_NAMED_PARAMS;
257 21 100         if(hooks->flags & XS_PARSE_SUBLIKE_FLAG_SIGNATURE_PARAM_ATTRIBUTES)
258 4           flags |= PARSE_SUBSIGNATURE_PARAM_ATTRIBUTES;
259             }
260              
261 21 100         if(flags)
262 10           sigop = parse_subsignature_ex(flags);
263             else
264 11           sigop = parse_subsignature(0);
265              
266 21 50         if(PL_parser->error_count) {
267             assert(PL_scopestack_ix == was_scopestack_ix);
268 0           LEAVE_with_name("parse_sublike");
269 0           return 0;
270             }
271             }
272              
273 21 50         if(lex_peek_unichar(0) != ')')
274 0           croak("Expected ')'");
275 21           lex_read_unichar(0);
276 21           lex_read_space(0);
277             }
278             #endif
279              
280 48 100         if(lex_peek_unichar(0) == '{') {
281             /* TODO: technically possible to have skip body flag */
282 44           ctx.body = parse_block(0);
283 44           SvREFCNT_inc(PL_compcv);
284             }
285 4 100         else if(require_parts & XS_PARSE_SUBLIKE_PART_BODY)
286 3           croak("Expected '{' for block body");
287 1 50         else if(lex_peek_unichar(0) == ';') {
288             /* nothing to be done */
289             }
290             else
291 0           croak("Expected '{' for block body or ';'");
292              
293             #ifdef HAVE_PARSE_SUBSIGNATURE
294 45 100         if(ctx.body && sigop) {
    100          
295             /* parse_block() returns an empy block as a stub op.
296             * no need to keep that if we we have a signature.
297             */
298 21 100         if (ctx.body->op_type == OP_STUB) {
299 6           op_free(ctx.body);
300 6           ctx.body = NULL;
301             }
302 21           ctx.body = op_append_list(OP_LINESEQ, sigop, ctx.body);
303             }
304             #endif
305              
306 45 50         if(PL_parser->error_count) {
307             /* parse_block() still sometimes returns a valid body even if a parse
308             * error happens.
309             * We need to destroy this partial body before returning a valid(ish)
310             * state to the keyword hook mechanism, so it will find the error count
311             * correctly
312             * See https://rt.cpan.org/Ticket/Display.html?id=130417
313             */
314 0           op_free(ctx.body);
315              
316             /* REALLY??! Do I really have to do this??
317             * See also:
318             * https://www.nntp.perl.org/group/perl.perl5.porters/2021/06/msg260642.html
319             */
320 0 0         while(PL_scopestack_ix > was_scopestack_ix)
321 0           LEAVE;
322              
323 0           *op_ptr = newOP(OP_NULL, 0);
324 0 0         if(ctx.name) {
325             SvREFCNT_dec(ctx.name);
326             assert(PL_scopestack_ix == was_scopestack_ix);
327 0           LEAVE_with_name("parse_sublike");
328 0           return KEYWORD_PLUGIN_STMT;
329             }
330             else {
331             assert(PL_scopestack_ix == was_scopestack_ix);
332 0           LEAVE_with_name("parse_sublike");
333 0           return KEYWORD_PLUGIN_EXPR;
334             }
335             }
336              
337 92 100         FOREACH_HOOKS_REVERSE {
    100          
338 47 100         if(hooks->pre_blockend)
339 12           (*hooks->pre_blockend)(aTHX_ &ctx, hookdata);
340             }
341              
342             #ifdef DEBUGGING
343             if(PL_scopestack_ix != was_scopestack_ix)
344             croak("ARGH: pre_blockend broke the scopestack (was %d, now %d)\n",
345             was_scopestack_ix, PL_scopestack_ix);
346             #endif
347              
348 45 100         if(ctx.body) {
349 44           ctx.body = block_end(save_ix, ctx.body);
350              
351 44 100         if(!have_dynamic_actions) {
352 41 100         if(ctx.name)
353 33           ctx.actions |= XS_PARSE_SUBLIKE_ACTION_SET_CVNAME|XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL;
354             else
355 8           ctx.actions &= ~(XS_PARSE_SUBLIKE_ACTION_SET_CVNAME|XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL);
356             }
357              
358             /* If we want both SET_CVNAME and INSTALL_SYMBOL actions we might as well
359             * let newATTRSUB() do it. If we only wanted one we need to be more subtle
360             */
361 44           bool action_set_cvname = ctx.actions & XS_PARSE_SUBLIKE_ACTION_SET_CVNAME;
362 44           bool action_install_symbol = ctx.actions & XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL;
363             OP *nameop = NULL;
364 44 100         if(ctx.name && action_set_cvname && action_install_symbol)
    100          
365 35           nameop = newSVOP(OP_CONST, 0, SvREFCNT_inc(ctx.name));
366              
367 44 50         if(!nameop && action_install_symbol)
368 0           warn("Setting XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL without _ACTION_SET_CVNAME is nonsensical");
369              
370 44           ctx.cv = newATTRSUB(floor_ix, nameop, NULL, ctx.attrs, ctx.body);
371              
372 44 100         if(!nameop && action_set_cvname) {
373             #if HAVE_PERL_VERSION(5,22,0)
374             STRLEN namelen;
375 1 50         const char *name = SvPV_const(ctx.name, namelen);
376             U32 hash;
377 1           PERL_HASH(hash, name, namelen);
378              
379             /* Core's CvNAME_HEK_set macro uses unshare_hek() which isn't exposed. But we
380             * likely don't need it here */
381             #ifndef unshare_hek
382             # define unshare_hek(h) (void)0
383             #endif
384             assert(!CvNAME_HEK(ctx.cv));
385              
386 1 50         CvNAME_HEK_set(ctx.cv,
387             share_hek(name, SvUTF8(ctx.name) ? -namelen : namelen, hash));
388             #endif
389             }
390              
391 44           ctx.attrs = NULL;
392 45           ctx.body = NULL;
393             }
394              
395 92 100         FOREACH_HOOKS_FORWARD {
    100          
396 47 100         if(hooks->post_newcv)
397 11           (*hooks->post_newcv)(aTHX_ &ctx, hookdata);
398             }
399              
400             assert(PL_scopestack_ix == was_scopestack_ix);
401 45           LEAVE_with_name("parse_sublike");
402              
403 45 100         if(!have_dynamic_actions) {
404 42 100         if(!ctx.name)
405 8           ctx.actions |= XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE;
406             else
407 34           ctx.actions &= ~XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE;
408             }
409              
410 45 100         if(!(ctx.actions & XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE)) {
411 35           *op_ptr = newOP(OP_NULL, 0);
412              
413 35           SvREFCNT_dec(ctx.name);
414             }
415             else {
416 10           *op_ptr = newUNOP(OP_REFGEN, 0,
417             newSVOP(OP_ANONCODE, 0, (SV *)ctx.cv));
418             }
419              
420 45 100         if(!have_dynamic_actions) {
421 42 100         if(!ctx.name)
422 8           ctx.actions |= XS_PARSE_SUBLIKE_ACTION_RET_EXPR;
423             else
424 34           ctx.actions &= ~XS_PARSE_SUBLIKE_ACTION_RET_EXPR;
425             }
426              
427 45 100         return (ctx.actions & XS_PARSE_SUBLIKE_ACTION_RET_EXPR) ? KEYWORD_PLUGIN_EXPR : KEYWORD_PLUGIN_STMT;
428             }
429              
430 0           static int IMPL_xs_parse_sublike_v4(pTHX_ const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr)
431             {
432 0           struct HooksAndData hd = { .hooks = hooks, .data = hookdata };
433 0           return parse(aTHX_ &hd, 1, op_ptr);
434             }
435              
436 0           static int IMPL_xs_parse_sublike_v3(pTHX_ const void *hooks, void *hookdata, OP **op_ptr)
437             {
438 0           croak("XS::Parse::Sublike ABI v3 is no longer supported; the caller should be rebuilt to use v4");
439             }
440              
441             struct Registration;
442             struct Registration {
443             int ver;
444             struct Registration *next;
445             const char *kw;
446             STRLEN kwlen;
447             union {
448             const struct XSParseSublikeHooks *hooks;
449             };
450             void *hookdata;
451              
452             STRLEN permit_hintkey_len;
453             };
454              
455             #define REGISTRATIONS_LOCK OP_CHECK_MUTEX_LOCK
456             #define REGISTRATIONS_UNLOCK OP_CHECK_MUTEX_UNLOCK
457              
458             static struct Registration *registrations;
459              
460 35           static void register_sublike(pTHX_ const char *kw, const void *hooks, void *hookdata, int ver)
461             {
462             struct Registration *reg;
463 35           Newx(reg, 1, struct Registration);
464              
465 35           reg->kw = savepv(kw);
466 35           reg->kwlen = strlen(kw);
467 35           reg->ver = ver;
468 35           reg->hooks = hooks;
469 35           reg->hookdata = hookdata;
470              
471 35 100         if(reg->hooks->permit_hintkey)
472 26           reg->permit_hintkey_len = strlen(reg->hooks->permit_hintkey);
473             else
474 9           reg->permit_hintkey_len = 0;
475              
476 35 100         if(!reg->hooks->permit && !reg->hooks->permit_hintkey)
    50          
477 0           croak("Third-party sublike keywords require a permit callback or hinthash key");
478              
479             REGISTRATIONS_LOCK;
480             {
481 35           reg->next = registrations;
482 35           registrations = reg;
483             }
484             REGISTRATIONS_UNLOCK;
485 35           }
486              
487 35           static void IMPL_register_xs_parse_sublike_v4(pTHX_ const char *kw, const struct XSParseSublikeHooks *hooks, void *hookdata)
488             {
489 35           register_sublike(aTHX_ kw, hooks, hookdata, 4);
490 35           }
491              
492 0           static void IMPL_register_xs_parse_sublike_v3(pTHX_ const char *kw, const void *hooks, void *hookdata)
493             {
494 0           croak("XS::Parse::Sublike ABI v3 is no longer supported; the caller should be rebuilt to use v4");
495             }
496              
497 26805           static const struct Registration *find_permitted(pTHX_ const char *kw, STRLEN kwlen)
498             {
499             const struct Registration *reg;
500              
501 26805           HV *hints = GvHV(PL_hintgv);
502              
503 71952 100         for(reg = registrations; reg; reg = reg->next) {
504 45197 100         if(reg->kwlen != kwlen || !strEQ(reg->kw, kw))
    100          
505 45145           continue;
506              
507 52 100         if(reg->hooks->permit_hintkey &&
    50          
508 35 100         (!hints || !hv_fetch(hints, reg->hooks->permit_hintkey, reg->permit_hintkey_len, 0)))
509 1           continue;
510              
511 68           if(reg->hooks->permit &&
512 17           !(*reg->hooks->permit)(aTHX_ reg->hookdata))
513 1           continue;
514              
515             return reg;
516             }
517              
518             return NULL;
519             }
520              
521 1           static int IMPL_xs_parse_sublike_any_v4(pTHX_ const struct XSParseSublikeHooks *hooksA, void *hookdataA, OP **op_ptr)
522             {
523 1           SV *kwsv = lex_scan_ident();
524 1 50         if(!kwsv || !SvCUR(kwsv))
    50          
525 0           croak("Expected a keyword to introduce a sub or sub-like construction");
526              
527 1 50         const char *kw = SvPV_nolen(kwsv);
528 1           STRLEN kwlen = SvCUR(kwsv);
529              
530 1           lex_read_space(0);
531              
532             const struct Registration *reg = NULL;
533             /* We permit 'sub' as a NULL set of hooks; anything else should be a registered keyword */
534 1 50         if(kwlen != 3 || !strEQ(kw, "sub")) {
    0          
    0          
    0          
    0          
535 1           reg = find_permitted(aTHX_ kw, kwlen);
536 1 50         if(!reg)
537 0 0         croak("Expected a keyword to introduce a sub or sub-like construction, found " QUOTED_PVNf,
    0          
538             QUOTED_PVNfARG(kw, kwlen));
539             }
540              
541             SvREFCNT_dec(kwsv);
542              
543 1           struct HooksAndData hd[] = {
544             { .hooks = hooksA, .data = hookdataA },
545             { 0 }
546             };
547             struct XSParseSublikeHooks hooks;
548              
549 1 50         if(reg) {
550 1           hd[1].hooks = reg->hooks;
551 1           hd[1].data = reg->hookdata;
552             }
553              
554 1 50         return parse(aTHX_ hd, 1 + !!reg, op_ptr);
555             }
556              
557 0           static int IMPL_xs_parse_sublike_any_v3(pTHX_ const void *hooksA, void *hookdataA, OP **op_ptr)
558             {
559 0           croak("XS::Parse::Sublike ABI v3 is no longer supported; the caller should be rebuilt to use v4");
560             }
561              
562 6           static void IMPL_register_xps_signature_attribute(pTHX_ const char *name, const struct XPSSignatureAttributeFuncs *funcs, void *funcdata)
563             {
564 6 50         if(funcs->ver < 5)
565 0           croak("Mismatch in signature param attribute ABI version field: module wants %d; we require >= 5\n",
566             funcs->ver);
567 6 50         if(funcs->ver > XSPARSESUBLIKE_ABI_VERSION)
568 0           croak("Mismatch in signature param attribute ABI version field: module wants %d; we support <= %d\n",
569             funcs->ver, XSPARSESUBLIKE_ABI_VERSION);
570              
571 6 50         if(!name || !(name[0] >= 'A' && name[0] <= 'Z'))
    50          
572 0           croak("Signature param attribute names must begin with a capital letter");
573              
574 6 50         if(!funcs->permit_hintkey)
575 0           croak("Signature param attributes require a permit hinthash key");
576              
577 6           register_subsignature_attribute(name, funcs, funcdata);
578 6           }
579              
580             #ifdef HAVE_FEATURE_CLASS
581             static bool permit_core_method(pTHX_ void *hookdata)
582             {
583             return FEATURE_CLASS_IS_ENABLED;
584             }
585              
586             static void pre_subparse_core_method(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
587             {
588             ctx->actions |= XS_PARSE_SUBLIKE_ACTION_CVf_IsMETHOD;
589             }
590              
591             static const struct XSParseSublikeHooks hooks_core_method = {
592             .permit = &permit_core_method,
593             .pre_subparse = &pre_subparse_core_method,
594             .require_parts = XS_PARSE_SUBLIKE_PART_SIGNATURE, /* enable signatures feature */
595             };
596             #endif
597              
598             static int (*next_keyword_plugin)(pTHX_ char *, STRLEN, OP **);
599              
600 26803           static int my_keyword_plugin(pTHX_ char *kw, STRLEN kwlen, OP **op_ptr)
601             {
602 26803           const struct Registration *reg = find_permitted(aTHX_ kw, kwlen);
603              
604 26803 100         if(!reg)
605 26755           return (*next_keyword_plugin)(aTHX_ kw, kwlen, op_ptr);
606              
607 48           lex_read_space(0);
608              
609             /* We'll abuse the SvPVX storage of an SV to keep an array of HooksAndData
610             * structures
611             */
612 48           SV *hdlsv = newSV(4 * sizeof(struct HooksAndData));
613 48           SAVEFREESV(hdlsv);
614 48           struct HooksAndData *hd = (struct HooksAndData *)SvPVX(hdlsv);
615             size_t nhooks = 1;
616              
617 48           struct XSParseSublikeHooks *hooks = (struct XSParseSublikeHooks *)reg->hooks;
618 48           hd[0].hooks = hooks;
619 48           hd[0].data = reg->hookdata;
620              
621 49 100         while(hooks->flags & XS_PARSE_SUBLIKE_FLAG_PREFIX) {
622             /* After a prefixing keyword, expect another one */
623 6           SV *kwsv = lex_scan_ident();
624 6           SAVEFREESV(kwsv);
625              
626 6 50         if(!kwsv || !SvCUR(kwsv))
    50          
627 0           croak("Expected a keyword to introduce a sub or sub-like construction");
628              
629 6 50         kw = SvPV_nolen(kwsv);
630 6           kwlen = SvCUR(kwsv);
631              
632 6           lex_read_space(0);
633              
634             /* We permit 'sub' as a NULL set of hooks; anything else should be a registered keyword */
635 6 100         if(kwlen == 3 && strEQ(kw, "sub"))
    50          
    50          
    50          
    50          
636             break;
637              
638 1           reg = find_permitted(aTHX_ kw, kwlen);
639 1 50         if(!reg)
640 0 0         croak("Expected a keyword to introduce a sub or sub-like construction, found " QUOTED_PVNf,
    0          
641             QUOTED_PVNfARG(kw, kwlen));
642              
643 1           hooks = (struct XSParseSublikeHooks *)reg->hooks;
644              
645 1 50         if(SvLEN(hdlsv) < (nhooks + 1) * sizeof(struct HooksAndData)) {
646 0 0         SvGROW(hdlsv, SvLEN(hdlsv) * 2);
    0          
647 0           hd = (struct HooksAndData *)SvPVX(hdlsv);
648             }
649 1           hd[nhooks].hooks = hooks;
650 1           hd[nhooks].data = reg->hookdata;
651             nhooks++;
652             }
653              
654 48           return parse(aTHX_ hd, nhooks, op_ptr);
655             }
656              
657             MODULE = XS::Parse::Sublike PACKAGE = XS::Parse::Sublike
658              
659             BOOT:
660             /* Legacy lookup mechanism using perl symbol table */
661 21           sv_setiv(get_sv("XS::Parse::Sublike::ABIVERSION", GV_ADDMULTI), 4);
662 21           sv_setuv(get_sv("XS::Parse::Sublike::PARSE", GV_ADDMULTI), PTR2UV(&IMPL_xs_parse_sublike_v3));
663 21           sv_setuv(get_sv("XS::Parse::Sublike::REGISTER", GV_ADDMULTI), PTR2UV(&IMPL_register_xs_parse_sublike_v3));
664 21           sv_setuv(get_sv("XS::Parse::Sublike::PARSEANY", GV_ADDMULTI), PTR2UV(&IMPL_xs_parse_sublike_any_v3));
665              
666             /* Newer mechanism */
667 21           sv_setiv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/ABIVERSION_MIN", 1), 4);
668 21           sv_setiv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/ABIVERSION_MAX", 1), XSPARSESUBLIKE_ABI_VERSION);
669              
670 21           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parse()@4", 1), PTR2UV(&IMPL_xs_parse_sublike_v4));
671 21           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/register()@4", 1), PTR2UV(&IMPL_register_xs_parse_sublike_v4));
672 21           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parseany()@4", 1), PTR2UV(&IMPL_xs_parse_sublike_any_v4));
673              
674 21           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/register_sigattr()@5", 1), PTR2UV(&IMPL_register_xps_signature_attribute));
675             #ifdef HAVE_FEATURE_CLASS
676             register_sublike(aTHX_ "method", &hooks_core_method, NULL, 4);
677             #endif
678              
679             wrap_keyword_plugin(&my_keyword_plugin, &next_keyword_plugin);