File Coverage

include/destructure.h
Criterion Covered Total %
statement 249 283 87.9
branch 139 190 73.1
condition n/a
subroutine n/a
pod n/a
total 388 473 82.0


line stmt bran cond sub pod time code
1             #ifndef DD_DESTRUCTURE_H
2             #define DD_DESTRUCTURE_H
3              
4             #define DD_MAX_ELEMS 4096
5             /* ---- pattern model ---------------------------------------------------------
6             *
7             * A `let` PATTERN is a tree. Each element is one of:
8             * DD_SCALAR - bind a $var (optionally with a // default expr)
9             * DD_HOLE - `undef` placeholder; consume a slot, bind nothing
10             * DD_SLURPY - trailing @rest / %rest
11             * DD_NESTED - a nested [..] / {..} pattern
12             *
13             * A pattern node is either an array pattern (keyed by position) or a hash
14             * pattern (keyed by name). Hash-pattern elements additionally carry `key`.
15             */
16              
17             #define DD_ARRAY 0 /* [ ... ] over an arrayref */
18             #define DD_HASH 1 /* { ... } over a hashref */
19             #define DD_LIST 2 /* ( ... ) over a list (list-context RHS) */
20              
21             /* Array-indexed shapes (positional element access): everything but a hash. */
22             #define DD_IS_ARRAYLIKE(shape) ((shape) != DD_HASH)
23              
24             #define DD_SCALAR 0
25             #define DD_HOLE 1
26             #define DD_SLURPY 2
27             #define DD_NESTED 3
28              
29             struct dd_pat; /* fwd */
30              
31             typedef struct dd_elem {
32             int kind; /* DD_SCALAR / DD_HOLE / DD_SLURPY / DD_NESTED */
33             SV *name; /* DD_SCALAR/DD_SLURPY: the lexical name incl. sigil */
34             char sigil; /* DD_SLURPY: '@' or '%' */
35             SV *key; /* hash-pattern element: the key (string), else NULL */
36             OP *deflt; /* DD_SCALAR: optional default expr op, else NULL */
37             struct dd_pat *nested; /* DD_NESTED: child pattern, else NULL */
38             } dd_elem;
39              
40             typedef struct dd_pat {
41             int shape; /* DD_ARRAY / DD_HASH / DD_LIST */
42             int n;
43             dd_elem elems[DD_MAX_ELEMS];
44             } dd_pat;
45              
46              
47             /* Read a bareword identifier from the lexer (or NULL if none). */
48 133           static SV *dd_lex_ident(pTHX) {
49 133           SV *buf = newSVpvs("");
50             I32 c;
51             while (1) {
52 412           c = lex_peek_unichar(0);
53 412 50         if (c == -1) break;
54 412 50         if (!isALNUM(c) && c != '_') break;
    100          
    50          
55 279           sv_catpvf(buf, "%c", (int)c);
56 279           lex_read_unichar(0);
57             }
58 133 100         if (SvCUR(buf) == 0) { SvREFCNT_dec(buf); return NULL; }
59 132           return buf;
60             }
61              
62             /* Hand-lex a quoted string literal ('...' or "..."), basic backslash escapes. */
63 1           static SV *dd_lex_string(pTHX) {
64 1           I32 quote = lex_read_unichar(0);
65 1           SV *sv = newSVpvs("");
66             I32 c;
67             while (1) {
68 11           c = lex_read_unichar(0);
69 11 50         if (c == -1) croak("let: unterminated string in pattern key");
70 11 50         if (c == '\\') {
71 0           I32 next = lex_read_unichar(0);
72 0 0         if (next == -1) croak("let: unterminated string in pattern key");
73 0 0         if (quote == '"') {
74 0           switch (next) {
75 0           case 'n': sv_catpvs(sv, "\n"); break;
76 0           case 't': sv_catpvs(sv, "\t"); break;
77 0           case 'r': sv_catpvs(sv, "\r"); break;
78 0           case '0': sv_catpvs(sv, "\0"); break;
79 0           default: sv_catpvf(sv, "%c", (int)next); break;
80             }
81             } else {
82 0 0         if (next != '\\' && next != '\'')
    0          
83 0           sv_catpvf(sv, "%c", '\\');
84 0           sv_catpvf(sv, "%c", (int)next);
85             }
86 11 100         } else if (c == quote) {
87 1           break;
88             } else {
89 10           sv_catpvf(sv, "%c", (int)c);
90             }
91             }
92 1           return sv;
93             }
94              
95             /* Read a sigil'd variable name ($foo, @foo, %foo) into an SV including the
96             * sigil; returns the sigil char via *sigil. Croaks if not a variable. */
97 108           static SV *dd_lex_var(pTHX_ char *sigil) {
98 108           I32 c = lex_peek_unichar(0);
99             SV *name, *id;
100 108 100         if (c != '$' && c != '@' && c != '%')
    100          
    50          
101 0           croak("let: expected a variable in pattern");
102 108           *sigil = (char)c;
103 108           lex_read_unichar(0);
104 108           id = dd_lex_ident(aTHX);
105 108 50         if (!id) croak("let: expected an identifier after '%c'", (int)*sigil);
106 108           name = newSVpvf("%c%" SVf, (int)*sigil, SVfARG(id));
107 108           SvREFCNT_dec(id);
108 108           return name;
109             }
110              
111             /* A fresh PADSV read op for the pad slot `off`. */
112 101           static OP *dd_padsv(pTHX_ PADOFFSET off) {
113 101           OP *o = newOP(OP_PADSV, 0);
114 101           o->op_targ = off;
115 101           return o;
116             }
117              
118             /* `$src->[idx]` : aelem over rv2av(OPf_REF, padsv(src)). */
119 34           static OP *dd_aelem(pTHX_ PADOFFSET src, IV idx) {
120 34           OP *deref = newUNOP(OP_RV2AV, OPf_REF, dd_padsv(aTHX_ src));
121 34           OP *key = newSVOP(OP_CONST, 0, newSViv(idx));
122 34           return newBINOP(OP_AELEM, 0, deref, key);
123             }
124              
125             /* `$src->{key}` : helem over rv2hv(OPf_REF, padsv(src)). */
126 23           static OP *dd_helem(pTHX_ PADOFFSET src, SV *key) {
127 23           OP *deref = newUNOP(OP_RV2HV, OPf_REF, dd_padsv(aTHX_ src));
128 23           OP *kop = newSVOP(OP_CONST, 0, newSVsv(key));
129 23           return newBINOP(OP_HELEM, 0, deref, kop);
130             }
131              
132             /* ---- tail() custom op ------------------------------------------------------
133             * A trailing `@rest` in an array pattern binds the elements of the source aref
134             * from index N onward. Rather than hand-wire a fragile range/slice optree, it
135             * is a self-contained custom op (same family as Switch::Declare's reftype op):
136             * evaluate the aref and N onto the stack, then replace them with the tail list.
137             * A non-aref source yields the empty list (no warning). */
138             static XOP dd_tail_xop;
139              
140 0           static OP *dd_pp_tail(pTHX) {
141 0           dSP;
142 0           IV n = POPi; /* second child: the start index */
143 0           SV *rv = POPs; /* first child: the source ref */
144             AV *av;
145             IV i, top;
146 0 0         if (!SvROK(rv) || SvTYPE(SvRV(rv)) != SVt_PVAV) {
    0          
147 0           RETURN; /* not an aref -> empty list */
148             }
149 0           av = (AV *)SvRV(rv);
150 0           top = av_len(av); /* last valid index, -1 if empty */
151 0 0         if (n < 0) n = 0;
152 0 0         EXTEND(SP, top - n + 1);
    0          
153 0 0         for (i = n; i <= top; i++) {
154 0           SV **el = av_fetch(av, i, 0);
155 0 0         PUSHs(el ? *el : &PL_sv_undef);
156             }
157 0           RETURN;
158             }
159              
160             /* @{$src}[idx .. end] as a custom op: NULL->CUSTOM over (padsv(src), const idx). */
161 0           static OP *dd_tail_op(pTHX_ PADOFFSET src, IV idx) {
162 0           OP *o = newBINOP(OP_NULL, OPf_WANT_LIST,
163             dd_padsv(aTHX_ src),
164             newSVOP(OP_CONST, 0, newSViv(idx)));
165 0           o->op_type = OP_CUSTOM;
166 0           o->op_ppaddr = dd_pp_tail;
167 0           return o;
168             }
169              
170             /* ---- hash %rest custom op --------------------------------------------------
171             * A trailing `%rest` in a hash pattern binds every key of the source hashref
172             * that was NOT named by an earlier element. Built as a list-shaped custom op:
173             * ( PUSHMARK, padsv(src), const key1, const key2, ... )
174             * At run time the source ref is the first stack item above the mark and the
175             * named keys follow; we replace them all with the surviving key => value list.
176             * A non-href source yields the empty list (no warning). */
177             static XOP dd_hrest_xop;
178              
179 5           static OP *dd_pp_hrest(pTHX) {
180 5           dSP; dMARK;
181 5           SV **items = MARK + 1;
182 5           IV nitems = (IV)(SP - MARK); /* href + excluded keys */
183 5 50         SV *rv = nitems > 0 ? items[0] : &PL_sv_undef;
184 5           IV nexcl = nitems > 0 ? nitems - 1 : 0;
185 5           SV **excl = items + 1; /* still valid until we overwrite below */
186             HV *hv;
187             HE *he;
188              
189 5 100         if (!SvROK(rv) || SvTYPE(SvRV(rv)) != SVt_PVHV) {
    50          
190 1           SP = MARK; /* not a href -> empty list */
191 1           PUTBACK;
192 1           return NORMAL;
193             }
194 4           hv = (HV *)SvRV(rv);
195              
196             /* Copy the excluded-key SV pointers out before we reset SP over them. */
197             {
198             IV i;
199 4           SV **keep = NULL;
200 4 100         Newx(keep, nexcl ? nexcl : 1, SV *);
    50          
    100          
201 8 100         for (i = 0; i < nexcl; i++) keep[i] = excl[i];
202              
203 4           SP = MARK; /* drop href + excluded keys */
204 4 50         EXTEND(SP, 2 * (IV)HvUSEDKEYS(hv));
    50          
    50          
    50          
    0          
205 4           hv_iterinit(hv);
206 14 100         while ((he = hv_iternext(hv))) {
207 10           SV *k = hv_iterkeysv(he); /* mortal */
208 10           int skip = 0;
209 17 100         for (i = 0; i < nexcl; i++) {
210 11 100         if (sv_eq(k, keep[i])) { skip = 1; break; }
211             }
212 10 100         if (skip) continue;
213 6           PUSHs(k);
214 6           PUSHs(hv_iterval(hv, he));
215             }
216 4           Safefree(keep);
217             }
218 4           PUTBACK;
219 4           return NORMAL;
220             }
221              
222             /* Build the %rest op for hash pattern `pat`, reading from `src`, excluding the
223             * keys of every non-slurpy element. */
224 5           static OP *dd_hrest_op(pTHX_ PADOFFSET src, dd_pat *pat) {
225             OP *list;
226             int i;
227 5           list = op_prepend_elem(OP_LIST, newOP(OP_PUSHMARK, 0), dd_padsv(aTHX_ src));
228 14 100         for (i = 0; i < pat->n; i++) {
229 9           dd_elem *el = &pat->elems[i];
230 9 100         if (el->kind == DD_SLURPY) continue;
231 4 50         if (el->key)
232 4           list = op_append_elem(OP_LIST, list,
233             newSVOP(OP_CONST, 0, newSVsv(el->key)));
234             }
235 5           list->op_type = OP_CUSTOM;
236 5           list->op_ppaddr = dd_pp_hrest;
237 5           list->op_flags |= OPf_WANT_LIST;
238 5           return list;
239             }
240              
241             /* ---- the parser ------------------------------------------------------------ */
242              
243             static void dd_parse_pattern(pTHX_ dd_pat *pat);
244              
245             /* Parse one element of an array/hash pattern into *el. `shape` says which. */
246 130           static void dd_parse_elem(pTHX_ dd_elem *el, int shape) {
247             I32 c;
248 130           el->kind = DD_SCALAR;
249 130           el->name = NULL; el->key = NULL; el->deflt = NULL; el->nested = NULL;
250 130           el->sigil = 0;
251              
252 130           lex_read_space(0);
253              
254             /* hash pattern: either a trailing %rest slurpy, or KEY => ... */
255 130 100         if (shape == DD_HASH) {
256             SV *key;
257 31           c = lex_peek_unichar(0);
258 31 100         if (c == '%') { /* %rest: remaining keys */
259             char sigil;
260 5           el->name = dd_lex_var(aTHX_ &sigil);
261 5           el->sigil = sigil;
262 5           el->kind = DD_SLURPY;
263 5           return;
264             }
265 26 100         if (c == '"' || c == '\'')
    50          
266 1           key = dd_lex_string(aTHX);
267             else {
268 25           key = dd_lex_ident(aTHX);
269 25 100         if (!key) croak("let: expected a key in hash pattern");
270             }
271 25           el->key = key;
272 25           lex_read_space(0);
273             /* expect => (fat comma) */
274 25 100         if (lex_peek_unichar(0) != '=' ||
275 24 50         PL_parser->bufptr[1] != '>')
276 1           croak("let: expected '=>' after key '%" SVf "'", SVfARG(key));
277 24           lex_read_unichar(0); /* = */
278 24           lex_read_unichar(0); /* > */
279 24           lex_read_space(0);
280             }
281              
282 123           c = lex_peek_unichar(0);
283              
284             /* nested pattern */
285 123 100         if (c == '[' || c == '{') {
    100          
286 13           Newxz(el->nested, 1, dd_pat);
287 13           el->kind = DD_NESTED;
288 13           dd_parse_pattern(aTHX_ el->nested);
289 13           return;
290             }
291              
292             /* hole: `undef` (positional patterns only - a hash key with no binding
293             * makes no sense) */
294 110 100         if (DD_IS_ARRAYLIKE(shape) && (c == 'u')) {
    100          
295             /* peek the word `undef` */
296 7 50         if (strnEQ(PL_parser->bufptr, "undef", 5)) {
297 7           char after = PL_parser->bufptr[5];
298 7 50         if (!isALNUM(after) && after != '_') {
    50          
299             int i;
300 42 100         for (i = 0; i < 5; i++) lex_read_unichar(0);
301 7           el->kind = DD_HOLE;
302 7           return;
303             }
304             }
305             }
306              
307             /* a variable: $x (scalar, with optional default) or @rest/%rest (slurpy) */
308             {
309             char sigil;
310 103           el->name = dd_lex_var(aTHX_ &sigil);
311 103           el->sigil = sigil;
312 103 100         if (sigil == '@' || sigil == '%') {
    100          
313 11           el->kind = DD_SLURPY;
314 11           return;
315             }
316             /* scalar - optional `= DEFAULT` (but not the `=>` of the next pair) */
317 92           lex_read_space(0);
318 92           c = lex_peek_unichar(0);
319 92 100         if (c == '=' && PL_parser->bufptr[1] != '>') {
    50          
320 10           lex_read_unichar(0); /* = */
321 10           lex_read_space(0);
322 10           el->deflt = parse_termexpr(0);
323             }
324             }
325             }
326              
327             /* Parse a whole [..] or {..} pattern (leading bracket at the cursor). */
328 77           static void dd_parse_pattern(pTHX_ dd_pat *pat) {
329             I32 open, close;
330 77           pat->n = 0;
331 77           lex_read_space(0);
332 77           open = lex_peek_unichar(0);
333 77 100         if (open == '[') { pat->shape = DD_ARRAY; close = ']'; }
334 29 100         else if (open == '{') { pat->shape = DD_HASH; close = '}'; }
335 9 100         else if (open == '(') { pat->shape = DD_LIST; close = ')'; }
336 1           else croak("let: expected '[', '{' or '(' to open a pattern");
337 76           lex_read_unichar(0);
338              
339 76           lex_read_space(0);
340 76 100         if (lex_peek_unichar(0) == close) { lex_read_unichar(0); return; }
341              
342 57           while (1) {
343             dd_elem *el;
344             I32 c;
345 130 50         if (pat->n >= DD_MAX_ELEMS) croak("let: too many pattern elements");
346 130           el = &pat->elems[pat->n];
347 130           dd_parse_elem(aTHX_ el, pat->shape);
348 128           pat->n++;
349              
350             /* a slurpy @rest / %rest must be the final element */
351 128 100         if (el->kind == DD_SLURPY) {
352 16           lex_read_space(0);
353 16 100         if (lex_peek_unichar(0) != close)
354 1           croak("let: slurpy '%" SVf "' must be the last pattern element",
355             SVfARG(el->name));
356 15           lex_read_unichar(0);
357 15           return;
358             }
359              
360 112           lex_read_space(0);
361 112           c = lex_peek_unichar(0);
362 112 100         if (c == ',') {
363 58           lex_read_unichar(0);
364 58           lex_read_space(0);
365 58 100         if (lex_peek_unichar(0) == close) { lex_read_unichar(0); return; }
366 57           continue;
367             }
368 54 100         if (c == close) { lex_read_unichar(0); return; }
369 1           croak("let: expected ',' or '%c' in pattern", (int)close);
370             }
371             }
372              
373             /* ---- codegen ---------------------------------------------------------------
374             *
375             * Emit binding statements for `pat`, reading from the pad temp `src` (which
376             * already holds the arrayref/hashref to destructure). Appends OP_LINESEQ
377             * STATEOPs onto *seqp. `uniq` is bumped to make hidden temp names unique. */
378              
379             static unsigned long dd_seq = 0;
380              
381 49           static OP *dd_make_my(pTHX_ SV *name, OP *rhs) {
382 49           PADOFFSET off = pad_add_name_pvn(SvPVX(name), SvCUR(name), 0, NULL, NULL);
383             OP *lhs;
384             /* For @/% the lvalue is a PADAV/PADHV; for $ a PADSV. */
385 49           char sigil = SvPVX(name)[0];
386 49 50         if (sigil == '@') { lhs = newOP(OP_PADAV, 0); }
387 49 100         else if (sigil == '%') { lhs = newOP(OP_PADHV, 0); }
388 44           else { lhs = newOP(OP_PADSV, 0); }
389 49           lhs->op_targ = off;
390 49           lhs->op_private |= OPpLVAL_INTRO; /* my */
391 49           return newASSIGNOP(OPf_STACKED, lhs, 0, rhs);
392             }
393              
394             /* Allocate an unnamed (user-invisible) pad temp and return its offset. */
395 39           static PADOFFSET dd_temp(pTHX) {
396             char buf[64];
397 39           int n = my_snprintf(buf, sizeof(buf), "$_Destructure_Declare_t%lu", dd_seq++);
398 39           return pad_add_name_pvn(buf, (STRLEN)n, 0, NULL, NULL);
399             }
400              
401             static void dd_emit(pTHX_ dd_pat *pat, PADOFFSET src, OP **seqp);
402              
403             /* Build the RHS op that reads pattern element `el` (at array index `idx`)
404             * from source ref `src`, applying any default. */
405 44           static OP *dd_elem_rhs(pTHX_ dd_pat *pat, dd_elem *el, IV idx, PADOFFSET src) {
406 28           OP *get = DD_IS_ARRAYLIKE(pat->shape) ? dd_aelem(aTHX_ src, idx)
407 44 100         : dd_helem(aTHX_ src, el->key);
408 44 100         if (el->deflt) {
409             /* $src->[idx] // DEFAULT */
410 10           get = newLOGOP(OP_DOR, 0, get, el->deflt);
411 10           el->deflt = NULL; /* consumed */
412             }
413 44           return get;
414             }
415              
416             /* Slurpy RHS: the remaining contents as a list.
417             * array/list @rest : the tail elements from `idx` onward
418             * array/list %rest : the same tail, assigned as key => value pairs
419             * hash %rest : every source key not named by an earlier element
420             * hash @rest : meaningless (a hash has no positional tail) -> error
421             */
422 5           static OP *dd_slurpy_rhs(pTHX_ dd_pat *pat, dd_elem *el, IV idx, PADOFFSET src) {
423 5 50         if (pat->shape == DD_HASH) {
424 5 50         if (el->sigil == '%')
425 5           return dd_hrest_op(aTHX_ src, pat);
426 0           croak("let: '%" SVf "' is invalid in a hash pattern (use a %%rest slurpy)",
427             SVfARG(el->name));
428             }
429             /* array / list: @rest or %rest both take the positional tail. */
430 0           return dd_tail_op(aTHX_ src, idx);
431             }
432              
433 39           static void dd_emit(pTHX_ dd_pat *pat, PADOFFSET src, OP **seqp) {
434             int i;
435 39           IV idx = 0;
436 103 100         for (i = 0; i < pat->n; i++) {
437 64           dd_elem *el = &pat->elems[i];
438 64           switch (el->kind) {
439 2           case DD_HOLE:
440 2           idx++;
441 2           break;
442 44           case DD_SCALAR: {
443 44           OP *rhs = dd_elem_rhs(aTHX_ pat, el, idx, src);
444 44           OP *stmt = newSTATEOP(0, NULL, dd_make_my(aTHX_ el->name, rhs));
445 44           *seqp = op_append_list(OP_LINESEQ, *seqp, stmt);
446 44           idx++;
447 44           break;
448             }
449 5           case DD_SLURPY: {
450 5           OP *rhs = dd_slurpy_rhs(aTHX_ pat, el, idx, src);
451 5           OP *stmt = newSTATEOP(0, NULL, dd_make_my(aTHX_ el->name, rhs));
452 5           *seqp = op_append_list(OP_LINESEQ, *seqp, stmt);
453 5           break;
454             }
455 13           case DD_NESTED: {
456             /* my $tN = $src->[idx] (or ->{key}); then recurse with src=$tN */
457 13           PADOFFSET t = dd_temp(aTHX);
458 6           OP *get = DD_IS_ARRAYLIKE(pat->shape) ? dd_aelem(aTHX_ src, idx)
459 13 100         : dd_helem(aTHX_ src, el->key);
460 13           OP *lhs = dd_padsv(aTHX_ t);
461             OP *stmt;
462 13           lhs->op_private |= OPpLVAL_INTRO;
463 13           stmt = newSTATEOP(0, NULL, newASSIGNOP(OPf_STACKED, lhs, 0, get));
464 13           *seqp = op_append_list(OP_LINESEQ, *seqp, stmt);
465 13           dd_emit(aTHX_ el->nested, t, seqp);
466 13           idx++;
467 13           break;
468             }
469             }
470             }
471 39           }
472              
473 71           static void dd_free_pat(pTHX_ dd_pat *pat) {
474             int i;
475 194 100         for (i = 0; i < pat->n; i++) {
476 123           dd_elem *el = &pat->elems[i];
477 123 100         if (el->name) SvREFCNT_dec(el->name);
478 123 100         if (el->key) SvREFCNT_dec(el->key);
479 123 50         if (el->deflt) op_free(el->deflt);
480 123 100         if (el->nested) { dd_free_pat(aTHX_ el->nested); Safefree(el->nested); }
481             }
482 71           }
483              
484             /* ---- fast path: flat patterns lower to one native list-assignment ----------
485             *
486             * A flat array/list pattern - every element a plain scalar or `undef` hole,
487             * with at most one trailing slurpy, and no defaults or nested patterns - is
488             * exactly what a single `my (...) = ...` list-assignment expresses. Emitting
489             * that one aassign (instead of a temp plus N per-element scalar assignments)
490             * matches hand-written native speed. Hash patterns, defaults and nesting still
491             * take the general per-element dd_emit() path. */
492 58           static int dd_is_listassign(dd_pat *pat) {
493             int i;
494 58 100         if (pat->shape == DD_HASH) return 0;
495 107 100         for (i = 0; i < pat->n; i++) {
496 75           dd_elem *el = &pat->elems[i];
497 75 100         if (el->kind == DD_SCALAR) { if (el->deflt) return 0; }
    100          
498 20 100         else if (el->kind == DD_HOLE) { /* -> undef in the LHS list */ }
499 14 100         else if (el->kind == DD_SLURPY) { if (i != pat->n - 1) return 0; }
    50          
500 4           else return 0; /* DD_NESTED */
501             }
502 32           return 1;
503             }
504              
505             /* Build the `my (LHS)` list: padsv/padav/padhv with OPpLVAL_INTRO, OP_UNDEF for
506             * holes, the whole list flagged OPf_PARENS so newASSIGNOP makes a list assign. */
507 32           static OP *dd_listassign_lhs(pTHX_ dd_pat *pat) {
508 32           OP *list = newLISTOP(OP_LIST, 0, NULL, NULL);
509             int i;
510 91 100         for (i = 0; i < pat->n; i++) {
511 59           dd_elem *el = &pat->elems[i];
512             OP *v;
513 59 100         if (el->kind == DD_HOLE) {
514 5           v = newOP(OP_UNDEF, 0);
515             } else {
516 54           PADOFFSET off = pad_add_name_pvn(SvPVX(el->name), SvCUR(el->name),
517             0, NULL, NULL);
518 54           char sigil = SvPVX(el->name)[0];
519 54 100         if (sigil == '@') v = newOP(OP_PADAV, 0);
520 45 100         else if (sigil == '%') v = newOP(OP_PADHV, 0);
521 44           else v = newOP(OP_PADSV, 0);
522 54           v->op_targ = off;
523 54           v->op_private |= OPpLVAL_INTRO;
524             }
525 59           list = op_append_elem(OP_LIST, list, v);
526             }
527 32           list->op_flags |= OPf_PARENS;
528 32           return list;
529             }
530              
531             /* An empty arrayref `[]`, used to guard an undef array source so that, like the
532             * per-element path, an undef source binds empties rather than dying on @{undef}. */
533 26           static OP *dd_empty_aref(pTHX) {
534 26           return op_convert_list(OP_ANONLIST, OPf_SPECIAL, NULL);
535             }
536              
537             #endif /* DD_DESTRUCTURE_H */