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   1309 use strict;
  3         7  
  3         72  
4 3     3   12 use warnings;
  3         6  
  3         65  
5 3     3   12 use parent qw(FLAT::NFA);
  3         5  
  3         26  
6 3     3   154 use Carp;
  3         13  
  3         188  
7 3     3   18 use FLAT::Transition;
  3         3  
  3         85  
8              
9 3     3   13 use constant LAMBDA => '#lambda';
  3         5  
  3         4008  
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 831 my $pkg = shift;
23 546         2259 my $self = $pkg->SUPER::new(@_); # <-- SUPER is FLAT::NFA
24 546         1178 return $self;
25             }
26              
27             # Singleton is no different than the NFA singleton
28             sub singleton {
29 546     546 0 1396 my ( $class, $char ) = @_;
30 546         1340 my $pfa = $class->new;
31 546 50       1913 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         1997 $pfa->add_states(2);
42 546         1528 $pfa->set_starting(0);
43 546         1711 $pfa->set_accepting(1);
44 546         1511 $pfa->set_transition( 0, 1, $char );
45             }
46 546         2907 return $pfa;
47             }
48              
49             # attack of the clones
50 648     648 0 1833 sub as_pfa { $_[0]->clone() }
51              
52             sub set_starting {
53 1455     1455 1 3072 my ( $self, @states ) = @_;
54 1455         3794 $self->_assert_states(@states);
55 1455         3767 $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 83     83 0 127 my $self = shift;
64 83         155 my $symbol = shift;
65 83         243 my @starting = $self->get_starting;
66 83 50       346 if ( @starting > 1 ) {
67 83         314 my $newstart = $self->add_states(1);
68 83         161 map { $self->add_transition( $newstart, $_, $symbol ) } @starting;
  178         540  
69 83         345 $self->unset_starting(@starting);
70 83         249 $self->set_starting($newstart);
71             }
72             #
73 83         243 my @accepting = $self->get_accepting;
74 83 50       298 if ( @accepting > 1 ) {
75 83         248 my $newfinal = $self->add_states(1);
76 83         174 map { $self->add_transition( $_, $newfinal, $symbol ) } @accepting;
  178         357  
77 83         318 $self->unset_accepting(@accepting);
78 83         224 $self->set_accepting($newfinal);
79             }
80 83         242 return;
81             }
82              
83             # Implement the joining of two PFAs with lambda transitions
84             # Note: using epsilon pinches for simplicity
85             sub shuffle {
86 48     48 1 136 my @pfas = map { $_->as_pfa } @_;
  100         318  
87 48         212 my $result = $pfas[0]->clone;
88 48         373 $result->_swallow($_) for @pfas[ 1 .. $#pfas ];
89 48         205 $result->pinch(LAMBDA);
90 48         3086 $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 35     35 1 123 my @pfas = map { $_->as_pfa } @_;
  78         238  
123 35         159 my $result = $pfas[0]->clone;
124 35         289 $result->_swallow($_) for @pfas[ 1 .. $#pfas ];
125 35         175 $result->pinch('');
126 35         1778 $result;
127             }
128              
129             # joins two PFAs via concatenation - no change from NFA
130             sub concat {
131 156     156 1 436 my @pfas = map { $_->as_pfa } @_;
  470         1294  
132              
133 156         651 my $result = $pfas[0]->clone;
134 156         748 my @newstate = ( [ $result->get_states ] );
135 156         666 my @start = $result->get_starting;
136              
137 156         704 for ( 1 .. $#pfas ) {
138 314         1082 push @newstate, [ $result->_swallow( $pfas[$_] ) ];
139             }
140              
141 156         507 $result->unset_accepting( $result->get_states );
142 156         445 $result->unset_starting( $result->get_states );
143 156         554 $result->set_starting(@start);
144              
145 156         457 for my $pfa_id ( 1 .. $#pfas ) {
146 314         979 for my $s1 ( $pfas[ $pfa_id - 1 ]->get_accepting ) {
147 314         813 for my $s2 ( $pfas[$pfa_id]->get_starting ) {
148 314         1061 $result->set_transition( $newstate[ $pfa_id - 1 ][$s1], $newstate[$pfa_id][$s2], "" );
149             }
150             }
151             }
152              
153 156         510 $result->set_accepting( @{ $newstate[-1] }[ $pfas[-1]->get_accepting ] );
  156         652  
154              
155 156         7074 $result;
156             }
157              
158             # forms closure around a the given PFA - no change from NFA
159             sub kleene {
160 64     64 1 260 my $result = $_[0]->clone;
161              
162 64         305 my ( $newstart, $newfinal ) = $result->add_states(2);
163              
164 64         233 $result->set_transition( $newstart, $_, "" ) for $result->get_starting;
165 64         267 $result->unset_starting( $result->get_starting );
166 64         188 $result->set_starting($newstart);
167              
168 64         199 $result->set_transition( $_, $newfinal, "" ) for $result->get_accepting;
169 64         159 $result->unset_accepting( $result->get_accepting );
170 64         171 $result->set_accepting($newfinal);
171              
172 64         172 $result->set_transition( $newstart, $newfinal, "" );
173 64         191 $result->set_transition( $newfinal, $newstart, "" );
174              
175 64         1382 $result;
176             }
177              
178             sub as_nfa {
179 137     137 1 374 my $self = shift;
180 137         661 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         484 my @Dstates = [ sort( $self->get_starting() ) ]; # I suppose all start states are considered 'tied'
185 137         405 my %DONE = (); # |- what about all accepting states? I think so...
186             # the main while loop that ends when @Dstates becomes exhausted
187 137         250 my %NEW = ();
188 137         338 while (@Dstates) {
189 2357         3771 my $current = pop(@Dstates);
190 2357         3540 my $currentid = join( ',', @{$current} );
  2357         5162  
191 2357         4913 $DONE{$currentid}++; # mark done
192 2357         6131 foreach my $symbol ( $self->alphabet(), '' ) { # Sigma UNION epsilon
193 14534 100       23812 if ( LAMBDA eq $symbol ) {
194 1611         2435 my @NEXT = ();
195 1611         2244 my @tmp = $self->successors( [ @{$current} ], $symbol );
  1611         3994  
196 1611 100       4417 if (@tmp) {
197 606         1785 my @pred = $self->predecessors( [@tmp], LAMBDA );
198 606 100       99588 if ( $self->array_is_subset( [@pred], [ @{$current} ] ) ) {
  606         2737  
199 101         264 push( @NEXT, @tmp, $self->array_complement( [ @{$current} ], [@pred] ) );
  101         393  
200 101         379 @NEXT = sort( $self->array_unique(@NEXT) );
201 101         364 my $nextid = join( ',', @NEXT );
202 101 100       452 push( @Dstates, [@NEXT] ) if ( !exists( $DONE{$nextid} ) );
203              
204             # make new states if none exist and track
205 101 100       339 if ( !exists( $NEW{$currentid} ) ) { $NEW{$currentid} = $result->add_states(1) }
  33         105  
206 101 100       344 if ( !exists( $NEW{$nextid} ) ) { $NEW{$nextid} = $result->add_states(1) }
  96         314  
207 101         412 $result->add_transition( $NEW{$currentid}, $NEW{$nextid}, '' );
208             }
209             }
210             }
211             else {
212 12923         13773 foreach my $node ( @{$current} ) {
  12923         17991  
213 22044         45701 my @tmp = $self->successors( [$node], $symbol );
214 22044         42179 foreach my $new (@tmp) {
215 3846         4843 my @NEXT = ();
216 3846         4932 push( @NEXT, $new, $self->array_complement( [ @{$current} ], [$node] ) );
  3846         11915  
217 3846         9861 @NEXT = sort( $self->array_unique(@NEXT) );
218 3846         8338 my $nextid = join( ',', @NEXT );
219 3846 100       10065 push( @Dstates, [@NEXT] ) if ( !exists( $DONE{$nextid} ) );
220              
221             # make new states if none exist and track
222 3846 100       7547 if ( !exists( $NEW{$currentid} ) ) { $NEW{$currentid} = $result->add_states(1) }
  104         287  
223 3846 100       7405 if ( !exists( $NEW{$nextid} ) ) { $NEW{$nextid} = $result->add_states(1) }
  1875         5204  
224 3846         9463 $result->add_transition( $NEW{$currentid}, $NEW{$nextid}, $symbol );
225             }
226             }
227             }
228             }
229             }
230 137         567 $result->set_starting( $NEW{ join( ",", sort $self->get_starting() ) } );
231 137         548 $result->set_accepting( $NEW{ join( ",", sort $self->get_accepting() ) } );
232 137         1642 return $result;
233             }
234              
235             1;
236              
237             __END__