File Coverage

blib/lib/Devel/OptreeDiff.pm
Criterion Covered Total %
statement 115 234 49.1
branch 31 72 43.0
condition 9 14 64.2
subroutine 26 47 55.3
pod 5 12 41.6
total 186 379 49.0


line stmt bran cond sub pod time code
1             package Devel::OptreeDiff;
2             BEGIN {
3 3     3   62627 $Devel::OptreeDiff::VERSION = '2.3';
4             }
5 3     3   25 use strict;
  3         6  
  3         83  
6 3     3   13 use warnings;
  3         5  
  3         76  
7 3     3   17 use base 'Exporter';
  3         5  
  3         325  
8 3     3   3048 use Algorithm::Diff qw();
  3         15463  
  3         71  
9 3     3   21 use B qw( svref_2object class cstring sv_undef walkoptree );
  3         6  
  3         308  
10 3     3   2866 use B::Utils qw();
  3         17989  
  3         97  
11 3         635 use vars qw( @EXPORT_OK
12             %SIDES
13 3     3   27 %ADDR %DONE_GV %LINKS @NODES @specialsv_name );
  3         4  
14              
15             @EXPORT_OK = 'fmt_optree_diff';
16              
17             @specialsv_name
18             = qw( Nullsv &PL_sv_undef &PL_sv_yes &PL_sv_no (SV*)pWARN_ALL (SV*)pWARN_NONE (SV*)pWARN_STD );
19              
20             # Create several functions as a wrapper over the functions from
21             # Algorithm::Diff.
22             BEGIN {
23 3     3   9 for my $method (
24             qw( LCS
25             diff
26             sdiff
27             traverse_sequences
28             traverse_balanced )
29             )
30             {
31 15         39 push @EXPORT_OK, "optree_$method";
32              
33             ## no critic eval
34 15 50   0 0 5203 eval "sub " . __PACKAGE__ . "::optree_$method {
  0 0   2 1 0  
  0 0   0 1 0  
  0 0   0 1 0  
  0 100   0 1 0  
  0 100       0  
  0 100       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  2 0       6  
  2 0       39  
  2         27  
  2         11  
  114         426  
  2         10  
  88         321  
  2         25  
  2         2950  
  14         20  
  52         168  
  52         289  
  2         127  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
35             local %SIDES;
36             my \@a = as_string( a =>
37             svref_2object( \$_[0] )->ROOT );
38             my \@b = as_string( b =>
39             svref_2object( \$_[1] )->ROOT );
40             my \@a_names = map { s/^(\\S+) // ? \$1 : \$_ } \@a;
41             my \@b_names = map { s/^(\\S+) // ?\$1 : \$_ } \@b;
42              
43             my \@diff =
44             Algorithm::Diff::$method
45             ( # The first two parameters are transformed into the
46             # data that this module will be capable of handling
47             # a diff on.
48             \\\@a, \\\@b,
49            
50             # All the additional parameters, if any are passed
51             # directly through to Algorithm::Diff::$method
52             \@_[ 2 .. \$#_ ]);
53              
54             for my \$chunk ( \@diff )
55             {
56             for my \$line ( \@\$chunk )
57             {
58             \$line->[2] = ( \$line->[0] eq '+'
59             ? \$b_names[\$line->[1]]
60             : \$a_names[\$line->[1]] )
61             . \".\$line->[2]\";
62             \$line->[2] =~ s/^([^.]+)\\.\\1\\s*/\$1/;
63             }
64             }
65             \@diff;
66             }
67              
68             1 "
69             or die $@;
70             }
71             }
72              
73             sub fmt_optree_diff {
74 2     2 1 101 my @chunks = map join( "", map "$_->[0] $_->[2]\n", @$_ ), &optree_diff;
75 2         19 for my $chunk (@chunks) {
76 14         18 my %seen;
77              
78             # Elide redundant node paths
79 14         63 $chunk =~ s((?<=^..)([^.\s]+)){
80 52 100       370 ( $seen{$1}++
81             ? ( ' ' x length $1 )
82             : $1 )
83             }meg;
84             }
85 2         20 @chunks;
86             }
87              
88             sub as_string {
89 4     4 0 12 my ( $side, $op ) = @_;
90              
91 4         7 local %ADDR;
92 4         8 local %DONE_GV;
93 4         7 local @NODES;
94 4         7 local %LINKS;
95              
96             # Serialize the optree
97 4         26 walkoptree( $op, 'OptreeDiff_as_string' );
98              
99             # Delete empty elements
100             # for my $n ( @NODES )
101             # {
102             # delete @{$n}{ grep !defined( $n->{$_ } ), keys %$n };
103             # }
104              
105 4         18 augment_nodes_with_node_path();
106              
107 4         8 map( { my $node = $_;
  46         57  
108              
109 697 100 66     2976 my @keys = (
    100 66        
110             sort {
111 46         155 ( ( $a eq 'name' and $b ne 'name' ) ? -1
112             : ( $a ne 'name' and $b eq 'name' ) ? 1
113             : ( $a cmp $b )
114             )
115             }
116             keys %$node
117             );
118              
119 46 100 100     1903 map( +(
    100          
120             $_ eq 'name' ? $node->{'node path'}
121             : defined $node->{$_}
122             ? "$node->{'node path'} $_ = $node->{$_}"
123             : ()
124             ),
125             grep( +( $_ ne 'node path' && $_ ne 'class' && $_ ne 'addr' ),
126             @keys ) );
127             } @NODES );
128             }
129              
130             sub augment_nodes_with_node_path {
131 4     4 0 9 for my $n (@NODES) {
132 46         62 my $addr = $n->{'addr'};
133 46         61 my $rel_from = $LINKS{$addr};
134              
135 46 100       75 if ( not $rel_from ) {
136 4         12 $n->{'node path'} = "/$n->{'name'}";
137             }
138             else {
139 42         76 $n->{'node path'} = $n->{'name'};
140 42         82 while ($rel_from) {
141 219         179 my $prev;
142 219 100       575 if ( grep $_ eq 'first', keys %$rel_from ) {
    50          
143 159         185 $prev = $rel_from->{'first'}{'prev'};
144 159         346 $n->{'node path'}
145             = "$rel_from->{'first'}{'name'}/$n->{'node path'}";
146             }
147             elsif ( grep $_ eq 'sibling', keys %$rel_from ) {
148 60         80 $prev = $rel_from->{'sibling'}{'prev'};
149 60         128 $n->{'node path'}
150             = "$rel_from->{'sibling'}{'name'}*$n->{'node path'}";
151             }
152              
153 219         505 $rel_from = $LINKS{$prev};
154             }
155 42         101 $n->{'node path'} = "/$n->{'node path'}";
156             }
157             }
158             }
159              
160             sub ADDR {
161 3     3 0 30 return 0;
162 0 0       0 return 0 if not $_[0];
163              
164 0   0     0 0xADD + ( $ADDR{ $_[0]->oldname }{ $_[0] } ||= scalar keys %ADDR );
165             }
166              
167             sub add_link {
168 74     74 0 175 my %p = @_;
169 74         69 my $from = ${ $p{'op'} };
  74         134  
170 74         146 my $rel = lc $p{'rel'};
171              
172 74         341 my $to = $p{'op'}->$rel;
173 74 50       192 return if not ref $to;
174 74         142 $to = $$to;
175              
176 74 100 66     365 return if not( $from and $to );
177              
178             # $LINKS{ $rel }{ $to } = $from;
179 42         162 $LINKS{$to}{$rel} = {
180             'prev' => $from,
181             'name' => $p{'op'}->oldname
182             };
183             }
184              
185             BEGIN {
186 3     3   16 for (qw( SIBLING FIRST )) {
187             ## no critic eval
188 6 50   46 0 528 eval "sub ${_}_CHECK {
  46 50   46 0 352  
  0 50       0  
  46         193  
  0         0  
189             return if not \$LINKS{ '\L$_\E' }{ \$_[0] };
190             push \@NODES, \"->$_\";
191             }
192             1 "
193             or die $@;
194             }
195             }
196              
197             # Now inject lots of methods into the B::*OP namespace so it can
198             # be called by B::walkoptree( $ROOT, 'OptreeDiff_as_string' )
199              
200             sub B::OP::OptreeDiff_as_string {
201 46     46   58 my ($op) = @_;
202              
203 46 50       104 return if not $$op;
204              
205 46         314 my $class = class $op;
206 46 50       132 bless $op, 'B::OP' if $class eq 'NULL';
207              
208 46         150 push(
209             @NODES,
210             { addr => $$op,
211             name => $op->oldname,
212             class => $class,
213             map( +( "op_$_", $op->$_ ), ( 'targ', 'flags', 'private' ) )
214             }
215             );
216 46         973 add_link(
217             op => $op,
218             rel => 'SIBLING'
219             );
220 46         1381 SIBLING_CHECK($op);
221 46         1114 FIRST_CHECK($op);
222             }
223              
224             sub B::UNOP::OptreeDiff_as_string {
225 28     28   35 my ($op) = @_;
226 28         48 add_link(
227             op => $op,
228             rel => 'first'
229             );
230              
231 28         481 $op->B::OP::OptreeDiff_as_string(),;
232             }
233              
234             sub B::BINOP::OptreeDiff_as_string {
235 11     11   14 my ($op) = @_;
236              
237 11         39 $op->B::UNOP::OptreeDiff_as_string(),;
238             }
239              
240             sub B::LOOP::OptreeDiff_as_string {
241 0     0   0 my ($op) = @_;
242 0         0 $op->B::BINOP::OptreeDiff_as_string(),
243             $NODES[-1]{"op_$_"} = ADDR( ${ $op->$_ } )
244 0         0 for (qw( redoop nextop lastop ));
245             }
246              
247             sub B::LOGOP::OptreeDiff_as_string {
248 3     3   6 my ($op) = @_;
249 3         25 $op->B::UNOP::OptreeDiff_as_string(),
250 3         13 $NODES[-1]{"op_other"} = ADDR( ${ $op->other } );
251             }
252              
253             sub B::LISTOP::OptreeDiff_as_string {
254 11     11   20 my ($op) = @_;
255 11         32 $op->B::BINOP::OptreeDiff_as_string(),;
256             }
257              
258             sub B::PMOP::OptreeDiff_as_string {
259 0     0   0 my ($op) = @_;
260              
261 0         0 $op->B::LISTOP::OptreeDiff_as_string(),
262             $NODES[-1]{"op_$_"} = ADDR( ${ $op->$_ } )
263 0         0 for (qw( pmreplroot pmreplstart pmnext ));
264 0         0 $NODES[-1]{"op_pmflags"} = ${ $op->pmflags };
  0         0  
265 0         0 $NODES[-1]{'op_pmregexp->precomp'} = cstring( $op->precomp );
266              
267             # Now recurse down for whatever the pmreplroot is.
268 0         0 $op->pmreplroot->OptreeDiff_as_string;
269             }
270              
271             sub B::COP::OptreeDiff_as_string {
272 4     4   6 my ($op) = @_;
273              
274 4         16 $op->B::OP::OptreeDiff_as_string();
275 12         12 $NODES[-1]{"cop_$_"} = eval { ${ $op->$_ } }
  12         136  
276 4         11 for (qw( label stashpv arybase ));
277 4         8 $NODES[-1]{'cop_warnings'} = ${ $op->warnings };
  4         23  
278 4 50       104 $NODES[-1]{'cop_io'} = cstring(
279             class( $op->io ) eq 'SPECIAL'
280             ? ''
281             : $op->io->as_string
282             );
283             }
284              
285             sub B::SVOP::OptreeDiff_as_string {
286 7     7   11 my ($op) = @_;
287              
288 7         31 $op->B::OP::OptreeDiff_as_string(),
289              
290             $op->sv->OptreeDiff_as_string;
291             }
292              
293             sub B::PVOP::OptreeDiff_as_string {
294 0     0   0 my ($op) = @_;
295              
296 0         0 $op->B::OP::OptreeDiff_as_string(),
297             $NODES[-1]{"op_pv"} = cstring( $op->pv );
298             }
299              
300             sub B::PADOP::OptreeDiff_as_string {
301 0     0   0 my ($op) = @_;
302              
303 0         0 $op->B::OP::OptreeDiff_as_string(), $NODES[-1]{'op_padix'} = $op->padix;
304             }
305              
306             sub B::NULL::OptreeDiff_as_string {
307 0     0   0 my ($sv) = @_;
308              
309 0         0 push(
310             @NODES,
311             { null => (
312 0 0       0 $$sv == ${ sv_undef() }
313             ? "&sv_undef\n"
314             : ADDR($$sv)
315             )
316             }
317             );
318             }
319              
320             sub B::SV::OptreeDiff_as_string {
321 0     0   0 my ($sv) = @_;
322              
323 0         0 push( @NODES, { class => class($sv) } );
324 0 0       0 if ($$sv) {
325 0         0 $NODES[-1]{'addr'} = ADDR($$sv);
326 0         0 $NODES[-1]{"sv $_"} = $sv->$_ for ( 'REFCNT', 'FLAGS' );
327             }
328             }
329              
330             sub B::RV::OptreeDiff_as_string {
331 0     0   0 my ($rv) = @_;
332              
333 0         0 B::SV::OptreeDiff_as_string($rv), $NODES[-1]{'RV'} = ADDR( ${ $rv->RV } );
  0         0  
334              
335             # Recurse and push another node onto the list
336 0         0 $rv->RV->OptreeDiff_as_string;
337             }
338              
339             sub B::PV::OptreeDiff_as_string {
340 0     0   0 my ($sv) = @_;
341              
342 0         0 my $pv = $sv->PV();
343 0 0       0 $pv = '' if not defined $pv;
344              
345 0         0 $sv->B::SV::OptreeDiff_as_string(), $NODES[-1]{'xpv_pv'} = cstring($pv);
346 0         0 $NODES[-1]{'xpv_cur'} = length $pv;
347             }
348              
349             sub B::IV::OptreeDiff_as_string {
350 0     0   0 my ($sv) = @_;
351              
352 0         0 $sv->B::SV::OptreeDiff_as_string(), $NODES[-1]{'xiv_iv'} = $sv->IV;
353             }
354              
355             sub B::NV::OptreeDiff_as_string {
356 0     0   0 my ($sv) = @_;
357              
358 0         0 $sv->B::IV::OptreeDiff_as_string(), $NODES[-1]{'xnv_nv'} = $sv->NV;
359             }
360              
361             sub B::PVIV::OptreeDiff_as_string {
362 0     0   0 my ($sv) = @_;
363              
364 0         0 $sv->B::PV::OptreeDiff_as_string(), $NODES[-1]{'xiv_iv'} = $sv->IV;
365             }
366              
367             sub B::PVNV::OptreeDiff_as_string {
368 0     0   0 my ($sv) = @_;
369              
370 0         0 $sv->B::PVIV::OptreeDiff_as_string(), $NODES[-1]{'xnv_nv'} = $sv->NV;
371             }
372              
373             sub B::PVLV::OptreeDiff_as_string {
374 0     0   0 my ($sv) = @_;
375              
376             $sv->B::PVNV::OptreeDiff_as_string(), $NODES[-1]{"xlv_\L$_"} = $sv->$_
377 0         0 for ( 'TARGOFF', 'TARGLEN' );
378 0         0 $NODES[-1]{'xlv_type'} = cstring( chr $sv->TYPE );
379             }
380              
381             sub B::BM::OptreeDiff_as_string {
382 0     0   0 my ($sv) = @_;
383              
384             $sv->B::PVNV::OptreeDiff_as_string(), $NODES[-1]{"xbm_\L$_"} = $sv->$_
385 0         0 for ( 'USEFUL', 'PREVIOUS' );
386 0         0 $NODES[-1]{'xbm_rare'} = cstring( chr $sv->RARE );
387             }
388              
389             sub B::CV::OptreeDiff_as_string {
390 0     0   0 my ($sv) = @_;
391 0         0 my ($stash) = $sv->STASH;
392 0         0 my ($start) = $sv->START;
393 0         0 my ($root) = $sv->ROOT;
394 0         0 my ($padlist) = $sv->PADLIST;
395 0         0 my ($gv) = $sv->GV;
396              
397 0         0 $sv->B::PVNV::OptreeDiff_as_string();
398              
399 0         0 $NODES[-1]{$_} = ADDR( ${ $sv->$_ } )
400 0         0 for ( 'STASH', 'START', 'ROOT', 'GV', 'PADLIST', 'OUTSIDE' );
401 0         0 $NODES[-1]{'DEPTH'} = $sv->DEPTH;
402              
403             $_->OptreeDiff_as_string
404 0         0 for grep $_,
405             map $sv->$_,
406             ( 'GV', 'PADLIST', 'ROOT', 'START' );
407             }
408              
409             sub B::AV::OptreeDiff_as_string {
410 0     0   0 my ($av) = @_;
411 0         0 my (@array) = $av->ARRAY;
412              
413 0         0 $av->B::SV::OptreeDiff_as_string,
414             $NODES[-1]{'ARRAY'} = join( ", ", map ADDR($$_), @array );
415 0         0 $NODES[-1]{'FILL'} = scalar @array;
416 0         0 $NODES[-1]{$_} = $av->$_ for qw( MAX OFF AvFLAGS );
417             }
418              
419             sub B::GV::OptreeDiff_as_string {
420 7     7   12 my ($gv) = @_;
421              
422 7         103 $NODES[-1]{'GV'} = join( "::", $gv->STASH->NAME, $gv->SAFENAME );
423             }
424              
425             sub B::SPECIAL::OptreeDiff_as_string {
426 0     0     my ($sv) = @_;
427              
428 0           $NODES[-1] .= join "", $specialsv_name[$$sv], "\n";
429             }
430              
431             1;
432             __END__