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   1797 use strict;
  3         7  
  3         83  
4 3     3   13 use warnings;
  3         5  
  3         87  
5 3     3   13 use parent qw(FLAT::NFA);
  3         4  
  3         27  
6 3     3   169 use Carp;
  3         13  
  3         182  
7 3     3   28 use FLAT::Transition;
  3         6  
  3         97  
8              
9 3     3   17 use constant LAMBDA => '#lambda';
  3         5  
  3         4445  
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 956 my $pkg = shift;
23 546         2451 my $self = $pkg->SUPER::new(@_); # <-- SUPER is FLAT::NFA
24 546         943 return $self;
25             }
26              
27             # Singleton is no different than the NFA singleton
28             sub singleton {
29 546     546 0 1685 my ( $class, $char ) = @_;
30 546         1556 my $pfa = $class->new;
31 546 50       2182 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         1871 $pfa->add_states(2);
42 546         1976 $pfa->set_starting(0);
43 546         1775 $pfa->set_accepting(1);
44 546         1633 $pfa->set_transition( 0, 1, $char );
45             }
46 546         2886 return $pfa;
47             }
48              
49             # attack of the clones
50 638     638 0 1896 sub as_pfa { $_[0]->clone() }
51              
52             sub set_starting {
53 1390     1390 1 3109 my ( $self, @states ) = @_;
54 1390         3668 $self->_assert_states(@states);
55 1390         3951 $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 145 my $self = shift;
64 74         123 my $symbol = shift;
65 74         247 my @starting = $self->get_starting;
66 74 50       244 if ( @starting > 1 ) {
67 74         213 my $newstart = $self->add_states(1);
68 74         154 map { $self->add_transition( $newstart, $_, $symbol ) } @starting;
  159         545  
69 74         314 $self->unset_starting(@starting);
70 74         249 $self->set_starting($newstart);
71             }
72             #
73 74         229 my @accepting = $self->get_accepting;
74 74 50       283 if ( @accepting > 1 ) {
75 74         221 my $newfinal = $self->add_states(1);
76 74         152 map { $self->add_transition( $_, $newfinal, $symbol ) } @accepting;
  159         359  
77 74         267 $self->unset_accepting(@accepting);
78 74         211 $self->set_accepting($newfinal);
79             }
80 74         205 return;
81             }
82              
83             # Implement the joining of two PFAs with lambda transitions
84             # Note: using epsilon pinches for simplicity
85             sub shuffle {
86 41     41 1 118 my @pfas = map { $_->as_pfa } @_;
  86         302  
87 41         165 my $result = $pfas[0]->clone;
88 41         324 $result->_swallow($_) for @pfas[ 1 .. $#pfas ];
89 41         153 $result->pinch(LAMBDA);
90 41         3695 $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 33     33 1 86 my @pfas = map { $_->as_pfa } @_;
  73         214  
123 33         132 my $result = $pfas[0]->clone;
124 33         270 $result->_swallow($_) for @pfas[ 1 .. $#pfas ];
125 33         122 $result->pinch('');
126 33         1715 $result;
127             }
128              
129             # joins two PFAs via concatenation - no change from NFA
130             sub concat {
131 155     155 1 480 my @pfas = map { $_->as_pfa } @_;
  479         1488  
132              
133 155         655 my $result = $pfas[0]->clone;
134 155         794 my @newstate = ( [ $result->get_states ] );
135 155         645 my @start = $result->get_starting;
136              
137 155         630 for ( 1 .. $#pfas ) {
138 324         1143 push @newstate, [ $result->_swallow( $pfas[$_] ) ];
139             }
140              
141 155         423 $result->unset_accepting( $result->get_states );
142 155         430 $result->unset_starting( $result->get_states );
143 155         578 $result->set_starting(@start);
144              
145 155         463 for my $pfa_id ( 1 .. $#pfas ) {
146 324         1183 for my $s1 ( $pfas[ $pfa_id - 1 ]->get_accepting ) {
147 324         871 for my $s2 ( $pfas[$pfa_id]->get_starting ) {
148 324         1161 $result->set_transition( $newstate[ $pfa_id - 1 ][$s1], $newstate[$pfa_id][$s2], "" );
149             }
150             }
151             }
152              
153 155         621 $result->set_accepting( @{ $newstate[-1] }[ $pfas[-1]->get_accepting ] );
  155         682  
154              
155 155         7660 $result;
156             }
157              
158             # forms closure around a the given PFA - no change from NFA
159             sub kleene {
160 55     55 1 347 my $result = $_[0]->clone;
161              
162 55         290 my ( $newstart, $newfinal ) = $result->add_states(2);
163              
164 55         219 $result->set_transition( $newstart, $_, "" ) for $result->get_starting;
165 55         214 $result->unset_starting( $result->get_starting );
166 55         212 $result->set_starting($newstart);
167              
168 55         206 $result->set_transition( $_, $newfinal, "" ) for $result->get_accepting;
169 55         161 $result->unset_accepting( $result->get_accepting );
170 55         204 $result->set_accepting($newfinal);
171              
172 55         145 $result->set_transition( $newstart, $newfinal, "" );
173 55         172 $result->set_transition( $newfinal, $newstart, "" );
174              
175 55         1419 $result;
176             }
177              
178             sub as_nfa {
179 137     137 1 314 my $self = shift;
180 137         704 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         438 my @Dstates = [ sort( $self->get_starting() ) ]; # I suppose all start states are considered 'tied'
185 137         346 my %DONE = (); # |- what about all accepting states? I think so...
186             # the main while loop that ends when @Dstates becomes exhausted
187 137         236 my %NEW = ();
188 137         435 while (@Dstates) {
189 2337         4439 my $current = pop(@Dstates);
190 2337         3366 my $currentid = join( ',', @{$current} );
  2337         5950  
191 2337         5149 $DONE{$currentid}++; # mark done
192 2337         6377 foreach my $symbol ( $self->alphabet(), '' ) { # Sigma UNION epsilon
193 14486 100       25779 if ( LAMBDA eq $symbol ) {
194 1541         2662 my @NEXT = ();
195 1541         2609 my @tmp = $self->successors( [ @{$current} ], $symbol );
  1541         4413  
196 1541 100       5071 if (@tmp) {
197 560         1954 my @pred = $self->predecessors( [@tmp], LAMBDA );
198 560 100       123440 if ( $self->array_is_subset( [@pred], [ @{$current} ] ) ) {
  560         2961  
199 86         212 push( @NEXT, @tmp, $self->array_complement( [ @{$current} ], [@pred] ) );
  86         328  
200 86         304 @NEXT = sort( $self->array_unique(@NEXT) );
201 86         318 my $nextid = join( ',', @NEXT );
202 86 100       423 push( @Dstates, [@NEXT] ) if ( !exists( $DONE{$nextid} ) );
203              
204             # make new states if none exist and track
205 86 100       275 if ( !exists( $NEW{$currentid} ) ) { $NEW{$currentid} = $result->add_states(1) }
  28         97  
206 86 100       245 if ( !exists( $NEW{$nextid} ) ) { $NEW{$nextid} = $result->add_states(1) }
  82         296  
207 86         340 $result->add_transition( $NEW{$currentid}, $NEW{$nextid}, '' );
208             }
209             }
210             }
211             else {
212 12945         14634 foreach my $node ( @{$current} ) {
  12945         17835  
213 22023         51014 my @tmp = $self->successors( [$node], $symbol );
214 22023         48970 foreach my $new (@tmp) {
215 3826         5305 my @NEXT = ();
216 3826         5192 push( @NEXT, $new, $self->array_complement( [ @{$current} ], [$node] ) );
  3826         13341  
217 3826         11391 @NEXT = sort( $self->array_unique(@NEXT) );
218 3826         9922 my $nextid = join( ',', @NEXT );
219 3826 100       10990 push( @Dstates, [@NEXT] ) if ( !exists( $DONE{$nextid} ) );
220              
221             # make new states if none exist and track
222 3826 100       9125 if ( !exists( $NEW{$currentid} ) ) { $NEW{$currentid} = $result->add_states(1) }
  109         343  
223 3826 100       8222 if ( !exists( $NEW{$nextid} ) ) { $NEW{$nextid} = $result->add_states(1) }
  1811         4565  
224 3826         11518 $result->add_transition( $NEW{$currentid}, $NEW{$nextid}, $symbol );
225             }
226             }
227             }
228             }
229             }
230 137         548 $result->set_starting( $NEW{ join( ",", sort $self->get_starting() ) } );
231 137         560 $result->set_accepting( $NEW{ join( ",", sort $self->get_accepting() ) } );
232 137         1756 return $result;
233             }
234              
235             1;
236              
237             __END__