File Coverage

lib/Syntax/Keyword/Match.xs
Criterion Covered Total %
statement 175 185 94.5
branch 115 240 47.9
condition n/a
subroutine n/a
pod n/a
total 290 425 68.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, 2021-2023 -- leonerd@leonerd.org.uk
5             */
6             #define PERL_NO_GET_CONTEXT
7              
8             #include "EXTERN.h"
9             #include "perl.h"
10             #include "XSUB.h"
11              
12             #include "XSParseKeyword.h"
13             #include "XSParseInfix.h"
14              
15             #define HAVE_PERL_VERSION(R, V, S) \
16             (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
17              
18             #include "hax/perl-backcompat.c.inc"
19              
20             #if HAVE_PERL_VERSION(5,32,0)
21             # define HAVE_OP_ISA
22             #endif
23              
24             #if HAVE_PERL_VERSION(5,18,0)
25             # define HAVE_BOOL_SvIV_please_nomg
26             #endif
27              
28             #if HAVE_PERL_VERSION(5,35,9)
29             # define HAVE_SV_NUMEQ_FLAGS
30             #endif
31              
32             #include "dispatchop.h"
33              
34             #ifndef HAVE_SV_NUMEQ_FLAGS
35             /* We'd like to call Perl_do_ncmp, except that isn't an exported API function
36             * Here's a near-copy of it for num-equality testing purposes */
37             #define do_numeq(left, right) S_do_numeq(aTHX_ left, right)
38 5           static bool S_do_numeq(pTHX_ SV *left, SV *right)
39             {
40             #ifndef HAVE_BOOL_SvIV_please_nomg
41             /* Before perl 5.18, SvIV_please_nomg() was void-returning */
42             SvIV_please_nomg(left);
43             SvIV_please_nomg(right);
44             #endif
45              
46 10 100         if(
47             #ifdef HAVE_BOOL_SvIV_please_nomg
48 9 50         SvIV_please_nomg(right) && SvIV_please_nomg(left)
    50          
    50          
    50          
    50          
49             #else
50             SvIOK(left) && SvIOK(right)
51             #endif
52             ) {
53             /* Compare as integers */
54 3 100         switch((SvUOK(left) ? 1 : 0) | (SvUOK(right) ? 2 : 0)) {
55             case 0: /* IV == IV */
56 0           return SvIVX(left) == SvIVX(right);
57              
58             case 1: /* UV == IV */
59             {
60 1           const IV riv = SvUVX(right);
61 1 50         if(riv < 0)
62             return 0;
63 1           return (SvUVX(left) == riv);
64             }
65              
66             case 2: /* IV == UV */
67             {
68 0           const IV liv = SvUVX(left);
69 0 0         if(liv < 0)
70             return 0;
71 0           return (liv == SvUVX(right));
72             }
73              
74             case 3: /* UV == UV */
75 2           return SvUVX(left) == SvUVX(right);
76             }
77             }
78             else {
79             /* Compare NVs */
80 2 50         NV const rnv = SvNV_nomg(right);
81 2 50         NV const lnv = SvNV_nomg(left);
82              
83 2           return lnv == rnv;
84             }
85 0           }
86             #endif
87              
88             #define newPADSVOP(type, flags, padix) MY_newPADSVOP(aTHX_ type, flags, padix)
89             static OP *MY_newPADSVOP(pTHX_ I32 type, I32 flags, PADOFFSET padix)
90             {
91 86           OP *op = newOP(type, flags);
92 86           op->op_targ = padix;
93             return op;
94             }
95              
96 4           static OP *pp_dispatch_numeq(pTHX)
97             {
98 4           dDISPATCH;
99 4           dTARGET;
100             int idx;
101              
102 4 50         bool has_magic = SvAMAGIC(TARG);
    0          
    0          
103              
104 8 50         for(idx = 0; idx < n_cases; idx++) {
105 8           SV *val = values[idx];
106              
107             SV *ret;
108 8 50         if(has_magic &&
    0          
109             (ret = amagic_call(TARG, val, eq_amg, 0))) {
110 0 0         if(SvTRUE(ret))
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
111 0           return dispatch[idx];
112             }
113             #ifdef HAVE_SV_NUMEQ_FLAGS
114             else if(sv_numeq_flags(TARG, val, SV_SKIP_OVERLOAD))
115             #else
116             /* stolen from core's pp_hot.c / pp_eq() */
117 8 100         else if((SvIOK_notUV(TARG) && SvIOK_notUV(val)) ?
    50          
    100          
118 3           SvIVX(TARG) == SvIVX(val) : (do_numeq(TARG, val)))
119             #endif
120 4           return dispatch[idx];
121             }
122              
123 0           return cDISPATCHOP->op_other;
124             }
125              
126 1000012           static OP *pp_dispatch_streq(pTHX)
127             {
128 1000012           dDISPATCH;
129 1000012           dTARGET;
130             int idx;
131              
132 1000012 100         bool has_magic = SvAMAGIC(TARG);
    50          
    50          
133              
134 10000059 100         for(idx = 0; idx < n_cases; idx++) {
135 9000057           SV *val = values[idx];
136              
137             SV *ret;
138 9000057 100         if(has_magic &&
    50          
139             (ret = amagic_call(TARG, val, seq_amg, 0))) {
140 3 50         if(SvTRUE(ret))
    50          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    50          
    50          
    100          
    50          
    0          
    100          
141 1           return dispatch[idx];
142             }
143 9000054 100         else if(sv_eq(TARG, val))
144 9           return dispatch[idx];
145             }
146              
147 1000002           return cDISPATCHOP->op_other;
148             }
149              
150             #ifdef HAVE_OP_ISA
151             static OP *pp_dispatch_isa(pTHX)
152             {
153             dDISPATCH;
154             dTARGET;
155             int idx;
156              
157             for(idx = 0; idx < n_cases; idx++)
158             if(sv_isa_sv(TARG, values[idx]))
159             return dispatch[idx];
160              
161             return cDISPATCHOP->op_other;
162             }
163             #endif
164              
165             struct MatchCaseBlock {
166             int n_cases;
167             struct MatchCase {
168             bool is_if;
169             OP *expr;
170             } *cases;
171              
172             OP *op;
173             };
174              
175 55           static OP *build_cases_nondispatch(pTHX_ XSParseInfixInfo *matchinfo, PADOFFSET padix, struct MatchCaseBlock *block, OP *elseop)
176             {
177 55           size_t n_cases = block->n_cases;
178              
179             assert(n_cases);
180              
181             OP *testop = NULL;
182              
183             U32 i;
184 112 100         for(i = 0; i < n_cases; i++) {
185 57           bool is_if = block->cases[i].is_if;
186 57           OP *caseop = block->cases[i].expr;
187              
188             OP *thistestop;
189              
190 57 100         if(is_if)
191             thistestop = caseop;
192 56           else switch(matchinfo->opcode) {
193             #ifdef HAVE_OP_ISA
194             case OP_ISA:
195             #endif
196             case OP_SEQ:
197             case OP_EQ:
198 45           thistestop = newBINOP(matchinfo->opcode, 0,
199             newPADSVOP(OP_PADSV, 0, padix), caseop);
200 45           break;
201              
202             case OP_MATCH:
203 4 50         if(caseop->op_type != OP_MATCH || cPMOPx(caseop)->op_first)
    50          
204 0           croak("Expected a regexp match");
205             thistestop = caseop;
206             #if HAVE_PERL_VERSION(5,22,0)
207 4           thistestop->op_targ = padix;
208             #else
209             cPMOPx(thistestop)->op_first = newPADSVOP(OP_PADSV, 0, padix);
210             thistestop->op_flags |= OPf_KIDS|OPf_STACKED;
211             #endif
212 4           break;
213             case OP_CUSTOM:
214             thistestop = xs_parse_infix_new_op(matchinfo, 0,
215             newPADSVOP(OP_PADSV, 0, padix), caseop);
216 7           break;
217             }
218              
219 57 100         if(testop)
220 2           testop = newLOGOP(OP_OR, 0, testop, thistestop);
221             else
222             testop = thistestop;
223             }
224              
225             assert(testop);
226              
227 55 100         if(elseop)
228 36           return newCONDOP(0, testop, block->op, elseop);
229             else
230 19           return newLOGOP(OP_AND, 0, testop, block->op);
231             }
232              
233 8           static OP *build_cases_dispatch(pTHX_ OPCODE matchtype, PADOFFSET padix, size_t n_cases, struct MatchCaseBlock *blocks, OP *elseop)
234             {
235             assert(n_cases);
236             assert(matchtype != OP_MATCH);
237              
238             U32 blocki;
239              
240 8           ENTER;
241              
242 8           SV *valuessv = newSV(n_cases * sizeof(SV *));
243 8           SV *dispatchsv = newSV(n_cases * sizeof(OP *));
244 8           SAVEFREESV(valuessv);
245 8           SAVEFREESV(dispatchsv);
246              
247 8           SV **values = (SV **)SvPVX(valuessv);
248 8           OP **dispatch = (OP **)SvPVX(dispatchsv);
249              
250             DISPATCHOP *o = alloc_DISPATCHOP();
251 8           o->op_type = OP_CUSTOM;
252 8           o->op_targ = padix;
253              
254 8           switch(matchtype) {
255             #ifdef HAVE_OP_ISA
256             case OP_ISA: o->op_ppaddr = &pp_dispatch_isa; break;
257             #endif
258 3           case OP_SEQ: o->op_ppaddr = &pp_dispatch_streq; break;
259 5           case OP_EQ: o->op_ppaddr = &pp_dispatch_numeq; break;
260             }
261              
262 8           o->op_first = NULL;
263              
264 8           o->n_cases = n_cases;
265 8           o->values = values;
266 8           o->dispatch = dispatch;
267              
268 8           OP *retop = newUNOP(OP_NULL, 0, (OP *)o);
269              
270             U32 idx = 0;
271             blocki = 0;
272 38 100         while(n_cases) {
273 30           struct MatchCaseBlock *block = &blocks[blocki];
274              
275 30           U32 this_n_cases = block->n_cases;
276              
277 30           OP *blockop = block->op;
278 30 50         OP *blockstart = LINKLIST(blockop);
279 30           blockop->op_next = retop;
280              
281 30           n_cases -= this_n_cases;
282              
283 60 100         for(U32 casei = 0; casei < this_n_cases; casei++) {
284 30           bool is_if = block->cases[casei].is_if;
285 30           OP *caseop = block->cases[casei].expr;
286              
287 30 50         if(is_if)
288 0           croak("TODO: case if dispatch");
289              
290             assert(caseop->op_type == OP_CONST);
291 60           values[idx] = SvREFCNT_inc(cSVOPx(caseop)->op_sv);
292 30           op_free(caseop);
293              
294 30           dispatch[idx] = blockstart;
295              
296 30           idx++;
297             }
298              
299             /* TODO: link chain of siblings */
300              
301 30           blocki++;
302             }
303              
304 8 100         if(elseop) {
305 7 100         o->op_other = LINKLIST(elseop);
306 7           elseop->op_next = retop;
307             /* TODO: sibling linkage */
308             }
309             else {
310 1           o->op_other = retop;
311             }
312              
313             /* Steal the SV buffers */
314 8           SvPVX(valuessv) = NULL; SvLEN(valuessv) = 0;
315 8           SvPVX(dispatchsv) = NULL; SvLEN(dispatchsv) = 0;
316              
317 8           LEAVE;
318              
319 8           return retop;
320             }
321              
322 34           static int build_match(pTHX_ OP **out, XSParseKeywordPiece *args[], size_t nargs, void *hookdata)
323             {
324             /* args:
325             * [0]: topic expression
326             * [1]: match type
327             * [2]: count of blocks
328             * [3]: count of case exprs = $N
329             * [4,5...]: $N * [if, case expr]s
330             * []: block
331             * [LAST]: default case if present
332             */
333             U32 argi = 0;
334              
335 34           OP *topic = args[argi++]->op;
336 34           XSParseInfixInfo *matchinfo = args[argi++]->infix;
337 34           int n_blocks = args[argi++]->i;
338              
339             /* Extract the raw args into a better data structure we can work with */
340             struct MatchCaseBlock *blocks;
341              
342 34 50         Newx(blocks, n_blocks, struct MatchCaseBlock);
343 34           SAVEFREEPV(blocks);
344              
345             int blocki;
346 119 100         for(blocki = 0; blocki < n_blocks; blocki++) {
347 85           struct MatchCaseBlock *block = &blocks[blocki];
348              
349 85           int n_cases = args[argi++]->i;
350              
351 85           block->n_cases = n_cases;
352              
353 85 50         Newx(block->cases, n_cases, struct MatchCase);
354 85           SAVEFREEPV(block->cases);
355              
356 172 100         for(int i = 0; i < n_cases; i++) {
357 87           block->cases[i].is_if = args[argi++]->i;
358 87           block->cases[i].expr = args[argi++]->op;
359             }
360              
361 85           block->op = args[argi++]->op;
362             }
363              
364 34           bool has_default = args[argi]->i;
365             OP *o = NULL;
366 34 100         if(has_default)
367 14           o = args[argi + 1]->op;
368              
369 34           bool use_dispatch = hv_fetchs(GvHV(PL_hintgv), "Syntax::Keyword::Match/experimental(dispatch)", 0);
370              
371             /* The name is totally meaningless and never used, but if we don't set a
372             * name and instead use pad_alloc(SVs_PADTMP) then the peephole optimiser
373             * for aassign will crash
374             */
375 34           PADOFFSET padix = pad_add_name_pvs("$(Syntax::Keyword::Match/topic)", 0, NULL, NULL);
376 34           intro_my();
377              
378 34           OP *startop = newBINOP(OP_SASSIGN, 0,
379             topic, newPADSVOP(OP_PADSV, OPf_MOD|OPf_REF|(OPpLVAL_INTRO << 8), padix));
380 34           PL_hints |= HINT_BLOCK_SCOPE; /* ensures that op_scope() creates a full ENTER+LEAVE pair */
381              
382             int n_dispatch = 0;
383              
384 34           blocki = n_blocks-1;
385              
386             /* Roll up the blocks backwards, from end to beginning */
387 119 100         while(blocki >= 0) {
388 85           struct MatchCaseBlock *block = &blocks[blocki--];
389              
390 85           int n_cases = block->n_cases;
391              
392             /* perl expects a strict optree, where each block appears exactly once.
393             * We can't reĆ¼se the block between dispatch and non-dispatch ops, so
394             * we'll have to decide which strategy to use here
395             */
396             bool this_block_dispatch = use_dispatch;
397              
398 172 100         for(U32 casei = 0; casei < n_cases; casei++) {
399 87 100         if(block->cases[casei].is_if) {
400             this_block_dispatch = false;
401 1           continue;
402             }
403              
404             /* TODO: forbid the , operator in the case label */
405 86           OP *caseop = block->cases[casei].expr;
406              
407 86           switch(matchinfo->opcode) {
408             #ifdef HAVE_OP_ISA
409             case OP_ISA:
410             /* bareword class names are permitted */
411             if(caseop->op_type == OP_CONST && caseop->op_private & OPpCONST_BARE)
412             caseop->op_private &= ~(OPpCONST_BARE|OPpCONST_STRICT);
413             /* FALLTHROUGH */
414             #endif
415             case OP_SEQ:
416             case OP_EQ:
417 75 100         if(use_dispatch && caseop->op_type == OP_CONST)
    100          
418 30           continue;
419              
420             /* FALLTHROUGH */
421             case OP_MATCH:
422             case OP_CUSTOM:
423             this_block_dispatch = false;
424 56           break;
425             }
426             }
427              
428 85 100         if(this_block_dispatch) {
429 30           n_dispatch += n_cases;
430 30           continue;
431             }
432              
433 55 100         if(n_dispatch) {
434 1           o = build_cases_dispatch(aTHX_ matchinfo->opcode, padix,
435             n_dispatch, block + 1, o);
436             n_dispatch = 0;
437             }
438              
439 85           o = build_cases_nondispatch(aTHX_ matchinfo, padix, block, o);
440             }
441              
442 34 100         if(n_dispatch)
443 7           o = build_cases_dispatch(aTHX_ matchinfo->opcode, padix,
444             n_dispatch, blocks, o);
445              
446 34           *out = newLISTOP(OP_LINESEQ, 0, startop, o);
447              
448 34           return KEYWORD_PLUGIN_STMT;
449             }
450              
451             static const struct XSParseKeywordHooks hooks_match = {
452             .flags = XPK_FLAG_BLOCKSCOPE,
453             .permit_hintkey = "Syntax::Keyword::Match/match",
454              
455             .pieces = (const struct XSParseKeywordPieceType []){
456             XPK_PARENS( /* ( EXPR : OP ) */
457             XPK_TERMEXPR_SCALARCTX,
458             XPK_COLON,
459             XPK_INFIX_MATCH_NOSMART
460             ),
461             XPK_INTRO_MY,
462             XPK_BRACES( /* { blocks... } */
463             XPK_REPEATED( /* case (EXPR) {BLOCK} */
464             XPK_COMMALIST(
465             XPK_KEYWORD("case"),
466             XPK_OPTIONAL( XPK_KEYWORD("if") ),
467             XPK_PARENS( XPK_TERMEXPR_SCALARCTX )
468             ),
469             XPK_BLOCK
470             ),
471             XPK_OPTIONAL( /* default { ... } */
472             XPK_KEYWORD("default"),
473             XPK_BLOCK
474             )
475             ),
476             0,
477             },
478             .build = &build_match,
479             };
480              
481             #ifndef HAVE_OP_ISA
482             #include "hax/newOP_CUSTOM.c.inc"
483              
484             /* Can't use sv_isa_sv() because that was only added in 5.32 */
485 6           static bool S_sv_isa_sv(pTHX_ SV *sv, SV *namesv)
486             {
487 6 50         if(!SvROK(sv) || !SvOBJECT(SvRV(sv)))
    50          
488             return FALSE;
489              
490             /* Also can't use GV_NOUNIVERSAL here because that also only turned up in 5.32 */
491 6           GV *isagv = gv_fetchmeth_pvn(SvSTASH(SvRV(sv)), "isa", 3, -1, 0);
492             /* This probably finds UNIVERSAL::isa; if so we can avoid it and just do it
493             * directly ourselves by calling sv_derived_from_sv()
494             */
495 6 50         if(isagv && !strEQ(HvNAME(GvSTASH(isagv)), "UNIVERSAL")) {
    50          
    50          
    50          
    0          
    50          
    50          
    100          
496 2           dSP;
497 2 50         CV *isacv = isGV(isagv) ? GvCV(isagv) : MUTABLE_CV(isagv);
498              
499             PUTBACK;
500              
501 2           ENTER;
502 2           SAVETMPS;
503              
504 2 50         EXTEND(SP, 2);
505 2 50         PUSHMARK(SP);
506 2           PUSHs(sv);
507 2           PUSHs(namesv);
508 2           PUTBACK;
509              
510 2           call_sv((SV *)isacv, G_SCALAR);
511              
512 2           SPAGAIN;
513 2           SV *retsv = POPs;
514 2 50         bool ret = SvTRUE(retsv);
    50          
    0          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    100          
    50          
    0          
    100          
    0          
515 2           PUTBACK;
516              
517 2 50         FREETMPS;
518 2           LEAVE;
519              
520 2           return ret;
521             }
522              
523 4           return sv_derived_from_sv(sv, namesv, 0);
524             }
525              
526 6           static OP *pp_isa(pTHX)
527             {
528 6           dSP;
529 6           SV *rhs = POPs;
530 6           SV *lhs = TOPs;
531              
532 6 100         SETs(boolSV(S_sv_isa_sv(aTHX_ lhs, rhs)));
533 6           RETURN;
534             }
535              
536 7           static OP *newop_isa(pTHX_ U32 flags, OP *lhs, OP *rhs, SV **parsedata, void *hookdata)
537             {
538             /* Avoid strictness failure on bareword RHS */
539 7 50         if(rhs->op_type == OP_CONST && rhs->op_private & OPpCONST_BARE)
    50          
540 7           rhs->op_private &= ~(OPpCONST_BARE|OPpCONST_STRICT);
541              
542 7           return newBINOP_CUSTOM(&pp_isa, flags, lhs, rhs);
543             }
544              
545             static const struct XSParseInfixHooks hooks_isa = {
546             .flags = 0,
547             .cls = XPI_CLS_ISA,
548              
549             .new_op = &newop_isa,
550             };
551             #endif
552              
553             MODULE = Syntax::Keyword::Match PACKAGE = Syntax::Keyword::Match
554              
555             BOOT:
556 17           boot_xs_parse_keyword(0.36);
557 17           boot_xs_parse_infix(0);
558              
559             register_xs_parse_keyword("match", &hooks_match, NULL);
560             #ifndef HAVE_OP_ISA
561             register_xs_parse_infix("isa", &hooks_isa, NULL);
562             #endif