File Coverage

lib/Syntax/Keyword/MultiSub.xs
Criterion Covered Total %
statement 79 82 96.3
branch 38 60 63.3
condition n/a
subroutine n/a
pod n/a
total 117 142 82.3


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             #include "EXTERN.h"
7             #include "perl.h"
8             #include "XSUB.h"
9              
10             #include "XSParseSublike.h"
11              
12             #include "perl-backcompat.c.inc"
13              
14             #include "newOP_CUSTOM.c.inc"
15              
16             struct MultiSubOption {
17             int args_min, args_max;
18             CV *cv;
19             };
20              
21             #define get_optionsav(cv, padix) S_get_optionsav(aTHX_ cv, padix)
22             static AV *S_get_optionsav(pTHX_ CV *cv, PADOFFSET padix)
23             {
24 16           PADLIST *pl = CvPADLIST(cv);
25 16           AV *optionsav = (AV *)PadARRAY(PadlistARRAY(pl)[1])[padix];
26             return optionsav;
27             }
28              
29 9           static OP *pp_dispatch_multisub(pTHX)
30             {
31 9 50         IV nargs = av_count(GvAV(PL_defgv));
32 9           CV *runcv = find_runcv(0);
33 18           AV *optionsav = get_optionsav(runcv, PL_op->op_targ);
34              
35 9           CV *jumpcv = NULL;
36              
37 9 50         IV noptions = av_count(optionsav);
38             IV optioni;
39 19 100         for(optioni = 0; optioni < noptions; optioni++) {
40 18           struct MultiSubOption *option = (struct MultiSubOption *)AvARRAY(optionsav)[optioni];
41              
42 18 100         if(nargs < option->args_min)
43 1           continue;
44 17 100         if(option->args_max > -1 && nargs > option->args_max)
    100          
45 9           continue;
46              
47 8           jumpcv = option->cv;
48 8           break;
49             }
50              
51 9 100         if(!jumpcv)
52 3 50         croak("Unable to find a function body for a call to &%s::%s having %d arguments",
    50          
53 2 50         HvNAME(CvSTASH(runcv)), GvNAME(CvGV(runcv)), nargs);
    0          
    50          
    50          
54              
55             /* Now pretend to be goto &$cv
56             * Reuse the same PL_op structure and just call that ppfunc */
57             assert(PL_op->op_flags & OPf_STACKED);
58 8           dSP;
59 8           mPUSHs(newRV_inc((SV *)jumpcv));
60 8           PUTBACK;
61             assert(SvROK(TOPs) && SvTYPE(SvRV(TOPs)) == SVt_PVCV);
62 8           return (PL_ppaddr[OP_GOTO])(aTHX);
63             }
64              
65             /* XSParseSublikeContext moddata keys */
66             #define MODDATA_KEY_NAME "Syntax::Keyword::MultiSub/name"
67             #define MODDATA_KEY_COMPMULTICV "Syntax::Keyword::MultiSub/compmulticv"
68              
69 7           static void parse_pre_subparse(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
70             {
71 7           SV *name = ctx->name;
72              
73 7           CV *multicv = get_cvn_flags(SvPVX(name), SvCUR(name), SvUTF8(name) ? SVf_UTF8 : 0);
74 7 100         if(!multicv) {
75 3           ENTER;
76              
77 3           I32 floor_ix = start_subparse(FALSE, 0);
78 3           SAVEFREESV(PL_compcv);
79              
80 3           I32 save_ix = block_start(TRUE);
81              
82 3           PADOFFSET padix = pad_add_name_pvs("@(Syntax::Keyword::MultiSub/options)", 0, NULL, NULL);
83 3           intro_my();
84              
85 3           OP *dispatchop = newOP_CUSTOM(&pp_dispatch_multisub, OPf_STACKED);
86 3           dispatchop->op_targ = padix;
87              
88 3           OP *body = block_end(save_ix, dispatchop);
89              
90 6           SvREFCNT_inc(PL_compcv);
91              
92 3           multicv = newATTRSUB(floor_ix, newSVOP(OP_CONST, 0, SvREFCNT_inc(name)), NULL, NULL, body);
93              
94 3           LEAVE;
95             }
96              
97 7           hv_stores(ctx->moddata, MODDATA_KEY_NAME, SvREFCNT_inc(name));
98 7           hv_stores(ctx->moddata, MODDATA_KEY_COMPMULTICV, SvREFCNT_inc(multicv));
99              
100             /* Do not let this sub be installed as a named symbol */
101 7           ctx->actions &= ~XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL;
102 7           }
103              
104 7           static void parse_post_newcv(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
105             {
106 7           CV *cv = ctx->cv;
107 7 50         if(!cv)
108             return;
109              
110 7           SV *name = *hv_fetchs(ctx->moddata, MODDATA_KEY_NAME, 0);
111 7           CV *multicv = (CV *)*hv_fetchs(ctx->moddata, MODDATA_KEY_COMPMULTICV, 0);
112              
113 7           PADNAMELIST *pln = PadlistNAMES(CvPADLIST(multicv));
114             /* We can't use pad_findmy_pvn() because it gets upset about seqnums */
115             PADOFFSET padix;
116 7 50         for(padix = 1; padix <= PadnamelistMAX(pln); padix++)
117 7 50         if(strEQ(PadnamePV(PadnamelistARRAY(pln)[padix]), "@(Syntax::Keyword::MultiSub/options)"))
118             break;
119             assert(padix <= PadnamelistMAX(pln));
120              
121 14           AV *optionsav = get_optionsav(multicv, padix);
122 11 50         bool final_is_slurpy = av_count(optionsav) &&
    0          
    100          
    50          
123 4 50         (((struct MultiSubOption *)AvARRAY(optionsav)[AvFILL(optionsav)])->args_max == -1);
124              
125             int args_min, args_max;
126              
127 7           OP *o = CvSTART(cv);
128 7 50         while(o) {
129             redo:
130 14           switch(o->op_type) {
131             case OP_NEXTSTATE:
132 7           o = o->op_next;
133 7           goto redo;
134              
135             case OP_ARGCHECK: {
136             #if HAVE_PERL_VERSION(5, 31, 5)
137             struct op_argcheck_aux *aux = (struct op_argcheck_aux *)cUNOP_AUXo->op_aux;
138             char slurpy = aux->slurpy;
139             args_max = aux->params;
140             args_min = args_max - aux->opt_params;
141             #else
142 7           UNOP_AUX_item *aux = cUNOP_AUXo->op_aux;
143 7           char slurpy = aux[2].iv;
144              
145 7           args_max = aux[0].iv;
146 7           args_min = args_max - aux[1].iv;
147             #endif
148 7 100         if(slurpy) {
149 1 50         if(final_is_slurpy)
150 0           croak("Already have a slurpy function body for multi sub %" SVf, name);
151             args_max = -1;
152             }
153             goto done;
154             }
155              
156             default:
157 0           croak("TODO: Unsure how to find argcheck op within %s", PL_op_name[o->op_type]);
158             }
159             }
160             done: ;
161              
162 7 50         IV noptions = av_count(optionsav);
163             IV optioni;
164 12 100         for(optioni = 0; optioni < noptions; optioni++) {
165 5           struct MultiSubOption *option = (struct MultiSubOption *)AvARRAY(optionsav)[optioni];
166              
167 5 50         if(option->args_max == -1 || args_min > option->args_max)
    100          
168 4           continue;
169 1 50         if(args_max < option->args_min)
170 1           continue;
171              
172 0           croak("Ambiguous argument count for multi sub %" SVf, name);
173             }
174              
175             struct MultiSubOption *option;
176 7           Newx(option, 1, struct MultiSubOption);
177              
178 7           option->args_min = args_min;
179 7           option->args_max = args_max;
180 7           option->cv = cv_clone(cv); /* Because it is currently a protosub */
181              
182 7           av_push(optionsav, (SV *)option);
183             }
184              
185             static struct XSParseSublikeHooks hooks_multi = {
186             .permit_hintkey = "Syntax::Keyword::MultiSub/multi",
187             .flags = XS_PARSE_SUBLIKE_FLAG_PREFIX|XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS,
188             .require_parts = XS_PARSE_SUBLIKE_PART_NAME,
189             .pre_subparse = parse_pre_subparse,
190             .post_newcv = parse_post_newcv,
191             };
192              
193             MODULE = Syntax::Keyword::MultiSub PACKAGE = Syntax::Keyword::MultiSub
194              
195             BOOT:
196 2           boot_xs_parse_sublike(0.15);
197              
198 2           register_xs_parse_sublike("multi", &hooks_multi, NULL);