File Coverage

Custom.xs
Criterion Covered Total %
statement 163 203 80.3
branch 102 184 55.4
condition n/a
subroutine n/a
pod n/a
total 265 387 68.4


line stmt bran cond sub pod time code
1             #include "EXTERN.h"
2             #include "perl.h"
3             #include "XSUB.h"
4             #include "ppport.h"
5              
6             /* Custom infix operators hang off core's PL_infix_plugin, added in 5.38.
7             * Detect it and compile the whole machinery only there; on older perls this
8             * translation unit is just the bootstrap plus a warning import(). */
9             #ifdef PERL_VERSION_GE
10             # if PERL_VERSION_GE(5,38,0)
11             # define IC_HAVE_INFIX 1
12             # endif
13             #elif (PERL_REVISION == 5 && PERL_VERSION >= 38) || PERL_REVISION > 5
14             # define IC_HAVE_INFIX 1
15             #endif
16              
17             #ifdef IC_HAVE_INFIX
18              
19             typedef OP *(*ic_build_fn)(pTHX_ SV **, OP *, OP *, struct Perl_custom_infix *);
20             typedef void (*ic_parse_fn)(pTHX_ SV **, struct Perl_custom_infix *);
21              
22             /* One registry entry per declared operator.
23             *
24             * `cdef` MUST be the first member: core hands our build_op callback a pointer
25             * to it, and we recover the whole entry by casting that pointer back (the same
26             * container-of trick XS::Parse::Infix uses). */
27             typedef struct ic_entry {
28             struct Perl_custom_infix cdef; /* prec / parse / build_op */
29             char *glyph; /* operator bytes (UTF-8), owned */
30             STRLEN glyph_len;
31             char *hintkey; /* "Infix::Custom/" + hex(glyph) */
32             STRLEN hintkey_len;
33             IV id; /* hint value that activates us */
34             CV *cv; /* call mode: the sub to invoke */
35             OPCODE optype; /* op mode: native binop to build */
36             struct ic_entry *next;
37             } ic_entry;
38              
39             static ic_entry *ic_registry = NULL; /* newest first */
40             static IV ic_next_id = 1;
41             static Perl_infix_plugin_t ic_next_infix_plugin;
42              
43             /* ---- precedence vocabulary ------------------------------------------------
44             * Friendly name -> the value of core's enum Perl_custom_infix_precedence
45             * (perl.h). Associativity is a fixed property of each tier, so choosing a
46             * precedence is also choosing associativity. Returns -1 for an unknown name. */
47             static IV
48 13           ic_prec_value(const char *name)
49             {
50 13 50         if (strEQ(name, "low")) return INFIX_PREC_LOW;
51 13 50         if (strEQ(name, "logical_or_low")) return INFIX_PREC_LOGICAL_OR_LOW;
52 13 50         if (strEQ(name, "logical_and_low")) return INFIX_PREC_LOGICAL_AND_LOW;
53 13 50         if (strEQ(name, "assign")) return INFIX_PREC_ASSIGN;
54 13 50         if (strEQ(name, "logical_or")) return INFIX_PREC_LOGICAL_OR;
55 13 50         if (strEQ(name, "logical_and")) return INFIX_PREC_LOGICAL_AND;
56 13 50         if (strEQ(name, "rel")) return INFIX_PREC_REL;
57 13 100         if (strEQ(name, "add")) return INFIX_PREC_ADD;
58 3 50         if (strEQ(name, "mul")) return INFIX_PREC_MUL;
59 0 0         if (strEQ(name, "pow")) return INFIX_PREC_POW;
60 0 0         if (strEQ(name, "high")) return INFIX_PREC_HIGH;
61 0           return -1;
62             }
63              
64             /* Map a perl operator symbol to its OP code (version-correct, unlike hardcoding
65             * numbers on the Perl side). Returns OP_NULL for an unknown symbol. */
66             static OPCODE
67 4           ic_opcode(const char *sym)
68             {
69 4 100         if (strEQ(sym, "+")) return OP_ADD;
70 2 50         if (strEQ(sym, "-")) return OP_SUBTRACT;
71 2 100         if (strEQ(sym, "*")) return OP_MULTIPLY;
72 1 50         if (strEQ(sym, "/")) return OP_DIVIDE;
73 1 50         if (strEQ(sym, "%")) return OP_MODULO;
74 1 50         if (strEQ(sym, "**")) return OP_POW;
75 1 50         if (strEQ(sym, ".")) return OP_CONCAT;
76 0 0         if (strEQ(sym, "x")) return OP_REPEAT;
77 0 0         if (strEQ(sym, "|")) return OP_BIT_OR;
78 0 0         if (strEQ(sym, "&")) return OP_BIT_AND;
79 0 0         if (strEQ(sym, "^")) return OP_BIT_XOR;
80 0 0         if (strEQ(sym, "<<")) return OP_LEFT_SHIFT;
81 0 0         if (strEQ(sym, ">>")) return OP_RIGHT_SHIFT;
82 0           return OP_NULL;
83             }
84              
85             /* Canonical hint key: "Infix::Custom/" + lowercase hex of the glyph's UTF-8
86             * bytes. Hex keeps the key pure-ASCII, so setting %^H here and reading it in
87             * the dispatcher never disagree over the UTF-8 flag on a wide operator. Caller
88             * frees the returned buffer with Safefree(). */
89             static char *
90 27           ic_hintkey(pTHX_ const char *glyph, STRLEN glyph_len, STRLEN *out_len)
91             {
92             static const char hexd[] = "0123456789abcdef";
93 27           STRLEN plen = sizeof("Infix::Custom/") - 1;
94 27           STRLEN klen = plen + glyph_len * 2;
95             char *k, *p;
96             STRLEN i;
97              
98 27           Newx(k, klen + 1, char);
99 27           Copy("Infix::Custom/", k, plen, char);
100 27           p = k + plen;
101 114 100         for (i = 0; i < glyph_len; i++) {
102 87           unsigned char b = (unsigned char)glyph[i];
103 87           *p++ = hexd[b >> 4];
104 87           *p++ = hexd[b & 0xf];
105             }
106 27           *p = '\0';
107 27           *out_len = klen;
108 27           return k;
109             }
110              
111             /* Is entry `e` lexically active at the current point of compilation? Read the
112             * compile-time hints hash (%^H) and compare the stored id. A per-glyph key with
113             * the id as value lets nested scopes rebind the same glyph: the innermost
114             * scope's id is the one visible here. */
115             static int
116 43           ic_active(pTHX_ ic_entry *e)
117             {
118 43           HV *hints = GvHV(PL_hintgv);
119             SV **hent;
120 43 50         if (!hints)
121 0           return 0;
122 43           hent = hv_fetch(hints, e->hintkey, (I32)e->hintkey_len, 0);
123 43 100         return hent && *hent && SvIOK(*hent) && SvIV(*hent) == e->id;
    50          
    50          
    100          
124             }
125              
126             /* Call mode: `lhs OP rhs` -> cv(lhs, rhs), an entersub over
127             * (pushmark, lhs, rhs, ). */
128             static OP *
129 13           ic_build_call(pTHX_ SV **opdata, OP *lhs, OP *rhs, struct Perl_custom_infix *def)
130             {
131 13           ic_entry *e = (ic_entry *)def;
132             OP *cvop, *list;
133             PERL_UNUSED_ARG(opdata);
134              
135 13           cvop = newSVOP(OP_CONST, 0, newRV_inc((SV *)e->cv));
136 13           list = op_append_elem(OP_LIST,
137             op_append_elem(OP_LIST, lhs, rhs),
138             cvop);
139 13           return newUNOP(OP_ENTERSUB, OPf_STACKED, list);
140             }
141              
142             /* Op mode: `lhs OP rhs` -> a native binary op (no sub-call overhead). */
143             static OP *
144 6           ic_build_binop(pTHX_ SV **opdata, OP *lhs, OP *rhs, struct Perl_custom_infix *def)
145             {
146 6           ic_entry *e = (ic_entry *)def;
147             PERL_UNUSED_ARG(opdata);
148 6           return newBINOP(e->optype, 0, lhs, rhs);
149             }
150              
151             /* Method mode parse stage: consume the bareword identifier that follows the
152             * operator (the method name) before the parser tries to read it as a normal
153             * term -- which would trip `strict subs`. Store it in *opdata for build. */
154             static void
155 12           ic_parse_ident(pTHX_ SV **opdata, struct Perl_custom_infix *def)
156             {
157 12           SV *name = newSVpvs("");
158             I32 c;
159             PERL_UNUSED_ARG(def);
160              
161 12           lex_read_space(0);
162 74 50         while ((c = lex_peek_unichar(0)) != -1 && isWORDCHAR(c)) {
    50          
    100          
163 62           sv_catpvf(name, "%c", (int)c);
164 62           lex_read_unichar(0);
165             }
166 12           *opdata = name;
167              
168             /* The infix-plugin API is strictly binary: after we return, the parser
169             * still parses an rhs operand. We have already consumed the method name,
170             * so inject a dummy `undef` for it to read; ic_build_method discards it. */
171 12           lex_stuff_pvs(" undef ", 0);
172 12           }
173              
174             /* Method mode build: `lhs OP name` -> cv(lhs, "name"), reusing call mode but
175             * with the captured bareword as a constant string instead of a parsed rhs. */
176             static OP *
177 12           ic_build_method(pTHX_ SV **opdata, OP *lhs, OP *rhs, struct Perl_custom_infix *def)
178             {
179 12           ic_entry *e = (ic_entry *)def;
180 12 50         SV *name = (opdata && *opdata) ? *opdata : NULL;
    50          
181             OP *cvop, *nameop, *list;
182              
183 12 50         if (rhs) /* method mode does not use a parsed rhs */
184 12           op_free(rhs);
185 12 50         nameop = newSVOP(OP_CONST, 0, name ? newSVsv(name) : newSVpvs(""));
186 12           cvop = newSVOP(OP_CONST, 0, newRV_inc((SV *)e->cv));
187 12           list = op_append_elem(OP_LIST,
188             op_append_elem(OP_LIST, lhs, nameop),
189             cvop);
190 12           return newUNOP(OP_ENTERSUB, OPf_STACKED, list);
191             }
192              
193             /* The dispatcher core calls for every candidate infix operator. Among the
194             * operators that match the input AND are lexically in scope, the longest glyph
195             * wins (so `|>` beats a registered `|`). Anything we don't claim chains to the
196             * plugin installed before us. */
197             static STRLEN
198 270783           ic_infix_plugin(pTHX_ char *opname, STRLEN oplen,
199             struct Perl_custom_infix **def)
200             {
201 270783           ic_entry *e, *best = NULL;
202              
203 710895 100         for (e = ic_registry; e; e = e->next) {
204 440112 100         if (e->glyph_len <= oplen
205 76067 100         && memEQ(opname, e->glyph, e->glyph_len)
206 43 100         && ic_active(aTHX_ e)
207 34 100         && (!best || e->glyph_len > best->glyph_len))
    50          
208 32           best = e;
209             }
210              
211 270783 100         if (best) {
212 32           *def = &best->cdef;
213 32           return best->glyph_len;
214             }
215 270751           return ic_next_infix_plugin(aTHX_ opname, oplen, def);
216             }
217              
218             /* Add an operator to the global registry; returns its activation id. */
219             static IV
220 13           ic_register(pTHX_ SV *glyph_sv, IV prec, CV *cv, OPCODE optype,
221             ic_build_fn build_op, ic_parse_fn parse)
222             {
223             STRLEN len;
224 13           char *g = SvPVutf8(glyph_sv, len);
225             ic_entry *e;
226              
227 13           Newxz(e, 1, ic_entry);
228 13           e->glyph = savepvn(g, len);
229 13           e->glyph_len = len;
230 13           e->id = ic_next_id++;
231 13 100         e->cv = cv ? (CV *)SvREFCNT_inc((SV *)cv) : NULL;
232 13           e->optype = optype;
233 13           e->cdef.prec = (enum Perl_custom_infix_precedence)prec;
234 13           e->cdef.parse = parse;
235 13           e->cdef.build_op = build_op;
236 13           e->hintkey = ic_hintkey(aTHX_ e->glyph, e->glyph_len, &e->hintkey_len);
237 13           e->next = ic_registry;
238 13           ic_registry = e;
239 13           return e->id;
240             }
241              
242             /* Activate operator `id` for the glyph in the current lexical scope: set
243             * %^H{ hintkey } = id and flag the hints hash for per-scope save/restore (this
244             * is what `$^H{...} = ...` does under the hood). */
245             static void
246 13           ic_set_hint(pTHX_ SV *glyph_sv, IV id)
247             {
248             STRLEN len, klen;
249 13           char *g = SvPVutf8(glyph_sv, len);
250 13           char *key = ic_hintkey(aTHX_ g, len, &klen);
251 13           HV *hints = GvHV(PL_hintgv);
252              
253 13 50         if (hints) {
254 13           (void)hv_store(hints, key, (I32)klen, newSViv(id), 0);
255 13           PL_hints |= HINT_LOCALIZE_HH;
256             }
257 13           Safefree(key);
258 13           }
259              
260             static void
261 1           ic_del_hint(pTHX_ SV *glyph_sv)
262             {
263             STRLEN len, klen;
264 1           char *g = SvPVutf8(glyph_sv, len);
265 1           char *key = ic_hintkey(aTHX_ g, len, &klen);
266 1           HV *hints = GvHV(PL_hintgv);
267              
268 1 50         if (hints)
269 1           (void)hv_delete(hints, key, (I32)klen, G_DISCARD);
270 1           Safefree(key);
271 1           }
272              
273             /* Drop every Infix::Custom hint from the current scope (`no Infix::Custom;`). */
274             static void
275 0           ic_del_all_hints(pTHX)
276             {
277 0           HV *hints = GvHV(PL_hintgv);
278             HE *he;
279             AV *doomed;
280             SSize_t i;
281              
282 0 0         if (!hints)
283 0           return;
284 0           doomed = (AV *)sv_2mortal((SV *)newAV()); /* collect keys, then delete */
285 0           hv_iterinit(hints);
286 0 0         while ((he = hv_iternext(hints))) {
287             STRLEN kl;
288 0 0         char *k = HePV(he, kl);
289 0 0         if (kl >= sizeof("Infix::Custom/") - 1
290 0 0         && memEQ(k, "Infix::Custom/", sizeof("Infix::Custom/") - 1))
291 0           av_push(doomed, newSVpvn(k, kl));
292             }
293 0 0         for (i = 0; i <= av_top_index(doomed); i++) {
    0          
294 0           SV **kp = av_fetch(doomed, i, 0);
295 0 0         if (kp && *kp) {
    0          
296             STRLEN kl;
297 0           char *k = SvPV(*kp, kl);
298 0           (void)hv_delete(hints, k, (I32)kl, G_DISCARD);
299             }
300             }
301             }
302              
303             /* A sample C build_op, used only by the test suite to exercise the build_op
304             * escape hatch. Lowers `lhs OP rhs` to a (distinctive) native subtraction. */
305             static OP *
306 1           ic_sample_build(pTHX_ SV **opdata, OP *lhs, OP *rhs, struct Perl_custom_infix *def)
307             {
308             PERL_UNUSED_ARG(opdata);
309             PERL_UNUSED_ARG(def);
310 1           return newBINOP(OP_SUBTRACT, 0, lhs, rhs);
311             }
312              
313             /* Parse @_ for import(): fill in the requested glyph and one of the three
314             * lowering modes plus the precedence name. Croaks on malformed input. */
315             static void
316 15           ic_do_import(pTHX_ I32 ax, I32 items)
317             {
318 15           SV *op_sv = NULL, *call_sv = NULL, *prec_sv = NULL;
319 15           SV *binop_sv = NULL, *build_sv = NULL;
320 15           bool method = FALSE;
321 15           I32 i, start = 1; /* ST(0) is the class */
322             IV prec, id, modes;
323              
324 15 100         if (items <= 1) /* bare `use Infix::Custom;` */
325 2           return;
326              
327             /* shorthand: (CLASS, GLYPH, \&code, ...rest) */
328 13 50         if (items >= 3 && !SvROK(ST(1))
    50          
329 13 100         && SvROK(ST(2)) && SvTYPE(SvRV(ST(2))) == SVt_PVCV) {
    50          
330 1           op_sv = ST(1);
331 1           call_sv = ST(2);
332 1           start = 3;
333             }
334              
335 13 50         if ((items - start) % 2 != 0)
336 0           croak("Infix::Custom: odd number of options");
337              
338 51 100         for (i = start; i + 1 < items; i += 2) {
339 38           const char *k = SvPV_nolen(ST(i));
340 38           SV *v = ST(i + 1);
341 38 100         if (strEQ(k, "op")) op_sv = v;
342 26 100         else if (strEQ(k, "call")) call_sv = v;
343 19 100         else if (strEQ(k, "binop")) binop_sv = v;
344 15 100         else if (strEQ(k, "build_op")) build_sv = v;
345 14 100         else if (strEQ(k, "prec")) prec_sv = v;
346 1 50         else if (strEQ(k, "method")) method = SvTRUE(v);
347 0           else croak("Infix::Custom: unknown option '%s'", k);
348             }
349              
350 13 50         if (!op_sv || !SvOK(op_sv) || !SvCUR(op_sv))
    50          
    50          
351 0           croak("Infix::Custom: missing 'op' (the operator glyph)");
352             {
353             STRLEN gl;
354 13           const char *g = SvPV(op_sv, gl);
355             STRLEN j;
356 55 100         for (j = 0; j < gl; j++)
357 42 50         if (isSPACE(g[j]))
358 0           croak("Infix::Custom: operator glyph may not contain whitespace");
359             }
360              
361 13           modes = (call_sv ? 1 : 0) + (binop_sv ? 1 : 0) + (build_sv ? 1 : 0);
362 13 50         if (modes == 0)
363 0           croak("Infix::Custom: give one of 'call', 'binop' or 'build_op'");
364 13 50         if (modes > 1)
365 0           croak("Infix::Custom: give only one of 'call', 'binop' or 'build_op'");
366              
367 13 50         prec = ic_prec_value(prec_sv ? SvPV_nolen(prec_sv) : "low");
368 13 50         if (prec < 0)
369 0 0         croak("Infix::Custom: unknown prec '%s' (low logical_or_low "
370             "logical_and_low assign logical_or logical_and rel add mul pow "
371             "high)", prec_sv ? SvPV_nolen(prec_sv) : "low");
372              
373 13 100         if (method && !call_sv)
    50          
374 0           croak("Infix::Custom: 'method' requires 'call'");
375              
376 13 100         if (call_sv) {
377             CV *cv;
378 8 50         if (SvROK(call_sv) && SvTYPE(SvRV(call_sv)) == SVt_PVCV)
    50          
379 8           cv = (CV *)SvRV(call_sv);
380             else /* a sub name */
381 0           cv = get_cv(SvPV_nolen(call_sv), 0);
382 8 50         if (!cv)
383 0           croak("Infix::Custom: 'call' is not a sub or CODE reference");
384 8 100         if (method)
385             /* RHS is a bareword method name, captured by the parse stage. */
386 1           id = ic_register(aTHX_ op_sv, prec, cv, OP_NULL,
387             ic_build_method, ic_parse_ident);
388             else
389 7           id = ic_register(aTHX_ op_sv, prec, cv, OP_NULL,
390             ic_build_call, NULL);
391             }
392 5 100         else if (binop_sv) {
393 4           OPCODE ot = ic_opcode(SvPV_nolen(binop_sv));
394 4 50         if (ot == OP_NULL)
395 0           croak("Infix::Custom: unknown binop '%s'", SvPV_nolen(binop_sv));
396 4           id = ic_register(aTHX_ op_sv, prec, NULL, ot, ic_build_binop, NULL);
397             }
398             else {
399 1           ic_build_fn fn = INT2PTR(ic_build_fn, SvIV(build_sv));
400 1 50         if (!fn)
401 0           croak("Infix::Custom: 'build_op' must be a non-null function pointer");
402 1           id = ic_register(aTHX_ op_sv, prec, NULL, OP_NULL, fn, NULL);
403             }
404              
405 13           ic_set_hint(aTHX_ op_sv, id);
406             }
407              
408             #endif /* IC_HAVE_INFIX */
409              
410             MODULE = Infix::Custom PACKAGE = Infix::Custom
411              
412             BOOT:
413             #ifdef IC_HAVE_INFIX
414 8           wrap_infix_plugin(ic_infix_plugin, &ic_next_infix_plugin);
415             #endif
416              
417             void
418             import(...)
419             PPCODE:
420             #ifdef IC_HAVE_INFIX
421 15           ic_do_import(aTHX_ ax, items);
422 15           XSRETURN_EMPTY;
423             #else
424             {
425             static int warned = 0;
426             PERL_UNUSED_VAR(ax);
427             if (items > 1 && !warned) {
428             warned = 1;
429             warn("Infix::Custom: custom infix operators require perl 5.38+; "
430             "declarations are inert on this perl (%s)\n", "<5.38");
431             }
432             XSRETURN_EMPTY;
433             }
434             #endif
435              
436             void
437             unimport(...)
438             PPCODE:
439             #ifdef IC_HAVE_INFIX
440             {
441             I32 i;
442 1 50         if (items <= 1)
443 0           ic_del_all_hints(aTHX);
444             else
445 2 100         for (i = 1; i < items; i++)
446 1           ic_del_hint(aTHX_ ST(i));
447 1           XSRETURN_EMPTY;
448             }
449             #else
450             PERL_UNUSED_VAR(ax);
451             XSRETURN_EMPTY;
452             #endif
453              
454             IV
455             _sample_build_op()
456             CODE:
457             #ifdef IC_HAVE_INFIX
458 2 50         RETVAL = PTR2IV(ic_sample_build);
459             #else
460             RETVAL = 0;
461             #endif
462             OUTPUT:
463             RETVAL