File Coverage

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