File Coverage

lib/XS/Parse/Sublike.xs
Criterion Covered Total %
statement 203 241 84.2
branch 164 214 76.6
condition n/a
subroutine n/a
pod n/a
total 367 455 80.6


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