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   2635 use strict;
  6         19  
  6         183  
4 6     6   28 use warnings;
  6         11  
  6         150  
5 6     6   29 use parent qw(FLAT);
  6         18  
  6         50  
6 6     6   282 use Carp;
  6         8  
  6         401  
7              
8 6     6   2570 use FLAT::Transition::Simple;
  6         16  
  6         10874  
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 1870 my $pkg = shift;
29 976         6052 bless {
30             STATES => [],
31             TRANS => [],
32             ALPHA => {},
33             ALPHA_BLESSED => {},
34             }, $pkg;
35             }
36              
37             sub get_states {
38 218000     218000 1 294702 my $self = shift;
39 218000         350548 return 0 .. ( $self->num_states - 1 );
40             }
41              
42             sub num_states {
43 224328     224328 1 295145 my $self = shift;
44 224328         267315 return scalar @{ $self->{STATES} };
  224328         779602  
45             }
46              
47             sub is_state {
48 369280     369280 1 514951 my ( $self, $state ) = @_;
49 369280         938936 exists $self->{STATES}->[$state];
50             }
51              
52             sub _assert_states {
53 209602     209602   335657 my ( $self, @states ) = @_;
54 209602         317712 for (@states) {
55 369138 50       563911 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 135     135 1 629 my ( $self, @states ) = @_;
68              
69 135         509 $self->_assert_states(@states);
70              
71 135         496 for my $s ( sort { $b <=> $a } @states ) {
  0         0  
72 135         292 $self->_decr_alphabet($_) for @{ splice @{ $self->{TRANS} }, $s, 1 };
  135         208  
  135         656  
73              
74 135         478 $self->_decr_alphabet( splice @$_, $s, 1 ) for @{ $self->{TRANS} };
  135         625  
75              
76 135         371 splice @{ $self->{STATES} }, $s, 1;
  135         737  
77             }
78             }
79              
80             sub add_states {
81 4804     4804 1 9761 my ( $self, $num ) = @_;
82 4804         9904 my $id = $self->num_states;
83              
84 4804         14685 for my $s ( $id .. ( $id + $num - 1 ) ) {
85 5422         7588 push @$_, undef for @{ $self->{TRANS} };
  5422         66380  
86 5422         8430 push @{ $self->{TRANS} }, [ (undef) x ( $s + 1 ) ];
  5422         21576  
87 5422         8640 push @{ $self->{STATES} },
  5422         18561  
88             {
89             starting => 0,
90             accepting => 0
91             };
92             }
93              
94             return wantarray
95 4804 100       20344 ? ( $id .. ( $id + $num - 1 ) )
96             : $id + $num - 1;
97             }
98              
99             ##############
100              
101             sub is_starting {
102 37580     37580 1 53978 my ( $self, $state ) = @_;
103 37580         69622 $self->_assert_states($state);
104 37580         75811 return $self->{STATES}[$state]{starting};
105             }
106              
107             sub set_starting {
108 430     430 1 1092 my ( $self, @states ) = @_;
109 430         1267 $self->_assert_states(@states);
110 430         1733 $self->{STATES}[$_]{starting} = 1 for @states;
111             }
112              
113             sub unset_starting {
114 854     854 1 2671 my ( $self, @states ) = @_;
115 854         2502 $self->_assert_states(@states);
116 854         9763 $self->{STATES}[$_]{starting} = 0 for @states;
117             }
118              
119             sub get_starting {
120 3969     3969 1 6439 my $self = shift;
121 3969         10102 return grep { $self->is_starting($_) } $self->get_states;
  37578         59138  
122             }
123              
124             ##############
125              
126             sub is_accepting {
127 34913     34913 1 50516 my ( $self, $state ) = @_;
128 34913         63147 $self->_assert_states($state);
129 34913         66339 return $self->{STATES}[$state]{accepting};
130             }
131              
132             sub set_accepting {
133 2124     2124 1 4551 my ( $self, @states ) = @_;
134 2124         5172 $self->_assert_states(@states);
135 2124         6498 $self->{STATES}[$_]{accepting} = 1 for @states;
136             }
137              
138             sub unset_accepting {
139 860     860 1 3483 my ( $self, @states ) = @_;
140 860         2675 $self->_assert_states(@states);
141 860         10274 $self->{STATES}[$_]{accepting} = 0 for @states;
142             }
143              
144             sub get_accepting {
145 2184     2184 1 3609 my $self = shift;
146 2184         4486 return grep { $self->is_accepting($_) } $self->get_states;
  32988         49002  
147             }
148              
149             ###############
150              
151             sub _decr_alphabet {
152 15048     15048   24774 my ( $self, $t ) = @_;
153              
154 15048 100       32288 return if not defined $t;
155 5734         13717 for ( $t->alphabet ) {
156 10767 100       23488 delete $self->{ALPHA}{$_} if not --$self->{ALPHA}{$_};
157              
158             # supposed to delete key when _decrement_count returns 0, so need to test this
159 10767 100       25220 delete $self->{ALPHA_BLESSED}{$_} if not $self->{ALPHA_BLESSED}{$_}->_decrement_count;
160             }
161             }
162              
163             sub _incr_alphabet {
164 13719     13719   22765 my ( $self, $t ) = @_;
165              
166 13719 50       27920 return if not defined $t;
167 13719         28981 for ( $t->alphabet ) {
168 21903         37294 $self->{ALPHA}{$_}++;
169              
170 21903 100       40334 if ( !exists( $self->{ALPHA_BLESSED}{$_} ) ) {
171 2891         12011 $self->{ALPHA_BLESSED}{$_} = $self->{ALPHA_CLASS}->new($_);
172             }
173             else {
174 19012         42382 $self->{ALPHA_BLESSED}{$_}->_increment_count;
175             }
176             }
177             }
178              
179             sub set_transition {
180 1143     1143 1 2846 my ( $self, $state1, $state2, @label ) = @_;
181 1143         3447 $self->remove_transition( $state1, $state2 );
182              
183 1143         3128 @label = grep defined, @label;
184 1143 50       2824 return if not @label;
185              
186 1143         4153 my $t = $self->{TRANS_CLASS}->new(@label);
187 1143         2442 $self->{TRANS}[$state1][$state2] = $t;
188 1143         2452 $self->_incr_alphabet($t);
189             }
190              
191             sub add_transition {
192 12576     12576 1 28617 my ( $self, $state1, $state2, @label ) = @_;
193              
194 12576         30378 @label = grep defined, @label;
195 12576 50       25423 return if not @label;
196              
197 12576         26399 my $t = $self->get_transition( $state1, $state2 );
198 12576         29488 $self->_decr_alphabet($t);
199              
200 12576 100       25591 if ( !$t ) {
201 7507         23445 $t = $self->{TRANS}[$state1][$state2] = $self->{TRANS_CLASS}->new;
202             }
203              
204 12576         34201 $t->add(@label);
205 12576         25804 $self->_incr_alphabet($t);
206             }
207              
208             sub get_transition {
209 18252     18252 1 30911 my ( $self, $state1, $state2 ) = @_;
210 18252         38918 $self->_assert_states( $state1, $state2 );
211              
212 18252         36256 $self->{TRANS}[$state1][$state2];
213             }
214              
215             sub remove_transition {
216 1143     1143 1 2270 my ( $self, $state1, $state2 ) = @_;
217              
218 1143         3469 $self->_decr_alphabet( $self->{TRANS}[$state1][$state2] );
219 1143         1919 $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 113054     113054 1 200785 my ( $self, $state, $symb ) = @_;
229              
230 113054 100       269413 my @states = ref $state eq 'ARRAY' ? @$state : ($state);
231 113054 100       248738 my @symbs =
    100          
232             defined $symb
233             ? ( ref $symb eq 'ARRAY' ? @$symb : ($symb) )
234             : ();
235              
236 113054         231097 $self->_assert_states(@states);
237              
238 113054         147524 my %succ;
239 113054         155765 for my $s (@states) {
240 208379         353001 $succ{$_}++ for grep {
241 15884292         20948695 my $t = $self->{TRANS}[$s][$_];
242 15884292 100       29934547 defined $t && ( @symbs ? $t->does(@symbs) : 1 )
    100          
243             } $self->get_states;
244             }
245              
246 113054         427777 return keys %succ;
247             }
248              
249             sub predecessors {
250 560     560 0 1026 my $self = shift;
251 560         1904 $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 4190     4190 1 6824 my $self = shift;
274 4190         6372 grep length, keys %{ $self->{ALPHA} };
  4190         23453  
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   4032 use Storable 'dclone';
  6         18733  
  6         5268  
304              
305             sub clone {
306 4260     4260 1 1304467 dclone( $_[0] );
307             }
308              
309             sub _transpose {
310 560     560   1188 my $self = shift;
311 560         1802 my $N = $self->num_states - 1;
312              
313             $self->{TRANS} = [
314             map {
315 560         2413 my $row = $_;
  22224         29799  
316 22224         25159 [ map { $_->[$row] } @{ $self->{TRANS} } ]
  1880232         2546018  
  22224         35428  
317             } 0 .. $N
318             ];
319             }
320              
321             # tests to see if set1 is a subset of set2
322             sub array_is_subset {
323 3353     3353 0 5521 my $self = shift;
324 3353         4405 my $set1 = shift;
325 3353         4542 my $set2 = shift;
326 3353 50       8065 $set1 = [$set1] if not ref $set1;
327 3353 100       6252 $set2 = [$set2] if not ref $set2;
328 3353         4444 my $ok = 1;
329 3353         5454 my %setcount = ();
330 3353         4708 foreach ( $self->array_unique( @{$set1} ), $self->array_unique( @{$set2} ) ) {
  3353         7235  
  3353         6030  
331 7979         11812 $setcount{$_}++;
332             }
333 3353         5787 foreach ( $self->array_unique( @{$set1} ) ) {
  3353         6229  
334 3643 100       7893 if ( $setcount{$_} != 2 ) {
335 1648         2375 $ok = 0;
336 1648         3092 last;
337             }
338             }
339 3353         14696 return $ok;
340             }
341              
342             sub array_unique {
343 13964     13964 0 18546 my $self = shift;
344 13964         18443 my %ret = ();
345 13964         21826 foreach (@_) {
346 19288         31787 $ret{$_}++;
347             }
348 13964         39874 return keys(%ret);
349             }
350              
351             sub array_complement {
352 4030     4030 0 6696 my $self = shift;
353 4030         5553 my $set1 = shift;
354 4030         5712 my $set2 = shift;
355 4030         5587 my @ret = ();
356              
357             # convert set1 to a hash
358 4030         5213 my %set1hash = map { $_ => 1 } @{$set1};
  8034         17905  
  4030         6491  
359              
360             # iterate of set2 and test if $set1
361 4030         6573 foreach ( @{$set2} ) {
  4030         7822  
362 4086 50       10696 if ( !defined $set1hash{$_} ) {
363 0         0 push( @ret, $_ );
364             }
365             }
366             ## Now do the same using $set2
367             # convert set2 to a hash
368 4030         6141 my %set2hash = map { $_ => 1 } @{$set2};
  4086         9121  
  4030         6008  
369              
370             # iterate of set1 and test if $set1
371 4030         6380 foreach ( @{$set1} ) {
  4030         6997  
372 8034 100       15516 if ( !defined $set2hash{$_} ) {
373 3948         7738 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 4030         11624 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   1009 my ( $self, $other ) = @_;
409 412         893 my $N1 = $self->num_states;
410 412         785 my $N2 = $other->num_states;
411              
412 412         638 push @$_, (undef) x $N2 for @{ $self->{TRANS} };
  412         3922  
413              
414 412         760 push @{ $self->{TRANS} }, [ (undef) x $N1, @{ clone $_ } ] for @{ $other->{TRANS} };
  412         1079  
  1522         3647  
  1522         2543  
415              
416 412         823 push @{ $self->{STATES} }, @{ clone $other->{STATES} };
  412         860  
  412         979  
417              
418 412         986 for ( keys %{ $other->{ALPHA} } ) {
  412         1351  
419 697         1484 $self->{ALPHA}{$_} += $other->{ALPHA}{$_};
420              
421             # towards objects as symbols
422 697 100       1413 if ( !exists( $self->{ALPHA_BLESSED}{$_} ) ) {
423 550         1189 $self->{ALPHA_BLESSED}{$_} = $other->{ALPHA_BLESSED}{$_};
424             }
425             else {
426 147         532 $self->{ALPHA_BLESSED}{$_}->_increment_count( $other->{ALPHA_BLESSED}{$_}->get_count );
427             }
428             }
429              
430 412         1161 return map { $_ + $N1 } $other->get_states;
  1522         3485  
431             }
432              
433             1;
434              
435             __END__