File Coverage

lib/XS/Parse/Sublike.xs
Criterion Covered Total %
statement 284 339 83.7
branch 221 296 74.6
condition n/a
subroutine n/a
pod n/a
total 505 635 79.5


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-2024 -- 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             #if HAVE_PERL_VERSION(5, 18, 0)
28             # define HAVE_LEXICAL_SUB
29             #endif
30              
31             /* We always need this included to get the struct and function definitions
32             * visible, even though we won't be calling it
33             */
34             #include "parse_subsignature_ex.h"
35              
36             #if HAVE_PERL_VERSION(5, 26, 0)
37             # include "make_argcheck_aux.c.inc"
38              
39             # if !HAVE_PERL_VERSION(5, 31, 3)
40             # define parse_subsignature(flags) parse_subsignature_ex(0, NULL, NULL, 0) /* ignore core flags as there are none */
41             # endif
42              
43             # define HAVE_PARSE_SUBSIGNATURE
44             #endif
45              
46             #if !HAVE_PERL_VERSION(5, 22, 0)
47             # include "block_start.c.inc"
48             # include "block_end.c.inc"
49             #endif
50              
51             #ifndef wrap_keyword_plugin
52             # include "wrap_keyword_plugin.c.inc"
53             #endif
54              
55             #include "lexer-additions.c.inc"
56              
57             #define QUOTED_PVNf "\"%.*s\"%s"
58             #define QUOTED_PVNfARG(pv,len) ((len) <= 255 ? (int)(len) : 255), (pv), ((len) <= 255 ? "" : "...")
59              
60             /* Non-documented internal flags we use for our own purposes */
61             enum {
62             XS_PARSE_SUBLIKE_ACTION_CVf_IsMETHOD = (1<<31), /* do we set CVf_IsMETHOD? */
63             };
64              
65 90           static int parse(pTHX_
66             struct HooksAndData hooksanddata[],
67             size_t nhooks,
68             OP **op_ptr)
69             {
70             /* We need to reserve extra space in here for the sigctx pointer. To
71             * simplify much code here lets just pretend `ctx` is the actual context
72             * struct stored within
73             */
74 90           struct XPSContextWithPointer ctx_with_ptr = { 0 };
75             #define ctx (ctx_with_ptr.ctx)
76              
77             IV hooki;
78             const struct XSParseSublikeHooks *hooks;
79             void *hookdata;
80              
81             U8 require_parts = 0, skip_parts = 0;
82             bool have_dynamic_actions = FALSE;
83              
84 90           ENTER_with_name("parse_sublike");
85             /* From here onwards any `return` must be prefixed by LEAVE_with_name() */
86 90           U32 was_scopestack_ix = PL_scopestack_ix;
87              
88 90           ctx.moddata = newHV();
89 90           SAVEFREESV(ctx.moddata);
90              
91 190 100         FOREACH_HOOKS_FORWARD {
    100          
92 100           require_parts |= hooks->require_parts;
93 100           skip_parts |= hooks->skip_parts;
94 100 100         if(!(hooks->flags & XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL))
95 83           require_parts |= XS_PARSE_SUBLIKE_PART_BODY;
96 100 100         if(hooks->flags & XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS)
97             have_dynamic_actions = TRUE;
98             }
99              
100 90 100         if(!(skip_parts & XS_PARSE_SUBLIKE_PART_NAME)) {
101 89           ctx.name = lex_scan_packagename();
102 89           lex_read_space(0);
103             }
104 90 100         if((require_parts & XS_PARSE_SUBLIKE_PART_NAME) && !ctx.name)
    100          
105 1           croak("Expected name for sub-like construction");
106              
107 89 100         if(ctx.name && strstr(SvPV_nolen(ctx.name), "::")) {
    100          
108 3 100         FOREACH_HOOKS_FORWARD {
    100          
109 2 100         if(hooks->flags & XS_PARSE_SUBLIKE_FLAG_ALLOW_PKGNAME)
110             continue;
111              
112 1           croak("Declaring this sub-like function in another package is not permitted");
113             }
114             }
115              
116             /* Initial idea of actions are determined by whether we have a name */
117 176           ctx.actions = ctx.name
118             ? /* named */ XS_PARSE_SUBLIKE_ACTION_SET_CVNAME|XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL
119 88 100         : /* anon */ XS_PARSE_SUBLIKE_ACTION_CVf_ANON|XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE|XS_PARSE_SUBLIKE_ACTION_RET_EXPR;
120              
121 186 100         FOREACH_HOOKS_FORWARD {
    100          
122 98 100         if(hooks->pre_subparse)
123 23           (*hooks->pre_subparse)(aTHX_ &ctx, hookdata);
124             }
125              
126             #ifdef DEBUGGING
127             if(PL_scopestack_ix != was_scopestack_ix)
128             croak("ARGH: pre_subparse broke the scopestack (was %d, now %d)\n",
129             was_scopestack_ix, PL_scopestack_ix);
130             #endif
131              
132 88 100         if(!have_dynamic_actions) {
133 83 100         if(ctx.name)
134 74           ctx.actions &= ~XS_PARSE_SUBLIKE_ACTION_CVf_ANON;
135             else
136 9           ctx.actions |= XS_PARSE_SUBLIKE_ACTION_CVf_ANON;
137             }
138              
139             int subparse_flags = 0;
140 88 100         if(ctx.actions & XS_PARSE_SUBLIKE_ACTION_CVf_ANON)
141             subparse_flags |= CVf_ANON;
142             #ifdef HAVE_FEATURE_CLASS
143 88 100         if(ctx.actions & XS_PARSE_SUBLIKE_ACTION_CVf_IsMETHOD)
144 2           subparse_flags |= CVf_IsMETHOD;
145             #endif
146              
147             /* TODO: We should find a way to put this in the main ctx structure, but we
148             * can't easily change that without breaking ABI compat.
149             */
150             PADOFFSET lexname_padix = 0;
151              
152 88 100         if(ctx.name && (ctx.actions & XS_PARSE_SUBLIKE_ACTION_INSTALL_LEXICAL)) {
    100          
153 2           SV *ampname = newSVpvf("&%" SVf, SVfARG(ctx.name));
154 2           SAVEFREESV(ampname);
155 2           lexname_padix = pad_add_name_sv(ampname, 0, NULL, NULL);
156             }
157              
158 88           I32 floor_ix = start_subparse(FALSE, subparse_flags);
159 88           SAVEFREESV(PL_compcv);
160              
161             #ifdef HAVE_LEXICAL_SUB
162 88 100         if(ctx.actions & XS_PARSE_SUBLIKE_ACTION_INSTALL_LEXICAL)
163             /* Lexical subs always have CVf_CLONE */
164 2           CvCLONE_on(PL_compcv);
165             #endif
166              
167 88 100         if(!(skip_parts & XS_PARSE_SUBLIKE_PART_ATTRS) && (lex_peek_unichar(0) == ':')) {
    100          
168 5           lex_read_unichar(0);
169 5           lex_read_space(0);
170              
171 5           ctx.attrs = newLISTOP(OP_LIST, 0, NULL, NULL);
172              
173             while(1) {
174 10           SV *attr = newSV(0);
175 10           SV *val = newSV(0);
176 10 100         if(!lex_scan_attrval_into(attr, val))
177             break;
178 5           lex_read_space(0);
179 5 50         if(lex_peek_unichar(0) == ':') {
180 0           lex_read_unichar(0);
181 0           lex_read_space(0);
182             }
183              
184             bool handled = FALSE;
185              
186 10 100         FOREACH_HOOKS_FORWARD {
    100          
187 5 100         if(hooks->filter_attr)
188 1           handled |= (*hooks->filter_attr)(aTHX_ &ctx, attr, val, hookdata);
189             }
190              
191 5 100         if(handled) {
192 1           SvREFCNT_dec(attr);
193 1           SvREFCNT_dec(val);
194 1           continue;
195             }
196              
197 4 50         if(strEQ(SvPVX(attr), "lvalue")) {
198 0           CvLVALUE_on(PL_compcv);
199 0           continue;
200             }
201              
202 4 100         if(SvPOK(val))
203 2           sv_catpvf(attr, "(%" SVf ")", val);
204 4           SvREFCNT_dec(val);
205              
206 4           ctx.attrs = op_append_elem(OP_LIST, ctx.attrs, newSVOP(OP_CONST, 0, attr));
207             }
208             }
209              
210 88           PL_hints |= HINT_LOCALIZE_HH;
211 88           I32 save_ix = block_start(TRUE);
212              
213 186 100         FOREACH_HOOKS_FORWARD {
    100          
214 98 100         if(hooks->post_blockstart)
215 18           (*hooks->post_blockstart)(aTHX_ &ctx, hookdata);
216             }
217              
218             #ifdef DEBUGGING
219             if(PL_scopestack_ix != was_scopestack_ix)
220             croak("ARGH: post_blockstart broke the scopestack (was %d, now %d)\n",
221             was_scopestack_ix, PL_scopestack_ix);
222             #endif
223              
224             #ifdef HAVE_PARSE_SUBSIGNATURE
225             OP *sigop = NULL;
226 88 100         if(!(skip_parts & XS_PARSE_SUBLIKE_PART_SIGNATURE) && (lex_peek_unichar(0) == '(')) {
    100          
227 57           lex_read_unichar(0);
228 57           lex_read_space(0);
229              
230 57 100         if(require_parts & XS_PARSE_SUBLIKE_PART_SIGNATURE) {
231             #if HAVE_PERL_VERSION(5, 41, 8)
232 2           SAVEFEATUREBITS();
233 2           PL_compiling.cop_features.bits[FEATURE_SIGNATURES_INDEX] |= FEATURE_SIGNATURES_BIT;
234             #elif HAVE_PERL_VERSION(5, 32, 0)
235             SAVEI32(PL_compiling.cop_features);
236             PL_compiling.cop_features |= FEATURE_SIGNATURES_BIT;
237             #else
238             /* So far this is only used by the "method" keyword hack for perl 5.38
239             * onwards so this doesn't technically matter. Yet...
240             */
241             croak("TODO: import_pragma(\"feature\", \"signatures\")");
242             #endif
243             }
244              
245             U32 flags = 0;
246             bool have_sighooks = false;
247 120 100         FOREACH_HOOKS_FORWARD {
    100          
248 63 100         if(hooks->flags & XS_PARSE_SUBLIKE_FLAG_SIGNATURE_NAMED_PARAMS)
249 34           flags |= PARSE_SUBSIGNATURE_NAMED_PARAMS;
250 63 100         if(hooks->flags & XS_PARSE_SUBLIKE_FLAG_SIGNATURE_PARAM_ATTRIBUTES)
251 17           flags |= PARSE_SUBSIGNATURE_PARAM_ATTRIBUTES;
252 63 100         if(hooks->flags & XS_PARSE_SUBLIKE_FLAG_SIGNATURE_REFALIAS)
253 18           flags |= PARSE_SUBSIGNATURE_REFALIAS;
254 63 50         if(hooks->ver >= 7 && (hooks->start_signature || hooks->finish_signature))
    100          
    50          
255             have_sighooks = true;
256             }
257              
258 57 100         if(flags || have_sighooks)
259 44           sigop = parse_subsignature_ex(flags, &ctx_with_ptr, hooksanddata, nhooks);
260             else {
261             #if HAVE_PERL_VERSION(5, 31, 3)
262             /* core's parse_subsignature doesn't seem able to handle empty sigs
263             * RT132284
264             * https://github.com/Perl/perl5/issues/17689
265             */
266 13 100         if(lex_peek_unichar(0) == ')') {
267             /* Inject an empty OP_ARGCHECK much as core would do if it encountered
268             * an empty signature */
269             UNOP_AUX_item *aux = make_argcheck_aux(0, 0, 0);
270              
271 3           sigop = op_prepend_elem(OP_LINESEQ, newSTATEOP(0, NULL, NULL),
272             newUNOP_AUX(OP_ARGCHECK, 0, NULL, aux));
273              
274             /* a nextstate at the end handles context correctly for an empty
275             * sub body */
276 3           sigop = op_append_elem(OP_LINESEQ, sigop, newSTATEOP(0, NULL, NULL));
277              
278             #if HAVE_PERL_VERSION(5,31,5)
279             /* wrap the list of arg ops in a NULL aux op. This serves two
280             * purposes. First, it makes the arg list a separate subtree
281             * from the body of the sub, and secondly the null op may in
282             * future be upgraded to an OP_SIGNATURE when implemented. For
283             * now leave it as ex-argcheck
284             */
285 3           sigop = newUNOP_AUX(OP_ARGCHECK, 0, sigop, NULL);
286 3           op_null(sigop);
287             #endif
288             }
289             else
290             #endif
291 10           sigop = parse_subsignature(0);
292              
293 13 50         if(PL_parser->error_count) {
294             assert(PL_scopestack_ix == was_scopestack_ix);
295 0           LEAVE_with_name("parse_sublike");
296 0           return 0;
297             }
298             }
299              
300 56 50         if(lex_peek_unichar(0) != ')')
301 0           croak("Expected ')'");
302 56           lex_read_unichar(0);
303 56           lex_read_space(0);
304             }
305             #endif
306              
307 87 100         if(lex_peek_unichar(0) == '{') {
308             /* TODO: technically possible to have skip body flag */
309 83           ctx.body = parse_block(0);
310 83 50         SvREFCNT_inc(PL_compcv);
311             }
312 4 100         else if(require_parts & XS_PARSE_SUBLIKE_PART_BODY)
313 3           croak("Expected '{' for block body");
314 1 50         else if(lex_peek_unichar(0) == ';') {
315             /* nothing to be done */
316             }
317             else
318 0           croak("Expected '{' for block body or ';'");
319              
320             #ifdef HAVE_PARSE_SUBSIGNATURE
321 84 100         if(ctx.body && sigop) {
    100          
322             /* parse_block() returns an empy block as a stub op.
323             * no need to keep that if we we have a signature.
324             */
325 56 100         if (ctx.body->op_type == OP_STUB) {
326 13           op_free(ctx.body);
327 13           ctx.body = NULL;
328             }
329 56           ctx.body = op_append_list(OP_LINESEQ, sigop, ctx.body);
330             }
331             #endif
332              
333 84 50         if(PL_parser->error_count) {
334             /* parse_block() still sometimes returns a valid body even if a parse
335             * error happens.
336             * We need to destroy this partial body before returning a valid(ish)
337             * state to the keyword hook mechanism, so it will find the error count
338             * correctly
339             * See https://rt.cpan.org/Ticket/Display.html?id=130417
340             */
341 0           op_free(ctx.body);
342              
343             /* REALLY??! Do I really have to do this??
344             * See also:
345             * https://www.nntp.perl.org/group/perl.perl5.porters/2021/06/msg260642.html
346             */
347 0 0         while(PL_scopestack_ix > was_scopestack_ix)
348 0           LEAVE;
349              
350 0           *op_ptr = newOP(OP_NULL, 0);
351 0 0         if(ctx.name) {
352 0           SvREFCNT_dec(ctx.name);
353             assert(PL_scopestack_ix == was_scopestack_ix);
354 0           LEAVE_with_name("parse_sublike");
355 0           return KEYWORD_PLUGIN_STMT;
356             }
357             else {
358             assert(PL_scopestack_ix == was_scopestack_ix);
359 0           LEAVE_with_name("parse_sublike");
360 0           return KEYWORD_PLUGIN_EXPR;
361             }
362             }
363              
364 178 100         FOREACH_HOOKS_REVERSE {
    100          
365 94 100         if(hooks->pre_blockend)
366 20           (*hooks->pre_blockend)(aTHX_ &ctx, hookdata);
367             }
368              
369             #ifdef DEBUGGING
370             if(PL_scopestack_ix != was_scopestack_ix)
371             croak("ARGH: pre_blockend broke the scopestack (was %d, now %d)\n",
372             was_scopestack_ix, PL_scopestack_ix);
373             #endif
374              
375 84 100         if(ctx.body) {
376 83           ctx.body = block_end(save_ix, ctx.body);
377              
378 83 100         if(!have_dynamic_actions) {
379 78 100         if(ctx.name) {
380 70           ctx.actions |= XS_PARSE_SUBLIKE_ACTION_SET_CVNAME;
381 70 100         if(!(ctx.actions & XS_PARSE_SUBLIKE_ACTION_INSTALL_LEXICAL))
382 68           ctx.actions |= XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL;
383             }
384             else
385 8           ctx.actions &= ~(XS_PARSE_SUBLIKE_ACTION_SET_CVNAME|XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL);
386             }
387              
388             /* If we want both SET_CVNAME and INSTALL_SYMBOL actions we might as well
389             * let newATTRSUB() do it. If we only wanted one we need to be more subtle
390             */
391 83           bool action_set_cvname = ctx.actions & XS_PARSE_SUBLIKE_ACTION_SET_CVNAME;
392 83           bool action_install_symbol = ctx.actions & XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL;
393 83           bool action_install_lexical = ctx.actions & XS_PARSE_SUBLIKE_ACTION_INSTALL_LEXICAL;
394 83 50         if(action_install_symbol && action_install_lexical)
395 0           croak("Cannot both ACTION_INSTALL_SYMBOL and ACTION_INSTALL_LEXICAL");
396              
397             OP *nameop = NULL;
398 83 100         if(ctx.name && action_set_cvname && action_install_symbol)
    100          
399 72           nameop = newSVOP(OP_CONST, 0, SvREFCNT_inc(ctx.name));
400              
401 83 50         if(!nameop && action_install_symbol)
402 0           warn("Setting XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL without _ACTION_SET_CVNAME is nonsensical");
403              
404 83 100         if(action_install_lexical) {
405             #ifdef HAVE_LEXICAL_SUB
406             assert(lexname_padix);
407 2           nameop = newOP(OP_PADANY, 0);
408 2           nameop->op_targ = lexname_padix;
409              
410 2           ctx.cv = newMYSUB(floor_ix, nameop, NULL, ctx.attrs, ctx.body);
411             #else
412             PERL_UNUSED_VAR(lexname_padix);
413             croak("XS_PARSE_SUBLIKE_ACTION_INSTALL_LEXICAL is not supported on this version of Perl");
414             #endif
415             }
416             else
417 81           ctx.cv = newATTRSUB(floor_ix, nameop, NULL, ctx.attrs, ctx.body);
418              
419 83 100         if(!nameop && action_set_cvname) {
420             #if HAVE_PERL_VERSION(5,22,0)
421             STRLEN namelen;
422 1           const char *name = SvPV_const(ctx.name, namelen);
423             U32 hash;
424 1 50         PERL_HASH(hash, name, namelen);
425              
426             /* Core's CvNAME_HEK_set macro uses unshare_hek() which isn't exposed. But we
427             * likely don't need it here */
428             #ifndef unshare_hek
429             # define unshare_hek(h) (void)0
430             #endif
431             assert(!CvNAME_HEK(ctx.cv));
432              
433 1 50         CvNAME_HEK_set(ctx.cv,
    0          
    50          
434             share_hek(name, SvUTF8(ctx.name) ? -namelen : namelen, hash));
435             #endif
436             }
437              
438 83           ctx.attrs = NULL;
439 83           ctx.body = NULL;
440             }
441              
442 178 100         FOREACH_HOOKS_FORWARD {
    100          
443 94 100         if(hooks->post_newcv)
444 19           (*hooks->post_newcv)(aTHX_ &ctx, hookdata);
445             }
446              
447             assert(PL_scopestack_ix == was_scopestack_ix);
448 84           LEAVE_with_name("parse_sublike");
449              
450 84 100         if(!have_dynamic_actions) {
451 79 100         if(!ctx.name)
452 8           ctx.actions |= XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE;
453             else
454 71           ctx.actions &= ~XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE;
455             }
456              
457 84 100         if(!(ctx.actions & XS_PARSE_SUBLIKE_ACTION_REFGEN_ANONCODE)) {
458 74           *op_ptr = newOP(OP_NULL, 0);
459              
460 74           SvREFCNT_dec(ctx.name);
461             }
462             else {
463 10           *op_ptr = newUNOP(OP_REFGEN, 0,
464             newSVOP(OP_ANONCODE, 0, (SV *)ctx.cv));
465             }
466              
467 84 100         if(!have_dynamic_actions) {
468 79 100         if(!ctx.name)
469 8           ctx.actions |= XS_PARSE_SUBLIKE_ACTION_RET_EXPR;
470             else
471 71           ctx.actions &= ~XS_PARSE_SUBLIKE_ACTION_RET_EXPR;
472             }
473              
474 84 100         return (ctx.actions & XS_PARSE_SUBLIKE_ACTION_RET_EXPR) ? KEYWORD_PLUGIN_EXPR : KEYWORD_PLUGIN_STMT;
475             #undef ctx
476             }
477              
478 0           static int IMPL_xs_parse_sublike_v6(pTHX_ const struct XSParseSublikeHooks *hooks, void *hookdata, OP **op_ptr)
479             {
480 0           struct HooksAndData hd = { .hooks = hooks, .data = hookdata };
481 0           return parse(aTHX_ &hd, 1, op_ptr);
482             }
483              
484             struct Registration;
485             struct Registration {
486             struct Registration *next;
487             const char *kw;
488             STRLEN kwlen;
489             union {
490             const struct XSParseSublikeHooks *hooks;
491             };
492             void *hookdata;
493              
494             STRLEN permit_hintkey_len;
495             };
496              
497             #define REGISTRATIONS_LOCK OP_CHECK_MUTEX_LOCK
498             #define REGISTRATIONS_UNLOCK OP_CHECK_MUTEX_UNLOCK
499              
500             static struct Registration *registrations;
501              
502 157           static void register_sublike(pTHX_ const char *kw, const struct XSParseSublikeHooks *hooks, void *hookdata, int ver)
503             {
504 157 50         if(ver < 4)
505 0           croak("Mismatch in sublike keyword registration ABI version field: module wants %u; we require >= 4\n",
506             ver);
507 157 50         if(ver > XSPARSESUBLIKE_ABI_VERSION)
508 0           croak("Mismatch in sublike keyword registration ABI version field: module wants %u; we support <= %d\n",
509             ver, XSPARSESUBLIKE_ABI_VERSION);
510              
511             struct Registration *reg;
512 157           Newx(reg, 1, struct Registration);
513              
514 157           reg->kw = savepv(kw);
515 157           reg->kwlen = strlen(kw);
516 157           reg->hooks = hooks;
517 157           reg->hookdata = hookdata;
518              
519 157 100         if(reg->hooks->permit_hintkey)
520 119           reg->permit_hintkey_len = strlen(reg->hooks->permit_hintkey);
521             else
522 38           reg->permit_hintkey_len = 0;
523              
524 157 100         if(!reg->hooks->permit && !reg->hooks->permit_hintkey)
    50          
525 0           croak("Third-party sublike keywords require a permit callback or hinthash key");
526              
527             REGISTRATIONS_LOCK;
528             {
529 157           reg->next = registrations;
530 157           registrations = reg;
531             }
532             REGISTRATIONS_UNLOCK;
533 157           }
534              
535 73           static void IMPL_register_xs_parse_sublike_v6(pTHX_ const char *kw, const struct XSParseSublikeHooks *hooks, void *hookdata)
536             {
537 73           int ver = hooks->ver;
538 73 50         if(!ver)
539             /* Caller forgot to set .ver but for source-level compat we'll presume they
540             * wanted version 6, the first ABI version that added the .ver field
541             */
542             ver = 6;
543              
544 73           register_sublike(aTHX_ kw, hooks, hookdata, ver);
545 73           }
546              
547 130095           static const struct Registration *find_permitted(pTHX_ const char *kw, STRLEN kwlen)
548             {
549             const struct Registration *reg;
550              
551 130095           HV *hints = GvHV(PL_hintgv);
552              
553 868546 100         for(reg = registrations; reg; reg = reg->next) {
554 738546 100         if(reg->kwlen != kwlen || !strnEQ(reg->kw, kw, kwlen))
    100          
555 726081           continue;
556              
557 12465 100         if(reg->hooks->permit_hintkey &&
    50          
558 12442 100         (!hints || !hv_fetch(hints, reg->hooks->permit_hintkey, reg->permit_hintkey_len, 0)))
559 12369           continue;
560              
561 121           if(reg->hooks->permit &&
562 25           !(*reg->hooks->permit)(aTHX_ reg->hookdata))
563 1           continue;
564              
565             return reg;
566             }
567              
568             return NULL;
569             }
570              
571 1           static int IMPL_xs_parse_sublike_any_v6(pTHX_ const struct XSParseSublikeHooks *hooksA, void *hookdataA, OP **op_ptr)
572             {
573 1           SV *kwsv = lex_scan_ident();
574 1 50         if(!kwsv || !SvCUR(kwsv))
    50          
575 0           croak("Expected a keyword to introduce a sub or sub-like construction");
576              
577 1           const char *kw = SvPV_nolen(kwsv);
578 1           STRLEN kwlen = SvCUR(kwsv);
579              
580 1           lex_read_space(0);
581              
582             const struct Registration *reg = NULL;
583             /* We permit 'sub' as a NULL set of hooks; anything else should be a registered keyword */
584 1 50         if(kwlen != 3 || !strEQ(kw, "sub")) {
    0          
585 1           reg = find_permitted(aTHX_ kw, kwlen);
586 1 50         if(!reg)
587 0 0         croak("Expected a keyword to introduce a sub or sub-like construction, found " QUOTED_PVNf,
588             QUOTED_PVNfARG(kw, kwlen));
589             }
590              
591 1           SvREFCNT_dec(kwsv);
592              
593 1           struct HooksAndData hd[] = {
594             { .hooks = hooksA, .data = hookdataA },
595             { 0 }
596             };
597              
598 1 50         if(reg) {
599 1           hd[1].hooks = reg->hooks;
600 1           hd[1].data = reg->hookdata;
601             }
602              
603 1           return parse(aTHX_ hd, 1 + !!reg, op_ptr);
604             }
605              
606 9           static void IMPL_register_xps_signature_attribute(pTHX_ const char *name, const struct XPSSignatureAttributeFuncs *funcs, void *funcdata)
607             {
608 9 50         if(funcs->ver < 5)
609 0           croak("Mismatch in signature param attribute ABI version field: module wants %u; we require >= 5\n",
610             funcs->ver);
611 9 50         if(funcs->ver > XSPARSESUBLIKE_ABI_VERSION)
612 0           croak("Mismatch in signature param attribute ABI version field: module wants %u; we support <= %d\n",
613             funcs->ver, XSPARSESUBLIKE_ABI_VERSION);
614              
615 9 50         if(!name || !(name[0] >= 'A' && name[0] <= 'Z'))
    50          
616 0           croak("Signature param attribute names must begin with a capital letter");
617              
618 9 50         if(!funcs->permit_hintkey)
619 0           croak("Signature param attributes require a permit hinthash key");
620              
621 9           register_subsignature_attribute(name, funcs, funcdata);
622 9           }
623              
624             #ifdef HAVE_FEATURE_CLASS
625 2           static bool permit_core_method(pTHX_ void *hookdata)
626             {
627 2 50         return FEATURE_CLASS_IS_ENABLED;
    50          
    50          
628             }
629              
630 2           static void pre_subparse_core_method(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
631             {
632 2           ctx->actions |= XS_PARSE_SUBLIKE_ACTION_CVf_IsMETHOD;
633 2           }
634              
635             static const struct XSParseSublikeHooks hooks_core_method = {
636             .ver = XSPARSESUBLIKE_ABI_VERSION,
637             .permit = &permit_core_method,
638             .pre_subparse = &pre_subparse_core_method,
639             .require_parts = XS_PARSE_SUBLIKE_PART_SIGNATURE, /* enable signatures feature */
640             };
641             #endif
642              
643             #ifdef HAVE_LEXICAL_SUB
644 2           static void pre_subparse_lexical_sub(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
645             {
646 2           ctx->actions &= ~XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL;
647 2           ctx->actions |= XS_PARSE_SUBLIKE_ACTION_INSTALL_LEXICAL;
648 2           }
649              
650             static const struct XSParseSublikeHooks hooks_lexical_sub = {
651             .ver = XSPARSESUBLIKE_ABI_VERSION,
652             /* no permit needed */
653             .pre_subparse = &pre_subparse_lexical_sub,
654             };
655             #endif
656              
657             /* Sublike::Extended */
658              
659             static struct XSParseSublikeHooks hooks_extended = {
660             .ver = XSPARSESUBLIKE_ABI_VERSION,
661             .permit_hintkey = "Sublike::Extended/extended",
662             .flags = XS_PARSE_SUBLIKE_FLAG_PREFIX|
663             XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL|
664             XS_PARSE_SUBLIKE_FLAG_SIGNATURE_NAMED_PARAMS|
665             XS_PARSE_SUBLIKE_FLAG_SIGNATURE_PARAM_ATTRIBUTES|
666             XS_PARSE_SUBLIKE_FLAG_SIGNATURE_REFALIAS,
667              
668             /* No hooks */
669             };
670              
671             static struct XSParseSublikeHooks hooks_extended_sub = {
672             .ver = XSPARSESUBLIKE_ABI_VERSION,
673             .permit_hintkey = "Sublike::Extended/extended-sub",
674             .flags = XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL|
675             XS_PARSE_SUBLIKE_FLAG_SIGNATURE_NAMED_PARAMS|
676             XS_PARSE_SUBLIKE_FLAG_SIGNATURE_PARAM_ATTRIBUTES|
677             XS_PARSE_SUBLIKE_FLAG_SIGNATURE_REFALIAS,
678              
679             /* No hooks */
680             };
681              
682             /* keyword plugin */
683              
684             static int (*next_keyword_plugin)(pTHX_ char *, STRLEN, OP **);
685              
686 153750           static int my_keyword_plugin(pTHX_ char *kw, STRLEN kwlen, OP **op_ptr)
687             {
688             char *orig_kw = kw;
689             STRLEN orig_kwlen = kwlen;
690              
691             #ifdef HAVE_LEXICAL_SUB
692 153750           char *was_parser_bufptr = PL_parser->bufptr;
693              
694             bool is_lexical_sub = false;
695              
696 153750 100         if(kwlen == 2 && strEQ(kw, "my")) {
    100          
697 23670           lex_read_space(0);
698              
699 23670           I32 c = lex_peek_unichar(0);
700 23670 50         if(!isIDFIRST_uni(c))
    50          
    100          
    0          
701 23667           goto next_keyword;
702              
703 3           kw = PL_parser->bufptr;
704              
705 3           lex_read_unichar(0);
706 12 50         while((c = lex_peek_unichar(0)) && isALNUM_uni(c))
    50          
    50          
    100          
    0          
707 9           lex_read_unichar(0);
708              
709 3           kwlen = PL_parser->bufptr - kw;
710              
711             is_lexical_sub = true;
712             }
713             #endif
714              
715 130083           const struct Registration *reg = find_permitted(aTHX_ kw, kwlen);
716              
717 130083 100         if(!reg) {
718             #ifdef HAVE_LEXICAL_SUB
719 129994 100         if(PL_parser->bufptr > was_parser_bufptr)
720 1           PL_parser->bufptr = was_parser_bufptr;
721 129993           next_keyword:
722             #endif
723 153661           return (*next_keyword_plugin)(aTHX_ orig_kw, orig_kwlen, op_ptr);
724             }
725              
726 89           lex_read_space(0);
727              
728             /* We'll abuse the SvPVX storage of an SV to keep an array of HooksAndData
729             * structures
730             */
731 89           SV *hdlsv = newSV(4 * sizeof(struct HooksAndData));
732 89           SAVEFREESV(hdlsv);
733 89           struct HooksAndData *hd = (struct HooksAndData *)SvPVX(hdlsv);
734             size_t nhooks = 0;
735              
736             #ifdef HAVE_LEXICAL_SUB
737 89 100         if(is_lexical_sub) {
738 2           hd[nhooks].hooks = &hooks_lexical_sub;
739 2           hd[nhooks].data = NULL;
740             nhooks++;
741             }
742             #endif
743              
744 89           struct XSParseSublikeHooks *hooks = (struct XSParseSublikeHooks *)reg->hooks;
745              
746 89           hd[nhooks].hooks = hooks;
747 89           hd[nhooks].data = reg->hookdata;
748 89           nhooks++;
749              
750 94 100         while(hooks->flags & XS_PARSE_SUBLIKE_FLAG_PREFIX) {
751             /* After a prefixing keyword, expect another one */
752 11           SV *kwsv = lex_scan_ident();
753 11           SAVEFREESV(kwsv);
754              
755 11 50         if(!kwsv || !SvCUR(kwsv))
    50          
756 0           croak("Expected a keyword to introduce a sub or sub-like construction");
757              
758 11           kw = SvPV_nolen(kwsv);
759 11           kwlen = SvCUR(kwsv);
760              
761 11           lex_read_space(0);
762              
763 11           reg = find_permitted(aTHX_ kw, kwlen);
764              
765             /* We permit 'sub' as a NULL set of hooks; anything else should be a registered keyword */
766 11 100         if(!reg && kwlen == 3 && strEQ(kw, "sub"))
    50          
767             break;
768 5 50         if(!reg)
769 0 0         croak("Expected a keyword to introduce a sub or sub-like construction, found " QUOTED_PVNf,
770             QUOTED_PVNfARG(kw, kwlen));
771              
772 5           hooks = (struct XSParseSublikeHooks *)reg->hooks;
773              
774 5 50         if(SvLEN(hdlsv) < (nhooks + 1) * sizeof(struct HooksAndData)) {
775 0 0         SvGROW(hdlsv, SvLEN(hdlsv) * 2);
    0          
776 0           hd = (struct HooksAndData *)SvPVX(hdlsv);
777             }
778 5           hd[nhooks].hooks = hooks;
779 5           hd[nhooks].data = reg->hookdata;
780             nhooks++;
781             }
782              
783             /* See if Sublike::Extended wants to claim this one. If it wanted 'sub' it
784             * has already claimed that above */
785 89 100         if(kwlen != 3 || !strEQ(kw, "sub")) {
    50          
786 81           HV *hints = GvHV(PL_hintgv);
787 81           SV *keysv = sv_2mortal(newSVpvf("Sublike::Extended/extended-%.*s", (int)kwlen, kw));
788 81 50         if(hints && hv_exists_ent(hints, keysv, 0)) {
    100          
789 2 50         if(SvLEN(hdlsv) < (nhooks + 1) * sizeof(struct HooksAndData)) {
790 0 0         SvGROW(hdlsv, SvLEN(hdlsv) * 2);
    0          
791 0           hd = (struct HooksAndData *)SvPVX(hdlsv);
792             }
793             /* This hook has the prefix flag set, but it doesn't matter because
794             * we've finished processing those already
795             */
796 2           hd[nhooks].hooks = &hooks_extended;
797 2           hd[nhooks].data = NULL;
798             nhooks++;
799             }
800             }
801              
802 89           return parse(aTHX_ hd, nhooks, op_ptr);
803             }
804              
805             /* API v3 back-compat */
806              
807 0           static int IMPL_xs_parse_sublike_v3(pTHX_ const void *hooks, void *hookdata, OP **op_ptr)
808             {
809 0           croak("XS::Parse::Sublike ABI v3 is no longer supported; the caller should be rebuilt to use v4");
810             }
811              
812 0           static void IMPL_register_xs_parse_sublike_v3(pTHX_ const char *kw, const void *hooks, void *hookdata)
813             {
814 0           croak("XS::Parse::Sublike ABI v3 is no longer supported; the caller should be rebuilt to use v4");
815             }
816              
817 0           static int IMPL_xs_parse_sublike_any_v3(pTHX_ const void *hooksA, void *hookdataA, OP **op_ptr)
818             {
819 0           croak("XS::Parse::Sublike ABI v3 is no longer supported; the caller should be rebuilt to use v4");
820             }
821              
822             /* API v4 back-compat */
823              
824             struct XSParseSublikeHooks_v4 {
825             U16 flags;
826             U8 require_parts;
827             U8 skip_parts;
828             const char *permit_hintkey;
829             bool (*permit)(pTHX_ void *hookdata);
830             void (*pre_subparse) (pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
831             void (*post_blockstart)(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
832             void (*pre_blockend) (pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
833             void (*post_newcv) (pTHX_ struct XSParseSublikeContext *ctx, void *hookdata);
834             bool (*filter_attr) (pTHX_ struct XSParseSublikeContext *ctx, SV *attr, SV *val, void *hookdata);
835             };
836              
837             #define STRUCT_XSPARSESUBLIKEHOOKS_FROM_v4(hooks_v4) \
838             (struct XSParseSublikeHooks){ \
839             .ver = 4, \
840             .flags = hooks_v4->flags, \
841             .require_parts = hooks_v4->require_parts, \
842             .skip_parts = hooks_v4->skip_parts, \
843             .permit_hintkey = hooks_v4->permit_hintkey, \
844             .permit = hooks_v4->permit, \
845             .pre_subparse = hooks_v4->pre_subparse, \
846             .filter_attr = (hooks_v4->flags & XS_PARSE_SUBLIKE_FLAG_FILTERATTRS) \
847             ? hooks_v4->filter_attr \
848             : NULL, \
849             .post_blockstart = hooks_v4->post_blockstart, \
850             .pre_blockend = hooks_v4->pre_blockend, \
851             .post_newcv = hooks_v4->post_newcv, \
852             }
853              
854 0           static int IMPL_xs_parse_sublike_v4(pTHX_ const struct XSParseSublikeHooks_v4 *hooks_v4, void *hookdata, OP **op_ptr)
855             {
856 0           return IMPL_xs_parse_sublike_v6(aTHX_
857 0 0         &STRUCT_XSPARSESUBLIKEHOOKS_FROM_v4(hooks_v4),
858             hookdata,
859             op_ptr);
860             }
861              
862 0           static void IMPL_register_xs_parse_sublike_v4(pTHX_ const char *kw, const struct XSParseSublikeHooks_v4 *hooks_v4, void *hookdata)
863             {
864             struct XSParseSublikeHooks *hooks;
865 0           Newx(hooks, 1, struct XSParseSublikeHooks);
866 0 0         *hooks = STRUCT_XSPARSESUBLIKEHOOKS_FROM_v4(hooks_v4);
867              
868 0           register_sublike(aTHX_ kw, hooks, hookdata, 4);
869 0           }
870              
871 0           static int IMPL_xs_parse_sublike_any_v4(pTHX_ const struct XSParseSublikeHooks_v4 *hooksA_v4, void *hookdataA, OP **op_ptr)
872             {
873 0           return IMPL_xs_parse_sublike_any_v6(aTHX_
874 0 0         &STRUCT_XSPARSESUBLIKEHOOKS_FROM_v4(hooksA_v4),
875             hookdataA,
876             op_ptr);
877             }
878              
879             MODULE = XS::Parse::Sublike PACKAGE = XS::Parse::Sublike
880              
881             BOOT:
882             /* Legacy lookup mechanism using perl symbol table */
883 28           sv_setiv(get_sv("XS::Parse::Sublike::ABIVERSION", GV_ADDMULTI), 4);
884 28           sv_setuv(get_sv("XS::Parse::Sublike::PARSE", GV_ADDMULTI), PTR2UV(&IMPL_xs_parse_sublike_v3));
885 28           sv_setuv(get_sv("XS::Parse::Sublike::REGISTER", GV_ADDMULTI), PTR2UV(&IMPL_register_xs_parse_sublike_v3));
886 28           sv_setuv(get_sv("XS::Parse::Sublike::PARSEANY", GV_ADDMULTI), PTR2UV(&IMPL_xs_parse_sublike_any_v3));
887              
888             /* Newer mechanism */
889 28           sv_setiv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/ABIVERSION_MIN", 1), 4);
890 28           sv_setiv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/ABIVERSION_MAX", 1), XSPARSESUBLIKE_ABI_VERSION);
891              
892 28           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parse()@4", 1), PTR2UV(&IMPL_xs_parse_sublike_v4));
893 28           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parse()@6", 1), PTR2UV(&IMPL_xs_parse_sublike_v6));
894 28           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/register()@6", 1), PTR2UV(&IMPL_register_xs_parse_sublike_v6));
895 28           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/register()@4", 1), PTR2UV(&IMPL_register_xs_parse_sublike_v4));
896 28           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parseany()@4", 1), PTR2UV(&IMPL_xs_parse_sublike_any_v4));
897 28           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/parseany()@6", 1), PTR2UV(&IMPL_xs_parse_sublike_any_v6));
898 28           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/signature_add_param()@7", 1), PTR2UV(&XPS_signature_add_param));
899 28           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/signature_query()@8", 1), PTR2UV(&XPS_signature_query));
900              
901 28           sv_setuv(*hv_fetchs(PL_modglobal, "XS::Parse::Sublike/register_sigattr()@5", 1), PTR2UV(&IMPL_register_xps_signature_attribute));
902             #ifdef HAVE_FEATURE_CLASS
903 28           register_sublike(aTHX_ "method", &hooks_core_method, NULL, 4);
904             #endif
905              
906 28           wrap_keyword_plugin(&my_keyword_plugin, &next_keyword_plugin);
907              
908 28           register_sublike(aTHX_ "extended", &hooks_extended, NULL, 4);
909 28           register_sublike(aTHX_ "sub", &hooks_extended_sub, NULL, 4);
910              
911 28           boot_parse_subsignature_ex();