diff options
Diffstat (limited to 'lib/FLAT/NFA.pm')
-rw-r--r-- | lib/FLAT/NFA.pm | 509 |
1 files changed, 0 insertions, 509 deletions
diff --git a/lib/FLAT/NFA.pm b/lib/FLAT/NFA.pm deleted file mode 100644 index 344ea76..0000000 --- a/lib/FLAT/NFA.pm +++ /dev/null @@ -1,509 +0,0 @@ -package FLAT::NFA; - -use strict; -use base 'FLAT::FA'; - -use FLAT::Transition; - -=head1 NAME - -FLAT::NFA - Nondeterministic finite automata - -=head1 SYNOPSIS - -A FLAT::NFA object is a finite automata whose transitions are labeled -either with characters or the empty string (epsilon). - -=cut - -sub new { - my $pkg = shift; - my $self = $pkg->SUPER::new(@_); - $self->{TRANS_CLASS} = "FLAT::Transition"; - return $self; -} - -sub singleton { - my ($class, $char) = @_; - my $nfa = $class->new; - - if (not defined $char) { - $nfa->add_states(1); - $nfa->set_starting(0); - } elsif ($char eq "") { - $nfa->add_states(1); - $nfa->set_starting(0); - $nfa->set_accepting(0); - } else { - $nfa->add_states(2); - $nfa->set_starting(0); - $nfa->set_accepting(1); - $nfa->set_transition(0, 1, $char); - } - return $nfa; -} - -sub as_nfa { $_[0]->clone } - -sub union { - my @nfas = map { $_->as_nfa } @_; - my $result = $nfas[0]->clone; - $result->_swallow($_) for @nfas[1 .. $#nfas]; - $result; -} - -sub concat { - my @nfas = map { $_->as_nfa } @_; - - my $result = $nfas[0]->clone; - my @newstate = ([ $result->get_states ]); - my @start = $result->get_starting; - - for (1 .. $#nfas) { - push @newstate, [ $result->_swallow( $nfas[$_] ) ]; - } - - $result->unset_accepting($result->get_states); - $result->unset_starting($result->get_states); - $result->set_starting(@start); - - for my $nfa_id (1 .. $#nfas) { - for my $s1 ($nfas[$nfa_id-1]->get_accepting) { - for my $s2 ($nfas[$nfa_id]->get_starting) { - $result->set_transition( - $newstate[$nfa_id-1][$s1], - $newstate[$nfa_id][$s2], "" ); - }} - } - - $result->set_accepting( - @{$newstate[-1]}[ $nfas[-1]->get_accepting ] ); - - $result; -} - -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 reverse { - my $self = $_[0]->clone; - $self->_transpose; - - my @start = $self->get_starting; - my @final = $self->get_accepting; - - $self->unset_accepting( $self->get_states ); - $self->unset_starting( $self->get_states ); - - $self->set_accepting( @start ); - $self->set_starting( @final ); - - $self; -} - -########### - -sub is_empty { - my $self = shift; - - my @queue = $self->get_starting; - my %seen = map { $_ => 1 } @queue; - - while (@queue) { - return 0 if grep { $self->is_accepting($_) } @queue; - @queue = grep { !$seen{$_}++ } $self->successors(\@queue); - } - return 1; -} - -sub is_finite { - my $self = shift; - - my @alphabet = $self->alphabet; - return 1 if @alphabet == 0; - - my @queue = $self->get_starting; - my %seen = map { $_ => 1 } @queue; - - while (@queue) { - @queue = grep { !$seen{$_}++ } $self->successors(\@queue); - } - - for my $s ( grep { $self->is_accepting($_) } keys %seen ) { - @queue = $self->epsilon_closure($s); - %seen = map { $_ => 1 } @queue; - - while (@queue) { - my @next = $self->epsilon_closure( - $self->successors(\@queue, \@alphabet) ); - - return 0 if grep { $s eq $_ } @next; - @queue = grep { !$seen{$_}++ } @next; - } - } - return 1; -} - -sub epsilon_closure { - my ($self, @states) = @_; - my %seen = map { $_ => 1 } @states; - my @queue = @states; - - while (@queue) { - @queue = grep { ! $seen{$_}++ } $self->successors( \@queue, "" ); - } - - keys %seen; -} - - -sub contains { - my ($self, $string) = @_; - - my @active = $self->epsilon_closure( $self->get_starting ); - for my $char (split //, $string) { - return 0 if ! @active; - @active = $self->epsilon_closure( $self->successors(\@active, $char) ); - } - return !! grep { $self->is_accepting($_) } @active; -} - -sub trace { - my ($self, $string) = @_; - - my @trace = ([ $self->epsilon_closure( $self->get_starting ) ]); - - for my $char (split //, $string) { - push @trace, - [ $self->epsilon_closure( $self->successors($trace[-1], $char) ) ]; - } - return @trace; -} -############ - -sub _extend_alphabet { - my ($self, @alpha) = @_; - - my %alpha = map { $_ => 1 } @alpha; - delete $alpha{$_} for $self->alphabet; - - return if not keys %alpha; - - my $trash = $self->add_states(1); - for my $state ($self->get_states) { - next if $state eq $trash; - for my $char (keys %alpha) { - $self->add_transition($state, $trash, $char); - } - } - $self->add_transition($trash, $trash, $self->alphabet); -} - -######## transformations - -# subset construction -sub as_dfa { - my $self = shift; - - my $result = FLAT::DFA->new; - my %subset; - - my %final = map { $_ => 1 } $self->get_accepting; - my @start = sort { $a <=> $b } $self->epsilon_closure( $self->get_starting ); - - my $start = $subset{ _SET_ID(@start) } = $result->add_states(1); - $result->set_starting($start); - - $result->set_accepting( $start ) - if grep $_, @final{@start}; - - my @queue = (\@start); - while (@queue) { - my @states = @{ shift @queue }; - my $S = $subset{ _SET_ID(@states) }; - - for my $symb ($self->alphabet) { - my @to = $self->epsilon_closure( - $self->successors(\@states, $symb) ); - - if ( not exists $subset{_SET_ID(@to)} ) { - push @queue, \@to; - my $T = $subset{_SET_ID(@to)} = $result->add_states(1); - $result->set_accepting($T) - if grep $_, @final{@to}; - } - - $result->add_transition($S, $subset{ _SET_ID(@to) }, $symb); - } - } - - $result; -} - -############ Formatted output - -# Format that Dr. Sukhamay KUNDU likes to use in his assignments :) -# This format is just a undirected graph - so transition and state info is lost - -sub as_undirected { - my $self = shift; - my @symbols = $self->alphabet(); - my @states = $self->get_states(); - my %edges = (); - foreach (@states) { - my $s = $_; - foreach (@symbols) { - my $a = $_; - # foreach state, get all nodes connected to it; ignore symbols and - # treat transitions simply as directed - push(@{$edges{$s}},$self->successors($s,$a)); - foreach ($self->successors($s,$a)) { - push(@{$edges{$_}},$s); - } - } - } - my @lines = (($#states+1)); - foreach (sort{$a <=> $b;}(keys(%edges))) { #<-- iterate over numerically sorted list of keys - @{$edges{$_}} = sort {$a <=> $b;} $self->array_unique(@{$edges{$_}}); #<- make items unique and sort numerically - push(@lines,sprintf("%s(%s):%s",$_,($#{$edges{$_}}+1),join(' ',@{$edges{$_}}))); - } - return join("\n",@lines); - } - -# Format that Dr. Sukhamay KUNDU likes to use in his assignments :) -# This format is just a directed graph - so transition and state info is lost - -sub as_digraph { - my $self = shift; - my @symbols = $self->alphabet(); - my @states = $self->get_states(); - my @lines = (); - foreach (@states) { - my $s = $_; - my @edges = (); - foreach (@symbols) { - my $a = $_; - # foreach state, get all nodes connected to it; ignore symbols and - # treat transitions simply as directed - push(@edges,$self->successors($s,$a)); - } - @edges = sort {$a <=> $b;} $self->array_unique(@edges); #<- make items unique and sort numerically - push(@lines,sprintf("%s(%s): %s",$s,($#edges+1),join(' ',@edges))); - } - return sprintf("%s\n%s",($#states+1),join("\n",@lines)); -} - - -# Graph Description Language, aiSee, etc -sub as_gdl { - my $self = shift; - - my @states = map { - sprintf qq{node: { title:"%s" shape:circle borderstyle: %s}\n}, - $_, - ($self->is_accepting($_) ? "double bordercolor: red" : "solid") - } $self->get_states; - - my @trans; - for my $s1 ($self->get_states) { - for my $s2 ($self->get_states) { - my $t = $self->get_transition($s1, $s2); - - if (defined $t) { - push @trans, sprintf qq[edge: { source: "%s" target: "%s" label: "%s" arrowstyle: line }\n], - $s1, $s2, $t->as_string; - } - }} - - return sprintf "graph: {\ndisplay_edge_labels: yes\n\n%s\n%s}\n", - join("", @states), - join("", @trans); -} - -# Graphviz: dot, etc -## digraph, directed -sub as_graphviz { - my $self = shift; - - my @states = map { - sprintf qq{%s [label="%s",shape=%s]\n}, - $_, - ($self->is_starting($_) ? "start ($_)" : "$_"), - ($self->is_accepting($_) ? "doublecircle" : "circle") - } $self->get_states; - - my @trans; - for my $s1 ($self->get_states) { - for my $s2 ($self->get_states) { - my $t = $self->get_transition($s1, $s2); - - if (defined $t) { - push @trans, sprintf qq[%s -> %s [label="%s"]\n], - $s1, $s2, $t->as_string; - } - }} - - return sprintf "digraph G {\ngraph [rankdir=LR]\n\n%s\n%s}\n", - join("", @states), - join("", @trans); -} -## undirected -sub as_undirected_graphviz { - my $self = shift; - - my @states = map { - sprintf qq{%s [label="%s",shape=%s]\n}, - $_, - ("$_"), - ("circle") - } $self->get_states; - - my @trans; - for my $s1 ($self->get_states) { - for my $s2 ($self->get_states) { - my $t = $self->get_transition($s1, $s2); - - if (defined $t) { - push @trans, sprintf qq[%s -- %s\n], - $s1, $s2, $t->as_string; - } - }} - - return sprintf "graph G {\ngraph [rankdir=LR]\n\n%s\n%s}\n", - join("", @states), - join("", @trans); -} - -sub _SET_ID { return join "\0", sort { $a <=> $b } @_; } - -sub as_summary { - my $self = shift; - my $out = ''; - $out .= sprintf ("States : "); - my @start; - my @final; - foreach ($self->get_states()) { - $out .= sprintf "'$_' "; - if ($self->is_starting($_)) { - push(@start,$_); - } - if ($self->is_accepting($_)) { - push(@final,$_); - } - } - $out .= sprintf ("\nStart State : '%s'\n",join('',@start)); - $out .= sprintf ("Final State(s) : "); - foreach (@final) { - $out .= sprintf "'$_' "; - } - $out .= sprintf ("\nAlphabet : "); - foreach ($self->alphabet()) { - $out .= sprintf "'$_' "; - } - $out .= sprintf ("\nTransitions :\n"); - my @trans; - for my $s1 ($self->get_states) { - for my $s2 ($self->get_states) { - my $t = $self->get_transition($s1, $s2); - if (defined $t) { - push @trans, sprintf qq[%s -> %s on "%s"\n], - $s1, $s2, $t->as_string; - } - }} - $out .= join('',@trans); - return $out; -} - -1; - -__END__ - -=head1 USAGE - -In addition to implementing the interface specified in L<FLAT>, FLAT::NFA -objects provide the following NFA-specific methods: - -=over - -=item $nfa-E<gt>epsilon_closure(@states) - -Returns the set of states (without duplicates) which are reachable from -@states via zero or more epsilon-labeled transitions. - -=item $nfa-E<gt>trace($string) - -Returns a list of N+1 arrayrefs, where N is the length of $string. The -I-th arrayref contains the states which are reachable from the starting -state(s) of $nfa after reading I characters of $string. Correctly accounts -for epsilon transitions. - -=item $nfa-E<gt>as_undirected - -Outputs FA in a format that may be easily read into an external program as -a description of an undirected graph. - -=item $nfa-E<gt>as_digraph - -Outputs FA in a format that may be easily read into an external program as -a description of an directed graph. - -=item $nfa-E<gt>as_gdl - -Outputs FA in Graph Description Language (GDL), including directed transitions -with symbols and state names labeled. - -=item $nfa-E<gt>as_graphviz - -Outputs FA in Graphviz format, including directed transitions with symbols and -and state names labeled. This output may be directly piped into any of the -Graphviz layout programs, and in turn one may output an image using a single -commandline instruction. C<fash> uses this function to implement its "nfa2gv" -command: - - fash nfa2gv "a*b" | dot -Tpng > nfa.png - -=item $nfa-E<gt>as_undirected_graphviz - -Outputs FA in Graphviz format, with out the directed transitions or labels. -The output is suitable for any of the Graphvize layout programs, as discussed -above. - -=item $nfa-E<gt>as_summary - -Outputs a summary of the FA, including its states, symbols, and transition matrix. -It is useful for manually validating what the FA looks like. - -=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. |