File Coverage

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