1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
|
package FLAT::PFA;
use strict;
use base 'FLAT::NFA';
use Carp;
use FLAT::Transition;
use constant LAMBDA => '#lambda';
# Note: in a PFA, states are made up of active nodes. In this implementation, we have
# decided to retain the functionality of the state functions in FA.pm, although the entities
# being manipulated are technically nodes, not states. States are only explicitly tracked
# once the PFA is serialized into an NFA. Therefore, the TRANS member of the PFA object is
# the nodal transition function, gamma. The state transition function, delta, is not used
# in anyway, but is derived out of the PFA->NFA conversion process.
# The new way of doing things eliminated from PFA.pm of FLAT::Legacy is the
# need to explicitly track: start nodes, final nodes, symbols, and lambda & epsilon symbols,
sub new {
my $pkg = shift;
my $self = $pkg->SUPER::new(@_); # <-- SUPER is FLAT::NFA
return $self;
}
# Singleton is no different than the NFA singleton
sub singleton {
my ($class, $char) = @_;
my $pfa = $class->new;
if (not defined $char) {
$pfa->add_states(1);
$pfa->set_starting(0);
} elsif ($char eq "") {
$pfa->add_states(1);
$pfa->set_starting(0);
$pfa->set_accepting(0);
} else {
$pfa->add_states(2);
$pfa->set_starting(0);
$pfa->set_accepting(1);
$pfa->set_transition(0, 1, $char);
}
return $pfa;
}
# attack of the clones
sub as_pfa { $_[0]->clone() }
sub set_starting {
my ($self, @states) = @_;
$self->_assert_states(@states);
$self->{STATES}[$_]{starting} = 1 for @states;
}
# Creates a single start state with epsilon transitions from
# the former start states;
# Creates a single final state with epsilon transitions from
# the former accepting states
sub pinch {
my $self = shift;
my $symbol = shift;
my @starting = $self->get_starting;
if (@starting > 1) {
my $newstart = $self->add_states(1);
map {$self->add_transition($newstart,$_,$symbol)} @starting;
$self->unset_starting(@starting);
$self->set_starting($newstart);
}
#
my @accepting = $self->get_accepting;
if (@accepting > 1) {
my $newfinal = $self->add_states(1);
map {$self->add_transition($_,$newfinal,$symbol)} @accepting;
$self->unset_accepting(@accepting);
$self->set_accepting($newfinal);
}
return;
}
# Implement the joining of two PFAs with lambda transitions
# Note: using epsilon pinches for simplicity
sub shuffle {
my @pfas = map { $_->as_pfa } @_;
my $result = $pfas[0]->clone;
$result->_swallow($_) for @pfas[1 .. $#pfas];
$result->pinch(LAMBDA);
$result;
}
##############
sub is_tied {
my ($self, $state) = @_;
$self->_assert_states($state);
return $self->{STATES}[$state]{tied};
}
sub set_tied {
my ($self, @states) = @_;
$self->_assert_states(@states);
$self->{STATES}[$_]{tied} = 1 for @states;
}
sub unset_tied {
my ($self, @states) = @_;
$self->_assert_states(@states);
$self->{STATES}[$_]{tied} = 0 for @states;
}
sub get_tied {
my $self = shift;
return grep { $self->is_tied($_) } $self->get_states;
}
##############
# joins two PFAs in a union (or) - no change from NFA
sub union {
my @pfas = map { $_->as_pfa } @_;
my $result = $pfas[0]->clone;
$result->_swallow($_) for @pfas[1 .. $#pfas];
$result->pinch('');
$result;
}
# joins two PFAs via concatenation - no change from NFA
sub concat {
my @pfas = map { $_->as_pfa } @_;
my $result = $pfas[0]->clone;
my @newstate = ([ $result->get_states ]);
my @start = $result->get_starting;
for (1 .. $#pfas) {
push @newstate, [ $result->_swallow( $pfas[$_] ) ];
}
$result->unset_accepting($result->get_states);
$result->unset_starting($result->get_states);
$result->set_starting(@start);
for my $pfa_id (1 .. $#pfas) {
for my $s1 ($pfas[$pfa_id-1]->get_accepting) {
for my $s2 ($pfas[$pfa_id]->get_starting) {
$result->set_transition(
$newstate[$pfa_id-1][$s1],
$newstate[$pfa_id][$s2], "" );
}}
}
$result->set_accepting(
@{$newstate[-1]}[ $pfas[-1]->get_accepting ] );
$result;
}
# forms closure around a the given PFA - no change from NFA
sub kleene {
my $result = $_[0]->clone;
my ($newstart, $newfinal) = $result->add_states(2);
$result->set_transition($newstart, $_, "")
for $result->get_starting;
$result->unset_starting( $result->get_starting );
$result->set_starting($newstart);
$result->set_transition($_, $newfinal, "")
for $result->get_accepting;
$result->unset_accepting( $result->get_accepting );
$result->set_accepting($newfinal);
$result->set_transition($newstart, $newfinal, "");
$result->set_transition($newfinal, $newstart, "");
$result;
}
sub as_nfa {
my $self = shift;
my $result = FLAT::NFA->new();
# Dstates is initially populated with the start state, which
# is exactly the set of all nodes marked as a starting node
my @Dstates = [sort($self->get_starting())]; # I suppose all start states are considered 'tied'
my %DONE = (); # |- what about all accepting states? I think so...
# the main while loop that ends when @Dstates becomes exhausted
my %NEW = ();
while (@Dstates) {
my $current = pop(@Dstates);
my $currentid = join(',',@{$current});
$DONE{$currentid}++; # mark done
foreach my $symbol ($self->alphabet(),'') { # Sigma UNION epsilon
if (LAMBDA eq $symbol) {
my @NEXT = ();
my @tmp = $self->successors([@{$current}],$symbol);
if (@tmp) {
my @pred = $self->predecessors([@tmp],LAMBDA);
if ($self->array_is_subset([@pred],[@{$current}])) {
push(@NEXT,@tmp,$self->array_complement([@{$current}],[@pred]));
@NEXT = sort($self->array_unique(@NEXT));
my $nextid = join(',',@NEXT);
push(@Dstates,[@NEXT]) if (!exists($DONE{$nextid}));
# make new states if none exist and track
if (!exists($NEW{$currentid})) {$NEW{$currentid} = $result->add_states(1)};
if (!exists($NEW{$nextid})) {$NEW{$nextid} = $result->add_states(1) };
$result->add_transition($NEW{$currentid},$NEW{$nextid},'');
}
}
} else {
foreach my $node (@{$current}) {
my @tmp = $self->successors([$node],$symbol);
foreach my $new (@tmp) {
my @NEXT = ();
push(@NEXT,$new,$self->array_complement([@{$current}],[$node]));
@NEXT = sort($self->array_unique(@NEXT));
my $nextid = join(',',@NEXT);
push(@Dstates,[@NEXT]) if (!exists($DONE{$nextid}));
# make new states if none exist and track
if (!exists($NEW{$currentid})) {$NEW{$currentid} = $result->add_states(1)};
if (!exists($NEW{$nextid})) {$NEW{$nextid} = $result->add_states(1) };
$result->add_transition($NEW{$currentid},$NEW{$nextid},$symbol);
}
}
}
}
}
$result->set_starting($NEW{join(",",sort $self->get_starting())});
$result->set_accepting($NEW{join(",",sort $self->get_accepting())});
return $result;
}
1;
__END__
=head1 NAME
FLAT::PFA - Parallel finite automata
=head1 SYNOPSIS
A FLAT::PFA object is a finite automata whose transitions are labeled either
with characters, the empty string (epsilon), or a concurrent line of execution
(lambda). It essentially models two FSA in a non-deterministic way such that
a string is valid it puts the FSA of the shuffled languages both into a final,
or accepting, state. A PFA is an NFA, and as such exactly describes a regular
language.
A PFA contains nodes and states. A state is made up of whatever nodes happen
to be active. There are two transition functions, nodal transitions and state
transitions. When a PFA is converted into a NFA, there is no longer a need for
nodes or nodal transitions, so they go are eliminated. PFA model state spaces
much more compactly than NFA, and an N state PFA may represent 2**N non-deterministic
states. This also means that a PFA may represent up to 2^(2^N) deterministic states.
=head1 USAGE
(not implemented yet)
In addition to implementing the interface specified in L<FLAT> and L<FLAT::NFA>,
FLAT::PFA objects provide the following PFA-specific methods:
=over
=item $pfa-E<gt>shuffle
Shuffle construct for building a PFA out of a PRE (i.e., a regular expression with
the shuffle operator)
=item $pfa-E<gt>as_nfa
Converts a PFA to an NFA by enumerating all states; similar to the Subset Construction
Algorithm, it does not implement e-closure. Instead it treats epsilon transitions
normally, and joins any states resulting from a lambda (concurrent) transition
using an epsilon transition.
=back
=head1 AUTHORS & ACKNOWLEDGEMENTS
FLAT is written by Mike Rosulek E<lt>mike at mikero dot comE<gt> and
Brett Estrade E<lt>estradb at gmail dot comE<gt>.
The initial version (FLAT::Legacy) by Brett Estrade was work towards an
MS thesis at the University of Southern Mississippi.
Please visit the Wiki at http://www.0x743.com/flat
=head1 LICENSE
This module is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
|