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   2400 use strict;
  6         12  
  6         156  
4 6     6   28 use warnings;
  6         8  
  6         136  
5 6     6   24 use parent qw(FLAT);
  6         10  
  6         33  
6 6     6   259 use Carp;
  6         9  
  6         323  
7              
8 6     6   2351 use FLAT::Transition::Simple;
  6         13  
  6         9984  
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 1787 my $pkg = shift;
29 976         5860 bless {
30             STATES => [],
31             TRANS => [],
32             ALPHA => {},
33             ALPHA_BLESSED => {},
34             }, $pkg;
35             }
36              
37             sub get_states {
38 223429     223429 1 248717 my $self = shift;
39 223429         293916 return 0 .. ( $self->num_states - 1 );
40             }
41              
42             sub num_states {
43 229936     229936 1 234480 my $self = shift;
44 229936         231011 return scalar @{ $self->{STATES} };
  229936         659776  
45             }
46              
47             sub is_state {
48 376317     376317 1 449692 my ( $self, $state ) = @_;
49 376317         806710 exists $self->{STATES}->[$state];
50             }
51              
52             sub _assert_states {
53 214514     214514   288893 my ( $self, @states ) = @_;
54 214514         273337 for (@states) {
55 376172 50       468009 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 136     136 1 665 my ( $self, @states ) = @_;
68              
69 136         313 $self->_assert_states(@states);
70              
71 136         383 for my $s ( sort { $b <=> $a } @states ) {
  0         0  
72 136         236 $self->_decr_alphabet($_) for @{ splice @{ $self->{TRANS} }, $s, 1 };
  136         287  
  136         608  
73              
74 136         429 $self->_decr_alphabet( splice @$_, $s, 1 ) for @{ $self->{TRANS} };
  136         489  
75              
76 136         273 splice @{ $self->{STATES} }, $s, 1;
  136         508  
77             }
78             }
79              
80             sub add_states {
81 4937     4937 1 8070 my ( $self, $num ) = @_;
82 4937         9542 my $id = $self->num_states;
83              
84 4937         13007 for my $s ( $id .. ( $id + $num - 1 ) ) {
85 5551         7504 push @$_, undef for @{ $self->{TRANS} };
  5551         57584  
86 5551         7197 push @{ $self->{TRANS} }, [ (undef) x ( $s + 1 ) ];
  5551         18314  
87 5551         7343 push @{ $self->{STATES} },
  5551         16495  
88             {
89             starting => 0,
90             accepting => 0
91             };
92             }
93              
94             return wantarray
95 4937 100       18228 ? ( $id .. ( $id + $num - 1 ) )
96             : $id + $num - 1;
97             }
98              
99             ##############
100              
101             sub is_starting {
102 38236     38236 1 46058 my ( $self, $state ) = @_;
103 38236         57757 $self->_assert_states($state);
104 38236         64978 return $self->{STATES}[$state]{starting};
105             }
106              
107             sub set_starting {
108 430     430 1 956 my ( $self, @states ) = @_;
109 430         1085 $self->_assert_states(@states);
110 430         1415 $self->{STATES}[$_]{starting} = 1 for @states;
111             }
112              
113             sub unset_starting {
114 909     909 1 2596 my ( $self, @states ) = @_;
115 909         2193 $self->_assert_states(@states);
116 909         8413 $self->{STATES}[$_]{starting} = 0 for @states;
117             }
118              
119             sub get_starting {
120 4095     4095 1 5631 my $self = shift;
121 4095         8560 return grep { $self->is_starting($_) } $self->get_states;
  38234         49238  
122             }
123              
124             ##############
125              
126             sub is_accepting {
127 34773     34773 1 43232 my ( $self, $state ) = @_;
128 34773         52945 $self->_assert_states($state);
129 34773         56356 return $self->{STATES}[$state]{accepting};
130             }
131              
132             sub set_accepting {
133 2187     2187 1 4185 my ( $self, @states ) = @_;
134 2187         5145 $self->_assert_states(@states);
135 2187         6075 $self->{STATES}[$_]{accepting} = 1 for @states;
136             }
137              
138             sub unset_accepting {
139 915     915 1 2832 my ( $self, @states ) = @_;
140 915         2065 $self->_assert_states(@states);
141 915         8098 $self->{STATES}[$_]{accepting} = 0 for @states;
142             }
143              
144             sub get_accepting {
145 2224     2224 1 3242 my $self = shift;
146 2224         4781 return grep { $self->is_accepting($_) } $self->get_states;
  32762         41278  
147             }
148              
149             ###############
150              
151             sub _decr_alphabet {
152 15408     15408   21778 my ( $self, $t ) = @_;
153              
154 15408 100       28564 return if not defined $t;
155 5725         11272 for ( $t->alphabet ) {
156 10773 100       18909 delete $self->{ALPHA}{$_} if not --$self->{ALPHA}{$_};
157              
158             # supposed to delete key when _decrement_count returns 0, so need to test this
159 10773 100       20921 delete $self->{ALPHA_BLESSED}{$_} if not $self->{ALPHA_BLESSED}{$_}->_decrement_count;
160             }
161             }
162              
163             sub _incr_alphabet {
164 14006     14006   20300 my ( $self, $t ) = @_;
165              
166 14006 50       24690 return if not defined $t;
167 14006         26168 for ( $t->alphabet ) {
168 22268         31256 $self->{ALPHA}{$_}++;
169              
170 22268 100       35133 if ( !exists( $self->{ALPHA_BLESSED}{$_} ) ) {
171 2881         10931 $self->{ALPHA_BLESSED}{$_} = $self->{ALPHA_CLASS}->new($_);
172             }
173             else {
174 19387         36785 $self->{ALPHA_BLESSED}{$_}->_increment_count;
175             }
176             }
177             }
178              
179             sub set_transition {
180 1116     1116 1 2203 my ( $self, $state1, $state2, @label ) = @_;
181 1116         2940 $self->remove_transition( $state1, $state2 );
182              
183 1116         2904 @label = grep defined, @label;
184 1116 50       2140 return if not @label;
185              
186 1116         3641 my $t = $self->{TRANS_CLASS}->new(@label);
187 1116         2124 $self->{TRANS}[$state1][$state2] = $t;
188 1116         2237 $self->_incr_alphabet($t);
189             }
190              
191             sub add_transition {
192 12890     12890 1 24303 my ( $self, $state1, $state2, @label ) = @_;
193              
194 12890         28227 @label = grep defined, @label;
195 12890 50       22897 return if not @label;
196              
197 12890         21895 my $t = $self->get_transition( $state1, $state2 );
198 12890         27237 $self->_decr_alphabet($t);
199              
200 12890 100       21463 if ( !$t ) {
201 7857         21580 $t = $self->{TRANS}[$state1][$state2] = $self->{TRANS_CLASS}->new;
202             }
203              
204 12890         28458 $t->add(@label);
205 12890         23109 $self->_incr_alphabet($t);
206             }
207              
208             sub get_transition {
209 19370     19370 1 28492 my ( $self, $state1, $state2 ) = @_;
210 19370         34515 $self->_assert_states( $state1, $state2 );
211              
212 19370         31510 $self->{TRANS}[$state1][$state2];
213             }
214              
215             sub remove_transition {
216 1116     1116 1 1831 my ( $self, $state1, $state2 ) = @_;
217              
218 1116         3167 $self->_decr_alphabet( $self->{TRANS}[$state1][$state2] );
219 1116         1838 $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 116103     116103 1 163954 my ( $self, $state, $symb ) = @_;
229              
230 116103 100       237454 my @states = ref $state eq 'ARRAY' ? @$state : ($state);
231 116103 100       214514 my @symbs =
    100          
232             defined $symb
233             ? ( ref $symb eq 'ARRAY' ? @$symb : ($symb) )
234             : ();
235              
236 116103         192589 $self->_assert_states(@states);
237              
238 116103         129587 my %succ;
239 116103         136790 for my $s (@states) {
240 213486         312715 $succ{$_}++ for grep {
241 15922378         17635522 my $t = $self->{TRANS}[$s][$_];
242 15922378 100       25317733 defined $t && ( @symbs ? $t->does(@symbs) : 1 )
    100          
243             } $self->get_states;
244             }
245              
246 116103         379951 return keys %succ;
247             }
248              
249             sub predecessors {
250 606     606 0 952 my $self = shift;
251 606         1757 $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 4267     4267 1 5469 my $self = shift;
274 4267         4723 grep length, keys %{ $self->{ALPHA} };
  4267         20416  
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   3578 use Storable 'dclone';
  6         17101  
  6         4889  
304              
305             sub clone {
306 4408     4408 1 1078268 dclone( $_[0] );
307             }
308              
309             sub _transpose {
310 606     606   969 my $self = shift;
311 606         1506 my $N = $self->num_states - 1;
312              
313             $self->{TRANS} = [
314             map {
315 606         2164 my $row = $_;
  21758         22737  
316 21758         20914 [ map { $_->[$row] } @{ $self->{TRANS} } ]
  1787340         1972795  
  21758         29167  
317             } 0 .. $N
318             ];
319             }
320              
321             # tests to see if set1 is a subset of set2
322             sub array_is_subset {
323 3840     3840 0 5482 my $self = shift;
324 3840         3887 my $set1 = shift;
325 3840         4397 my $set2 = shift;
326 3840 50       7264 $set1 = [$set1] if not ref $set1;
327 3840 100       6017 $set2 = [$set2] if not ref $set2;
328 3840         4558 my $ok = 1;
329 3840         5342 my %setcount = ();
330 3840         4233 foreach ( $self->array_unique( @{$set1} ), $self->array_unique( @{$set2} ) ) {
  3840         6571  
  3840         6157  
331 9097         10724 $setcount{$_}++;
332             }
333 3840         5638 foreach ( $self->array_unique( @{$set1} ) ) {
  3840         6051  
334 4153 100       8117 if ( $setcount{$_} != 2 ) {
335 2038         2429 $ok = 0;
336 2038         2892 last;
337             }
338             }
339 3840         14321 return $ok;
340             }
341              
342             sub array_unique {
343 15467     15467 0 16719 my $self = shift;
344 15467         16235 my %ret = ();
345 15467         20302 foreach (@_) {
346 21137         28314 $ret{$_}++;
347             }
348 15467         34859 return keys(%ret);
349             }
350              
351             sub array_complement {
352 4072     4072 0 5490 my $self = shift;
353 4072         4602 my $set1 = shift;
354 4072         4692 my $set2 = shift;
355 4072         5370 my @ret = ();
356              
357             # convert set1 to a hash
358 4072         4663 my %set1hash = map { $_ => 1 } @{$set1};
  8243         15619  
  4072         5329  
359              
360             # iterate of set2 and test if $set1
361 4072         6134 foreach ( @{$set2} ) {
  4072         6151  
362 4135 50       8764 if ( !defined $set1hash{$_} ) {
363 0         0 push( @ret, $_ );
364             }
365             }
366             ## Now do the same using $set2
367             # convert set2 to a hash
368 4072         4676 my %set2hash = map { $_ => 1 } @{$set2};
  4135         7775  
  4072         5268  
369              
370             # iterate of set1 and test if $set1
371 4072         5205 foreach ( @{$set1} ) {
  4072         5251  
372 8243 100       12975 if ( !defined $set2hash{$_} ) {
373 4108         6166 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 4072         9771 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   840 my ( $self, $other ) = @_;
409 412         790 my $N1 = $self->num_states;
410 412         640 my $N2 = $other->num_states;
411              
412 412         599 push @$_, (undef) x $N2 for @{ $self->{TRANS} };
  412         3294  
413              
414 412         687 push @{ $self->{TRANS} }, [ (undef) x $N1, @{ clone $_ } ] for @{ $other->{TRANS} };
  412         790  
  1556         2941  
  1556         2137  
415              
416 412         719 push @{ $self->{STATES} }, @{ clone $other->{STATES} };
  412         739  
  412         827  
417              
418 412         791 for ( keys %{ $other->{ALPHA} } ) {
  412         1368  
419 720         1262 $self->{ALPHA}{$_} += $other->{ALPHA}{$_};
420              
421             # towards objects as symbols
422 720 100       1310 if ( !exists( $self->{ALPHA_BLESSED}{$_} ) ) {
423 572         1038 $self->{ALPHA_BLESSED}{$_} = $other->{ALPHA_BLESSED}{$_};
424             }
425             else {
426 148         536 $self->{ALPHA_BLESSED}{$_}->_increment_count( $other->{ALPHA_BLESSED}{$_}->get_count );
427             }
428             }
429              
430 412         1007 return map { $_ + $N1 } $other->get_states;
  1556         2821  
431             }
432              
433             1;
434              
435             __END__