File Coverage

lib/Syntax/Keyword/Inplace.xs
Criterion Covered Total %
statement 57 61 93.4
branch 31 48 64.5
condition n/a
subroutine n/a
pod n/a
total 88 109 80.7


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, 2022 -- leonerd@leonerd.org.uk
5             */
6             #include "EXTERN.h"
7             #include "perl.h"
8             #include "XSUB.h"
9              
10             #include "XSParseKeyword.h"
11              
12             #include "perl-backcompat.c.inc"
13              
14             enum {
15             OPpDUP_MOVEMARK = (1<<0),
16             };
17              
18             static XOP xop_dup;
19 27           static OP *pp_dup(pTHX)
20             {
21 27           dSP;
22 27           SV *sv = TOPs;
23              
24 27 50         if(PL_op->op_flags & OPf_REF)
25 27 50         XPUSHs(sv);
26             else
27 0 0         XPUSHs(sv_mortalcopy(sv));
28              
29 27 50         if(PL_op->op_private & OPpDUP_MOVEMARK)
30 27           (*PL_markstack_ptr)++;
31              
32 27           RETURN;
33             }
34              
35             #define newDUPOP(flags) S_newDUPOP(aTHX_ flags)
36             static OP *S_newDUPOP(pTHX_ U32 flags)
37             {
38 27           OP *o = newUNOP(OP_CUSTOM, flags, NULL);
39 27           o->op_ppaddr = &pp_dup;
40             return o;
41             }
42              
43             static bool arg_is_acceptable(OP *argop)
44             {
45 28 50         switch(argop->op_type) {
    100          
46             case OP_PADSV:
47             case OP_RV2SV:
48             case OP_HELEM:
49             case OP_AELEM:
50             return TRUE;
51             }
52              
53             return FALSE;
54             }
55              
56 27           static OP *build_inplace_coreop(pTHX_ OP *op)
57             {
58             /* Turn EXPR... -> OP into EXPR... -> DUP -> OP -> SASSIGN */
59             /* The tree shape of this will be horrible */
60              
61 27           OP *expr = cUNOPx(op)->op_first;
62              
63 27 100         if(!arg_is_acceptable(expr))
64 1           croak("Cannot use %s as an argument to an inplace operator", PL_op_name[expr->op_type]);
65              
66             /* Thread the DUP op in without it appearing structurally */
67             OP *dupop = newDUPOP(OPf_REF);
68              
69 26           dupop->op_next = expr->op_next;
70 26           expr->op_next = dupop;
71              
72             /* This really weird OP_SASSIGN is a binop with only one child. Don't worry.
73             * At runtime it will still see two SVs because of the DUP; but they'll be
74             * in the wrong order so we'll have to swap them */
75 26           OP *assignop = newBINOP(OP_SASSIGN, (OPpASSIGN_BACKWARDS << 8), op, newOP(OP_NULL, 0));
76              
77 26           assignop->op_next = op->op_next;
78 26           op->op_next = assignop;
79              
80 26           return assignop;
81             }
82              
83 3           static OP *build_inplace_entersub(pTHX_ OP *op)
84             {
85 3           OP *args = cUNOPx(op)->op_first;
86              
87 3 50         if(!args->op_type && args->op_targ == OP_LIST)
    50          
88 3           args = cLISTOPx(args)->op_first;
89              
90             assert(args->op_type == OP_PUSHMARK);
91              
92 3 50         OP *arg = OpSIBLING(args);
93              
94             /* If this op has a single argument then OpSIBLING of arg will be set,
95             * but OpSIBLING of that will be NULL
96             */
97              
98 3 100         if(!OpSIBLING(arg))
    50          
99 1           croak("Cannot use a function call with no arguments as an inplace operator");
100 2 50         if(OpSIBLING(OpSIBLING(arg)))
    100          
    50          
    50          
101 1           croak("Cannot use a function call with more than one argument as an inplace operator");
102              
103 1 50         if(!arg_is_acceptable(arg))
104 0           croak("Cannot use %s as an argument to an inplace operator", PL_op_name[arg->op_type]);
105              
106 1 50         OP *start = LINKLIST(op);
107 1           op->op_next = start;
108              
109             /* Thread the DUP op in without it appearing structurally */
110             OP *dupop = newDUPOP(OPf_REF | (OPpDUP_MOVEMARK << 8));
111              
112 1           dupop->op_next = arg->op_next;
113 1           arg->op_next = dupop;
114              
115             /* This really weird OP_SASSIGN is a binop with only one child. Don't worry.
116             * At runtime it will still see two SVs because of the DUP; but they'll be
117             * in the wrong order so we'll have to swap them */
118 1           OP *assignop = newBINOP(OP_SASSIGN, (OPpASSIGN_BACKWARDS << 8), op, newOP(OP_NULL, 0));
119              
120 1           assignop->op_next = op->op_next;
121 1           op->op_next = assignop;
122              
123 1           return assignop;
124             }
125              
126 32           static int build_inplace(pTHX_ OP **out, XSParseKeywordPiece *arg0, void *hookdata)
127             {
128 32           OP *op = arg0->op;
129 32           OPCODE optype = op->op_type;
130              
131             #if 0
132             warn("Initial optree:\n");
133             op_dump(op);
134             #endif
135              
136             /* Any retscalar + unop is fine */
137 32 100         if((PL_opargs[optype] & OA_RETSCALAR) &&
    50          
138 29           ((PL_opargs[optype] & OA_CLASS_MASK) == OA_UNOP))
139 0           *out = build_inplace_coreop(aTHX_ op);
140             /* Any retscalar baseop_or_unop is fine provided it has a kid */
141 32 100         else if((PL_opargs[optype] & OA_RETSCALAR) &&
    100          
142 29           ((PL_opargs[optype] & OA_CLASS_MASK) == OA_BASEOP_OR_UNOP)) {
143 27 50         if(!(op->op_flags & OPf_KIDS))
144 0           croak("Cannot use %s as an inplace operator without an expression", PL_op_name[optype]);
145 27           *out = build_inplace_coreop(aTHX_ op);
146             }
147             /* We'll possibly allow entersub but only of a single argument */
148 5 100         else if(optype == OP_ENTERSUB)
149 3           *out = build_inplace_entersub(aTHX_ op);
150             else
151 2           croak("Cannot use %s as an inplace operator", PL_op_name[optype]);
152              
153             #if 0
154             warn("Final optree\n");
155             op_dump(*out);
156             #endif
157              
158 27           return KEYWORD_PLUGIN_EXPR;
159             }
160              
161             static const struct XSParseKeywordHooks hooks_inplace = {
162             .permit_hintkey = "Syntax::Keyword::Inplace/inplace",
163             .piece1 = XPK_TERMEXPR,
164             .build1 = &build_inplace,
165             };
166              
167             MODULE = Syntax::Keyword::Inplace PACKAGE = Syntax::Keyword::Inplace
168              
169             BOOT:
170 4           boot_xs_parse_keyword(0.13);
171              
172 4           XopENTRY_set(&xop_dup, xop_name, "dup");
173 4           XopENTRY_set(&xop_dup, xop_desc, "duplicate");
174 4           XopENTRY_set(&xop_dup, xop_class, OA_UNOP);
175 4           Perl_custom_op_register(aTHX_ &pp_dup, &xop_dup);
176              
177             register_xs_parse_keyword("inplace", &hooks_inplace, NULL);