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