File Coverage

lib/Syntax/Keyword/Match.xs
Criterion Covered Total %
statement 143 152 94.0
branch 61 80 76.2
condition n/a
subroutine n/a
pod n/a
total 204 232 87.9


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             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             if(
47             #ifdef HAVE_BOOL_SvIV_please_nomg
48             SvIV_please_nomg(right) && SvIV_please_nomg(left)
49             #else
50             SvIOK(left) && SvIOK(right)
51             #endif
52             ) {
53             /* Compare as integers */
54             switch((SvUOK(left) ? 1 : 0) | (SvUOK(right) ? 2 : 0)) {
55             case 0: /* IV == IV */
56             return SvIVX(left) == SvIVX(right);
57              
58             case 1: /* UV == IV */
59             {
60             const IV riv = SvUVX(right);
61             if(riv < 0)
62             return 0;
63             return (SvUVX(left) == riv);
64             }
65              
66             case 2: /* IV == UV */
67             {
68             const IV liv = SvUVX(left);
69             if(liv < 0)
70             return 0;
71             return (liv == SvUVX(right));
72             }
73              
74             case 3: /* UV == UV */
75             return SvUVX(left) == SvUVX(right);
76             }
77             }
78             else {
79             /* Compare NVs */
80             NV const rnv = SvNV_nomg(right);
81             NV const lnv = SvNV_nomg(left);
82              
83             return lnv == rnv;
84             }
85             }
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 105           OP *op = newOP(type, flags);
92 105           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           if(has_magic &&
109 0           (ret = amagic_call(TARG, val, eq_amg, 0))) {
110 0 0         if(SvTRUE(ret))
111 0           return dispatch[idx];
112             }
113             #ifdef HAVE_SV_NUMEQ_FLAGS
114 8 100         else if(sv_numeq_flags(TARG, val, SV_SKIP_OVERLOAD))
115             #else
116             /* stolen from core's pp_hot.c / pp_eq() */
117             else if((SvIOK_notUV(TARG) && SvIOK_notUV(val)) ?
118             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 9000060           if(has_magic &&
139 3           (ret = amagic_call(TARG, val, seq_amg, 0))) {
140 3 100         if(SvTRUE(ret))
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 1           static OP *pp_dispatch_isa(pTHX)
152             {
153 1           dDISPATCH;
154 1           dTARGET;
155             int idx;
156              
157 1 50         for(idx = 0; idx < n_cases; idx++)
158 1 50         if(sv_isa_sv(TARG, values[idx]))
159 1           return dispatch[idx];
160              
161 0           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 68           static OP *build_cases_nondispatch(pTHX_ XSParseInfixInfo *matchinfo, PADOFFSET padix, struct MatchCaseBlock *block, OP *elseop)
176             {
177 68           size_t n_cases = block->n_cases;
178              
179             assert(n_cases);
180              
181             OP *testop = NULL;
182              
183             U32 i;
184 139 100         for(i = 0; i < n_cases; i++) {
185 71           bool is_if = block->cases[i].is_if;
186 71           OP *caseop = block->cases[i].expr;
187              
188             OP *thistestop;
189              
190 71 100         if(is_if)
191             thistestop = caseop;
192 69           else switch(matchinfo->opcode) {
193             #ifdef HAVE_OP_ISA
194             case OP_ISA:
195             #endif
196             case OP_SEQ:
197             case OP_EQ:
198 63           thistestop = newBINOP(matchinfo->opcode, 0,
199             newPADSVOP(OP_PADSV, 0, padix), caseop);
200 63           break;
201              
202 6           case OP_MATCH:
203 6 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 6           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 6           break;
213             case OP_CUSTOM:
214 0           thistestop = xs_parse_infix_new_op(matchinfo, 0,
215             newPADSVOP(OP_PADSV, 0, padix), caseop);
216 0           break;
217             }
218              
219 71 100         if(testop)
220 3           testop = newLOGOP(OP_OR, 0, testop, thistestop);
221             else
222             testop = thistestop;
223             }
224              
225             assert(testop);
226              
227 68 100         if(elseop)
228 44           return newCONDOP(0, testop, block->op, elseop);
229             else
230 24           return newLOGOP(OP_AND, 0, testop, block->op);
231             }
232              
233 9           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 9           ENTER;
241              
242 9           SV *valuessv = newSV(n_cases * sizeof(SV *));
243 9           SV *dispatchsv = newSV(n_cases * sizeof(OP *));
244 9           SAVEFREESV(valuessv);
245 9           SAVEFREESV(dispatchsv);
246              
247 9           SV **values = (SV **)SvPVX(valuessv);
248 9           OP **dispatch = (OP **)SvPVX(dispatchsv);
249              
250 9           DISPATCHOP *o = alloc_DISPATCHOP();
251 9           o->op_type = OP_CUSTOM;
252 9           o->op_targ = padix;
253              
254 9           switch(matchtype) {
255             #ifdef HAVE_OP_ISA
256 1           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 9           o->op_first = NULL;
263              
264 9           o->n_cases = n_cases;
265 9           o->values = values;
266 9           o->dispatch = dispatch;
267              
268 9           OP *retop = newUNOP(OP_NULL, 0, (OP *)o);
269              
270             U32 idx = 0;
271             blocki = 0;
272 41 100         while(n_cases) {
273 32           struct MatchCaseBlock *block = &blocks[blocki];
274              
275 32           U32 this_n_cases = block->n_cases;
276              
277 32           OP *blockop = block->op;
278 32 50         OP *blockstart = LINKLIST(blockop);
279 32           blockop->op_next = retop;
280              
281 32           n_cases -= this_n_cases;
282              
283 64 100         for(U32 casei = 0; casei < this_n_cases; casei++) {
284 32           bool is_if = block->cases[casei].is_if;
285 32           OP *caseop = block->cases[casei].expr;
286              
287 32 50         if(is_if)
288 0           croak("TODO: case if dispatch");
289              
290             assert(caseop->op_type == OP_CONST);
291 32 50         values[idx] = SvREFCNT_inc(cSVOPx(caseop)->op_sv);
292 32           op_free(caseop);
293              
294 32           dispatch[idx] = blockstart;
295              
296 32           idx++;
297             }
298              
299             /* TODO: link chain of siblings */
300              
301 32           blocki++;
302             }
303              
304 9 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 2           o->op_other = retop;
311             }
312              
313             /* Steal the SV buffers */
314 9           SvPVX(valuessv) = NULL; SvLEN(valuessv) = 0;
315 9           SvPVX(dispatchsv) = NULL; SvLEN(dispatchsv) = 0;
316              
317 9           LEAVE;
318              
319 9           return retop;
320             }
321              
322 42           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 42           OP *topic = args[argi++]->op;
336 42           XSParseInfixInfo *matchinfo = args[argi++]->infix;
337 42           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 42           Newx(blocks, n_blocks, struct MatchCaseBlock);
343 42           SAVEFREEPV(blocks);
344              
345             int blocki;
346 142 100         for(blocki = 0; blocki < n_blocks; blocki++) {
347 100           struct MatchCaseBlock *block = &blocks[blocki];
348              
349 100           int n_cases = args[argi++]->i;
350              
351 100           block->n_cases = n_cases;
352              
353 100           Newx(block->cases, n_cases, struct MatchCase);
354 100           SAVEFREEPV(block->cases);
355              
356 203 100         for(int i = 0; i < n_cases; i++) {
357 103           block->cases[i].is_if = args[argi++]->i;
358 103           block->cases[i].expr = args[argi++]->op;
359             }
360              
361 100           block->op = args[argi++]->op;
362             }
363              
364 42           bool has_default = args[argi]->i;
365             OP *o = NULL;
366 42 100         if(has_default)
367 16           o = args[argi + 1]->op;
368              
369 42           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 42           PADOFFSET padix = pad_add_name_pvs("$(Syntax::Keyword::Match/topic)", 0, NULL, NULL);
376 42           intro_my();
377              
378 42           OP *startop = newBINOP(OP_SASSIGN, 0,
379             topic, newPADSVOP(OP_PADSV, OPf_MOD|OPf_REF|(OPpLVAL_INTRO << 8), padix));
380 42           PL_hints |= HINT_BLOCK_SCOPE; /* ensures that op_scope() creates a full ENTER+LEAVE pair */
381              
382             int n_dispatch = 0;
383              
384 42           blocki = n_blocks-1;
385              
386             /* Roll up the blocks backwards, from end to beginning */
387 142 100         while(blocki >= 0) {
388 100           struct MatchCaseBlock *block = &blocks[blocki--];
389              
390 100           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 203 100         for(U32 casei = 0; casei < n_cases; casei++) {
399 103 100         if(block->cases[casei].is_if) {
400             this_block_dispatch = false;
401 2           continue;
402             }
403              
404             /* TODO: forbid the , operator in the case label */
405 101           OP *caseop = block->cases[casei].expr;
406              
407 101           switch(matchinfo->opcode) {
408             #ifdef HAVE_OP_ISA
409 9           case OP_ISA:
410             /* bareword class names are permitted */
411 9 50         if(caseop->op_type == OP_CONST && caseop->op_private & OPpCONST_BARE)
    50          
412 9           caseop->op_private &= ~(OPpCONST_BARE|OPpCONST_STRICT);
413             /* FALLTHROUGH */
414             #endif
415             case OP_SEQ:
416             case OP_EQ:
417 95 100         if(use_dispatch && caseop->op_type == OP_CONST)
    100          
418 32           continue;
419              
420             /* FALLTHROUGH */
421             case OP_MATCH:
422             case OP_CUSTOM:
423             this_block_dispatch = false;
424             break;
425             }
426             }
427              
428 100 100         if(this_block_dispatch) {
429 32           n_dispatch += n_cases;
430 32           continue;
431             }
432              
433 68 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 68           o = build_cases_nondispatch(aTHX_ matchinfo, padix, block, o);
440             }
441              
442 42 100         if(n_dispatch)
443 8           o = build_cases_dispatch(aTHX_ matchinfo->opcode, padix,
444             n_dispatch, blocks, o);
445              
446 42           *out = newLISTOP(OP_LINESEQ, 0, startop, o);
447              
448 42           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             static bool S_sv_isa_sv(pTHX_ SV *sv, SV *namesv)
486             {
487             if(!SvROK(sv) || !SvOBJECT(SvRV(sv)))
488             return FALSE;
489              
490             /* Also can't use GV_NOUNIVERSAL here because that also only turned up in 5.32 */
491             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             if(isagv && !strEQ(HvNAME(GvSTASH(isagv)), "UNIVERSAL")) {
496             dSP;
497             CV *isacv = isGV(isagv) ? GvCV(isagv) : MUTABLE_CV(isagv);
498              
499             PUTBACK;
500              
501             ENTER;
502             SAVETMPS;
503              
504             EXTEND(SP, 2);
505             PUSHMARK(SP);
506             PUSHs(sv);
507             PUSHs(namesv);
508             PUTBACK;
509              
510             call_sv((SV *)isacv, G_SCALAR);
511              
512             SPAGAIN;
513             SV *retsv = POPs;
514             bool ret = SvTRUE(retsv);
515             PUTBACK;
516              
517             FREETMPS;
518             LEAVE;
519              
520             return ret;
521             }
522              
523             return sv_derived_from_sv(sv, namesv, 0);
524             }
525              
526             static OP *pp_isa(pTHX)
527             {
528             dSP;
529             SV *rhs = POPs;
530             SV *lhs = TOPs;
531              
532             SETs(boolSV(S_sv_isa_sv(aTHX_ lhs, rhs)));
533             RETURN;
534             }
535              
536             static OP *newop_isa(pTHX_ U32 flags, OP *lhs, OP *rhs, SV **parsedata, void *hookdata)
537             {
538             /* Avoid strictness failure on bareword RHS */
539             if(rhs->op_type == OP_CONST && rhs->op_private & OPpCONST_BARE)
540             rhs->op_private &= ~(OPpCONST_BARE|OPpCONST_STRICT);
541              
542             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 19           boot_xs_parse_keyword(0.36);
557 19           boot_xs_parse_infix(0);
558              
559 19           register_xs_parse_keyword("match", &hooks_match, NULL);
560             #ifndef HAVE_OP_ISA
561             register_xs_parse_infix("isa", &hooks_isa, NULL);
562             #endif