File Coverage

blib/lib/Devel/Chitin/OpTree/BINOP.pm
Criterion Covered Total %
statement 142 149 95.3
branch 48 56 85.7
condition 31 51 60.7
subroutine 27 27 100.0
pod 1 13 7.6
total 249 296 84.1


line stmt bran cond sub pod time code
1             package Devel::Chitin::OpTree::BINOP;
2 35     35   234 use base 'Devel::Chitin::OpTree::UNOP';
  35         63  
  35         3850  
3              
4             our $VERSION = '0.15';
5              
6 35     35   240 use strict;
  35         84  
  35         904  
7 35     35   260 use warnings;
  35         97  
  35         1076  
8              
9 35     35   232 use Carp;
  35         92  
  35         22205  
10              
11             # probably an ex-lineseq with 2 kids
12             *pp_lineseq = \&Devel::Chitin::OpTree::LISTOP::pp_lineseq;
13              
14             sub last {
15 941     941 1 2685 shift->{children}->[-1];
16             }
17              
18             sub pp_sassign {
19 343     343 0 697 my($self, %params) = @_;
20             # normally, the args are ordered: value, variable
21             my($var, $value) = $params{is_swapped}
22 343 100       1380 ? ($self->first->deparse, $self->last->deparse)
23             : ($self->last->deparse, $self->first->deparse);
24 343         1511 return join(' = ', $var, $value);
25             }
26              
27             sub pp_aassign {
28 98     98 0 165 my $self = shift;
29              
30 98         149 my $container;
31 98 100 66     227 if ($self->is_null
      66        
      33        
      33        
      33        
      33        
      33        
      33        
32             and
33             # assigning-to is optimized away
34             $self->last->is_null and $self->last->_ex_name eq 'pp_list'
35             and
36             $self->last->children->[1]->is_null and $self->last->children->[1]->is_array_container
37             and
38             # value is an in-place sort: @a = sort @a;
39             $self->first->is_null and $self->first->_ex_name eq 'pp_list'
40             and
41             $self->first->children->[1]->op->name eq 'sort'
42             and
43             $self->first->children->[1]->op->private & B::OPpSORT_INPLACE
44             ) {
45             # since we're optimized away, we can't find out what variable we're
46             # assigning . It's the variable the sort is acting on.
47 7         15 $container = $self->first->children->[1]->children->[-1]->deparse;
48              
49             } else {
50 91         237 $container = $self->last->deparse;
51             }
52              
53 98         358 "$container = " . $self->first->deparse;
54             }
55              
56             sub pp_refassign {
57 3     3 0 5 my $self = shift;
58              
59 3         5 my $left;
60 3 100       6 if ($self->op->flags & B::OPf_STACKED) {
61 2         6 $left = $self->last->deparse;
62             } else {
63 1         4 $left = $self->_padname_sv->PV;
64             }
65              
66 3         18 my $right = $self->first->deparse;
67 3         13 "\\${left} = $right";
68             }
69              
70             sub pp_list {
71 170     170 0 251 my $self = shift;
72              
73             # 'list' is usually a LISTOP, but if we got here's it's because we're
74             # actually a 'null' ex-list, and there's only one item in the list.
75             # $self->first will be a pushmark
76             # @list = @other_list;
77             # We can emit a value without surrounding parens unless it's a scalar
78             # being assigned to
79              
80 170         316 my $contents = $self->last->deparse;
81              
82 170 100 100     366 if ($self->last->is_scalar_container
83             or
84             $self->is_list_reference_alias
85             ) {
86 4         17 "(${contents})";
87              
88             } else {
89 166         625 $contents;
90             }
91             }
92              
93             foreach my $cond ( [lt => '<'],
94             [le => '<='],
95             [gt => '>'],
96             [ge => '>='],
97             [eq => '=='],
98             [ne => '!='],
99             [ncmp => '<=>'],
100             [slt => 'lt'],
101             [sle => 'le'],
102             [sgt => 'gt'],
103             [sge => 'ge'],
104             [seq => 'eq'],
105             [sne => 'ne'],
106             [scmp => 'cmp'],
107             )
108             {
109             my $expr = ' ' . $cond->[1] . ' ';
110             my $sub = sub {
111 35     35   74 my $self = shift;
112 35         99 return join($expr, $self->first->deparse, $self->last->deparse);
113             };
114             my $subname = 'pp_' . $cond->[0];
115 35     35   285 no strict 'refs';
  35         74  
  35         37627  
116             *$subname = $sub;
117             }
118              
119             sub pp_stringify {
120 5     5 0 10 my $self = shift;
121              
122 5 50 33     17 unless ($self->first->op->name eq 'null'
123             and
124             $self->first->_ex_name eq 'pp_pushmark'
125             ) {
126 0         0 die "unknown stringify ".$self->first->op->name;
127             }
128              
129 5         17 my $children = $self->children;
130 5 50       16 unless (@$children == 2) {
131             die "expected 2 children but got " . scalar(@$children)
132 0         0 . ': ' . join(', ', map { $_->op->name } @$children);
  0         0  
133             }
134              
135 5 100 100     13 if ($self->is_null
      66        
136             and $self->op->private & B::OPpTARGET_MY
137             and $children->[1]->op->name eq 'concat'
138             ) {
139 1         6 $children->[1]->deparse(skip_concat => 1, force_quotes => ['qq(', ')']);
140              
141             } else {
142 4         29 my $target = $self->_maybe_targmy;
143              
144 4         18 "${target}qq(" . $children->[1]->deparse(skip_concat => 1, skip_quotes => 1) . ')';
145             }
146             }
147              
148             sub pp_concat {
149 17     17 0 29 my $self = shift;
150 17         48 my %params = @_;
151              
152 17         39 my $first = $self->first;
153 17 100 100     39 if ($self->op->flags & B::OPf_STACKED
154             and
155             $first->op->name ne 'concat'
156             ) {
157             # This is an assignment-concat: $a .= 'foo'
158 1         4 $first->deparse . ' .= ' . $self->last->deparse;
159              
160             } else {
161 16         38 my $target = $self->_maybe_targmy;
162             my $concat_str = join($params{skip_concat} ? '' : ' . ',
163 16 100       97 $first->deparse(%params, $params{force_quotes} ? (skip_quotes => 1) : ()),
    100          
164             $self->last->deparse(%params));
165 16 100       45 if ($params{force_quotes}) {
166 1         2 $concat_str = join($concat_str, @{$params{force_quotes}});
  1         3  
167             }
168 16         65 $target . $concat_str;
169             }
170             }
171              
172             sub pp_reverse {
173             # a BINOP reverse is only acting on a single item
174             # 0th child is pushmark, skip it
175 2     2 0 7 'reverse(' . shift->last->deparse . ')';
176             }
177              
178             sub pp_leaveloop {
179 12     12 0 22 my $self = shift;
180              
181 12 50       27 if (my $deparsed = $self->_deparse_postfix_while) {
182 0         0 return $deparsed;
183             }
184              
185 12         28 my $enterloop = $self->first;
186 12 100       32 if ($enterloop->op->name eq 'enteriter') {
187 7         20 return $self->_deparse_foreach;
188              
189             #} elsif ($enterloop->op->name eq 'entergiven') {
190             # return $self->_deparse_given;
191              
192             } else {
193 5         15 return $self->_deparse_while_until;
194             }
195             }
196              
197             # Part of the reverted given/whereso/whereis from 5.27.7
198             #sub _deparse_given {
199             # my $self = shift;
200             #
201             # my $enter_op = $self->first;
202             # my $topic_op = $enter_op->first;
203             # my $topic = $topic_op->deparse;
204             # my $block_content = $topic_op->sibling->deparse(omit_braces => 1);
205             #
206             # "given ($topic) {$block_content}";
207             #}
208              
209             sub _deparse_while_until {
210 5     5   7 my $self = shift;
211              
212             # while loops are structured like this:
213             # leaveloop
214             # enterloop
215             # null
216             # and/or
217             # null
218             # condition
219             # lineseq
220             # loop contents
221 5         11 my $condition_op = $self->last->first; # the and/or
222 5         19 my $enterloop = $self->first;
223 5 100       14 my $loop_invocation = $condition_op->op->name eq 'and'
224             ? 'while'
225             : 'until';
226 5         9 my $continue_content = '';
227 5         8 my $loop_content;
228 5 100       16 if ($enterloop->nextop->op->name eq 'unstack') {
229             # no continue
230             # loop contents are wrapped in a lineseq
231 4         22 $loop_content = '{' . $self->_indent_block_text( $condition_op->other->deparse, force_multiline => 1 ) . '}';
232             } else {
233             # has continue
234             # loop and continue contents are wrapped in scopes
235 1         5 my $children = $condition_op->other->children;
236 1         4 $loop_content = $children->[0]->deparse(force_multiline => 1);
237 1         5 $continue_content = ' continue ' . $children->[1]->deparse(force_multiline => 1);
238             }
239              
240 5         19 my $loop_condition = $condition_op->first->deparse;
241 5 100       24 if ($condition_op->op->name eq 'and') {
242 4         9 $loop_invocation = 'while';
243              
244             } else {
245 1         3 $loop_invocation = 'until';
246 1         7 $loop_condition =~ s/^!//;
247             }
248              
249 5         27 "$loop_invocation ($loop_condition) ${loop_content}${continue_content}";
250             }
251              
252             sub _deparse_foreach {
253 7     7   13 my $self = shift;
254             # foreach loops look like this:
255             # leaveloop
256             # enteriter
257             # pushmark
258             # list
259             # ... (iterate-over list)
260             # iteration variable
261             # null
262             # and
263             # iter
264             # lineseq
265             # loop contents
266 7         16 my $enteriter = $self->first;
267              
268 7         31 my $list_op = $enteriter->children->[1];
269 7         10 my $iter_list;
270 7 100 100     15 if ($enteriter->op->flags & B::OPf_STACKED
    50          
271             and
272             $list_op->children->[2]
273             ) {
274             # range
275 1         3 $iter_list = '(' . join(' .. ', map { $_->deparse } @{$list_op->children}[1,2]) . ')';
  2         5  
  1         12  
276              
277             } elsif ($list_op->is_null) {# and $enteriter->op->private & B::OPpITER_REVERSED) {
278             # either foreach(reverse @list) or foreach (@list)
279 6         25 $iter_list = $list_op->Devel::Chitin::OpTree::LISTOP::pp_list;
280              
281             } else {
282 0         0 $iter_list = $list_op->deparse;
283             }
284              
285 7         23 my $var_op = $enteriter->children->[2];
286 7 100       27 my $var = $var_op
287             ? '$' . $var_op->deparse(skip_sigil => 1)
288             : $enteriter->pp_padsv;
289              
290 7         21 my $loop_content_op = $enteriter->sibling->first->first->sibling; # should be a lineseq
291 7         21 my $loop_content = $loop_content_op->deparse;
292              
293 7 100       21 if ($loop_content_op->first->isa('Devel::Chitin::OpTree::COP')) {
294 6         19 $loop_content = $self->_indent_block_text( $loop_content );
295 6         45 "foreach $var $iter_list {$loop_content}";
296             } else {
297 1 50       3 Carp::croak("In postfix foreach, expected loop var '\$_', but got '$var'") unless $var eq '$_';
298 1         6 "$loop_content foreach $iter_list"
299             }
300             }
301              
302             # leave is normally a LISTOP, but this happens when this is run
303             # in the debugger
304             # sort { ; } @list
305             # The leave is turned into a null:
306             # ex-leave
307             # enter
308             # stub
309             *pp_leave = \&Devel::Chitin::OpTree::LISTOP::pp_leave;
310              
311             # from B::Concise
312 35     35   304 use constant DREFAV => 32;
  35         81  
  35         3931  
313 35     35   282 use constant DREFHV => 64;
  35         73  
  35         1991  
314 35     35   228 use constant DREFSV => 96;
  35         88  
  35         24968  
315              
316             sub pp_helem {
317 2     2 0 5 my $self = shift;
318              
319 2         7 my $first = $self->first;
320 2         5 my($hash, $key) = ($first->deparse, $self->last->deparse);
321 2 50       8 if ($self->_is_chain_deref('rv2hv', DREFHV)) {
322             # This is a dereference, like $a->{foo}
323 0         0 substr($hash, 1) . '->{' . $key . '}';
324             } else {
325 2         9 substr($hash, 0, 1) = '$';
326 2         13 "${hash}{${key}}";
327             }
328             }
329              
330             sub _is_chain_deref {
331 4     4   13 my($self, $expected_first_op, $expected_flag) = @_;
332 4         12 my $child = $self->first;
333 4 100       21 return unless $child->isa('Devel::Chitin::OpTree::UNOP');
334              
335 1 50       5 $child->op->name eq $expected_first_op
336             and
337             $child->first->op->private & $expected_flag
338             }
339              
340             sub pp_aelem {
341 5     5 0 13 my $self = shift;
342 5         14 my $first = $self->first;
343              
344 5         18 my($array, $elt) = ($first->deparse, $self->last->deparse);
345 5 100 66     17 if ($self->is_null
    50 66        
      66        
346             and
347             ($first->op->name eq 'aelemfast_lex' or $first->op->name eq 'aelemfast')
348             and
349             $self->last->is_null
350             ) {
351 3         14 $array;
352              
353             } elsif ($self->_is_chain_deref('rv2av', DREFAV)) {
354             # This is a dereference, like $a->[1]
355 0         0 substr($array, 1) . '->[' . $elt . ']';
356              
357             } else {
358 2         8 substr($array, 0, 1) = '$';
359 2         5 my $idx = $self->last->deparse;
360 2         13 "${array}[${idx}]";
361             }
362             }
363              
364             sub pp_smartmatch {
365 3     3 0 7 my $self = shift;
366 3         6 $self->last->deparse;
367             }
368              
369             sub pp_lslice {
370 2     2 0 3 my $self = shift;
371              
372 2         6 my $list = $self->last->deparse(skip_parens => 1);
373 2         6 my $idx = $self->first->deparse(skip_parens => 1);
374 2         10 "($list)[$idx]";
375             }
376              
377             # Operators
378             # OP name operator targmy?
379             foreach my $a ( [ pp_add => '+', 1 ],
380             [ pp_i_add => '+', 1 ],
381             [ pp_subtract => '-', 1 ],
382             [ pp_i_subtract => '-', 1 ],
383             [ pp_multiply => '*', 1 ],
384             [ pp_i_multiply => '*', 1 ],
385             [ pp_divide => '/', 1 ],
386             [ pp_i_divide => '/', 1 ],
387             [ pp_modulo => '%', 1 ],
388             [ pp_i_modulo => '%', 1 ],
389             [ pp_pow => '**', 1 ],
390             [ pp_left_shift => '<<', 1 ],
391             [ pp_right_shift => '>>', 1 ],
392             [ pp_repeat => 'x', 0 ],
393             [ pp_bit_and => '&', 0 ],
394             [ pp_bit_or => '|', 0 ],
395             [ pp_bit_xor => '^', 0 ],
396             [ pp_xor => 'xor', 0 ],
397             [ pp_sbit_and => '&.', 0 ],
398             [ pp_sbit_or => '|.', 0 ],
399             [ pp_sbit_xor => '^.', 0 ],
400             ) {
401             my($pp_name, $perl_name, $targmy) = @$a;
402             my $sub = sub {
403 54     54   86 my $self = shift;
404              
405 54 100       94 if ($self->op->flags & B::OPf_STACKED) {
406             # This is an assignment op: +=
407 13         31 my $first = $self->first->deparse;
408 13         44 "$first ${perl_name}= " . $self->last->deparse;
409             } else {
410 41 100       88 my $target = $targmy ? $self->_maybe_targmy : '';
411 41         114 $target . $self->first->deparse . " $perl_name " . $self->last->deparse;
412             }
413             };
414 35     35   322 no strict 'refs';
  35         68  
  35         2681  
415             *$pp_name = $sub;
416             }
417              
418             1;
419              
420             __END__