File Coverage

blib/lib/FLAT/Legacy/FA/DFA.pm
Criterion Covered Total %
statement 127 384 33.0
branch 25 106 23.5
condition 2 18 11.1
subroutine 13 32 40.6
pod 0 23 0.0
total 167 563 29.6


line stmt bran cond sub pod time code
1             # $Revision: 1.5 $ $Date: 2006/03/02 21:00:28 $ $Author: estrabd $
2              
3             package FLAT::Legacy::FA::DFA;
4              
5 3     3   3392 use base 'FLAT::Legacy::FA';
  3         4  
  3         255  
6 3     3   18 use strict;
  3         5  
  3         67  
7 3     3   11 use Carp;
  3         3  
  3         160  
8              
9 3     3   1560 use FLAT::Legacy::FA::NFA;
  3         8  
  3         95  
10 3     3   1326 use FLAT::Legacy::FA::RE;
  3         8  
  3         99  
11 3     3   20 use Data::Dumper;
  3         4  
  3         13309  
12              
13             sub new {
14 121     121 0 231 my $class = shift;
15 121         936 bless {
16             _START_STATE => undef, # start states -> plural!
17             _STATES => [], # Set of all states
18             _FINAL_STATES => [], # Set of final states, subset of _STATES
19             _SYMBOLS => [], # Symbols
20             _TRANSITIONS => {}, # Transition table
21             }, $class;
22             }
23              
24             sub jump_start {
25 0     0 0 0 my $self = shift;
26 0         0 my $DFA = FLAT::Legacy::FA::DFA->new();
27 0         0 my $symbol = shift;
28 0 0       0 if (!defined($symbol)) {
29             # add 1 state that is the start and final state
30 0         0 $DFA->add_state(0);
31             # set start and final
32 0         0 $DFA->set_start(0);
33 0         0 $DFA->add_final(0);
34             } else {
35 0         0 chomp($symbol);
36             # add states
37 0         0 $DFA->add_state(0,1);
38             # add symbol
39 0         0 $DFA->add_symbol($symbol);
40             # set start and final
41 0         0 $DFA->set_start(0);
42 0         0 $DFA->add_final(1);
43             # add single transition
44 0         0 $DFA->add_transition(0,$symbol,1);
45             }
46 0         0 return $DFA;
47             }
48              
49             sub load_file {
50 0     0 0 0 my $self = shift;
51 0         0 my $file = shift;
52 0 0       0 if (-e $file) {
53 0         0 open (DFA,"<$file");
54 0         0 my $string = undef;
55 0         0 while () {
56 0         0 $string .= $_;
57             }
58 0         0 close (DFA);
59 0         0 $self->load_string($string);
60             }
61             }
62              
63             sub load_string {
64 0     0 0 0 my $self = shift;
65 0         0 my $string = shift;
66 0         0 my @lines = split("\n",$string);
67 0         0 my $CURR_STATE = undef;
68 0         0 foreach (@lines) {
69             # strip comments
70 0         0 $_ =~ s/\s*#.*$//;
71             # check if line is a state, transition, or keyword
72 0 0 0     0 if (m/^\s*([\w\d]*)\s*:\s*$/) {
    0 0        
    0          
73             #print STDERR "Found transitions for state $1\n";
74 0         0 $self->add_state($1);
75 0         0 $CURR_STATE = $1;
76             } elsif (m/^\s*([\w\d]*)\s*([\w\d,]*)\s*$/ && ! m/^$/) {
77             # treat as transition
78             #print STDERR "Input: '$1' goes to $2\n";
79 0         0 my @s = split(',',$2);
80 0         0 $self->add_transition($CURR_STATE,$1,@s);
81 0         0 $self->add_symbol($1);
82             } elsif (m/^\s*([\w\d]*)\s*::\s*([\w\d,]*)\s*$/ && ! m/^$/) {
83             # Check for known header keywords
84 0         0 my $val = $2;
85 0 0       0 if ($1 =~ m/START/i) {
    0          
86 0         0 $self->set_start($val);
87             } elsif ($1 =~ m/FINAL/i) {
88 0         0 my @s = split(',',$val);
89 0         0 $self->add_final(@s);
90             } else {
91 0         0 print STDERR "WARNING: $1 is not a valid header...\n";
92             }
93             }
94             }
95             }
96              
97             sub clone {
98 0     0 0 0 my $self = shift;
99 0         0 my $DFA = FLAT::Legacy::FA::DFA->new();
100 0         0 $DFA->add_state($self->get_states());
101 0         0 $DFA->add_final($self->get_final());
102 0         0 $DFA->add_symbol($self->get_symbols());
103 0         0 $DFA->set_start($self->get_start());
104 0         0 foreach my $state ($self->get_states()) {
105 0         0 foreach my $symbol (keys %{$self->{_TRANSITIONS}{$state}}) {
  0         0  
106 0         0 $DFA->add_transition($state,$symbol,$self->{_TRANSITIONS}{$state}{$symbol});
107             }
108             }
109 0         0 return $DFA;
110             }
111              
112             sub to_nfa {
113 0     0 0 0 my $self = shift;
114 0         0 my $NFA = FLAT::Legacy::FA::NFA->new();
115 0         0 $NFA->add_state($self->get_states());
116 0         0 $NFA->add_final($self->get_final());
117 0         0 $NFA->add_symbol($self->get_symbols());
118 0         0 $NFA->set_start($self->get_start());
119 0         0 foreach my $state ($self->get_states()) {
120 0         0 foreach my $symbol (keys %{$self->{_TRANSITIONS}{$state}}) {
  0         0  
121 0         0 $NFA->add_transition($state,$symbol,$self->{_TRANSITIONS}{$state}{$symbol});
122             }
123             }
124 0         0 return $NFA;
125             }
126              
127             sub minimize() {
128 120     120 0 763 my $self = shift;
129 120         210 my @PI1 = ();
130             # Anon sub used to get group #
131             my $state2group = sub {
132 4575     4575   3285 my $array = shift;
133 4575         2945 my $state = shift;
134 4575         2836 my $c = 0;
135 4575         3116 foreach my $x (@{$array}) {
  4575         4131  
136 8314         4857 foreach my $y (@{$x}) {
  8314         6281  
137 73679 100       86476 return $c if ($y eq $state);
138             }
139 3739         3015 $c++;
140             }
141 120         713 };
142             # Anon sub attempts to identify identical 2d arrays
143             my $getsig = sub {
144 240     240   242 my $array = shift;
145 240         305 my @str = ();
146 240         188 foreach my $x (@{$array}) {
  240         258  
147 920         570 my $str = crypt(join('',@{$x}),0);
  920         2526  
148 920         956 push(@str,$str);
149             }
150 240         490 @str = sort(@str);
151 240         912 return join('',@str);
152 120         483 };
153             # Anon sub removes duplicate and dead state
154             my $cleanup = sub {
155 1354     1354   1043 my $oldname = shift;
156 1354         949 my $newname = shift;
157 1354         933 my $i = 0;
158             # will add $newstate as long as it is not already there
159 1354         1489 my @new = ();
160 1354         2098 foreach my $state ($self->get_states()) {
161             # just remove state
162 29976 100       33772 if ($state ne $oldname) {
163 28622         19515 push(@new,$state);
164             }
165 29976         69838 $self->{_STATES} = [@new];
166             }
167             # replace name if start state
168 1354 50       3408 if ($self->is_start($oldname)) {
169 0         0 $self->set_start($newname);
170             }
171             # replace transitions
172 1354         1093 foreach my $state (keys %{$self->{_TRANSITIONS}}) {
  1354         4738  
173 29976         16783 foreach my $symbol (keys %{$self->{_TRANSITIONS}{$state}}) {
  29976         33943  
174             # rename destination states
175 37117 100       46937 if ($self->{_TRANSITIONS}{$state}{$symbol} eq $oldname) {
176 1101         1126 $self->{_TRANSITIONS}{$state}{$symbol} = $newname;
177             }
178             # rename start state
179 37117 100       45195 if ($state eq $oldname) {
180 1763         2658 $self->add_transition($newname,$symbol,$self->{_TRANSITIONS}{$state}{$symbol});
181             }
182             }
183 29976 100       39707 if ($state eq $oldname) {
184             # delete all transitions of old state
185 1354         2482 delete($self->{_TRANSITIONS}{$state});
186             }
187             }
188             # replace final states
189 1354         2481 $i = 0;
190 1354         2559 foreach ($self->get_final()) {
191 3393 100       3973 if ($_ eq $oldname) {
192 88 50       197 if ($self->is_final($newname)) {
193 88         259 $self->remove_final($oldname);
194             } else {
195 0         0 $self->{_FINAL_STATES}[$i] = $newname;
196             }
197             }
198 3393         2563 $i++;
199             }
200 1354         2557 return;
201 120         542 };
202             # Step 1 - create a group containing 2 sets of states: accepting (F) and non-accepting (S-F)
203 120         196 my @tmp = ();
204 120         323 foreach ($self->get_states()) {
205 2034 100       3125 if (!$self->is_final($_)) {
206 1697         2173 push(@tmp,$_);
207             }
208             }
209 120         334 push(@PI1,[$self->get_final()]);
210 120         350 push(@PI1,[@tmp]);
211 120         186 undef @tmp;
212             # Steps 2 & 3 - get final group of partitions through an iterative process
213             # ...aka - figure out what states can be merged into one
214 120         213 my $sig_before = 'x';
215 120         149 my $sig_after = 'y';
216 120         256 while ($sig_before ne $sig_after) {
217             # print Dumper(@PI1);
218 120         243 my %PI2 = ();
219 120         171 my $group_number = 0;
220 120         218 foreach my $group_ref (@PI1) {
221 240         205 foreach my $group_state (@{$group_ref}) {
  240         290  
222 2034         2379 my $mygroup = $state2group->(\@PI1,$group_state); # seed with own group number
223 2034         3460 foreach my $symbol ($self->get_symbols()) {
224 4056 100       5761 if ($self->has_transition_on($group_state,$symbol)) {
225 2541         3317 my $trans_to_group = $state2group->(\@PI1,$self->get_transition_on($group_state,$symbol));
226 2541         3588 $mygroup .= $trans_to_group.$symbol;
227             }
228             }
229 2034 50       2889 if (defined($mygroup)) {
230 2034 100       2963 if (!defined($PI2{$mygroup})) {
231 680         1296 $PI2{$mygroup} = [];
232             }
233 2034         1390 push(@{$PI2{$mygroup}},$group_state);
  2034         3249  
234             }
235             }
236 240         305 $group_number++;
237             }
238             # copy to @PI1
239 120         420 @PI1 = sort(@PI1);
240 120         245 $sig_before = $getsig->(\@PI1);
241 120         283 @PI1 = ();
242 120         334 foreach my $g (keys(%PI2)) {
243 680         638 push(@PI1,$PI2{$g});
244             }
245 120         758 @PI1 = sort(@PI1);
246 120         207 $sig_after = $getsig->(\@PI1);
247             }
248             # Steps 4 & 5 - reduce final subgroups into a single representative
249 120         186 my @removed = ();
250 120         207 foreach my $group_ref (@PI1) {
251 680         521 my $rep = shift(@{$group_ref});
  680         738  
252 680         515 foreach my $group_state (@{$group_ref}) {
  680         663  
253 1354         2282 foreach my $symbol ($self->get_symbols) {
254 2701         3202 my $trans = $self->get_transition_on($group_state);
255 2701 50       4246 if (defined($trans)) {
256 0         0 $self->add_transition($rep,$symbol,$trans);
257             }
258             }
259 1354         1804 $cleanup->($group_state,$rep);
260             #print STDERR "removed $group_state\n";
261 1354         1770 push(@removed,$group_state);
262             }
263             }
264 120         2889 return @removed;
265             }
266              
267             sub delete_state {
268 0     0 0 0 my $self = shift();
269 0         0 my @del_states = @_;
270 0         0 foreach my $del_state (@del_states) {
271 0 0       0 if ($del_state eq $self->get_start()) {
272 0         0 print STDERR "WARNING: The start state, $del_state, is being deleted!\n";
273 0         0 $self->set_start('');
274             }
275 0 0       0 if ($self->is_final($del_state)) {
276 0         0 print STDERR "WARNING: A final state, $del_state, is being deleted!\n";
277 0         0 my $c = 0;
278 0         0 foreach my $f ($self->get_final()) {
279 0 0       0 if ($f eq $del_state) {
280 0         0 delete($self->{_FINAL_STATES}[$c]);
281 0         0 last;
282             }
283 0         0 $c++;
284             }
285             }
286 0         0 my @new = ();
287 0         0 foreach my $f ($self->get_states()) {
288 0 0       0 if ($f ne $del_state) {
289 0         0 push(@new,$f);
290             }
291 0         0 $self->{_STATES} = [@new];
292             }
293 0 0       0 if (defined($self->{_TRANSITIONS}{$del_state})) {
294 0         0 delete($self->{_TRANSITIONS}{$del_state});
295             }
296             # delete transitions
297 0         0 foreach my $state (keys %{$self->{_TRANSITIONS}}) {
  0         0  
298 0         0 foreach my $symbol (keys %{$self->{_TRANSITIONS}{$state}}) {
  0         0  
299 0         0 my $trans = $self->get_transition_on($state,$symbol);
300 0 0       0 if (defined($trans)) {
301 0 0       0 if ($trans eq $del_state) {
302 0         0 delete($self->{_TRANSITIONS}{$state}{$symbol});
303             }
304             }
305             }
306             }
307             }
308 0         0 return;
309             }
310              
311             sub rename_state {
312 0     0 0 0 my $self = shift;
313 0         0 my $oldname = shift;
314 0         0 my $newname = shift;
315             # make sure $oldname is an actual state in this FA
316 0 0       0 if (!$self->is_state($newname)) {
317 0 0       0 if ($self->is_state($oldname)) {
318             # replace name in _STATES array
319 0         0 my $i = 0;
320 0         0 foreach ($self->get_states()) {
321 0 0       0 if ($_ eq $oldname) {
322 0         0 $self->{_STATES}[$i] = $newname;
323 0         0 last;
324             }
325 0         0 $i++;
326             }
327             # replace name if start state
328 0 0       0 if ($self->is_start($oldname)) {
329 0         0 $self->set_start($newname);
330             }
331             # replace transitions
332 0         0 foreach my $state (keys %{$self->{_TRANSITIONS}}) {
  0         0  
333 0         0 foreach my $symbol (keys %{$self->{_TRANSITIONS}{$state}}) {
  0         0  
334             # rename destination states
335 0 0       0 if ($self->{_TRANSITIONS}{$state}{$symbol} eq $oldname) {
336 0         0 $self->{_TRANSITIONS}{$state}{$symbol} = $newname;
337             }
338             # rename start state
339 0 0       0 if ($state eq $oldname) {
340 0         0 $self->add_transition($newname,$symbol,$self->{_TRANSITIONS}{$state}{$symbol});
341             }
342             }
343 0 0       0 if ($state eq $oldname) {
344             # delete all transitions of old state
345 0         0 delete($self->{_TRANSITIONS}{$state});
346             }
347             }
348             # replace final states
349 0         0 $i = 0;
350 0         0 foreach ($self->get_final()) {
351 0 0       0 if ($_ eq $oldname) {
352 0         0 $self->{_FINAL_STATES}[$i] = $newname;
353             }
354 0         0 $i++;
355             }
356             #
357             } else {
358 0         0 print STDERR "Warning: $oldname is not a current state\n";
359             }
360             } else {
361 0         0 print STDERR "Warning: $newname is a current state\n";
362             }
363 0         0 return;
364             }
365              
366             # Adds symbol
367             sub rename_symbol {
368 0     0 0 0 my $self = shift;
369 0         0 my $oldsymbol = shift;
370 0         0 my $newsymbol = shift;
371             # make sure $oldsymbol is a symbol and do not bother if
372             # $newsymbol ne $oldsymbol
373 0 0 0     0 if ($self->is_symbol($oldsymbol) && $newsymbol ne $oldsymbol) {
374             # change in $self->{_SYMBOLS}
375 0         0 my $i = 0;
376 0         0 foreach ($self->get_symbols()) {
377 0 0       0 if ($_ eq $oldsymbol) {
378 0         0 $self->{_SYMBOLS}[$i] = $newsymbol;
379 0         0 last;
380             }
381 0         0 $i++;
382             }
383             # change in $self->{_TRANSITIONS}
384             # replace transition symbols
385 0         0 foreach my $state (keys %{$self->{_TRANSITIONS}}) {
  0         0  
386 0         0 foreach my $symbol (keys %{$self->{_TRANSITIONS}{$state}}) {
  0         0  
387 0 0       0 if ($symbol eq $oldsymbol) {
388 0         0 $self->add_transition($state,$newsymbol,$self->{_TRANSITIONS}{$state}{$symbol});
389 0         0 delete($self->{_TRANSITIONS}{$state}{$symbol});
390             }
391             }
392             }
393             } else {
394 0         0 print STDERR "Warning: '$oldsymbol' is not a current symbol\n";
395             }
396 0         0 return;
397             }
398              
399             sub add_transition {
400 4304     4304 0 3784 my $self = shift;
401 4304         4015 my $state = shift;
402 4304         3249 my $symbol = shift;
403 4304         13467 $self->{_TRANSITIONS}{$state}{$symbol} = shift;
404 4304         5611 return;
405             }
406              
407             sub get_transition_on {
408 5242     5242 0 3771 my $self = shift;
409 5242         3418 my $state = shift;
410 5242         3332 my $symbol = shift;
411 5242         3389 my $ret = undef;
412 5242 100 66     7137 if ($self->is_state($state) && $self->is_symbol($symbol)) {
413 2541 50       5999 if (defined($self->{_TRANSITIONS}{$state}{$symbol})) {
414 2541         4699 $ret = $self->{_TRANSITIONS}{$state}{$symbol};
415             }
416             }
417 5242         10184 return $ret;
418             }
419              
420             sub reverse_dfa {
421 0     0 0   my $self = shift;
422 0           print "Convert to NFA, reverse that, then convert back to DFA and minimize...\n";
423             }
424              
425             sub to_gdl {
426 0     0 0   my $self = shift;
427 0           my $gdl = "graph: {\ndisplay_edge_labels: yes \n";
428 0           foreach my $state ($self->get_states()) {
429 0           my $style = "borderstyle: solid ";
430 0 0         if ($self->is_final($state)) {
431 0           $style = "borderstyle:double bordercolor:red";
432             }
433             # define node (state)
434 0           $gdl .= "node: { title:\"$state\" shape:circle $style }\n";
435             # define transitions
436 0           foreach my $symbol ($self->get_symbols()) {
437 0 0         if (defined($self->{_TRANSITIONS}{$state}{$symbol})) {
438 0           $gdl .= "edge: { source: \"$state\" target: \"$self->{_TRANSITIONS}{$state}{$symbol}\" label: \"$symbol\" arrowstyle: line }\n";
439             }
440             }
441             }
442 0           $gdl .= "}";
443 0           return $gdl;
444             }
445              
446             sub info {
447 0     0 0   my $self = shift;
448 0           my $out = '';
449 0           $out .= sprintf ("States : ");
450 0           foreach ($self->get_states()) {
451 0           $out .= sprintf "'$_' ";
452             }
453 0           $out .= sprintf ("\nStart State : '%s'\n",$self->get_start());
454 0           $out .= sprintf ("Final State(s) : ");
455 0           foreach ($self->get_final()) {
456 0           $out .= sprintf "'$_' ";
457             }
458 0           $out .= sprintf ("\nAlphabet : ");
459 0           foreach ($self->get_symbols()) {
460 0           $out .= sprintf "'$_' ";
461             }
462 0           $out .= sprintf ("\nTransitions :\n");
463 0           foreach ($self->get_states()) {
464 0           my $state = $_;
465 0           foreach (keys %{$self->{_TRANSITIONS}{$state}}) {
  0            
466 0           my $i = $_;
467 0           my $is_final = '';
468 0 0         if ($self->is_final($self->{_TRANSITIONS}{$state}{$i})) {
469 0           $is_final = '**';
470             }
471 0           $out .= sprintf ("\t('%s'),'%s' --> '%s' %s\n",$state,$i,$self->{_TRANSITIONS}{$state}{$i},$is_final);
472             }
473             }
474 0           $out .= sprintf "(** denotes final state)\n";
475 0           return $out;
476             }
477              
478             sub serialize {
479 0     0 0   my $self = shift;
480 0           my $out = '';
481 0           $out .= sprintf("START :: %s\n",$self->get_start());
482 0           $out .= sprintf("FINAL :: ");
483 0           $out .= sprintf("%s\n\n",join(',',$self->get_final()));
484 0           foreach my $state ($self->get_states()) {
485 0           $out .= sprintf("%s:\n",$state);
486 0           foreach my $symbol (keys %{$self->{_TRANSITIONS}{$state}}) {
  0            
487 0           $out .= sprintf("$symbol %s\n",$self->get_transition_on($state,$symbol));
488             }
489 0           $out .= sprintf("\n");
490             }
491 0           return $out;
492             }
493              
494             sub is_valid {
495 0     0 0   my $self = shift;
496 0           my $string = shift;
497 0           my $ok = 0;
498 0           my $bad = 0;
499 0           my $curr = $self->get_start();
500 0           my @symbols = split(//,$string);
501 0 0         if (defined($curr)) {
502 0           while (@symbols) {
503 0           my $s = shift @symbols;
504             # make sure that the symbol is in the alphabet
505 0 0         if ($self->is_symbol($s)) {
506 0           $curr = $self->get_transition_on($curr,$s);
507             # make sure that the transition is defined
508 0 0         if (!defined($curr)) {
509 0           $bad++;
510 0           last;
511             }
512             } else {
513 0           $bad++;
514 0           last;
515             }
516             }
517             # make sure that no symbols are left in the string,
518             # that the current state (if defined) is a final state
519             # that something $bad has not happened - namely a bad symbol or undefined transition
520 0 0 0       if (!@symbols && $self->is_final($curr) && !$bad) {
      0        
521 0           $ok++;
522             }
523             }
524 0           return $ok;
525             }
526              
527             sub get_last_state {
528 0     0 0   my $self = shift;
529 0           my $string = shift;
530 0           my $ok = 0;
531 0           my $bad = 0;
532 0           my $curr = $self->get_start();
533 0           my $prev = undef;
534 0           my @symbols = split(//,$string);
535 0 0         if (defined($curr)) {
536 0           while (@symbols) {
537 0           my $s = shift @symbols;
538             # make sure that the symbol is in the alphabet
539 0 0         if ($self->is_symbol($s)) {
540 0           $prev = $curr;
541 0           $curr = $self->get_transition_on($curr,$s);
542             # make sure that the transition is defined
543 0 0         if (!defined($curr)) {
544 0           last;
545             }
546             } else {
547 0           last;
548             }
549             }
550             }
551 0           return $curr;
552             }
553              
554             sub get_path {
555 0     0 0   my $self = shift;
556 0           my $string = shift;
557 0           my $ok = 0;
558 0           my $bad = 0;
559 0           my $curr = $self->get_start();
560 0           my @symbols = split(//,$string);
561 0           my @path = ();
562 0           push (@path,$curr);
563 0 0         if (defined($curr)) {
564 0           while (@symbols) {
565 0           my $s = shift @symbols;
566             # make sure that the symbol is in the alphabet
567 0 0         if ($self->is_symbol($s)) {
568 0           $curr = $self->get_transition_on($curr,$s);
569 0           push (@path,$curr);
570             # make sure that the transition is defined
571 0 0         if (!defined($curr)) {
572 0           last;
573             }
574             } else {
575 0           last;
576             }
577             }
578             }
579 0           return @path;
580             }
581              
582             sub generate_random {
583 0     0 0   my $self = shift;
584             }
585              
586             sub pump_strings {
587 0     0 0   my $self = shift;
588 0           return "Coming soon!";
589             }
590              
591             sub init_string_pump {
592 0     0 0   my $self = shift;
593 0           return "Coming soon!";
594             }
595              
596             sub pump_next {
597 0     0 0   my $self = shift;
598 0           return "Coming soon!";
599             }
600              
601             1;
602              
603             __END__