File Coverage

/usr/local/lib/perl5/site_perl/5.42.0/x86_64-linux/Destructure/Declare/Install/destructure.h
Criterion Covered Total %
statement 227 283 80.2
branch 113 190 59.4
condition n/a
subroutine n/a
pod n/a
total 340 473 71.8


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 19           static SV *dd_lex_ident(pTHX) {
49 19           SV *buf = newSVpvs("");
50             I32 c;
51             while (1) {
52 62           c = lex_peek_unichar(0);
53 62 50         if (c == -1) break;
54 62 50         if (!isALNUM(c) && c != '_') break;
    100          
    50          
55 43           sv_catpvf(buf, "%c", (int)c);
56 43           lex_read_unichar(0);
57             }
58 19 50         if (SvCUR(buf) == 0) { SvREFCNT_dec(buf); return NULL; }
59 19           return buf;
60             }
61              
62             /* Hand-lex a quoted string literal ('...' or "..."), basic backslash escapes. */
63 0           static SV *dd_lex_string(pTHX) {
64 0           I32 quote = lex_read_unichar(0);
65 0           SV *sv = newSVpvs("");
66             I32 c;
67             while (1) {
68 0           c = lex_read_unichar(0);
69 0 0         if (c == -1) croak("let: unterminated string in pattern key");
70 0 0         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 0 0         } else if (c == quote) {
87 0           break;
88             } else {
89 0           sv_catpvf(sv, "%c", (int)c);
90             }
91             }
92 0           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 15           static SV *dd_lex_var(pTHX_ char *sigil) {
98 15           I32 c = lex_peek_unichar(0);
99             SV *name, *id;
100 15 100         if (c != '$' && c != '@' && c != '%')
    100          
    50          
101 0           croak("let: expected a variable in pattern");
102 15           *sigil = (char)c;
103 15           lex_read_unichar(0);
104 15           id = dd_lex_ident(aTHX);
105 15 50         if (!id) croak("let: expected an identifier after '%c'", (int)*sigil);
106 15           name = newSVpvf("%c%" SVf, (int)*sigil, SVfARG(id));
107 15           SvREFCNT_dec(id);
108 15           return name;
109             }
110              
111             /* A fresh PADSV read op for the pad slot `off`. */
112 15           static OP *dd_padsv(pTHX_ PADOFFSET off) {
113 15           OP *o = newOP(OP_PADSV, 0);
114 15           o->op_targ = off;
115 15           return o;
116             }
117              
118             /* `$src->[idx]` : aelem over rv2av(OPf_REF, padsv(src)). */
119 4           static OP *dd_aelem(pTHX_ PADOFFSET src, IV idx) {
120 4           OP *deref = newUNOP(OP_RV2AV, OPf_REF, dd_padsv(aTHX_ src));
121 4           OP *key = newSVOP(OP_CONST, 0, newSViv(idx));
122 4           return newBINOP(OP_AELEM, 0, deref, key);
123             }
124              
125             /* `$src->{key}` : helem over rv2hv(OPf_REF, padsv(src)). */
126 4           static OP *dd_helem(pTHX_ PADOFFSET src, SV *key) {
127 4           OP *deref = newUNOP(OP_RV2HV, OPf_REF, dd_padsv(aTHX_ src));
128 4           OP *kop = newSVOP(OP_CONST, 0, newSVsv(key));
129 4           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 1           static OP *dd_pp_hrest(pTHX) {
180 1           dSP; dMARK;
181 1           SV **items = MARK + 1;
182 1           IV nitems = (IV)(SP - MARK); /* href + excluded keys */
183 1 50         SV *rv = nitems > 0 ? items[0] : &PL_sv_undef;
184 1           IV nexcl = nitems > 0 ? nitems - 1 : 0;
185 1           SV **excl = items + 1; /* still valid until we overwrite below */
186             HV *hv;
187             HE *he;
188              
189 1 50         if (!SvROK(rv) || SvTYPE(SvRV(rv)) != SVt_PVHV) {
    50          
190 0           SP = MARK; /* not a href -> empty list */
191 0           PUTBACK;
192 0           return NORMAL;
193             }
194 1           hv = (HV *)SvRV(rv);
195              
196             /* Copy the excluded-key SV pointers out before we reset SP over them. */
197             {
198             IV i;
199 1           SV **keep = NULL;
200 1 50         Newx(keep, nexcl ? nexcl : 1, SV *);
    50          
    50          
201 2 100         for (i = 0; i < nexcl; i++) keep[i] = excl[i];
202              
203 1           SP = MARK; /* drop href + excluded keys */
204 1 50         EXTEND(SP, 2 * (IV)HvUSEDKEYS(hv));
    50          
    50          
    50          
    0          
205 1           hv_iterinit(hv);
206 4 100         while ((he = hv_iternext(hv))) {
207 3           SV *k = hv_iterkeysv(he); /* mortal */
208 3           int skip = 0;
209 5 100         for (i = 0; i < nexcl; i++) {
210 3 100         if (sv_eq(k, keep[i])) { skip = 1; break; }
211             }
212 3 100         if (skip) continue;
213 2           PUSHs(k);
214 2           PUSHs(hv_iterval(hv, he));
215             }
216 1           Safefree(keep);
217             }
218 1           PUTBACK;
219 1           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 1           static OP *dd_hrest_op(pTHX_ PADOFFSET src, dd_pat *pat) {
225             OP *list;
226             int i;
227 1           list = op_prepend_elem(OP_LIST, newOP(OP_PUSHMARK, 0), dd_padsv(aTHX_ src));
228 3 100         for (i = 0; i < pat->n; i++) {
229 2           dd_elem *el = &pat->elems[i];
230 2 100         if (el->kind == DD_SLURPY) continue;
231 1 50         if (el->key)
232 1           list = op_append_elem(OP_LIST, list,
233             newSVOP(OP_CONST, 0, newSVsv(el->key)));
234             }
235 1           list->op_type = OP_CUSTOM;
236 1           list->op_ppaddr = dd_pp_hrest;
237 1           list->op_flags |= OPf_WANT_LIST;
238 1           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 18           static void dd_parse_elem(pTHX_ dd_elem *el, int shape) {
247             I32 c;
248 18           el->kind = DD_SCALAR;
249 18           el->name = NULL; el->key = NULL; el->deflt = NULL; el->nested = NULL;
250 18           el->sigil = 0;
251              
252 18           lex_read_space(0);
253              
254             /* hash pattern: either a trailing %rest slurpy, or KEY => ... */
255 18 100         if (shape == DD_HASH) {
256             SV *key;
257 5           c = lex_peek_unichar(0);
258 5 100         if (c == '%') { /* %rest: remaining keys */
259             char sigil;
260 1           el->name = dd_lex_var(aTHX_ &sigil);
261 1           el->sigil = sigil;
262 1           el->kind = DD_SLURPY;
263 1           return;
264             }
265 4 50         if (c == '"' || c == '\'')
    50          
266 0           key = dd_lex_string(aTHX);
267             else {
268 4           key = dd_lex_ident(aTHX);
269 4 50         if (!key) croak("let: expected a key in hash pattern");
270             }
271 4           el->key = key;
272 4           lex_read_space(0);
273             /* expect => (fat comma) */
274 4 50         if (lex_peek_unichar(0) != '=' ||
275 4 50         PL_parser->bufptr[1] != '>')
276 0           croak("let: expected '=>' after key '%" SVf "'", SVfARG(key));
277 4           lex_read_unichar(0); /* = */
278 4           lex_read_unichar(0); /* > */
279 4           lex_read_space(0);
280             }
281              
282 17           c = lex_peek_unichar(0);
283              
284             /* nested pattern */
285 17 100         if (c == '[' || c == '{') {
    50          
286 2           Newxz(el->nested, 1, dd_pat);
287 2           el->kind = DD_NESTED;
288 2           dd_parse_pattern(aTHX_ el->nested);
289 2           return;
290             }
291              
292             /* hole: `undef` (positional patterns only - a hash key with no binding
293             * makes no sense) */
294 15 100         if (DD_IS_ARRAYLIKE(shape) && (c == 'u')) {
    100          
295             /* peek the word `undef` */
296 1 50         if (strnEQ(PL_parser->bufptr, "undef", 5)) {
297 1           char after = PL_parser->bufptr[5];
298 1 50         if (!isALNUM(after) && after != '_') {
    50          
299             int i;
300 6 100         for (i = 0; i < 5; i++) lex_read_unichar(0);
301 1           el->kind = DD_HOLE;
302 1           return;
303             }
304             }
305             }
306              
307             /* a variable: $x (scalar, with optional default) or @rest/%rest (slurpy) */
308             {
309             char sigil;
310 14           el->name = dd_lex_var(aTHX_ &sigil);
311 14           el->sigil = sigil;
312 14 100         if (sigil == '@' || sigil == '%') {
    50          
313 1           el->kind = DD_SLURPY;
314 1           return;
315             }
316             /* scalar - optional `= DEFAULT` (but not the `=>` of the next pair) */
317 13           lex_read_space(0);
318 13           c = lex_peek_unichar(0);
319 13 100         if (c == '=' && PL_parser->bufptr[1] != '>') {
    50          
320 1           lex_read_unichar(0); /* = */
321 1           lex_read_space(0);
322 1           el->deflt = parse_termexpr(0);
323             }
324             }
325             }
326              
327             /* Parse a whole [..] or {..} pattern (leading bracket at the cursor). */
328 10           static void dd_parse_pattern(pTHX_ dd_pat *pat) {
329             I32 open, close;
330 10           pat->n = 0;
331 10           lex_read_space(0);
332 10           open = lex_peek_unichar(0);
333 10 100         if (open == '[') { pat->shape = DD_ARRAY; close = ']'; }
334 3 50         else if (open == '{') { pat->shape = DD_HASH; close = '}'; }
335 0 0         else if (open == '(') { pat->shape = DD_LIST; close = ')'; }
336 0           else croak("let: expected '[', '{' or '(' to open a pattern");
337 10           lex_read_unichar(0);
338              
339 10           lex_read_space(0);
340 10 50         if (lex_peek_unichar(0) == close) { lex_read_unichar(0); return; }
341              
342 8           while (1) {
343             dd_elem *el;
344             I32 c;
345 18 50         if (pat->n >= DD_MAX_ELEMS) croak("let: too many pattern elements");
346 18           el = &pat->elems[pat->n];
347 18           dd_parse_elem(aTHX_ el, pat->shape);
348 18           pat->n++;
349              
350             /* a slurpy @rest / %rest must be the final element */
351 18 100         if (el->kind == DD_SLURPY) {
352 2           lex_read_space(0);
353 2 50         if (lex_peek_unichar(0) != close)
354 0           croak("let: slurpy '%" SVf "' must be the last pattern element",
355             SVfARG(el->name));
356 2           lex_read_unichar(0);
357 2           return;
358             }
359              
360 16           lex_read_space(0);
361 16           c = lex_peek_unichar(0);
362 16 100         if (c == ',') {
363 8           lex_read_unichar(0);
364 8           lex_read_space(0);
365 8 50         if (lex_peek_unichar(0) == close) { lex_read_unichar(0); return; }
366 8           continue;
367             }
368 8 50         if (c == close) { lex_read_unichar(0); return; }
369 0           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 7           static OP *dd_make_my(pTHX_ SV *name, OP *rhs) {
382 7           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 7           char sigil = SvPVX(name)[0];
386 7 50         if (sigil == '@') { lhs = newOP(OP_PADAV, 0); }
387 7 100         else if (sigil == '%') { lhs = newOP(OP_PADHV, 0); }
388 6           else { lhs = newOP(OP_PADSV, 0); }
389 7           lhs->op_targ = off;
390 7           lhs->op_private |= OPpLVAL_INTRO; /* my */
391 7           return newASSIGNOP(OPf_STACKED, lhs, 0, rhs);
392             }
393              
394             /* Allocate an unnamed (user-invisible) pad temp and return its offset. */
395 6           static PADOFFSET dd_temp(pTHX) {
396             char buf[64];
397 6           int n = my_snprintf(buf, sizeof(buf), "$_Destructure_Declare_t%lu", dd_seq++);
398 6           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 6           static OP *dd_elem_rhs(pTHX_ dd_pat *pat, dd_elem *el, IV idx, PADOFFSET src) {
406 4           OP *get = DD_IS_ARRAYLIKE(pat->shape) ? dd_aelem(aTHX_ src, idx)
407 6 100         : dd_helem(aTHX_ src, el->key);
408 6 100         if (el->deflt) {
409             /* $src->[idx] // DEFAULT */
410 1           get = newLOGOP(OP_DOR, 0, get, el->deflt);
411 1           el->deflt = NULL; /* consumed */
412             }
413 6           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 1           static OP *dd_slurpy_rhs(pTHX_ dd_pat *pat, dd_elem *el, IV idx, PADOFFSET src) {
423 1 50         if (pat->shape == DD_HASH) {
424 1 50         if (el->sigil == '%')
425 1           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 6           static void dd_emit(pTHX_ dd_pat *pat, PADOFFSET src, OP **seqp) {
434             int i;
435 6           IV idx = 0;
436 16 100         for (i = 0; i < pat->n; i++) {
437 10           dd_elem *el = &pat->elems[i];
438 10           switch (el->kind) {
439 1           case DD_HOLE:
440 1           idx++;
441 1           break;
442 6           case DD_SCALAR: {
443 6           OP *rhs = dd_elem_rhs(aTHX_ pat, el, idx, src);
444 6           OP *stmt = newSTATEOP(0, NULL, dd_make_my(aTHX_ el->name, rhs));
445 6           *seqp = op_append_list(OP_LINESEQ, *seqp, stmt);
446 6           idx++;
447 6           break;
448             }
449 1           case DD_SLURPY: {
450 1           OP *rhs = dd_slurpy_rhs(aTHX_ pat, el, idx, src);
451 1           OP *stmt = newSTATEOP(0, NULL, dd_make_my(aTHX_ el->name, rhs));
452 1           *seqp = op_append_list(OP_LINESEQ, *seqp, stmt);
453 1           break;
454             }
455 2           case DD_NESTED: {
456             /* my $tN = $src->[idx] (or ->{key}); then recurse with src=$tN */
457 2           PADOFFSET t = dd_temp(aTHX);
458 0           OP *get = DD_IS_ARRAYLIKE(pat->shape) ? dd_aelem(aTHX_ src, idx)
459 2 50         : dd_helem(aTHX_ src, el->key);
460 2           OP *lhs = dd_padsv(aTHX_ t);
461             OP *stmt;
462 2           lhs->op_private |= OPpLVAL_INTRO;
463 2           stmt = newSTATEOP(0, NULL, newASSIGNOP(OPf_STACKED, lhs, 0, get));
464 2           *seqp = op_append_list(OP_LINESEQ, *seqp, stmt);
465 2           dd_emit(aTHX_ el->nested, t, seqp);
466 2           idx++;
467 2           break;
468             }
469             }
470             }
471 6           }
472              
473 10           static void dd_free_pat(pTHX_ dd_pat *pat) {
474             int i;
475 28 100         for (i = 0; i < pat->n; i++) {
476 18           dd_elem *el = &pat->elems[i];
477 18 100         if (el->name) SvREFCNT_dec(el->name);
478 18 100         if (el->key) SvREFCNT_dec(el->key);
479 18 50         if (el->deflt) op_free(el->deflt);
480 18 100         if (el->nested) { dd_free_pat(aTHX_ el->nested); Safefree(el->nested); }
481             }
482 10           }
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 8           static int dd_is_listassign(dd_pat *pat) {
493             int i;
494 8 100         if (pat->shape == DD_HASH) return 0;
495 14 100         for (i = 0; i < pat->n; i++) {
496 9           dd_elem *el = &pat->elems[i];
497 9 100         if (el->kind == DD_SCALAR) { if (el->deflt) return 0; }
    50          
498 1 50         else if (el->kind == DD_HOLE) { /* -> undef in the LHS list */ }
499 1 50         else if (el->kind == DD_SLURPY) { if (i != pat->n - 1) return 0; }
    50          
500 0           else return 0; /* DD_NESTED */
501             }
502 5           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 4           static OP *dd_listassign_lhs(pTHX_ dd_pat *pat) {
508 4           OP *list = newLISTOP(OP_LIST, 0, NULL, NULL);
509             int i;
510 12 100         for (i = 0; i < pat->n; i++) {
511 8           dd_elem *el = &pat->elems[i];
512             OP *v;
513 8 50         if (el->kind == DD_HOLE) {
514 0           v = newOP(OP_UNDEF, 0);
515             } else {
516 8           PADOFFSET off = pad_add_name_pvn(SvPVX(el->name), SvCUR(el->name),
517             0, NULL, NULL);
518 8           char sigil = SvPVX(el->name)[0];
519 8 100         if (sigil == '@') v = newOP(OP_PADAV, 0);
520 7 50         else if (sigil == '%') v = newOP(OP_PADHV, 0);
521 7           else v = newOP(OP_PADSV, 0);
522 8           v->op_targ = off;
523 8           v->op_private |= OPpLVAL_INTRO;
524             }
525 8           list = op_append_elem(OP_LIST, list, v);
526             }
527 4           list->op_flags |= OPf_PARENS;
528 4           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 4           static OP *dd_empty_aref(pTHX) {
534 4           return op_convert_list(OP_ANONLIST, OPf_SPECIAL, NULL);
535             }
536              
537             #endif /* DD_DESTRUCTURE_H */