File Coverage

blib/lib/FLAT/FA.pm
Criterion Covered Total %
statement 191 233 81.9
branch 36 46 78.2
condition n/a
subroutine 36 41 87.8
pod 22 29 75.8
total 285 349 81.6


line stmt bran cond sub pod time code
1             package FLAT::FA;
2              
3 6     6   2599 use strict;
  6         14  
  6         158  
4 6     6   28 use warnings;
  6         13  
  6         142  
5 6     6   28 use parent qw(FLAT);
  6         7  
  6         38  
6 6     6   272 use Carp;
  6         11  
  6         399  
7              
8 6     6   2504 use FLAT::Transition::Simple;
  6         14  
  6         10863  
9              
10             =head1 NAME
11              
12             FLAT::FA - Base class for regular finite automata
13              
14             =head1 SYNOPSIS
15              
16             A FLAT::FA object is a collection of states and transitions. Each state
17             may be labeled as starting or accepting. Each transition between states
18             is labeled with a transition object.
19              
20             =head1 USAGE
21              
22             FLAT::FA is a superclass that is not intended to be used directly. However,
23             it does provide the following methods:
24              
25             =cut
26              
27             sub new {
28 976     976 0 1666 my $pkg = shift;
29 976         6029 bless {
30             STATES => [],
31             TRANS => [],
32             ALPHA => {},
33             ALPHA_BLESSED => {},
34             }, $pkg;
35             }
36              
37             sub get_states {
38 221009     221009 1 283082 my $self = shift;
39 221009         315800 return 0 .. ( $self->num_states - 1 );
40             }
41              
42             sub num_states {
43 227344     227344 1 250213 my $self = shift;
44 227344         227658 return scalar @{ $self->{STATES} };
  227344         687908  
45             }
46              
47             sub is_state {
48 373173     373173 1 479256 my ( $self, $state ) = @_;
49 373173         866941 exists $self->{STATES}->[$state];
50             }
51              
52             sub _assert_states {
53 212381     212381   310128 my ( $self, @states ) = @_;
54 212381         276855 for (@states) {
55 373035 50       501044 croak "'$_' is not a state" if not $self->is_state($_);
56             }
57             }
58              
59             sub _assert_non_states {
60 0     0   0 my ( $self, @states ) = @_;
61 0         0 for (@states) {
62 0 0       0 croak "There is already a state called '$_'" if $self->is_state($_);
63             }
64             }
65              
66             sub delete_states {
67 131     131 1 644 my ( $self, @states ) = @_;
68              
69 131         354 $self->_assert_states(@states);
70              
71 131         404 for my $s ( sort { $b <=> $a } @states ) {
  0         0  
72 131         232 $self->_decr_alphabet($_) for @{ splice @{ $self->{TRANS} }, $s, 1 };
  131         166  
  131         661  
73              
74 131         374 $self->_decr_alphabet( splice @$_, $s, 1 ) for @{ $self->{TRANS} };
  131         471  
75              
76 131         176 splice @{ $self->{STATES} }, $s, 1;
  131         519  
77             }
78             }
79              
80             sub add_states {
81 4811     4811 1 8694 my ( $self, $num ) = @_;
82 4811         8893 my $id = $self->num_states;
83              
84 4811         14429 for my $s ( $id .. ( $id + $num - 1 ) ) {
85 5416         7183 push @$_, undef for @{ $self->{TRANS} };
  5416         69086  
86 5416         7429 push @{ $self->{TRANS} }, [ (undef) x ( $s + 1 ) ];
  5416         20943  
87 5416         7084 push @{ $self->{STATES} },
  5416         16933  
88             {
89             starting => 0,
90             accepting => 0
91             };
92             }
93              
94             return wantarray
95 4811 100       18627 ? ( $id .. ( $id + $num - 1 ) )
96             : $id + $num - 1;
97             }
98              
99             ##############
100              
101             sub is_starting {
102 37797     37797 1 50105 my ( $self, $state ) = @_;
103 37797         63960 $self->_assert_states($state);
104 37797         69590 return $self->{STATES}[$state]{starting};
105             }
106              
107             sub set_starting {
108 430     430 1 1116 my ( $self, @states ) = @_;
109 430         1086 $self->_assert_states(@states);
110 430         1442 $self->{STATES}[$_]{starting} = 1 for @states;
111             }
112              
113             sub unset_starting {
114 844     844 1 2482 my ( $self, @states ) = @_;
115 844         2064 $self->_assert_states(@states);
116 844         9421 $self->{STATES}[$_]{starting} = 0 for @states;
117             }
118              
119             sub get_starting {
120 3993     3993 1 6711 my $self = shift;
121 3993         7315 return grep { $self->is_starting($_) } $self->get_states;
  37795         55358  
122             }
123              
124             ##############
125              
126             sub is_accepting {
127 34515     34515 1 47361 my ( $self, $state ) = @_;
128 34515         57906 $self->_assert_states($state);
129 34515         61031 return $self->{STATES}[$state]{accepting};
130             }
131              
132             sub set_accepting {
133 2116     2116 1 4317 my ( $self, @states ) = @_;
134 2116         5084 $self->_assert_states(@states);
135 2116         6186 $self->{STATES}[$_]{accepting} = 1 for @states;
136             }
137              
138             sub unset_accepting {
139 850     850 1 3325 my ( $self, @states ) = @_;
140 850         2342 $self->_assert_states(@states);
141 850         9650 $self->{STATES}[$_]{accepting} = 0 for @states;
142             }
143              
144             sub get_accepting {
145 2160     2160 1 4307 my $self = shift;
146 2160         4000 return grep { $self->is_accepting($_) } $self->get_states;
  32542         45766  
147             }
148              
149             ###############
150              
151             sub _decr_alphabet {
152 15163     15163   24154 my ( $self, $t ) = @_;
153              
154 15163 100       31318 return if not defined $t;
155 5876         12842 for ( $t->alphabet ) {
156 11020 100       21364 delete $self->{ALPHA}{$_} if not --$self->{ALPHA}{$_};
157              
158             # supposed to delete key when _decrement_count returns 0, so need to test this
159 11020 100       24295 delete $self->{ALPHA_BLESSED}{$_} if not $self->{ALPHA_BLESSED}{$_}->_decrement_count;
160             }
161             }
162              
163             sub _incr_alphabet {
164 13822     13822   20361 my ( $self, $t ) = @_;
165              
166 13822 50       24125 return if not defined $t;
167 13822         26923 for ( $t->alphabet ) {
168 22126         34702 $self->{ALPHA}{$_}++;
169              
170 22126 100       36639 if ( !exists( $self->{ALPHA_BLESSED}{$_} ) ) {
171 2899         11903 $self->{ALPHA_BLESSED}{$_} = $self->{ALPHA_CLASS}->new($_);
172             }
173             else {
174 19227         41574 $self->{ALPHA_BLESSED}{$_}->_increment_count;
175             }
176             }
177             }
178              
179             sub set_transition {
180 1090     1090 1 2498 my ( $self, $state1, $state2, @label ) = @_;
181 1090         3071 $self->remove_transition( $state1, $state2 );
182              
183 1090         2754 @label = grep defined, @label;
184 1090 50       2533 return if not @label;
185              
186 1090         4120 my $t = $self->{TRANS_CLASS}->new(@label);
187 1090         2018 $self->{TRANS}[$state1][$state2] = $t;
188 1090         2275 $self->_incr_alphabet($t);
189             }
190              
191             sub add_transition {
192 12732     12732 1 25312 my ( $self, $state1, $state2, @label ) = @_;
193              
194 12732         27815 @label = grep defined, @label;
195 12732 50       24618 return if not @label;
196              
197 12732         24161 my $t = $self->get_transition( $state1, $state2 );
198 12732         26916 $self->_decr_alphabet($t);
199              
200 12732 100       23019 if ( !$t ) {
201 7545         21942 $t = $self->{TRANS}[$state1][$state2] = $self->{TRANS_CLASS}->new;
202             }
203              
204 12732         32623 $t->add(@label);
205 12732         24014 $self->_incr_alphabet($t);
206             }
207              
208             sub get_transition {
209 19236     19236 1 29333 my ( $self, $state1, $state2 ) = @_;
210 19236         36640 $self->_assert_states( $state1, $state2 );
211              
212 19236         33803 $self->{TRANS}[$state1][$state2];
213             }
214              
215             sub remove_transition {
216 1090     1090 1 2147 my ( $self, $state1, $state2 ) = @_;
217              
218 1090         2985 $self->_decr_alphabet( $self->{TRANS}[$state1][$state2] );
219 1090         1660 $self->{TRANS}[$state1][$state2] = undef;
220             }
221              
222             # given a state and a symbol, it tells you
223             # what the next state(s) are; do get successors
224             # for find the successors for a set of symbols,
225             # use array refs. For example:
226             # @NEXT=$self->successors([@nodes],[@symbols]);
227             sub successors {
228 115072     115072 1 185167 my ( $self, $state, $symb ) = @_;
229              
230 115072 100       245795 my @states = ref $state eq 'ARRAY' ? @$state : ($state);
231 115072 100       228782 my @symbs =
    100          
232             defined $symb
233             ? ( ref $symb eq 'ARRAY' ? @$symb : ($symb) )
234             : ();
235              
236 115072         204521 $self->_assert_states(@states);
237              
238 115072         136803 my %succ;
239 115072         137039 for my $s (@states) {
240 211323         338827 $succ{$_}++ for grep {
241 15959270         18333043 my $t = $self->{TRANS}[$s][$_];
242 15959270 100       25788878 defined $t && ( @symbs ? $t->does(@symbs) : 1 )
    100          
243             } $self->get_states;
244             }
245              
246 115072         400764 return keys %succ;
247             }
248              
249             sub predecessors {
250 560     560 0 888 my $self = shift;
251 560         1661 $self->clone->reverse->successors(@_);
252             }
253              
254             # reverse - no change from NFA
255             sub reverse {
256 0     0 1 0 my $self = $_[0]->clone;
257 0         0 $self->_transpose;
258              
259 0         0 my @start = $self->get_starting;
260 0         0 my @final = $self->get_accepting;
261              
262 0         0 $self->unset_accepting( $self->get_states );
263 0         0 $self->unset_starting( $self->get_states );
264              
265 0         0 $self->set_accepting(@start);
266 0         0 $self->set_starting(@final);
267              
268 0         0 $self;
269             }
270              
271             # get an array of all symbols
272             sub alphabet {
273 4226     4226 1 6376 my $self = shift;
274 4226         5983 grep length, keys %{ $self->{ALPHA} };
  4226         21228  
275             }
276              
277             # give an array of symbols, return the symbols that
278             # are in the alphabet
279             #sub is_in_alphabet {
280             # my $self = shift;
281             # my $
282             #}
283              
284             ############
285             sub prune {
286 0     0 1 0 my $self = shift;
287              
288 0         0 my @queue = $self->get_starting;
289 0         0 my %seen = map { $_ => 1 } @queue;
  0         0  
290              
291 0         0 while (@queue) {
292 0         0 @queue = grep { !$seen{$_}++ } $self->successors( \@queue );
  0         0  
293             }
294              
295 0         0 my @useless = grep { !$seen{$_} } $self->get_states;
  0         0  
296 0         0 $self->delete_states(@useless);
297              
298 0         0 return @useless;
299             }
300              
301             ############
302              
303 6     6   4150 use Storable 'dclone';
  6         19467  
  6         5261  
304              
305             sub clone {
306 4245     4245 1 1268489 dclone( $_[0] );
307             }
308              
309             sub _transpose {
310 560     560   1120 my $self = shift;
311 560         1622 my $N = $self->num_states - 1;
312              
313             $self->{TRANS} = [
314             map {
315 560         2425 my $row = $_;
  21852         26203  
316 21852         25731 [ map { $_->[$row] } @{ $self->{TRANS} } ]
  1854752         2397417  
  21852         35110  
317             } 0 .. $N
318             ];
319             }
320              
321             # tests to see if set1 is a subset of set2
322             sub array_is_subset {
323 3367     3367 0 4486 my $self = shift;
324 3367         4049 my $set1 = shift;
325 3367         4486 my $set2 = shift;
326 3367 50       6872 $set1 = [$set1] if not ref $set1;
327 3367 100       5492 $set2 = [$set2] if not ref $set2;
328 3367         3583 my $ok = 1;
329 3367         4329 my %setcount = ();
330 3367         3766 foreach ( $self->array_unique( @{$set1} ), $self->array_unique( @{$set2} ) ) {
  3367         6158  
  3367         5398  
331 8046         10043 $setcount{$_}++;
332             }
333 3367         4624 foreach ( $self->array_unique( @{$set1} ) ) {
  3367         5058  
334 3670 100       7070 if ( $setcount{$_} != 2 ) {
335 1622         2023 $ok = 0;
336 1622         2675 last;
337             }
338             }
339 3367         12216 return $ok;
340             }
341              
342             sub array_unique {
343 14013     14013 0 16718 my $self = shift;
344 14013         15320 my %ret = ();
345 14013         18316 foreach (@_) {
346 19520         28055 $ret{$_}++;
347             }
348 14013         35142 return keys(%ret);
349             }
350              
351             sub array_complement {
352 4037     4037 0 6331 my $self = shift;
353 4037         5161 my $set1 = shift;
354 4037         5110 my $set2 = shift;
355 4037         4979 my @ret = ();
356              
357             # convert set1 to a hash
358 4037         4840 my %set1hash = map { $_ => 1 } @{$set1};
  8178         17706  
  4037         6167  
359              
360             # iterate of set2 and test if $set1
361 4037         6646 foreach ( @{$set2} ) {
  4037         7154  
362 4091 50       9989 if ( !defined $set1hash{$_} ) {
363 0         0 push( @ret, $_ );
364             }
365             }
366             ## Now do the same using $set2
367             # convert set2 to a hash
368 4037         6498 my %set2hash = map { $_ => 1 } @{$set2};
  4091         8516  
  4037         5780  
369              
370             # iterate of set1 and test if $set1
371 4037         5783 foreach ( @{$set1} ) {
  4037         7056  
372 8178 100       14334 if ( !defined $set2hash{$_} ) {
373 4087         7061 push( @ret, $_ );
374             }
375             }
376              
377             # now @ret contains all items in $set1 not in $set 2 and all
378             # items in $set2 not in $set1
379 4037         11002 return @ret;
380             }
381              
382             # returns all items that 2 arrays have in common
383             sub array_intersect {
384 0     0 0 0 my $self = shift;
385 0         0 my $set1 = shift;
386 0         0 my $set2 = shift;
387 0         0 my %setcount = ();
388 0         0 my @ret = ();
389 0         0 foreach ( $self->array_unique( @{$set1} ) ) {
  0         0  
390 0         0 $setcount{$_}++;
391             }
392 0         0 foreach ( $self->array_unique( @{$set2} ) ) {
  0         0  
393 0         0 $setcount{$_}++;
394 0 0       0 push( @ret, $_ ) if ( $setcount{$_} > 1 );
395             }
396 0         0 return @ret;
397             }
398              
399             # given a set of symbols, returns only the valid ones
400             sub get_valid_symbols {
401 0     0 0 0 my $self = shift;
402 0         0 my $symbols = shift;
403 0         0 return $self->array_intersect( [ $self->alphabet() ], [ @{$symbols} ] );
  0         0  
404             }
405              
406             ## add an FA's states & transitions to this FA (as disjoint union)
407             sub _swallow {
408 412     412   828 my ( $self, $other ) = @_;
409 412         703 my $N1 = $self->num_states;
410 412         742 my $N2 = $other->num_states;
411              
412 412         587 push @$_, (undef) x $N2 for @{ $self->{TRANS} };
  412         3433  
413              
414 412         639 push @{ $self->{TRANS} }, [ (undef) x $N1, @{ clone $_ } ] for @{ $other->{TRANS} };
  412         880  
  1514         3560  
  1514         2316  
415              
416 412         723 push @{ $self->{STATES} }, @{ clone $other->{STATES} };
  412         681  
  412         837  
417              
418 412         865 for ( keys %{ $other->{ALPHA} } ) {
  412         1417  
419 694         1274 $self->{ALPHA}{$_} += $other->{ALPHA}{$_};
420              
421             # towards objects as symbols
422 694 100       1368 if ( !exists( $self->{ALPHA_BLESSED}{$_} ) ) {
423 544         1125 $self->{ALPHA_BLESSED}{$_} = $other->{ALPHA_BLESSED}{$_};
424             }
425             else {
426 150         558 $self->{ALPHA_BLESSED}{$_}->_increment_count( $other->{ALPHA_BLESSED}{$_}->get_count );
427             }
428             }
429              
430 412         1101 return map { $_ + $N1 } $other->get_states;
  1514         3969  
431             }
432              
433             1;
434              
435             __END__