File Coverage

blib/lib/Devel/DumpTrace/PPI.pm
Criterion Covered Total %
statement 25 27 92.5
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 34 36 94.4


line stmt bran cond sub pod time code
1             package Devel::DumpTrace::PPI;
2 16     16   1663 use Devel::DumpTrace;
  16         23  
  16         173  
3 16     16   66 use Devel::DumpTrace::Const;
  16         23  
  16         982  
4 16     16   75 use PadWalker;
  16         36  
  16         438  
5 16     16   79 use Scalar::Util;
  16         29  
  16         474  
6 16     16   7598 use Data::Dumper;
  16         75121  
  16         918  
7 16     16   102 use Carp;
  16         28  
  16         680  
8 16     16   74 use strict;
  16         28  
  16         265  
9 16     16   57 use warnings;
  16         26  
  16         781  
10              
11             local $| = 1;
12              
13             croak "Devel::DumpTrace::PPI may not be used ",
14             "when \$Devel::DumpTrace::NO_PPI ",
15             "is set (Did you load 'Devel::DumpTrace::noPPI'?\n"
16             if $Devel::DumpTrace::NO_PPI;
17 16     16   2526 eval {use PPI;
  0            
  0            
18             1}
19             or croak "PPI not installed. Can't use Devel::DumpTrace::PPI module";
20              
21              
22             # functions in this file that override functions in Devel/DumpTrace.pm
23              
24             *Devel::DumpTrace::get_source = *get_source_PPI;
25             *Devel::DumpTrace::evaluate_and_display_line = *evaluate_and_display_line_PPI;
26             *Devel::DumpTrace::handle_deferred_output = *handle_deferred_output_PPI;
27              
28             *evaluate = *Devel::DumpTrace::evaluate;
29             *current_position_string = *Devel::DumpTrace::current_position_string;
30             *dumptrace = *Devel::DumpTrace::dumptrace;
31              
32              
33              
34             $Devel::DumpTrace::PPI::VERSION = '0.27';
35             use constant ADD_IMPLICIT_ => 1;
36             use constant DECORATE_FOR => 1;
37             use constant DECORATE_FOREACH => 1;
38             use constant DECORATE_WHILE => 1;
39             use constant DECORATE_ELSIF => 1;
40              
41             use constant _DECORATE_REVERSE => 0;
42              
43             # built-in functions that may use $_ implicitly
44             # make %implicit_ a package, not lexical, variable on the off
45             # chance that anyone wants to customize this list
46             our %implicit_ = map {; $_ => 1 } qw(abs alarm chomp chop chr chroot cos
47             defined eval exp glob hex int lc lcfirst length log lstat mkdir
48             oct ord pos print quotemeta readlink readpipe ref require
49             reverse rmdir sin split sqrt stat study uc ucfirst unlink
50             unpack say);
51              
52             # for persisting the PPI documents we create
53             my (%ppi_src, %ppi_doc);
54              
55             my $last_file_sub_displayed = '';
56             my $last_file_line_displayed = '';
57             my %IGNORE_FILE_LINE = ();
58              
59             sub Devel::DumpTrace::PPI::import {
60             foreach my $PPI_package (grep { m{^PPI[/.]} } keys %INC) {
61             $PPI_package =~ s/\.pm$//;
62             $PPI_package =~ s{/}{::}g;
63             $Devel::DumpTrace::EXCLUDE_PKG{$PPI_package} = 1;
64             }
65             $Devel::DumpTrace::EXCLUDE_PKG{"Carp::Always"} = 1;
66             goto &Devel::DumpTrace::import;
67             }
68              
69             # Overrides get_source in Devel/DumpTrace.pm
70             sub get_source_PPI {
71             my ($file, $line) = @_;
72              
73             if (!defined $ppi_src{$file}) {
74             eval { _update_ppi_src_for_file($file) };
75             }
76             return \@{$ppi_src{$file}[$line]};
77             }
78              
79             sub _update_ppi_src_for_file {
80             my $file = shift;
81              
82             my $doc;
83             if ($file eq '-e' # code from perl -e '...'
84             || $file eq '-' # code from cat prog.pl | perl
85             || $file =~ /^\(eval \d+\)\[/) { # code from an eval statement
86             no strict 'refs'; ## no critic (NoStrict)
87             my $all_code = join "", @{"::_<$file"}[1 .. $#{"::_<$file"}];
88             $doc = $ppi_doc{$file} = PPI::Document->new(\$all_code);
89             } else {
90             $doc = $ppi_doc{$file} = PPI::Document->new($file);
91             }
92             $doc->index_locations;
93              
94             # find every separate statement in the document
95             # and store by its line number.
96              
97             # there may be more than one distinct statement per line ($a=4; $b=6;)
98             # but statements that are children of other statements should not
99             # be included ... ( if ($cond) { $x++ }\n ===> don't store $x++ )
100              
101             my $statements = $doc->find('PPI::Statement') || [];
102              
103             # use reverse ... ?
104             # then the child statements will be decorated before their parents
105             # if use reverse, then _update_ppi_src_for_element should unshift not push
106              
107             foreach my $element (_DECORATE_REVERSE ? reverse @$statements
108             : @$statements) {
109             my $_line = $element->line_number;
110             __decorate1($element, $file, $doc);
111             next if _element_has_ancestor_on_same_line($element,$_line);
112             __decorate2($element, $file);
113             _update_ppi_src_for_element($element, $file, $_line);
114             }
115             return;
116             }
117              
118             sub _element_has_ancestor_on_same_line {
119             my ($element,$_line) = @_;
120             #my $_line = $element->line_number;
121             my $parent = $element->parent;
122             while ($parent && ref($parent) ne 'PPI::Document') {
123             my $parent_line = $parent->line_number;
124             if (defined($parent_line)
125             && $parent_line == $_line
126             && ref($parent) =~ /^PPI::Statement/) {
127             return 1;
128             }
129             $parent = $parent->parent;
130             }
131             return 0;
132             }
133              
134             sub _update_ppi_src_for_element {
135             my ($element, $file, $_line) = @_;
136             # my $_line = $element->line_number;
137             $ppi_src{$file}[$_line] ||= [];
138             if (ref($element) =~ /^PPI::Statement/) {
139             my ($d1, $d2) = (0,0);
140             my @zrc = _get_source($file, $_line, $element, $d1, $d2);
141             my $elem = { %$element };
142             $elem->{children} = [ @zrc ];
143             if (_DECORATE_REVERSE) {
144             unshift @{$ppi_src{$file}[$_line]}, bless $elem, ref $element;
145             } else {
146             push @{$ppi_src{$file}[$_line]}, bless $elem, ref $element;
147             }
148             } else {
149             if (_DECORATE_REVERSE) {
150             unshift @{$ppi_src{$file}[$_line]}, $element;
151             } else {
152             push @{$ppi_src{$file}[$_line]}, $element;
153             }
154             }
155             }
156              
157             # decorate
158             sub __decorate1 {
159             my ($element, $file) = @_;
160             if (ADD_IMPLICIT_) {
161             __add_implicit_elements($element);
162             if (ref($element) eq 'PPI::Statement::Given') {
163             __add_implicit_to_given_when_blocks($element);
164             }
165             }
166             __decorate_first_statement_in_for_block($element, $file);
167             __decorate_first_statement_in_foreach_block($element, $file);
168             __decorate_first_statement_AFTER_for_block($element,$file);
169             return;
170             }
171              
172             sub __decorate2 {
173             my ($element, $file) = @_;
174             __remove_whitespace_and_comments_just_before_end_of_statement($element);
175             __decorate_first_statement_in_while_block($element, $file);
176             __decorate_first_statement_AFTER_while_block($element, $file);
177             __decorate_statements_in_ifelse_block($element, $file);
178             __decorate_last_statement_in_dowhile_block($element, $file);
179             return;
180             }
181              
182             # extract source from a compound statement that goes with the
183             # specified line. This involves removing tokens that appear on
184             # other lines AFTER a block opening ("{") has been observed.
185             sub _get_source {
186             my ($file, $line, $node, undef, undef, @src) = @_;
187             my @children = $node->elements;
188              
189             for my $element (@children) {
190             if (defined($element->line_number) && $element->line_number != $line) {
191             $_[3]++;
192             }
193             last if $_[3] && $_[4];
194             if ($element->first_token ne $element) {
195             my @zrc = _get_source($file, $line, $element, $_[3], $_[4]);
196             push @src, bless( { children=>[@zrc] }, ref($element) );
197             } else {
198             push @src, $element;
199             }
200             if (ref $element eq 'PPI::Token::Structure' && $element eq '{') {
201             $_[4]++;
202             }
203             }
204             return @src;
205             }
206              
207             sub _get_decorated_statements {
208              
209             # Many Perl flow control constructs are optimized to not
210             # allow a breakpoint at each iteration of a loop or at each
211             # condition evaluation of a complex if-elsif-else statement.
212             # So there are times when, while evaluating the first statement
213             # in a block, we also want to evaluate some other expressions
214             # from the parent flow control structure.
215              
216             my ($statements, $style) = @_;
217             my @s = @{$statements};
218             foreach my $ss (grep { $_->{__DECORATED__} } @{$statements}) {
219             my $ws = $style == DISPLAY_TERSE ? "\n\t\t\t" : " ";
220              
221             if ($ss->{__DECORATED__} eq 'foreach'
222             && $last_file_line_displayed ne $ss->{__FOREACH_LINE__}) {
223              
224             unshift @s,
225             __new_token('FOREACH: {'),
226             $ss->{__UPDATE__},
227             __new_token("} \t"); # don't use newline even in terse mode
228              
229             } elsif ($ss->{__DECORATED__} eq 'for'
230             && $last_file_line_displayed ne $ss->{__FOR_LINE__}) {
231              
232             unshift @s,
233             __new_token("FOR-UPDATE: {"),
234             $ss->{__CONTINUE__},
235             __new_token(" } FOR-COND: {"),
236             $ss->{__CONDITION__},
237             __new_token(" } $ws");
238              
239             } elsif ($ss->{__DECORATED__} eq 'while/until'
240             && $last_file_line_displayed ne $ss->{__WHILE_LINE__}) {
241              
242             unshift @s,
243             __new_token($ss->{__BLOCK_NAME__} . ": "),
244             $ss->{__CONDITION__},
245             __new_token(" $ws");
246              
247             } elsif ($ss->{__DECORATED__} eq 'if-elsif-else'
248             && $last_file_line_displayed eq $ss->{__IF_LINE__}) {
249              
250             unshift @s,
251             @{$ss->{__CONDITIONS__}},
252             __new_token(" ". $ws);
253              
254             } elsif ($ss->{__DECORATED__} eq 'do-while'
255             && $last_file_line_displayed ne $ss->{__DOWHILE_LINE__}) {
256              
257             push @s,
258             __new_token(" " . $ws),
259             __new_token($ss->{__SENSE__} . ": {"),
260             @{$ss->{__CONDITION__}},
261             __new_token("}");
262              
263             } elsif ($ss->{__DECORATED__} eq 'end-for'
264             && $last_file_line_displayed ne $ss->{__ENDFOR_LINE__}
265             && $ss->{__CONDITIONER__}
266             && $ss->{__CONDITIONER__}{__GP_CONDITION__}) {
267              
268             unshift @s,
269             __new_token("FOR-COND: {"),
270             $ss->{__CONDITIONER__}{__GP_CONDITION__},
271             __new_token("} $ws");
272              
273             } elsif ($ss->{__DECORATED__} eq 'end-while'
274             && $last_file_line_displayed ne $ss->{__ENDWHILE_LINE__}
275             && $ss->{__CONDITIONER__}
276             && $ss->{__CONDITIONER__}{__GP_CONDITION__}) {
277              
278             unshift @s,
279             __new_token($ss->{__SENSE__} . ": ("),
280             $ss->{__CONDITIONER__}{__GP_CONDITION__},
281             __new_token(") $ws");
282              
283             }
284             }
285             return @s;
286             }
287              
288             # Overrides &evaluate_and_display_line in Devel/DumpTrace.pm
289             sub evaluate_and_display_line_PPI {
290             my ($statements, $pkg, $file, $line, $sub) = @_;
291              
292             if (ref $statements ne 'ARRAY') {
293             my $doc = PPI::Document->new(\$statements);
294             $ppi_doc{"$file:$line"} = $doc;
295             $statements = [$doc->elements];
296             }
297              
298             my $style = Devel::DumpTrace::_display_style();
299             my $code;
300             my @s = _get_decorated_statements($statements, $style);
301             $code = join '', map { "$_" } @s;
302             chomp $code;
303             $code .= "\n";
304             $code =~ s/\n(.)/\n\t\t $1/g;
305              
306             if ($style > DISPLAY_TERSE) {
307             Devel::DumpTrace::separate();
308             dumptrace(2,0,current_position_string($file,$line,$sub),"\n");
309             dumptrace(3,1,$code);
310             unless ($IGNORE_FILE_LINE{"$file:$line"}) {
311             $last_file_sub_displayed = "$file:$sub";
312             $last_file_line_displayed = "$file:$line";
313             }
314             }
315              
316             my $xcode;
317             my @preval = ();
318              
319             # for a simple lexical declaration with no assignments,
320             # don't evaluate the code:
321             # my ($a, @b, %c);
322             # our $ZZZ;
323             # XXX - these expressions lifted from Devel::DumpTrace. Is that
324             # sufficient or should we analyze the PPI tokens?
325             if ($code =~ /^ \s* (my|our) \s*
326             [\$@%*\(] /x # lexical declaration
327              
328             && $code =~ / (?
329             \s* (\# .* )? $/x # single statement, single line
330              
331             && $code !~ /=/) { # NOT an assignment
332              
333             $xcode = $code;
334              
335             } else {
336              
337             # recursive preval calls will increase the depth levels
338             local $Devel::DumpTrace::DB_ARGS_DEPTH = 4;
339              
340             for my $s (@s) {
341             push @preval, preval($s, $style, $pkg);
342             }
343             $xcode = join '', @preval;
344             }
345              
346             chomp $xcode;
347             $xcode .= "\n";
348             $xcode =~ s/\n(.)/\n\t\t $1/g;
349              
350             if ($style >= DISPLAY_GABBY && $xcode ne $code) {
351             dumptrace(4,1,$xcode);
352             }
353             my $deferred = 0;
354             for my $preval (@preval) {
355             if (ref $preval) {
356              
357             $deferred++;
358             $Devel::DumpTrace::DEFERRED{"$sub : $file"} ||= [];
359             push @{$Devel::DumpTrace::DEFERRED{"$sub : $file"}},
360             { EXPRESSION => [ @preval ],
361             PACKAGE => $pkg,
362             MY_PAD => $Devel::DumpTrace::PAD_MY,
363             OUR_PAD => $Devel::DumpTrace::PAD_OUR,
364             SUB => $sub,
365             FILE => $file,
366             LINE => $line,
367             DISPLAY_FILE_AND_LINE => $style <= DISPLAY_TERSE,
368             };
369             last;
370             }
371             }
372             if ($deferred == 0) {
373             if ($style <= DISPLAY_TERSE) {
374             dumptrace(3,0, current_position_string($file,$line,$sub),
375             "\t$xcode");
376             unless ($IGNORE_FILE_LINE{"$file:$line"}) {
377             $last_file_sub_displayed = "$file:$sub";
378             $last_file_line_displayed = "$file:$line";
379             }
380             }
381             }
382             return;
383             }
384              
385             # any elements that appear AFTER the last assignment operator
386             # are evaluated and tokenized.
387             sub preval {
388             my ($statement,$style,$pkg) = @_;
389             if (ref($statement) =~ /PPI::Token/) {
390             if ($statement->{_PREVAL}) {
391             perform_variable_substitution($statement, 0, $style, $pkg)
392             unless $statement->{_DEFER};
393             return map { ref($_) eq 'ARRAY' ? @{$_} : $_ } $statement;
394             } else {
395             return map {"$_"} $statement->tokens;
396             }
397             }
398             $Devel::DumpTrace::DB_ARGS_DEPTH++;
399              
400             if (ref($statement) !~ /^PPI::/) {
401             Carp::confess "$statement is not a PPI token -- ", %$statement,
402             "\nThis is a bug. Report to bug-Devel-DumpTrace\@rt.cpan.org\n";
403             }
404              
405             my @e = $statement->elements;
406              
407             # look for implicit uses of special vars
408             if (ADD_IMPLICIT_) {
409             __append_implicit_to_naked_shift_pop(\@e);
410             }
411              
412             # find last assignment operator in this expression, if any.
413             my $lao_index = 0;
414             for my $i (0 .. $#e) {
415             if (ref($e[$i]) eq 'PPI::Token::Operator'
416             && $e[$i]->is_assignment_operator) {
417             $lao_index = $i;
418             }
419             }
420             _preval_render(\@e, $lao_index, $style, $pkg);
421             my @output = map { ref($_) eq 'ARRAY' ? @{$_} : $_ } @e;
422             $Devel::DumpTrace::DB_ARGS_DEPTH--;
423             return @output;
424             }
425              
426             sub _preval_render {
427              
428             # evaluate any PPI::Token::Symbol elements after element $z
429             # tokenize other PPI::Token::* elements
430             # pass other elements back to &preval recursively
431              
432             my ($e, $lao_index, $style, $pkg) = @_;
433             $Devel::DumpTrace::DB_ARGS_DEPTH++;
434             for (my $i=$lao_index; $i < @$e; $i++) {
435             if (ref($e->[$i]) eq 'PPI::Token::Symbol') {
436             next if $e->[$i]{_DEFER};
437             perform_variable_substitution(@$e, $i, $style, $pkg);
438             if ($i > 0 && ref($e->[$i-1]) eq 'PPI::Token::Cast') {
439             if ($e->[$i-1] eq '@' && $e->[$i] =~ /^\[(.*)\]$/) {
440              
441             # @$a => @[1,2,3] should render as @$a => (1,2,3)
442              
443             $e->[$i-1] = '';
444             $e->[$i] = '(' . substr($e->[$i],1,-1) . ')';
445             } elsif ($e->[$i-1] eq '%' && $e->[$i] =~ /^\{(.*)\}$/) {
446              
447             # render %$a as ('a'=>1;'b'=>2) , not %{'a'=>1;'b'=>2}
448              
449             $e->[$i-1] = '';
450             $e->[$i] = '(' . substr($e->[$i],1,-1) . ')';
451             }
452             }
453             } elsif (ref $e->[$i] eq 'PPI::Token::Magic') {
454             next if $e->[$i]{_DEFER};
455             perform_variable_substitution(@$e, $i, $style, '');
456             } elsif (ref($e->[$i]) =~ /PPI::Token/) {
457             $e->[$i] = "" . $e->[$i] if ref($e->[$i]) ne 'PPI::Token::Cast';
458             } else {
459             $e->[$i] = [ preval($e->[$i],$style,$pkg) ];
460             }
461             }
462             $Devel::DumpTrace::DB_ARGS_DEPTH--;
463             return;
464             }
465              
466             sub perform_variable_substitution_on_tokens {
467             # needed to evaluate complex lvalues
468             my ($elem, $style, $dpkg) = @_;
469             my @out = ();
470             my $ref = ref($elem);
471             if ($ref =~ /^PPI::Statement/ || $ref =~ /^PPI::Structure/) {
472             foreach my $e ($elem->elements()) {
473             $Devel::DumpTrace::DB_ARGS_DEPTH++;
474             push @out,
475             perform_variable_substitution_on_tokens($e, $style, $dpkg);
476             $Devel::DumpTrace::DB_ARGS_DEPTH--;
477             }
478             } elsif ($ref eq 'PPI::Token::Symbol') {
479             my @e = ($elem);
480             perform_variable_substitution(@e, 0, $style, $dpkg);
481             @out = "$e[0]";
482             } elsif ($ref eq 'PPI::Token::Magic') {
483             my @e = ($elem);
484             perform_variable_substitution(@e, 0, $style, '');
485             @out = "$e[0]";
486             } else {
487             @out = "$elem";
488             }
489             return join '', @out;
490             }
491              
492             # Overrides &handle_deferred_output in Devel/DumpTrace.pm
493             sub handle_deferred_output_PPI {
494              
495             my ($sub, $file) = @_;
496             my $deferred = pop @{$Devel::DumpTrace::DEFERRED{"$sub : $file"}};
497             return unless defined($deferred);
498              
499             my @e = grep defined, @{$deferred->{EXPRESSION}};
500             my $undeferred_output = join '', @e;
501             my $deferred_pkg = $deferred->{PACKAGE};
502             $Devel::DumpTrace::PAD_MY = $deferred->{MY_PAD};
503             $Devel::DumpTrace::PAD_OUR = $deferred->{OUR_PAD};
504             Devel::DumpTrace::refresh_pads();
505              
506             my $style = Devel::DumpTrace::_display_style();
507             for my $i (0 .. $#e) {
508             if (ref $e[$i] eq 'PPI::Token::Symbol') {
509             perform_variable_substitution(@e, $i, $style, $deferred_pkg);
510             } elsif (ref $e[$i] eq 'PPI::Token::Magic') {
511             perform_variable_substitution(@e, $i, $style, '');
512             } elsif (ref $e[$i] eq 'PPI::Token::Cast') {
513             eval { $e[$i] = "$e[$i]"; };
514             } elsif (ref($e[$i]) =~ /^PPI::/) {
515             $Devel::DumpTrace::DB_ARGS_DEPTH++;
516             $e[$i] = perform_variable_substitution_on_tokens(
517             $e[$i],$style,$deferred_pkg);
518             $Devel::DumpTrace::DB_ARGS_DEPTH--;
519             }
520             }
521             my $deferred_output = join '', @e;
522             chomp($undeferred_output,$deferred_output);
523             $undeferred_output .= "\n";
524             $undeferred_output =~ s/\n(.)/\n\t\t $1/g;
525             $deferred_output .= "\n";
526             $deferred_output =~ s/\n(.)/\n\t\t $1/g;
527             my $line = $deferred->{LINE};
528             $file = $deferred->{FILE};
529             $sub = $deferred->{SUB};
530             if ($deferred->{DISPLAY_FILE_AND_LINE}
531             || "$file:$sub" ne $last_file_sub_displayed) {
532              
533             if (Devel::DumpTrace::_display_style() > DISPLAY_TERSE) {
534             Devel::DumpTrace::separate();
535             dumptrace(4,0,current_position_string($file,$line,$sub),"\n");
536             dumptrace(4,1,$undeferred_output);
537             dumptrace(5,1,$deferred_output)
538             if $deferred_output ne $undeferred_output;
539             } else {
540             dumptrace(5,0,current_position_string($file,$line,$sub),
541             "\t$deferred_output");
542             }
543             } else {
544             dumptrace(5,1,$deferred_output);
545             }
546             unless ($IGNORE_FILE_LINE{"$file:$line"}) {
547             $last_file_sub_displayed = "$file:$sub";
548             $last_file_line_displayed = "$file:$line";
549             }
550             return;
551             }
552              
553             sub perform_variable_substitution {
554             my $pkg = pop @_;
555             my $style = pop @_;
556             my $i = pop @_;
557              
558             my $sigil = substr $_[$i], 0, 1;
559             return if $sigil eq '&' || $sigil eq '*';
560             my $varname = substr $_[$i], 1;
561             $varname =~ s/^\s+//;
562             $varname =~ s/\s+$//;
563             my $deref_op = '';
564             my $index_op = '';
565             my @keys;
566              
567             my $j = $i+1;
568             while ($j < @_ && ref($_[$j]) eq 'PPI::Token::Whitespace') {
569             $j++;
570             }
571             if (ref($_[$j]) eq 'PPI::Token::Operator' && $_[$j] eq '->') {
572             $deref_op = '->';
573             $j++;
574             while ($j < @_ && ref $_[$j] eq 'PPI::Token::Whitespace') {
575             $j++;
576             }
577             }
578             if (ref($_[$j]) =~ /^PPI::Structure::/) {
579             my @t = $_[$j]->tokens();
580             if ($t[0] eq '[') {
581             $index_op = '[';
582             push @keys, evaluate_subscript($pkg,@t);
583             } elsif ($t[0] eq '{') {
584             $index_op = '{';
585             push @keys, evaluate_subscript($pkg,@t);
586             }
587             }
588              
589             $_[$i] = evaluate($sigil,$varname,$deref_op,$index_op, $pkg, @keys);
590             $_[$i] =~ s/[\[\{]$//;
591             $_[$i] =~ s/\-\>$//;
592             if ($style < DISPLAY_GABBY) {
593             $_[$i] = "$sigil$varname$Devel::DumpTrace::XEVAL_SEPARATOR" . $_[$i];
594             }
595             return $_[$i];
596             }
597              
598             sub evaluate_subscript {
599             my ($pkg, @tokens) = @_;
600              
601             my $abbrev_style = Devel::DumpTrace::_abbrev_style();
602             if ($abbrev_style != ABBREV_SMART && $abbrev_style != ABBREV_MILD_SM) {
603             return ();
604             }
605              
606             shift @tokens;
607             pop @tokens;
608              
609             for (my $i=0; $i<@tokens; $i++) {
610             if (ref $tokens[$i] eq 'PPI::Token::Symbol') {
611             my $y0 = $tokens[$i];
612             my $y1 = perform_variable_substitution(
613             @tokens, $i, DISPLAY_GABBY, $pkg);
614             }
615             }
616              
617             my $ref = join ' ', map { ref($_), ref(\$_) } @tokens;
618             my $key;
619              
620             # don't evaluate expressions that may have side-effects
621             # Any PPI::Token::Symbol's left are probably function calls
622             # PPI::Token::Word could be function calls
623             # Avoid expressions with assignment, postfix operators
624             # Typeglobs won't get eval'd well
625             # actually, any tied variable can have side effects
626             # (through FETCH, e.g.)
627              
628             return if $ref =~ /PPI::Token::Symbol/;
629             return if $ref =~ /PPI::Token::Word/;
630             return if $ref =~ /GLOB/;
631              
632             my $expr = join '', @tokens;
633              
634             return if $expr =~ /=/;
635             return if $expr =~ /\+\+/;
636             return if $expr =~ /--/;
637              
638             $key = eval $expr;
639              
640             if ($@ || !defined $key) {
641             return;
642             } else {
643             return ($key);
644             }
645             }
646              
647             sub PPI::Token::Operator::is_assignment_operator {
648             my $op = $_[0]->{content};
649             for my $aop (qw(= += -= *= /= %= &= |= ^= .= x=
650             **= &&= ||= //= <<= >>= ++ --)) {
651             return 1 if $op eq $aop;
652             }
653             return;
654             }
655              
656             # 0.07: If a statement ends with whitespace and/or comments before the
657             # ';' token, remove them for appearances sake.
658             sub __remove_whitespace_and_comments_just_before_end_of_statement {
659             my $element = shift;
660             my @tokens = $element->tokens();
661             return if @tokens <= 3
662             || ref($tokens[-1]) ne 'PPI::Token::Structure'
663             || $tokens[-1] ne ';';
664              
665             my $j = -2;
666             while (defined($tokens[$j])
667             && (ref($tokens[$j]) eq 'PPI::Token::Whitespace'
668             || ref($tokens[$j]) eq 'PPI::Token::Comment'
669             || ref($tokens[$j]) eq 'PPI::Token::POD')) {
670             $j--;
671             }
672             if ($j < -3 && defined($tokens[$j])) {
673             for my $k ($j+1 .. -1) {
674             $tokens[$k]->delete();
675             }
676             }
677             return;
678             }
679              
680             sub __add_implicit_elements {
681             my ($statement) = @_;
682             return if ref($statement) eq 'PPI::Statement::End';
683              
684             my $e = $statement->{children};
685              
686             __insert_implicit_NR_into_flipflop($e);
687             __prepend_implicit_topic_to_naked_regexp($e);
688             __append_implicit_topic_to_naked_filetest($e);
689             __append_implicit_topic_to_naked_builtins($e);
690             __insert_implicit_topic_into_default_foreach($e);
691             __prepend_implicit_topic_for_readline_op($e);
692              
693             return;
694             }
695              
696             sub __prepend_implicit_topic_for_readline_op {
697             my $e = shift;
698              
699             #
700             # while (<$fh>) means while ( defined($_=<$fh>) )
701             # until (<$fh>) means until ( defined($_=<$fh>) )
702             #
703              
704             # also need to capture:
705             # expression while <$foo>
706             # while ( <$foo> )
707             # while ( readline($foo) ) #
708              
709             # also need to exclude:
710              
711             # while (<$fh> && condition)
712             # while (condition || <$fh>)
713             # while ( < $foo > )
714              
715             my ($ql,$ql2) = grep ref($_) eq 'PPI::Token::QuoteLike::Readline', @$e;
716             return if $ql2 || !$ql;
717             return if "$ql" !~ /^<[\$\*]?[\w:']+>$/;
718              
719             # we expect either:
720             #
721             # 1a. PPI::Token::QuoteLike::Readline as its own statement
722             # 1b. (grand)parent is a PPI::Structure::Condition
723             # 1c. condition is preceded by while}until (whitespace?)
724             # 1d. condition is followed by NULL | Structure(;)
725             #
726             # -or-
727             #
728             # 2a. PPI::Token::QuoteLike::Readline in statement with many tokens
729             # 2b. preceded by while|until (whitespace?)
730             # 2c. followed by NULL | Structure(;)
731              
732             my $ee = $e;
733             my $ql3 = $ql;
734             my @ref = map { ref } @$e;
735             my @oref = grep { $_ ne 'PPI::Token::Whitespace'
736             && $_ ne 'PPI::Token::QuoteLike::Readline' } @ref;
737              
738             if (@oref == 0) {
739             $ee = $ql->parent; # PPI::Statement::Expression
740             return if ref($ee) ne 'PPI::Statement::Expression';
741             $ee = $ee->parent; # PPI::Structure::Condition
742             $ql3 = "$ee";
743             return if ref($ee) ne 'PPI::Structure::Condition';
744             $ee = $ee->parent; # PPI::Statement
745             return if ref($ee) !~ /^PPI::Statement/;
746             $ee = $ee->{children};
747             }
748              
749             for (my $i=0; $i<@$ee; $i++) {
750              
751             next if $ee->[$i] ne "$ql" && $ee->[$i] ne $ql3;
752              
753             my $j = $i+1;
754             $j++ while $j<@$ee && ref($ee->[$j]) eq 'PPI::Token::Whitespace';
755             return if $j<@$ee && (ref($ee->[$j]) ne 'PPI::Token::Structure' ||
756             $ee->[$j] ne ';')
757             && ref($ee->[$j]) ne 'PPI::Structure::Block';
758              
759             $j = $i-1;
760             $j-- while $j>=0 && ref($ee->[$j]) eq 'PPI::Token::Whitespace';
761              
762             return if $j<0;
763             return if ref($ee->[$j]) ne 'PPI::Token::Word';
764             return if $ee->[$j] ne 'while' && $ee->[$j] ne 'until';
765              
766             for (my $k=0; $k<@$e; $k++) {
767             next if ref($e->[$k]) ne 'PPI::Token::QuoteLike::Readline';
768             splice @$e, $k, 0,
769             bless({content=>'$_',_DEFER=>1},'PPI::Token::Magic'),
770             bless({content=>'='}, 'PPI::Token::Operator');
771             return 1;
772             }
773             }
774             return;
775             }
776              
777             sub __prepend_implicit_topic_to_naked_regexp {
778             my $e = shift;
779              
780             #
781             # /pattern/ means $_ =~ /pattern/
782             #
783             for (my $i = 0; $i < @$e; $i++) {
784             next unless ref($e->[$i]) =~ /^PPI::Token::Regexp/;
785             my $j = $i-1;
786             $j-- while $j >= 0 && ref($e->[$j]) eq 'PPI::Token::Whitespace';
787             if ($j < 0 || ref($e->[$j]) ne 'PPI::Token::Operator'
788             || ($e->[$j] ne '=~' && $e->[$j] ne '!~')) {
789              
790             splice @$e, $i, 0,
791             bless( { content => '$_' }, 'PPI::Token::Magic' ),
792             bless( { content => '=~' }, 'PPI::Token::Operator' );
793             }
794             }
795             return;
796             }
797              
798             sub __append_implicit_topic_to_naked_filetest {
799             my $e = shift;
800              
801             #
802             # bare -X means -X $_ (except for -t)
803             #
804             for (my $i=0; $i<@$e; $i++) {
805             if (ref $e->[$i] eq 'PPI::Token::Operator'
806             && $e->[$i] =~ /^-[a-su-zA-Z]$/) {
807             my $j = $i + 1;
808             while ($j <= @$e && ref($e->[$j]) eq 'PPI::Token::Whitespace') {
809             $j++;
810             }
811             if ($j >= @$e || ref($e->[$j]) eq 'PPI::Token::Operator'
812             || ref($e->[$j]) eq 'PPI::Token::Structure') {
813             splice @$e, $i+1, 0,
814             bless( { content=>' ' }, 'PPI::Token::Whitespace' ),
815             bless( { content=>'$_' }, 'PPI::Token::Magic' );
816             }
817             }
818             }
819             return;
820             }
821              
822             sub __append_implicit_topic_to_naked_builtins {
823             my $e = shift;
824              
825             #
826             # for many builtin functions (print, sin, log, ...)
827             #
828             # func; means func($_);
829             #
830             #
831             # 0.12: but $hash{bareword}
832             # should not be $hash{bareword $_}
833             #
834             for (my $i=0; $i<@$e; $i++) {
835             if (ref($e->[$i]) eq 'PPI::Token::Word'
836             && defined $implicit_{"$e->[$i]"}) {
837              
838             my $j = $i + 1;
839             my $gparent = $e->[$i]->parent && $e->[$i]->parent->parent;
840             next if $gparent && ref($gparent) eq 'PPI::Structure::Subscript'
841             && $gparent =~ /^{/;
842             $j++ while $j <= @$e && ref($e->[$j]) eq 'PPI::Token::Whitespace';
843             if ($j >= @$e || ref($e->[$j]) eq 'PPI::Token::Structure'
844             || (ref($e->[$j]) eq 'PPI::Token::Operator' &&
845             ($e->[$j] eq '..' || $e->[$j] eq '...'))) {
846             if ($e->[$i] eq 'split') {
847             # naked split is parsed as split /\s+/, $_
848             splice @$e, $i+1, 0,
849             bless({content=>' '}, 'PPI::Token::Whitespace'),
850             bless({content=>'m/\\s+/'},
851             'PPI::Token::Regexp::Match'),
852             bless({content=>','}, 'PPI::Token::Operator'),
853             bless({content=>'$_'}, 'PPI::Token::Magic');
854             } else {
855             splice @$e, $i+1, 0,
856             bless({content=>' '}, 'PPI::Token::Whitespace'),
857             bless({content=>'$_'}, 'PPI::Token::Magic');
858             }
859             }
860             }
861             }
862             return;
863             }
864              
865             sub __insert_implicit_topic_into_default_foreach {
866             my $e = shift;
867              
868             # for (LIST) means for $_ (LIST)
869              
870             for (my $i=0; $i<@$e; $i++) {
871             next unless ref($e->[$i]) eq 'PPI::Token::Word'
872             && ($e->[$i] eq 'for' || $e->[$i] eq 'foreach');
873              
874             my $j = $i + 1;
875             $j++ while $j < @$e && ref($e->[$j]) eq 'PPI::Token::Whitespace';
876             if ($j < @$e && ref($e->[$j]) eq 'PPI::Structure::List') {
877              
878             splice @$e, $i+1, 0,
879             bless({content=>' '}, 'PPI::Token::Whitespace'),
880             bless({content=>'$_', _DEFER => 1}, 'PPI::Token::Magic');
881              
882             }
883             }
884             return;
885             }
886              
887             sub __insert_implicit_NR_into_flipflop {
888             # (m ... n) means ($.==m ... $.==n)
889             # (m .. n) means ($.==m .. $.==n) in list context only
890             my $e = shift;
891             for (my $i=0; $i<@$e; $i++) {
892             next unless ref($e->[$i]) eq 'PPI::Token::Operator';
893             next unless $e->[$i] eq '...' || $e->[$i] eq '..';
894             if ($e->[$i] eq '..') {
895             # must also guess whether this is evaluated in scalar context
896             # some heuristics we will use:
897             # no if preceded by 'for','foreach' keyword
898             # no if preceded by '=' operator
899             # yes if preceded by other assignment operator
900             # yes if preceded by 'if','while',or 'until'
901             my $ee = $e;
902             my $ff = join '', @$e;
903             my @ref = map { ref } @$e;
904             my @oref = grep { !/::Whitespace/ && !/::Operator/
905             && !/::Number/ } @ref;
906              
907             if (@oref == 0) {
908             $ee = $e->[0]->parent;
909             return if ref($ee) ne 'PPI::Statement::Expression';
910             $ee = $ee->parent;
911             return if ref($ee) ne 'PPI::Structure::Condition';
912             # ah, there we go. In list context PPI will call $ee
913             # a PPI::Structure::List
914             $ee = $ee->parent;
915             return if ref($ee) !~ /^PPI::Statement/;
916             $ee = $ee->{children};
917             }
918             for (my $k=0; $k<@$ee; $k++) {
919             next if $ee->[$k] ne $ff && $ee->[$k] ne "($ff)";
920             my $l = $k+1;
921             $l++ while $l<@$ee &&
922             'PPI::Token::Whitespace' eq ref $ee->[$l];
923             return if $l<@$ee &&
924             (ref($ee->[$l]) ne 'PPI::Token::Structure'
925             || $ee->[$l] ne ';') &&
926             ref($ee->[$l]) ne 'PPI::Structure::Block';
927             $l = $k-1;
928             $l-- while $l>=0 && 'PPI::Token::Whitespace' eq ref $ee->[$l];
929             return if $l<0;
930             return if ref($ee->[$l]) ne 'PPI::Token::Word';
931             return if $ee->[$l] ne 'if' && $ee->[$l] ne 'while' &&
932             $ee->[$l] ne 'until';
933             last;
934             }
935             }
936              
937             # token before the ... operator
938             my $j = $i - 1;
939             $j-- while $j>0 && ref($e->[$j]) eq 'PPI::Token::Whitespace';
940              
941             if ($j >= 0 && ref($e->[$j]) =~ /PPI::Token::Number/) {
942             if ($j==0) {
943             unshift @$e,
944             bless({content => '$.', _location => $e->[$j]{_location}},
945             'PPI::Token::Magic'),
946             bless({content => '==', _location => $e->[$j]{_location}},
947             'PPI::Token::Operator');
948             return 1;
949             } else {
950             splice @$e, $j, 0,
951             bless({content => '$.'}, 'PPI::Token::Magic'),
952             bless({content => '=='}, 'PPI::Token::Operator');
953             return 1;
954             # how to tell if Number represents a standalone expression?
955             # or a numerical expression like + 40
956             }
957             }
958             # token after the ... operator
959             $j = $i + 1;
960             $j++ while $j<@$e && ref($e->[$j]) eq 'PPI::Token::Whitespace';
961             if ($j < @$e && (ref($e->[$j]) eq 'PPI::Token::Number' ||
962             ref($e->[$j]) eq 'PPI::Token::Number::Float')) {
963             if ($j == $#$e) {
964             splice @$e, $j, 0,
965             bless({content => '$.'}, 'PPI::Token::Magic'),
966             bless({content => '=='}, 'PPI::Token::Operator');
967             return 1;
968             }
969             }
970             }
971             return;
972             }
973              
974             sub __append_implicit_to_naked_shift_pop {
975             #
976             # look for use of implicit @_/@ARGV with shift/pop.
977             #
978             # This cannot be done when the document is initially parsed
979             # (in &__add_implicit_elements, for example) because
980             # we can only definitively determine whether or not we
981             # are inside a subroutine at runtime.
982             #
983             my $e = shift;
984             for (my $i=0; $i<@$e; $i++) {
985             next if ref($e->[$i]) ne 'PPI::Token::Word';
986             next if $e->[$i] ne 'shift' && $e->[$i] ne 'pop';
987              
988             my $j = $i + 1;
989             while ($j <= @$e && ref($e->[$j]) eq 'PPI::Token::Whitespace') {
990             $j++;
991             }
992             if ($j >= @$e || ref($e->[$j]) eq 'PPI::Token::Operator'
993             || ref($e->[$j]) eq 'PPI::Token::Structure') {
994              
995             # found naked pop/shift. Determine if we are inside a sub
996             # so we know whether to apply @ARGV or @_.
997             my $n = 0;
998             my $xp = 0;
999             while (my @p = caller($n++)) {
1000             $xp += $p[CALLER_PKG] !~ /^Devel::DumpTrace/ &&
1001             $p[CALLER_SUB] !~ /^\(eval/;
1002             }
1003              
1004             if ($xp >= 2) { # inside sub, manip @_
1005             splice @$e, $i+1, 0,
1006             bless({content=>' '}, 'PPI::Token::Whitespace'),
1007             bless({content=>'@_'}, 'PPI::Token::Magic');
1008             } else { # not inside sub, manip @ARGV
1009             splice @$e, $i+1, 0,
1010             bless({content=>' '}, 'PPI::Token::Whitespace'),
1011             bless({content=>'@ARGV'}, 'PPI::Token::Symbol');
1012             }
1013             }
1014             }
1015             }
1016              
1017             sub __add_implicit_to_given_when_blocks {
1018              
1019             # given($foo)
1020             # when($bar) means when ($foo ~~ $bar)
1021             # when(\@list) means when ($foo ~~ \@list)
1022             # when(&func) means ???
1023             # when(\&func) means when (&func($foo))
1024             # when(m/patt/) means when ($foo ~= m/patt/)
1025             # when($a <=> cmp $b) means what it says
1026             # when(defined ... exists ... eof) means what it says
1027             # when(!something) means what it says
1028             # when(-X file) means what it says for X not in {sMAC}
1029             # when(flip..flop) means what it says
1030              
1031              
1032             # there are probably a lot of incorrect edge cases,
1033             # but this is a good start
1034              
1035              
1036              
1037             my $given = shift;
1038              
1039             # what to do with a given block:
1040             # extra token(s) from child PPI::Structure::Given
1041             # extract structure tokens from front,back
1042             # this becomes the _given_ expression
1043             # find child PPI::Structure::Block
1044             # find grandchild PPI::Statement::When
1045             # find greatgrandchild PPI::Structure::When
1046             # analyze PPI::Structure::When element
1047             # if there is an "implicit smart match",
1048             # insert << $_ ~~ >> at the beginning of the struct
1049             my $given_child = $given->find('PPI::Structure::Given') or return;
1050             my $given_expr
1051             = $given_child->[0]->find('PPI::Statement::Expression') or return;
1052              
1053             my $given_tok = join '', $given_expr->[0]->tokens;
1054              
1055             my $whens = $given->find('PPI::Statement::When') || [];
1056             foreach my $when (@$whens) {
1057             if ($when->parent->parent ne $given) {
1058             next;
1059             }
1060             my $structure = $when->find('PPI::Structure::When');
1061             unless ($structure) {
1062             next;
1063             }
1064             $structure = $structure->[0];
1065            
1066             my $when_expr = $structure->find('PPI::Statement::Expression');
1067             unless ($when_expr) {
1068             $when_expr = $structure->find('PPI::Statement');
1069             next unless $when_expr;
1070             }
1071             my $first_when_expr = $when_expr->[0];
1072             my $is_implicit_smart_match = 0;
1073              
1074             my @e = $first_when_expr->elements();
1075              
1076             if (ref($e[0]) eq 'PPI::Token::Word'
1077             || ref($e[0]) =~ /PPI::Token::Quote::/) {
1078              
1079             $is_implicit_smart_match = 2;
1080              
1081             if ($e[0] eq 'defined' || $e[0] eq 'exists' || $e[0] eq 'eof') {
1082             $is_implicit_smart_match = 0;
1083             }
1084              
1085             } elsif (ref($e[0]) eq 'PPI::Structure::Constructor'
1086             && $e[0] =~ /^\[/) {
1087              
1088             $is_implicit_smart_match = 3;
1089              
1090             } elsif (@e == 1 &&
1091             (ref($e[0]) eq 'PPI::Token::Symbol' ||
1092             ref($e[0]) eq 'PPI::Token::Magic' ||
1093             ref($e[0]) =~ 'PPI::Token::Number')) {
1094              
1095             $is_implicit_smart_match = 1;
1096              
1097             } elsif (ref($e[0]) eq 'PPI::Token::Cast' && $e[0] eq '\\'
1098             && ref($e[1]) =~ /PPI::Token::(Symbol|Magic)/
1099             && $e[1] !~ /[&*]/) {
1100              
1101             $is_implicit_smart_match = 4;
1102              
1103             }
1104              
1105             for (my $i=0; $i<@e; $i++) {
1106             my $e = $e[$i];
1107              
1108             if (ref($e) =~ /Operator/
1109             && ($e eq '<' || $e eq '>' || $e eq '==' ||
1110             $e eq '<=' || $e eq '>=' || $e eq 'le' || $e eq 'ge' ||
1111             $e eq 'lt' || $e eq 'gt' || $e eq 'eq' || $e eq 'ne' ||
1112             $e eq '<=>' || $e eq 'cmp' || $e eq '!' || $e eq 'not' ||
1113             $e eq '^' || $e eq 'xor' || $e eq '~~' || $e eq '..')) {
1114              
1115             $is_implicit_smart_match = 0;
1116             last;
1117             } elsif (ref($e) =~ /::Regexp/) {
1118              
1119             $is_implicit_smart_match = 0;
1120             last;
1121             } elsif (ref($e) eq 'PPI::Token::Cast' && $e eq '\\'
1122             && ref($e[$i+1]) eq 'PPI::Token::Symbol'
1123             && $e[$i+1] =~ /^&/) {
1124              
1125             $is_implicit_smart_match = 0;
1126             last;
1127             } elsif (ref($e) =~ /Operator/
1128             && ($e eq '||' || $e eq '&&' || $e eq '//' ||
1129             $e eq 'or' || $e eq 'and')) {
1130              
1131             # these operators make it ambiguous whether an implicit
1132             # smart match is being used. Disable for now and the task
1133             # of making this more sophisticated will go on the todo list.
1134              
1135             $is_implicit_smart_match = 0;
1136              
1137             }
1138            
1139             }
1140              
1141             if ($is_implicit_smart_match) {
1142             my $elem = $first_when_expr->{children};
1143             my $location = $elem->[0]->location;
1144              
1145             unshift @$elem,
1146             bless( { content => '$_', _location => $location },
1147             'PPI::Token::Magic' ),
1148             bless( { content => '~~', _location => $location },
1149             'PPI::Token::Operator');
1150             $first_when_expr->{children} = $elem;
1151             }
1152             }
1153             return;
1154             }
1155              
1156             # A C-style for-loop has the structure:
1157             # for (INIT ; CONDITION ; CONTINUE) BLOCK
1158             #
1159             # The CONDITION expression (and sometimes the CONTINUE) expression sure
1160             # would be interesting to see while you are tracing through a program.
1161             # Unfortunately, DB::DB will typically only get called at the very
1162             # start of the loop.
1163             #
1164             # One workaround might be to prepend the for statement to the
1165             # source code associated with the first statement in the BLOCK.
1166             # That way, each time a new iteration starts, you would get
1167             # the chance to observe the CONDITION and CONTINUE expressions.
1168             #
1169             sub __decorate_first_statement_in_for_block {
1170              
1171             # We expect a particular pattern of PPI elements to describe the
1172             # "first statement in the block of a C-style for-loop":
1173             #
1174             # PPI::Statement::Compound $gparent
1175             # PPI::Token::Word (for/foreach)
1176             # zero of more PPI::Token::Whitespace
1177             # PPI::Structure::For $for
1178             # ...
1179             # zero or more PPI::Token::Whitespace
1180             # PPI::Structure::Block $parent
1181             # zero or more PPI::Token::xxx
1182             # PPI::Statement::xxx $element
1183             #
1184             return unless DECORATE_FOR;
1185              
1186             my ($element, $file) = (@_);
1187             return if ref($element) !~ /^PPI::Statement/;
1188             my $parent = $element->parent;
1189             return if !defined($parent) || ref($parent) ne 'PPI::Structure::Block';
1190              
1191             my $gparent = $parent->parent;
1192             return if !defined($gparent) ||
1193             ref($gparent) ne 'PPI::Statement::Compound';
1194              
1195             my @parent_elem = grep { ref($_) !~ /^PPI::Token/ } $parent->elements();
1196             return if $parent_elem[0] ne $element;
1197              
1198             my @gparent_elem = grep { ref($_) !~ /^PPI::Token/ } $gparent->elements();
1199             my $for = $gparent_elem[0];
1200             return if ref($for) ne 'PPI::Structure::For';
1201             return if @gparent_elem < 2 || $gparent_elem[1] ne $parent;
1202              
1203             # now what do we do with it ... ?
1204             # we want to _prepend_ the tokens^H^H^H^H^H elements of $element
1205             # with all the tokens in $gparent up to $parent, plus all
1206             # the tokens of $parent up to $element.
1207              
1208             foreach my $gparent_elem ($gparent->elements()) {
1209              
1210             last if $gparent_elem eq $parent;
1211             if ($gparent_elem eq $for) {
1212              
1213             my @for_statements
1214             = grep { ref($_) =~ /^PPI::Statement/ } $for->elements();
1215              
1216             my $condition_statement = $for_statements[1]->clone();
1217             $element->{__CONDITION__} = $condition_statement;
1218             $gparent->{__GP_CONDITION__} = $condition_statement;
1219              
1220             if (@for_statements > 2) {
1221             my $continue_statement = $for_statements[2]->clone();
1222             $element->{__CONTINUE__} = $continue_statement->clone();
1223             } else {
1224             $element->{__CONTINUE__} = __new_null_statement()->clone();
1225             }
1226              
1227             my $line = $for->line_number;
1228             my $line2 = ($gparent->tokens)[-1]->line_number;
1229             $element->{__FOR_LINE__} = "$file:$line";
1230             $IGNORE_FILE_LINE{"$file:$line2"} = 1;
1231             $element->{__DECORATED__} = 'for';
1232             }
1233             }
1234             return;
1235             }
1236              
1237             sub __decorate_first_statement_AFTER_for_block {
1238              
1239            
1240             return unless DECORATE_FOR;
1241              
1242             my ($element, $file,$doc) = @_;
1243              
1244             return if ref($element) ne 'PPI::Statement::Compound';
1245             my @children = $element->children;
1246             @children = grep { ref($_) ne 'PPI::Token::Whitespace' } @children;
1247             return if ref($children[0]) ne 'PPI::Token::Word';
1248             return if "$children[0]" ne "for" && "$children[0]" ne "foreach";
1249            
1250             my $next = __next_sibling($element);
1251             return unless $next;
1252              
1253              
1254             if (0) {
1255             open TTY,">","/dev/tty";
1256             print TTY "WANT TO DECORATE STATEMENT\n\n\t$next\n\n\nAFTER FOR";
1257             print TTY "PREV STATEMENT IS\n\n\t$element\n\n",ref($element),"\n\n\n";
1258             local $Data::Dumper::Indent = 0;
1259             # print TTY "\n", Dumper($element);
1260             print TTY "\n",join("\n",sort keys %$element),"\n";
1261             print TTY "\n";
1262             }
1263              
1264             my $line = $next->line_number;
1265             $next->{__DECORATED__} = "end-for";
1266             $next->{__ENDFOR_LINE__} = "$file:$line";
1267             $next->{__CONDITIONER__} = $element;
1268             return;
1269             }
1270              
1271             sub __decorate_first_statement_in_foreach_block {
1272             # We expect a particular pattern of PPI elements to describe the
1273             # "first statement in block of a foreach loop"
1274             #
1275             #PPI::Statement::Compound $gparent
1276             # PPI::Token::Word for/foreach
1277             # zero or more PPI::Token::Whitespace
1278             # optional PPI::Token::Symbol optional $loop_var
1279             # zero or more PPI::Token::Whitespace
1280             # PPI::Structure::List $list
1281             # ...
1282             # zero or more PPI::Token::Whitespace
1283             # PPI::Structure::Block $parent
1284             # optional PPI::Token::Structure
1285             # zero or more PPI::Token::Whitespace
1286             # PPI::Statement $element
1287              
1288             return unless DECORATE_FOREACH;
1289              
1290             my ($element, $file) = @_;
1291             return if ref($element) !~ /^PPI::Statement/;
1292              
1293             my $parent = $element->parent;
1294             return if !defined($parent) || ref($parent) ne 'PPI::Structure::Block';
1295              
1296             my @parent_sts = grep { ref($_) !~ /^PPI::Token/ } $parent->elements();
1297             return if $parent_sts[0] ne $element; # not the first statement in block
1298              
1299             my $gparent = $parent->parent;
1300             return if !defined($gparent) ||
1301             ref($gparent) ne 'PPI::Statement::Compound';
1302             my $keyword = ($gparent->elements())[0];
1303             return if $keyword ne 'foreach' && $keyword ne 'for';
1304              
1305             my @gparent_elem = grep { ref($_) !~ /^PPI::Token/ } $gparent->elements();
1306             return if @gparent_elem < 2 || $gparent_elem[1] ne $parent;
1307             my $list = $gparent_elem[0];
1308             return if ref($list) ne 'PPI::Structure::List';
1309              
1310             # find the name of the loop var. Could be implicit $_ if var can't be
1311             # found in the PPI.
1312             my ($loop_var) = grep {
1313             ref($_) eq 'PPI::Token::Symbol'
1314             || ref($_) eq 'PPI::Token::Magic'
1315             } $gparent->elements();
1316             if (!defined($loop_var)) {
1317             $loop_var = bless { content => '$_' }, 'PPI::Token::Magic';
1318             } else {
1319             $loop_var = $loop_var->clone;
1320             }
1321             $loop_var->{_PREVAL} = 1;
1322              
1323              
1324             $element->{__DECORATED__} = 'foreach';
1325             my $line = $keyword->line_number;
1326             $element->{__FOREACH_LINE__} = "$file:$line";
1327             $element->{__UPDATE__} = $loop_var;
1328             return;
1329             }
1330              
1331             sub __decorate_first_statement_in_while_block {
1332             # We expect a particular pattern of PPI elements to describe the
1333             # "first statement in a while/until block"
1334             #
1335             # PPI::Statement::Compound $gparent
1336             # PPI::Token::Word (while/until)
1337             # zero or more PPI::Token::Whitespace
1338             # PPI::Structure::Condition $cond
1339             # ...
1340             # zero or more PPI::Token::Whitespace
1341             # PPI::Structure::Block $parent
1342             # zero or more PPI::Token::xxx
1343             # PPI::Statement::xxx $element
1344             #
1345             return unless DECORATE_WHILE;
1346              
1347             my ($element, $file) = (@_);
1348             return if ref($element) !~ /^PPI::Statement/;
1349             my $parent = $element->parent;
1350             return if !defined($parent) || ref($parent) ne 'PPI::Structure::Block';
1351              
1352             my $gparent = $parent->parent;
1353             return if !defined($gparent) ||
1354             ref($gparent) ne 'PPI::Statement::Compound';
1355              
1356             my @parent_elem = grep { ref($_) !~ /^PPI::Token/ } $parent->elements();
1357             return if $parent_elem[0] ne $element;
1358              
1359             my @gparent_elem = grep { ref($_) !~ /^PPI::Token/ } $gparent->elements();
1360             my $cond = $gparent_elem[0];
1361             return if ref($cond) ne 'PPI::Structure::Condition';
1362             return if @gparent_elem < 2 || $gparent_elem[1] ne $parent;
1363              
1364             my $cond_name = '';
1365             foreach my $gparent_elem ($gparent->elements()) {
1366              
1367             if (ref($gparent_elem) eq 'PPI::Token::Word' && $cond_name eq '') {
1368             $cond_name = "$gparent_elem";
1369             }
1370              
1371             last if $gparent_elem eq $parent;
1372             if ($gparent_elem eq $cond) {
1373              
1374             $element->{__BLOCK_NAME__} = uc ($cond_name || 'COND');
1375             $element->{__CONDITION__} = $cond->clone();
1376             $gparent->{__GP_CONDITION__} = $element->{__CONDITION__};
1377             my $line = $cond->line_number;
1378             $element->{__WHILE_LINE__} = "$file:$line";
1379             $element->{__DECORATED__} = 'while/until';
1380             return;
1381             }
1382             }
1383             return;
1384             }
1385              
1386             sub __decorate_first_statement_AFTER_while_block {
1387              
1388            
1389             return unless DECORATE_WHILE;
1390              
1391             my ($element, $file) = @_;
1392              
1393             return if ref($element) ne 'PPI::Statement::Compound';
1394             my @children = $element->children;
1395             @children = grep { ref($_) ne 'PPI::Token::Whitespace' } @children;
1396             return if ref($children[0]) ne 'PPI::Token::Word';
1397             return if "$children[0]" ne "while" && "$children[0]" ne "until";
1398             my $sense = uc($children[0]);
1399            
1400             my $next = __next_sibling($element);
1401             return unless $next;
1402              
1403              
1404             if (0) {
1405             open TTY,">","/dev/tty";
1406             print TTY "WANT TO DECORATE STATEMENT\n\n\t$next\n\n\nAFTER WHILE";
1407             print TTY "PREV STATEMENT IS\n\n\t$element\n\n",ref($element),"\n\n\n";
1408             local $Data::Dumper::Indent = 0;
1409             # print TTY "\n", Dumper($element);
1410             print TTY "\n",join("\n",sort keys %$element),"\n";
1411             print TTY "\n";
1412             }
1413              
1414             my $line = $next->line_number;
1415             $next->{__DECORATED__} = "end-while";
1416             $next->{__SENSE__} = $sense;
1417             $next->{__ENDWHILE_LINE__} = "$file:$line";
1418             $next->{__CONDITIONER__} = $element;
1419             return;
1420             }
1421              
1422             sub __decorate_last_statement_in_dowhile_block {
1423             # Looking for (optional whitespace removed):
1424             # PPI::Statement $gparent
1425             # PPI::Token::Word "do"
1426             # PPI::Structure::Block $parent
1427             # PPI::Token::Structure "{"
1428             # *PPI::Statement, PPI::Statement::xxx
1429             # PPI::Statement $element
1430             # ...
1431             # PPI::Token::Structure "}"
1432             # PPI::Token::Word "while/until"
1433             # ... @condition
1434              
1435             my ($element, $file) = @_;
1436             return if ref($element) !~ /^PPI::Statement/;
1437              
1438             my $parent = $element->parent;
1439             return if ref($parent) ne 'PPI::Structure::Block';
1440             my @parent_stmnts = grep {
1441             ref($_) =~ /^PPI::Statement/
1442             } $parent->elements;
1443             return if @parent_stmnts < 1 || $parent_stmnts[-1] ne $element;
1444              
1445             my $gparent = $parent->parent;
1446             return if ref($gparent) !~ /^PPI::Statement/;
1447             my @gparent_elem = grep {
1448             ref($_) ne 'PPI::Token::Whitespace'
1449             } $gparent->elements();
1450              
1451             return if $gparent_elem[0] ne "do";
1452             return if $gparent_elem[1] ne $parent;
1453             my $sense = $gparent_elem[2];
1454             return if $sense ne 'while' && $sense ne 'until';
1455             my @condition = map {
1456             my $z = $_->clone;
1457             $z->{_PREVAL} = $z->{_DEFER} = 1
1458             if ref($z) eq 'PPI::Token::Symbol'
1459             || ref($z) eq 'PPI::Token::Magic';
1460             $z
1461             } grep {
1462             $_->line_number > $sense->line_number
1463             || ($_->line_number == $sense->line_number
1464             && $_->column_number > $sense->column_number)
1465             } $gparent->elements();
1466              
1467             $element->{__DECORATED__} = 'do-while';
1468             my $line = $sense->line_number;
1469             $element->{__DOWHILE_LINE__} = "$file:$line";
1470             $element->{__SENSE__} = "DO-" . uc "$sense";
1471             $element->{__CONDITION__} = [ @condition ];
1472             return;
1473             }
1474              
1475             sub __next_sibling {
1476             my ($element) = @_;
1477             my $parent = $element->parent;
1478             return unless $parent;
1479             my $last = undef;
1480             foreach my $sib ($parent->children) {
1481             next if ref($sib) eq 'PPI::Token::Whitespace';
1482             if ($last eq $element) {
1483             return $sib;
1484             }
1485             $last = $sib;
1486             }
1487             return;
1488             }
1489              
1490             # in a long chain of if/elsif/else blocks,
1491             # say if(COND1) BLOCK1 elsif(COND2) BLOCK2 elsif(COND3) BLOCK3 else BLOCK4,
1492             # only the first condition (COND1) gets displayed in a trace. To get more
1493             # useful trace output, prepend conditions to the first statement in
1494             # each block to be displayed with the trace. That is, display
1495             # COND2 with the first statement in BLOCK2,
1496             # COND2 and COND3 with the first statement in BLOCK3, and
1497             # COND2 and COND3 with the first statement in BLOCK4.
1498             #
1499             sub __decorate_statements_in_ifelse_block {
1500              
1501             return unless DECORATE_ELSIF;
1502              
1503             my ($element, $file) = @_;
1504             return if ref($element) !~ /^PPI::Statement/;
1505             my $parent = $element->parent;
1506             return if !defined($parent) || ref($parent) ne 'PPI::Structure::Block';
1507              
1508             my $gparent = $parent->parent;
1509             return if !defined($gparent) ||
1510             ref($gparent) ne 'PPI::Statement::Compound';
1511              
1512             my @parent_elem = grep { ref($_) !~ /^PPI::Token/ } $parent->elements();
1513             return if $parent_elem[0] ne $element;
1514              
1515             my @gparent_elem = grep { ref($_) !~ /^PPI::Token/ } $gparent->elements();
1516             my $cond = $gparent_elem[0];
1517             return if ref($cond) ne 'PPI::Structure::Condition';
1518              
1519             my @gparent_blocks = grep { ref($_) eq 'PPI::Structure::Block'
1520             } $gparent->elements();
1521             return if @gparent_blocks < 2 || $gparent_blocks[0] eq $parent;
1522              
1523             my @gparent_cond = grep { ref($_) eq 'PPI::Structure::Condition'
1524             } $gparent->elements();
1525              
1526             my $line = $gparent_cond[0]->line_number;
1527             $element->{__IF_LINE__} = "$file:$line";
1528             $element->{__DECORATED__} = 'if-elsif-else';
1529             $element->{__CONDITIONS__} = [];
1530             my $ws = '';
1531             my $style = Devel::DumpTrace::_display_style();
1532             for (my $i=0; $i<@gparent_blocks; $i++) {
1533             if ($i < @gparent_cond) {
1534             push @{$element->{__CONDITIONS__}},
1535             __new_token("${ws}ELSEIF "),
1536             $gparent_cond[$i]->clone();
1537             } else {
1538             push @{$element->{__CONDITIONS__}}, __new_token("${ws}ELSE");
1539             }
1540             if ($gparent_blocks[$i] eq $parent) {
1541             return;
1542             }
1543             $ws ||= $style == DISPLAY_TERSE ? "\n\t\t\t" : " ";
1544             }
1545             return;
1546             }
1547              
1548             sub __new_token {
1549             my ($text) = @_;
1550             my $element = bless { content => $text }, 'PPI::Token';
1551             return $element->clone();
1552             }
1553              
1554             our $NULL_DOC = '';
1555             our $NULL_STATEMENT; # for empty clause in for(...; ...; ...) statement
1556             sub __new_null_statement {
1557             unless ($NULL_STATEMENT) {
1558             $NULL_DOC = PPI::Document->new(\' '); #' \');
1559             $NULL_STATEMENT = ($NULL_DOC->elements)[0];
1560             }
1561             return $NULL_STATEMENT->clone();
1562             }
1563              
1564             sub main::TOKENIZE {
1565             # invoke with perl -MDevel::DumpTrace::PPI -e TOKENIZE file
1566             # to see how an input script is tokenized by PPI and broken
1567             # into statements by this package
1568             open(my $fh, '<', $ARGV[0]) or die;
1569             my $all_code = join '', <$fh>;
1570             close $fh;
1571             my $doc = PPI::Document->new(\$all_code);
1572             $doc->index_locations;
1573             my $ss = $doc->find('PPI::Statement');
1574             foreach my $i (0 .. $#$ss) {
1575             my $s = $ss->[$i];
1576             my @e = $s->elements;
1577             for my $j (0 .. $#e) {
1578             my $w = "$e[$j]";
1579             $w =~ s/^\s+//;
1580             $w =~ s/\s+$//;
1581             my $ref = ref($e[$j]);
1582             $ref =~ s/PPI:://;
1583             printf "%d:%d\t%-20s\t%s\n", $i, $j, $ref, $w;
1584             }
1585             }
1586             }
1587              
1588             1;
1589              
1590             __END__