File Coverage

blib/lib/Algorithm/C3.pm
Criterion Covered Total %
statement 69 69 100.0
branch 30 30 100.0
condition 16 16 100.0
subroutine 4 4 100.0
pod 1 1 100.0
total 120 120 100.0


line stmt bran cond sub pod time code
1             package Algorithm::C3;
2              
3 11     11   778597 use strict;
  11         115  
  11         336  
4 11     11   57 use warnings;
  11         20  
  11         336  
5              
6 11     11   57 use Carp 'confess';
  11         28  
  11         7706  
7              
8             our $VERSION = '0.11';
9              
10             sub merge {
11 66     66 1 8178 my ($root, $parent_fetcher, $cache) = @_;
12              
13 66   100     315 $cache ||= {};
14              
15 66         104 my @STACK; # stack for simulating recursion
16              
17 66         152 my $pfetcher_is_coderef = ref($parent_fetcher) eq 'CODE';
18              
19 66 100 100     258 unless ($pfetcher_is_coderef or $root->can($parent_fetcher)) {
20 1         245 confess "Could not find method $parent_fetcher in $root";
21             }
22              
23 65         99 my $current_root = $root;
24 65         164 my $current_parents = [ $root->$parent_fetcher ];
25 65         458 my $recurse_mergeout = [];
26 65         103 my $i = 0;
27 65         147 my %seen = ( $root => 1 );
28              
29 65         107 my ($new_root, $mergeout, %tails);
30 65         96 while(1) {
31 794 100       1406 if($i < @$current_parents) {
32 387         554 $new_root = $current_parents->[$i++];
33              
34 387 100       769 if($seen{$new_root}) {
35 8         13 my @isastack;
36             my $reached;
37 8         18 for(my $i = 0; $i < $#STACK; $i += 4) {
38 27 100 100     87 if($reached || ($reached = ($STACK[$i] eq $new_root))) {
39 14         49 push(@isastack, $STACK[$i]);
40             }
41             }
42 8         24 my $isastack = join(q{ -> }, @isastack, $current_root, $new_root);
43 8         205 die "Infinite loop detected in parents of '$root': $isastack";
44             }
45 379         573 $seen{$new_root} = 1;
46              
47 379 100 100     880 unless ($pfetcher_is_coderef or $new_root->can($parent_fetcher)) {
48 1         139 confess "Could not find method $parent_fetcher in $new_root";
49             }
50              
51 378         644 push(@STACK, $current_root, $current_parents, $recurse_mergeout, $i);
52              
53 378         517 $current_root = $new_root;
54 378   100     981 $current_parents = $cache->{pfetch}->{$current_root} ||= [ $current_root->$parent_fetcher ];
55 378         1502 $recurse_mergeout = [];
56 378         496 $i = 0;
57 378         506 next;
58             }
59              
60 407         568 $seen{$current_root} = 0;
61              
62 407   100     851 $mergeout = $cache->{merge}->{$current_root} ||= do {
63              
64             # This do-block is the code formerly known as the function
65             # that was a perl-port of the python code at
66             # http://www.python.org/2.3/mro.html :)
67              
68             # Initial set (make sure everything is copied - it will be modded)
69 221         367 my @seqs = map { [@$_] } @$recurse_mergeout;
  210         531  
70 221 100       666 push(@seqs, [@$current_parents]) if @$current_parents;
71              
72             # Construct the tail-checking hash (actually, it's cheaper and still
73             # correct to re-use it throughout this function)
74 221         379 foreach my $seq (@seqs) {
75 339         910 $tails{$seq->[$_]}++ for (1..$#$seq);
76             }
77              
78 221         424 my @res = ( $current_root );
79 221         290 while (1) {
80 750         939 my $cand;
81             my $winner;
82 750         1116 foreach (@seqs) {
83 1879 100       2935 next if !@$_;
84 1129 100       1927 if(!$winner) { # looking for a winner
    100          
85 601         838 $cand = $_->[0]; # seq head is candidate
86 601 100       1040 next if $tails{$cand}; # he loses if in %tails
87              
88             # Handy warn to give a output like the ones on
89             # http://www.python.org/download/releases/2.3/mro/
90             #warn " = " . join(' + ', @res) . " + merge([" . join('] [', map { join(', ', @$_) } grep { @$_ } @seqs) . "])\n";
91 529         802 push @res => $winner = $cand;
92 529         669 shift @$_; # strip off our winner
93 529 100       1061 $tails{$_->[0]}-- if @$_; # keep %tails sane
94             }
95             elsif($_->[0] eq $winner) {
96 296         381 shift @$_; # strip off our winner
97 296 100       561 $tails{$_->[0]}-- if @$_; # keep %tails sane
98             }
99             }
100              
101             # Handy warn to give a output like the ones on
102             # http://www.python.org/download/releases/2.3/mro/
103             #warn " = " . join(' + ', @res) . "\n" if !$cand;
104              
105 750 100       1654 last if !$cand;
106 530 100       888 die q{Inconsistent hierarchy found while merging '}
107             . $current_root . qq{':\n\t}
108             . qq{current merge results [\n\t\t}
109             . (join ",\n\t\t" => @res)
110             . qq{\n\t]\n\t} . qq{merging failed on '$cand'\n}
111             if !$winner;
112             }
113 220         602 \@res;
114             };
115              
116 406 100       1348 return @$mergeout if !@STACK;
117              
118 351         465 $i = pop(@STACK);
119 351         500 $recurse_mergeout = pop(@STACK);
120 351         474 $current_parents = pop(@STACK);
121 351         445 $current_root = pop(@STACK);
122              
123 351         515 push(@$recurse_mergeout, $mergeout);
124             }
125             }
126              
127             1;
128              
129             __END__