File Coverage

blib/lib/YATT/Lite/Util/CycleDetector.pm
Criterion Covered Total %
statement 65 68 95.5
branch 10 16 62.5
condition 1 3 33.3
subroutine 16 16 100.0
pod 0 9 0.0
total 92 112 82.1


line stmt bran cond sub pod time code
1             # -*- coding: utf-8 -*-
2              
3             # This package is used to implement modified version of following algorithm:
4             #
5             # http://en.wikipedia.org/wiki/Topological_sorting#CITEREFCormenLeisersonRivestStein2001
6             #
7             # Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.;
8             # Stein, Clifford (2001),
9             # "Section 22.4: Topological sort", Introduction to Algorithms (2nd ed.),
10             # MIT Press and McGraw-Hill, pp. 549–552, ISBN 0-262-03293-7.
11             #
12              
13             package YATT::Lite::Util::CycleDetector;
14 7     7   42 use strict;
  7         14  
  7         217  
15 7     7   36 use warnings qw(FATAL all NONFATAL misc);
  7         11  
  7         272  
16 7     7   36 use Carp;
  7         12  
  7         444  
17              
18 7     7   35 use Exporter qw/import/;
  7         11  
  7         527  
19             our @EXPORT_OK = qw/Visits/;
20              
21             sub Visits () {__PACKAGE__}
22 7     7   40 use YATT::Lite::MFields qw/nodes time/;
  7         9  
  7         50  
23              
24             use YATT::Lite::Types
25 7     7   1889 ([Node => fields => [qw/fname discovered finished color parent/]]);
  7         16  
  7         67  
26             use YATT::Lite::Util::Enum
27 7         66 (NTYPE_ => [qw/WHITE GRAY BLACK/]
28 7     7   40 , EDGE_ => [qw/TREE BACK FORW CROSS/]);
  7         15  
29              
30             sub start {
31 23     23 0 50 my ($pack, $fname) = @_;
32 23         59 my Visits $vis = bless {}, $pack;
33 23         91 $vis->{time} = 0;
34 23         84 $vis->ensure_make_node($fname);
35 23         83 $vis->visit_node($fname);
36 23         78 $vis;
37             }
38              
39             sub fname2id {
40 155     155 0 246 (my Visits $vis, my $fname) = @_;
41 155         2861 my ($dev, $inode) = stat($fname);
42 155 50       307 if (grep {$_ eq ''} $dev, $inode) {
  310         843  
43 0         0 $fname; # Workaround
44             } else {
45 155         698 join "_", $dev, $inode;
46             }
47             }
48              
49             sub has_node {
50 7     7 0 15 (my Visits $vis, my $fname) = @_;
51 7         20 $vis->{nodes}{$vis->fname2id($fname)};
52             }
53              
54             sub ensure_make_node {
55 41     41 0 110 (my Visits $vis, my @path) = @_;
56 41         103 foreach my $fname (@path) {
57 41 100       148 next if $vis->{nodes}{$vis->fname2id($fname)};
58 39         123 $vis->make_node($fname);
59             }
60 41         95 @path;
61             }
62              
63             sub make_node {
64 39     39 0 76 (my Visits $vis, my ($fname)) = @_;
65 39         151 $vis->{nodes}{$vis->fname2id($fname)} = my Node $node = {};
66 39         110 $node->{fname} = $fname;
67 39         99 $node->{color} = NTYPE_WHITE;
68 39         101 $node;
69             }
70              
71             sub visit_node {
72 35     35 0 76 (my Visits $vis, my ($fname, $parent)) = @_;
73 35 50       105 my Node $node = $vis->{nodes}{$vis->fname2id($fname)}
74             or croak "No such path in visits! $fname";
75 35         69 $node->{color} = NTYPE_GRAY;
76 35         82 $node->{discovered} = ++$vis->{time};
77 35 50       83 $node->{parent} = $vis->{nodes}{$vis->fname2id($parent)} if $parent;
78 35         65 $node;
79             }
80              
81             sub finish_node {
82 20     20 0 41 (my Visits $vis, my $fname) = @_;
83 20 50       54 my Node $node = $vis->{nodes}{$vis->fname2id($fname)}
84             or croak "No such path in visits! $fname";
85 20         43 $node->{color} = NTYPE_BLACK;
86 20         50 $node->{finished} = ++$vis->{time};
87 20         85 $node;
88             }
89              
90             sub check_cycle {
91 13     13 0 32 (my Visits $vis, my ($to, $from)) = @_;
92 13 50       41 my Node $dest = $vis->{nodes}{$vis->fname2id($to)}
93             or croak "No such path in visits! $to";
94 13 100       40 if ($dest->{color} == NTYPE_WHITE) {
    50          
95             # tree edge
96 12         29 $vis->visit_node($to);
97             } elsif ($dest->{color} == NTYPE_GRAY) {
98             # back edge!
99 1         5 return [$to, $vis->list_cycle($dest)]
100             } else {
101             # forward or cross
102             }
103 12         56 return;
104             }
105              
106             sub list_cycle {
107 1     1 0 3 (my Visits $vis, my Node $node) = @_;
108 1         1 my @path;
109 1   33     9 while ($node and $node->{parent}) {
110 0         0 $node = $node->{parent};
111 0         0 push @path, $node->{fname};
112             }
113 1         5 @path;
114             }
115              
116             1;