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