File Coverage

lib/List/Keywords.xs
Criterion Covered Total %
statement 185 187 98.9
branch 77 114 67.5
condition n/a
subroutine n/a
pod n/a
total 262 301 87.0


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 -- leonerd@leonerd.org.uk
5             */
6             #define PERL_NO_GET_CONTEXT
7             /* needed on latest perl to get optimize_optree/finalize_optree */
8             #define PERL_USE_VOLATILE_API
9              
10             #include "EXTERN.h"
11             #include "perl.h"
12             #include "XSUB.h"
13              
14             #include "XSParseKeyword.h"
15              
16             #include "perl-backcompat.c.inc"
17             #include "op_sibling_splice.c.inc"
18              
19             #ifndef optimize_optree
20             # if HAVE_PERL_VERSION(5,28,0)
21             # define optimize_optree(op) Perl_optimize_optree(aTHX_ op)
22             # else
23             # define optimize_optree(op)
24             # endif
25             #endif
26              
27             #ifndef finalize_optree
28             # if HAVE_PERL_VERSION(5,16,0)
29             # define finalize_optree(op) Perl_finalize_optree(aTHX_ op)
30             # else
31             # define finalize_optree(op)
32             # endif
33             #endif
34              
35             /* We can't newLOGOP because that will force scalar context */
36             #define allocLOGOP_CUSTOM(func, flags, first, other) MY_allocLOGOP_CUSTOM(aTHX_ func, flags, first, other)
37             static LOGOP *MY_allocLOGOP_CUSTOM(pTHX_ OP *(*func)(pTHX), U32 flags, OP *first, OP *other)
38             {
39             LOGOP *logop;
40 51           NewOp(1101, logop, 1, LOGOP);
41              
42 51           logop->op_type = OP_CUSTOM;
43 51           logop->op_ppaddr = func;
44 51           logop->op_flags = OPf_KIDS | (U8)(flags);
45 51           logop->op_first = first;
46 51           logop->op_other = other;
47              
48             return logop;
49             }
50              
51 51           static OP *build_blocklist(pTHX_ PADOFFSET varix, OP *block, OP *list,
52             OP *(*pp_start)(pTHX), OP *(*pp_while)(pTHX), U8 op_private)
53             {
54             /* Follow the same optree shape as grep:
55             * LOGOP whileop
56             * LISTOP startop
57             * NULOP pushmark
58             * UNOP null
59             * {block scope goes here}
60             * ... {list values go here}
61             *
62             * the null op protects the block body from being executed initially,
63             * allowing it to be deferred
64             * whileop's ->op_other points at the start of the block
65             */
66              
67             /* Link block in execution order and remember its start */
68 51 50         OP *blockstart = LINKLIST(block);
69              
70             /* Hide the block inside an OP_NULL with no execution */
71 51           block = newUNOP(OP_NULL, 0, block);
72 51           block->op_next = block;
73              
74             /* Make startop op as the list with (shielded) block prepended */
75             OP *startop = list;
76 51 50         if(startop->op_type != OP_LIST)
77 51           startop = newLISTOP(OP_LIST, 0, startop, NULL);
78 51           op_sibling_splice(startop, cLISTOPx(startop)->op_first, 0, block);
79 51           startop->op_type = OP_CUSTOM;
80 51           startop->op_ppaddr = pp_start;
81 51           startop->op_targ = varix;
82              
83             LOGOP *whileop = allocLOGOP_CUSTOM(pp_while, 0, startop, blockstart);
84 51           whileop->op_private = startop->op_private = op_private;
85 51           whileop->op_targ = varix;
86              
87 51           OpLASTSIB_set(startop, (OP *)whileop);
88              
89             /* Temporarily set the whileop's op_next to NULL so as not to confuse
90             * a custom RPEEP that might be set. We'll store the real start value in
91             * there afterwards. See also
92             * https://rt.cpan.org/Ticket/Display.html?id=142471
93             */
94 51 50         OP *whilestart = LINKLIST(startop);
95 51           whileop->op_next = NULL;
96 51           startop->op_next = (OP *)whileop;
97 51           cUNOPx(block)->op_first->op_next = (OP *)whileop;
98              
99             /* Since the body of the block is now hidden from the peephole optimizer
100             * we'll have to run that manually now */
101             optimize_optree(block);
102 51           PL_rpeepp(aTHX_ blockstart);
103 51           finalize_optree(block);
104              
105 51           whileop->op_next = whilestart;
106 51           return (OP *)whileop;
107             }
108              
109             /* The same ppfuncs that implement `first` can also do `any` and `all` with
110             * minor changes of behaviour
111             */
112             enum {
113             FIRST_EMPTY_NO = (1<<0), /* \ */
114             FIRST_EMPTY_YES = (1<<1), /* - if neither, returns undef */
115             FIRST_RET_NO = (1<<2), /* \ */
116             FIRST_RET_YES = (1<<3), /* - if neither, returns $_ itself */
117             FIRST_STOP_ON_FALSE = (1<<4),
118             };
119              
120             static XOP xop_firststart;
121             static XOP xop_firstwhile;
122              
123 200034           static OP *pp_firststart(pTHX)
124             {
125             /* Insired by perl core's pp_grepstart() */
126 200034           dSP;
127 200034           PADOFFSET targ = PL_op->op_targ;
128              
129 200034 100         if(PL_stack_base + TOPMARK == SP) {
130             /* Empty */
131 9           U8 mode = PL_op->op_private;
132             (void)POPMARK;
133 9 50         XPUSHs((mode & FIRST_EMPTY_NO ) ? &PL_sv_no :
    100          
    100          
134             (mode & FIRST_EMPTY_YES) ? &PL_sv_yes :
135             &PL_sv_undef);
136 9           RETURNOP(PL_op->op_next->op_next);
137             }
138              
139 200025           PL_stack_sp = PL_stack_base + TOPMARK + 1;
140 200025 50         PUSHMARK(PL_stack_sp); /* current src item */
141              
142 200025           ENTER_with_name("first");
143              
144 400050           SV *src = PL_stack_base[TOPMARK];
145              
146 200025 100         if(SvPADTMP(src)) {
147 17           src = PL_stack_base[TOPMARK] = sv_mortalcopy(src);
148 17           PL_tmps_floor++;
149             }
150 200025           SvTEMP_off(src);
151              
152 200025 100         if(targ) {
153 4           SV **padentry = &PAD_SVl(targ);
154 4           save_clearsv(padentry);
155 4           sv_setsv(*padentry, src);
156             }
157             else {
158 200021           SAVE_DEFSV;
159 200021           DEFSV_set(src);
160             }
161              
162 200025           PUTBACK;
163              
164             /* Jump to body of block */
165 200025           return (cLOGOPx(PL_op->op_next))->op_other;
166             }
167              
168 10200163           static OP *pp_firstwhile(pTHX)
169             {
170             /* Inspired by perl core's pp_grepwhile() */
171 10200163           dSP;
172 10200163           dPOPss;
173 10200163           U8 mode = PL_op->op_private;
174 10200163           PADOFFSET targ = PL_op->op_targ;
175 10200163 100         SV *targsv = targ ? PAD_SVl(targ) : DEFSV;
    50          
176              
177 10200163 50         bool ret = SvTRUE_NN(sv);
    50          
    0          
    0          
    50          
    50          
    50          
    100          
    50          
    0          
    0          
    0          
    0          
    0          
178              
179 10200163           (*PL_markstack_ptr)++;
180              
181 10200163 100         if((mode & FIRST_STOP_ON_FALSE) ? !ret : ret) {
    100          
182             /* Stop */
183              
184             /* Technically this means that `first` will not necessarily return the
185             * value from the list, but instead returns whatever $_ was set to after
186             * the block has run; differing if the block modified it.
187             * I'm unsure how I feel about this, but both `CORE::grep` and
188             * `List::Util::first` do the same thing, so we are in good company
189             */
190 200016 100         SV *ret = (mode & FIRST_RET_NO ) ? &PL_sv_no :
191 200012 100         (mode & FIRST_RET_YES) ? &PL_sv_yes :
192             sv_mortalcopy(targsv);
193 200016           LEAVE_with_name("first");
194             (void)POPMARK;
195 400032           SP = PL_stack_base + POPMARK;
196 200016           PUSHs(ret);
197 200016           RETURN;
198             }
199              
200 10000147 100         if(UNLIKELY(PL_stack_base + *PL_markstack_ptr > SP)) {
201             /* Empty */
202 9           LEAVE_with_name("first");
203             (void)POPMARK;
204 18           SP = PL_stack_base + POPMARK;
205 9 100         PUSHs((mode & FIRST_EMPTY_NO ) ? &PL_sv_no :
    100          
206             (mode & FIRST_EMPTY_YES) ? &PL_sv_yes :
207             &PL_sv_undef);
208 9           RETURN;
209             }
210              
211 10000138           SV *src = PL_stack_base[TOPMARK];
212              
213 10000138 100         if(SvPADTMP(src)) {
214 132           src = PL_stack_base[TOPMARK] = sv_mortalcopy(src);
215 132           PL_tmps_floor++;
216             }
217 10000138           SvTEMP_off(src);
218              
219 10000138 100         if(targ)
220 29           sv_setsv(targsv, src);
221             else
222 10000109           DEFSV_set(src);
223              
224 10000138           PUTBACK;
225              
226 10000138           return cLOGOP->op_other;
227             }
228              
229 41           static int build_first(pTHX_ OP **out, XSParseKeywordPiece *args[], size_t nargs, void *hookdata)
230             {
231             size_t argi = 0;
232             PADOFFSET varix = 0;
233              
234 41           bool has_optvar = args[argi++]->i;
235 41 100         if(has_optvar) {
236 5           varix = args[argi++]->padix;
237             }
238              
239 41           OP *block = op_contextualize(op_scope(args[argi++]->op), G_SCALAR);
240 41           OP *list = args[argi++]->op;
241              
242 41 50         *out = build_blocklist(aTHX_ varix, block, list,
243 0           &pp_firststart, &pp_firstwhile, SvIV((SV *)hookdata));
244 41           return KEYWORD_PLUGIN_EXPR;
245             }
246              
247             static const struct XSParseKeywordPieceType pieces_optvar_blocklist[] = {
248             XPK_PREFIXED_BLOCK(
249             XPK_OPTIONAL(XPK_KEYWORD("my"), XPK_LEXVAR_MY(XPK_LEXVAR_SCALAR))
250             ),
251             XPK_LISTEXPR_LISTCTX,
252             {0},
253             };
254              
255             static const struct XSParseKeywordHooks hooks_first = {
256             .permit_hintkey = "List::Keywords/first",
257              
258             .pieces = pieces_optvar_blocklist,
259             .build = &build_first,
260             };
261              
262             static const struct XSParseKeywordHooks hooks_any = {
263             .permit_hintkey = "List::Keywords/any",
264             .pieces = pieces_optvar_blocklist,
265             .build = &build_first,
266             };
267              
268             static const struct XSParseKeywordHooks hooks_all = {
269             .permit_hintkey = "List::Keywords/all",
270             .pieces = pieces_optvar_blocklist,
271             .build = &build_first,
272             };
273              
274             static const struct XSParseKeywordHooks hooks_none = {
275             .permit_hintkey = "List::Keywords/none",
276             .pieces = pieces_optvar_blocklist,
277             .build = &build_first,
278             };
279              
280             static const struct XSParseKeywordHooks hooks_notall = {
281             .permit_hintkey = "List::Keywords/notall",
282             .pieces = pieces_optvar_blocklist,
283             .build = &build_first,
284             };
285              
286             static XOP xop_reducestart;
287             static XOP xop_reducewhile;
288              
289             enum {
290             REDUCE_REDUCE,
291             REDUCE_REDUCTIONS,
292             };
293              
294 9           static OP *pp_reducestart(pTHX)
295             {
296 9           dSP;
297 9           U8 mode = PL_op->op_private;
298              
299 9 100         if(PL_stack_base + TOPMARK == SP) {
300             /* Empty */
301             (void)POPMARK;
302 2 50         if(GIMME_V == G_SCALAR)
    100          
303 1 50         XPUSHs(&PL_sv_undef);
304 2           RETURNOP(PL_op->op_next->op_next);
305             }
306              
307 7 100         if(PL_stack_base + TOPMARK + 1 == SP) {
308             /* Single item */
309             (void)POPMARK;
310             /* Leave the singleton there it will be fine */
311 2           RETURNOP(PL_op->op_next->op_next);
312             }
313              
314 5           PL_stack_sp = PL_stack_base + TOPMARK + 1;
315 5 100         if(mode == REDUCE_REDUCTIONS)
316 3 50         PUSHMARK(PL_stack_sp);
317 5 50         PUSHMARK(PL_stack_sp);
318              
319 5           ENTER_with_name("reduce");
320              
321 5           GV *firstgv = gv_fetchpvs("a", GV_ADD|GV_NOTQUAL, SVt_PV);
322 5           GV *secondgv = gv_fetchpvs("b", GV_ADD|GV_NOTQUAL, SVt_PV);
323              
324 5           save_gp(firstgv, 0); save_gp(secondgv, 0);
325 5           GvINTRO_off(firstgv); GvINTRO_off(secondgv);
326 5           SAVEGENERICSV(GvSV(firstgv)); SAVEGENERICSV(GvSV(secondgv));
327 10           SvREFCNT_inc(GvSV(firstgv)); SvREFCNT_inc(GvSV(secondgv));
328              
329             /* Initial accumulator */
330 10           SV *sv = PL_stack_base[TOPMARK];
331              
332 5 100         if(mode == REDUCE_REDUCTIONS)
333 3           PL_stack_base[PL_markstack_ptr[-1]++] = sv_mortalcopy(sv);
334              
335 5 50         if(SvPADTMP(sv)) {
336 5           sv = PL_stack_base[TOPMARK] = sv_mortalcopy(sv);
337 5           PL_tmps_floor++;
338             }
339 5           SvTEMP_off(sv);
340 10           GvSV(firstgv) = SvREFCNT_inc(sv);
341              
342 5           (*PL_markstack_ptr)++;
343              
344             /* value */
345 10           sv = PL_stack_base[TOPMARK];
346              
347 5 50         if(SvPADTMP(sv)) {
348 5           sv = PL_stack_base[TOPMARK] = sv_mortalcopy(sv);
349 5           PL_tmps_floor++;
350             }
351 5           SvTEMP_off(sv);
352 10           GvSV(secondgv) = SvREFCNT_inc(sv);
353              
354 5           PUTBACK;
355              
356             /* Jump to body of block */
357 5           return (cLOGOPx(PL_op->op_next))->op_other;
358             }
359              
360 18           static OP *pp_reducewhile(pTHX)
361             {
362 18           dSP;
363 18           U8 mode = PL_op->op_private;
364 18           dPOPss;
365              
366 18 100         if(mode == REDUCE_REDUCTIONS)
367 11 100         PL_stack_base[PL_markstack_ptr[-1]++] = SvPADTMP(sv) ? sv_mortalcopy(sv) : sv;
368              
369 18           (*PL_markstack_ptr)++;
370              
371 18 100         if(UNLIKELY(PL_stack_base + *PL_markstack_ptr > SP)) {
372 5 50         U8 gimme = GIMME_V;
373 5           LEAVE_with_name("reduce");
374              
375 5 100         if(mode == REDUCE_REDUCTIONS) {
376             (void)POPMARK;
377 3           I32 items = --*PL_markstack_ptr - PL_markstack_ptr[-1];
378             (void)POPMARK;
379 6           SP = PL_stack_base + POPMARK;
380 3 100         if(gimme == G_SCALAR) {
381 1           SP[1] = SP[items];
382 1           SP += 1;
383             }
384 2 50         else if(gimme == G_ARRAY)
385 2           SP += items;
386             }
387             else {
388             (void)POPMARK;
389 4           SP = PL_stack_base + POPMARK;
390 2           PUSHs(SvREFCNT_inc(sv));
391             }
392 5           RETURN;
393             }
394              
395 13           GV *firstgv = gv_fetchpvs("a", GV_ADD|GV_NOTQUAL, SVt_PV);
396 13           GV *secondgv = gv_fetchpvs("b", GV_ADD|GV_NOTQUAL, SVt_PV);
397              
398 13           SvREFCNT_dec(GvSV(firstgv));
399 26           GvSV(firstgv) = SvREFCNT_inc(sv);
400              
401             /* next value */
402 26           sv = PL_stack_base[TOPMARK];
403              
404 13 50         if(SvPADTMP(sv)) {
405 13           sv = PL_stack_base[TOPMARK] = sv_mortalcopy(sv);
406 13           PL_tmps_floor++;
407             }
408 13           SvTEMP_off(sv);
409 26           GvSV(secondgv) = SvREFCNT_inc(sv);
410              
411 13           PUTBACK;
412              
413 13           return cLOGOP->op_other;
414             }
415              
416 10           static int build_reduce(pTHX_ OP **out, XSParseKeywordPiece *args[], size_t nargs, void *hookdata)
417             {
418             #if !HAVE_PERL_VERSION(5,20,0)
419             GV *firstgv = gv_fetchpvs("a", GV_ADD|GV_NOTQUAL, SVt_PV);
420             GV *secondgv = gv_fetchpvs("b", GV_ADD|GV_NOTQUAL, SVt_PV);
421              
422             GvMULTI_on(firstgv);
423             GvMULTI_on(secondgv);
424             #endif
425              
426 10 50         *out = build_blocklist(aTHX_ 0, args[0]->op, args[1]->op,
427 0           &pp_reducestart, &pp_reducewhile, SvIV((SV *)hookdata));
428 10           return KEYWORD_PLUGIN_EXPR;
429             }
430              
431             static const struct XSParseKeywordPieceType pieces_blocklist[] = {
432             XPK_BLOCK_SCALARCTX,
433             XPK_LISTEXPR_LISTCTX,
434             {0},
435             };
436              
437             static const struct XSParseKeywordHooks hooks_reduce = {
438             .permit_hintkey = "List::Keywords/reduce",
439              
440             .pieces = pieces_blocklist,
441             .build = &build_reduce,
442             };
443              
444             static const struct XSParseKeywordHooks hooks_reductions = {
445             .permit_hintkey = "List::Keywords/reductions",
446              
447             .pieces = pieces_blocklist,
448             .build = &build_reduce,
449             };
450              
451             MODULE = List::Keywords PACKAGE = List::Keywords
452              
453             BOOT:
454 10           boot_xs_parse_keyword(0.05);
455              
456 10           register_xs_parse_keyword("first", &hooks_first, newSViv(0));
457              
458             /* Variations on first */
459 10           register_xs_parse_keyword("any", &hooks_any,
460             newSViv(FIRST_EMPTY_NO |FIRST_RET_YES));
461 10           register_xs_parse_keyword("all", &hooks_all,
462             newSViv(FIRST_EMPTY_YES|FIRST_RET_NO|FIRST_STOP_ON_FALSE));
463 10           register_xs_parse_keyword("none", &hooks_none,
464             newSViv(FIRST_EMPTY_YES|FIRST_RET_NO));
465 10           register_xs_parse_keyword("notall", &hooks_notall,
466             newSViv(FIRST_EMPTY_NO |FIRST_RET_YES|FIRST_STOP_ON_FALSE));
467              
468 10           XopENTRY_set(&xop_firststart, xop_name, "firststart");
469 10           XopENTRY_set(&xop_firststart, xop_desc, "first");
470 10           XopENTRY_set(&xop_firststart, xop_class, OA_LISTOP);
471 10           Perl_custom_op_register(aTHX_ &pp_firststart, &xop_firststart);
472              
473 10           XopENTRY_set(&xop_firstwhile, xop_name, "firstwhile");
474 10           XopENTRY_set(&xop_firstwhile, xop_desc, "first iter");
475 10           XopENTRY_set(&xop_firstwhile, xop_class, OA_LOGOP);
476 10           Perl_custom_op_register(aTHX_ &pp_firstwhile, &xop_firstwhile);
477              
478 10           register_xs_parse_keyword("reduce", &hooks_reduce, newSViv(REDUCE_REDUCE));
479 10           register_xs_parse_keyword("reductions", &hooks_reductions, newSViv(REDUCE_REDUCTIONS));
480              
481 10           XopENTRY_set(&xop_reducestart, xop_name, "reducestart");
482 10           XopENTRY_set(&xop_reducestart, xop_desc, "reduce");
483 10           XopENTRY_set(&xop_reducestart, xop_class, OA_LISTOP);
484 10           Perl_custom_op_register(aTHX_ &pp_reducestart, &xop_reducestart);
485              
486 10           XopENTRY_set(&xop_reducewhile, xop_name, "reducewhile");
487 10           XopENTRY_set(&xop_reducewhile, xop_desc, "reduce iter");
488 10           XopENTRY_set(&xop_reducewhile, xop_class, OA_LOGOP);
489 10           Perl_custom_op_register(aTHX_ &pp_reducewhile, &xop_reducewhile);