File Coverage

blib/lib/FLAT/PFA.pm
Criterion Covered Total %
statement 137 154 88.9
branch 22 26 84.6
condition n/a
subroutine 16 20 80.0
pod 6 14 42.8
total 181 214 84.5


line stmt bran cond sub pod time code
1             package FLAT::PFA;
2              
3 3     3   1639 use strict;
  3         6  
  3         87  
4 3     3   16 use warnings;
  3         5  
  3         85  
5 3     3   41 use parent qw(FLAT::NFA);
  3         5  
  3         16  
6 3     3   211 use Carp;
  3         7  
  3         259  
7 3     3   28 use FLAT::Transition;
  3         4  
  3         103  
8              
9 3     3   15 use constant LAMBDA => '#lambda';
  3         7  
  3         4908  
10              
11             # Note: in a PFA, states are made up of active nodes. In this implementation, we have
12             # decided to retain the functionality of the state functions in FA.pm, although the entities
13             # being manipulated are technically nodes, not states. States are only explicitly tracked
14             # once the PFA is serialized into an NFA. Therefore, the TRANS member of the PFA object is
15             # the nodal transition function, gamma. The state transition function, delta, is not used
16             # in anyway, but is derived out of the PFA->NFA conversion process.
17              
18             # The new way of doing things eliminated from PFA.pm of FLAT::Legacy is the
19             # need to explicitly track: start nodes, final nodes, symbols, and lambda & epsilon symbols,
20              
21             sub new {
22 546     546 0 1240 my $pkg = shift;
23 546         2472 my $self = $pkg->SUPER::new(@_); # <-- SUPER is FLAT::NFA
24 546         1215 return $self;
25             }
26              
27             # Singleton is no different than the NFA singleton
28             sub singleton {
29 546     546 0 1411 my ( $class, $char ) = @_;
30 546         1572 my $pfa = $class->new;
31 546 50       1867 if ( not defined $char ) {
    50          
32 0         0 $pfa->add_states(1);
33 0         0 $pfa->set_starting(0);
34             }
35             elsif ( $char eq "" ) {
36 0         0 $pfa->add_states(1);
37 0         0 $pfa->set_starting(0);
38 0         0 $pfa->set_accepting(0);
39             }
40             else {
41 546         2050 $pfa->add_states(2);
42 546         1838 $pfa->set_starting(0);
43 546         1792 $pfa->set_accepting(1);
44 546         1698 $pfa->set_transition( 0, 1, $char );
45             }
46 546         3082 return $pfa;
47             }
48              
49             # attack of the clones
50 635     635 0 1901 sub as_pfa { $_[0]->clone() }
51              
52             sub set_starting {
53 1400     1400 1 3161 my ( $self, @states ) = @_;
54 1400         4399 $self->_assert_states(@states);
55 1400         4137 $self->{STATES}[$_]{starting} = 1 for @states;
56             }
57              
58             # Creates a single start state with epsilon transitions from
59             # the former start states;
60             # Creates a single final state with epsilon transitions from
61             # the former accepting states
62             sub pinch {
63 74     74 0 152 my $self = shift;
64 74         146 my $symbol = shift;
65 74         237 my @starting = $self->get_starting;
66 74 50       282 if ( @starting > 1 ) {
67 74         243 my $newstart = $self->add_states(1);
68 74         201 map { $self->add_transition( $newstart, $_, $symbol ) } @starting;
  158         528  
69 74         327 $self->unset_starting(@starting);
70 74         232 $self->set_starting($newstart);
71             }
72             #
73 74         263 my @accepting = $self->get_accepting;
74 74 50       291 if ( @accepting > 1 ) {
75 74         232 my $newfinal = $self->add_states(1);
76 74         162 map { $self->add_transition( $_, $newfinal, $symbol ) } @accepting;
  158         399  
77 74         291 $self->unset_accepting(@accepting);
78 74         256 $self->set_accepting($newfinal);
79             }
80 74         228 return;
81             }
82              
83             # Implement the joining of two PFAs with lambda transitions
84             # Note: using epsilon pinches for simplicity
85             sub shuffle {
86 43     43 1 144 my @pfas = map { $_->as_pfa } @_;
  88         307  
87 43         216 my $result = $pfas[0]->clone;
88 43         363 $result->_swallow($_) for @pfas[ 1 .. $#pfas ];
89 43         194 $result->pinch(LAMBDA);
90 43         3042 $result;
91             }
92              
93             ##############
94              
95             sub is_tied {
96 0     0 0 0 my ( $self, $state ) = @_;
97 0         0 $self->_assert_states($state);
98 0         0 return $self->{STATES}[$state]{tied};
99             }
100              
101             sub set_tied {
102 0     0 0 0 my ( $self, @states ) = @_;
103 0         0 $self->_assert_states(@states);
104 0         0 $self->{STATES}[$_]{tied} = 1 for @states;
105             }
106              
107             sub unset_tied {
108 0     0 0 0 my ( $self, @states ) = @_;
109 0         0 $self->_assert_states(@states);
110 0         0 $self->{STATES}[$_]{tied} = 0 for @states;
111             }
112              
113             sub get_tied {
114 0     0 0 0 my $self = shift;
115 0         0 return grep { $self->is_tied($_) } $self->get_states;
  0         0  
116             }
117              
118             ##############
119              
120             # joins two PFAs in a union (or) - no change from NFA
121             sub union {
122 31     31 1 95 my @pfas = map { $_->as_pfa } @_;
  70         244  
123 31         167 my $result = $pfas[0]->clone;
124 31         258 $result->_swallow($_) for @pfas[ 1 .. $#pfas ];
125 31         136 $result->pinch('');
126 31         1751 $result;
127             }
128              
129             # joins two PFAs via concatenation - no change from NFA
130             sub concat {
131 152     152 1 493 my @pfas = map { $_->as_pfa } @_;
  477         1567  
132              
133 152         740 my $result = $pfas[0]->clone;
134 152         797 my @newstate = ( [ $result->get_states ] );
135 152         662 my @start = $result->get_starting;
136              
137 152         685 for ( 1 .. $#pfas ) {
138 325         1162 push @newstate, [ $result->_swallow( $pfas[$_] ) ];
139             }
140              
141 152         583 $result->unset_accepting( $result->get_states );
142 152         486 $result->unset_starting( $result->get_states );
143 152         629 $result->set_starting(@start);
144              
145 152         580 for my $pfa_id ( 1 .. $#pfas ) {
146 325         1186 for my $s1 ( $pfas[ $pfa_id - 1 ]->get_accepting ) {
147 325         967 for my $s2 ( $pfas[$pfa_id]->get_starting ) {
148 325         1162 $result->set_transition( $newstate[ $pfa_id - 1 ][$s1], $newstate[$pfa_id][$s2], "" );
149             }
150             }
151             }
152              
153 152         540 $result->set_accepting( @{ $newstate[-1] }[ $pfas[-1]->get_accepting ] );
  152         613  
154              
155 152         6854 $result;
156             }
157              
158             # forms closure around a the given PFA - no change from NFA
159             sub kleene {
160 68     68 1 338 my $result = $_[0]->clone;
161              
162 68         439 my ( $newstart, $newfinal ) = $result->add_states(2);
163              
164 68         267 $result->set_transition( $newstart, $_, "" ) for $result->get_starting;
165 68         284 $result->unset_starting( $result->get_starting );
166 68         252 $result->set_starting($newstart);
167              
168 68         245 $result->set_transition( $_, $newfinal, "" ) for $result->get_accepting;
169 68         190 $result->unset_accepting( $result->get_accepting );
170 68         223 $result->set_accepting($newfinal);
171              
172 68         239 $result->set_transition( $newstart, $newfinal, "" );
173 68         250 $result->set_transition( $newfinal, $newstart, "" );
174              
175 68         1700 $result;
176             }
177              
178             sub as_nfa {
179 137     137 1 323 my $self = shift;
180 137         762 my $result = FLAT::NFA->new();
181              
182             # Dstates is initially populated with the start state, which
183             # is exactly the set of all nodes marked as a starting node
184 137         487 my @Dstates = [ sort( $self->get_starting() ) ]; # I suppose all start states are considered 'tied'
185 137         415 my %DONE = (); # |- what about all accepting states? I think so...
186             # the main while loop that ends when @Dstates becomes exhausted
187 137         282 my %NEW = ();
188 137         467 while (@Dstates) {
189 2334         4384 my $current = pop(@Dstates);
190 2334         3586 my $currentid = join( ',', @{$current} );
  2334         6075  
191 2334         5598 $DONE{$currentid}++; # mark done
192 2334         6394 foreach my $symbol ( $self->alphabet(), '' ) { # Sigma UNION epsilon
193 14405 100       28304 if ( LAMBDA eq $symbol ) {
194 1541         2833 my @NEXT = ();
195 1541         2537 my @tmp = $self->successors( [ @{$current} ], $symbol );
  1541         4791  
196 1541 100       5035 if (@tmp) {
197 560         2072 my @pred = $self->predecessors( [@tmp], LAMBDA );
198 560 100       115299 if ( $self->array_is_subset( [@pred], [ @{$current} ] ) ) {
  560         3004  
199 92         228 push( @NEXT, @tmp, $self->array_complement( [ @{$current} ], [@pred] ) );
  92         379  
200 92         396 @NEXT = sort( $self->array_unique(@NEXT) );
201 92         379 my $nextid = join( ',', @NEXT );
202 92 100       448 push( @Dstates, [@NEXT] ) if ( !exists( $DONE{$nextid} ) );
203              
204             # make new states if none exist and track
205 92 100       366 if ( !exists( $NEW{$currentid} ) ) { $NEW{$currentid} = $result->add_states(1) }
  26         96  
206 92 100       342 if ( !exists( $NEW{$nextid} ) ) { $NEW{$nextid} = $result->add_states(1) }
  86         303  
207 92         389 $result->add_transition( $NEW{$currentid}, $NEW{$nextid}, '' );
208             }
209             }
210             }
211             else {
212 12864         16637 foreach my $node ( @{$current} ) {
  12864         19149  
213 21573         53608 my @tmp = $self->successors( [$node], $symbol );
214 21573         48690 foreach my $new (@tmp) {
215 3813         5705 my @NEXT = ();
216 3813         5998 push( @NEXT, $new, $self->array_complement( [ @{$current} ], [$node] ) );
  3813         13178  
217 3813         11024 @NEXT = sort( $self->array_unique(@NEXT) );
218 3813         9903 my $nextid = join( ',', @NEXT );
219 3813 100       11106 push( @Dstates, [@NEXT] ) if ( !exists( $DONE{$nextid} ) );
220              
221             # make new states if none exist and track
222 3813 100       9036 if ( !exists( $NEW{$currentid} ) ) { $NEW{$currentid} = $result->add_states(1) }
  111         410  
223 3813 100       7856 if ( !exists( $NEW{$nextid} ) ) { $NEW{$nextid} = $result->add_states(1) }
  1819         5283  
224 3813         11139 $result->add_transition( $NEW{$currentid}, $NEW{$nextid}, $symbol );
225             }
226             }
227             }
228             }
229             }
230 137         590 $result->set_starting( $NEW{ join( ",", sort $self->get_starting() ) } );
231 137         643 $result->set_accepting( $NEW{ join( ",", sort $self->get_accepting() ) } );
232 137         1744 return $result;
233             }
234              
235             1;
236              
237             __END__