| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | #================================= FSM.pm ==================================== | 
| 2 |  |  |  |  |  |  | # Filename:             FSM.pm | 
| 3 |  |  |  |  |  |  | # Description:          A simple Finite State Machine. | 
| 4 |  |  |  |  |  |  | # Original Author:      Dale M. Amon | 
| 5 |  |  |  |  |  |  | # Revised by:           $Author: amon $ | 
| 6 |  |  |  |  |  |  | # Date:                 $Date: 2008-08-28 23:14:03 $ | 
| 7 |  |  |  |  |  |  | # Version:              $Revision: 1.7 $ | 
| 8 |  |  |  |  |  |  | # License:		LGPL 2.1, Perl Artistic or BSD | 
| 9 |  |  |  |  |  |  | # | 
| 10 |  |  |  |  |  |  | #============================================================================= | 
| 11 | 1 |  |  | 1 |  | 750 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 39 |  | 
| 12 | 1 |  |  | 1 |  | 5855 | use Fault::DebugPrinter; | 
|  | 1 |  |  |  |  | 2852 |  | 
|  | 1 |  |  |  |  | 67 |  | 
| 13 | 1 |  |  | 1 |  | 7634 | use Fault::ErrorHandler; | 
|  | 1 |  |  |  |  | 419 |  | 
|  | 1 |  |  |  |  | 874 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | package DMA::FSM; | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | #============================================================================= | 
| 18 |  |  |  |  |  |  | #			Exported Routines | 
| 19 |  |  |  |  |  |  | #============================================================================= | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | sub FSM { | 
| 22 | 0 |  |  | 0 | 1 |  | my ($fst, $blackboard, @lexlst) = @_; | 
| 23 | 0 |  |  |  |  |  | my ($next,$lexaction,$lexeme,$printlex) = (undef,undef,undef,""); | 
| 24 | 0 |  |  |  |  |  | my ($state,$mode) = ("S0","RUN"); | 
| 25 | 0 |  |  |  |  |  | my $branch; | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | # No one gets out of this loop without the state tables permission! | 
| 28 | 0 |  |  |  |  |  | while (1) { | 
| 29 | 0 |  |  |  |  |  | $lexeme = shift @lexlst; | 
| 30 | 0 | 0 |  |  |  |  | $printlex = (defined $lexeme) ? $lexeme : ""; | 
| 31 | 0 |  |  |  |  |  | Fault::DebugPrinter->dbg (3, "\nCurstate $state: <$printlex>"); | 
| 32 |  |  |  |  |  |  |  | 
| 33 | 0 |  |  |  |  |  | LEXANAL: while ($_=$mode) { | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | # We should never see an undefined state unless we've made a mistake. | 
| 36 | 0 | 0 |  |  |  |  | if (! exists $fst->{$state} ) { | 
| 37 | 0 |  |  |  |  |  | Fault::ErrorHandler->die ("FATAL: Impossible state during parse!"); | 
| 38 |  |  |  |  |  |  | } | 
| 39 |  |  |  |  |  |  |  | 
| 40 | 0 | 0 |  |  |  |  | if (/RUN/) | 
| 41 |  |  |  |  |  |  | { | 
| 42 | 0 | 0 |  |  |  |  | if (!defined $lexeme) | 
| 43 | 0 |  |  |  |  |  | { ($state,$lexaction) = @{$fst->{$state}}[0..1]; | 
|  | 0 |  |  |  |  |  |  | 
| 44 | 0 |  |  |  |  |  | Fault::DebugPrinter->dbg | 
| 45 |  |  |  |  |  |  | (4," Nextstate $state: End of Lexemes"); | 
| 46 |  |  |  |  |  |  | } | 
| 47 |  |  |  |  |  |  | else { | 
| 48 | 0 |  |  |  |  |  | ($branch, $lexeme) = | 
| 49 | 0 |  |  |  |  |  | (&{$fst->{$state}[2]} ($lexeme, $blackboard )); | 
| 50 | 0 | 0 |  |  |  |  | $printlex = (defined $lexeme) ? $lexeme : ""; | 
| 51 | 0 | 0 |  |  |  |  | if ($branch) { | 
| 52 | 0 |  |  |  |  |  | ($state,$lexaction) = @{$fst->{$state}}[3..4]; | 
|  | 0 |  |  |  |  |  |  | 
| 53 | 0 |  |  |  |  |  | Fault::DebugPrinter->dbg | 
| 54 |  |  |  |  |  |  | (4, | 
| 55 |  |  |  |  |  |  | " Nextstate $state: Left  branch with lexeme <$printlex>"); | 
| 56 |  |  |  |  |  |  | } | 
| 57 |  |  |  |  |  |  | else | 
| 58 | 0 |  |  |  |  |  | { ($state,$lexaction) = @{$fst->{$state}}[5..6]; | 
|  | 0 |  |  |  |  |  |  | 
| 59 | 0 |  |  |  |  |  | Fault::DebugPrinter->dbg | 
| 60 |  |  |  |  |  |  | (4, | 
| 61 |  |  |  |  |  |  | " Nextstate $state,$lexaction: Right branch with <$printlex>"); | 
| 62 |  |  |  |  |  |  | } | 
| 63 |  |  |  |  |  |  | } | 
| 64 | 0 |  |  |  |  |  | Fault::DebugPrinter->dbg (4, " Lexeme action: $lexaction"); | 
| 65 | 0 | 0 |  |  |  |  | if ($lexaction eq "TSTL") {if ($lexeme)   {next LEXANAL;} | 
|  | 0 | 0 |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | else           {last LEXANAL;}} | 
| 67 | 0 | 0 |  |  |  |  | if ($lexaction eq "SAME")                 {next LEXANAL;} | 
|  | 0 |  |  |  |  |  |  | 
| 68 | 0 | 0 |  |  |  |  | if ($lexaction eq "NEXT")                 {last LEXANAL;} | 
|  | 0 |  |  |  |  |  |  | 
| 69 | 0 | 0 |  |  |  |  | if ($lexaction eq "FAIL") {$mode = "ERR";  next LEXANAL;} | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 70 | 0 | 0 |  |  |  |  | if ($lexaction eq "DONE") {$mode = "DONE"; next LEXANAL;} | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | Fault::DebugPrinter->dbg | 
| 72 | 0 |  |  |  |  |  | (4," NextState $state: No such Action $lexaction"); | 
| 73 | 0 |  |  |  |  |  | $lexaction = "FAIL"; next LEXANAL; | 
|  | 0 |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | } | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | # DONE: Parse succeeded! | 
| 77 | 0 | 0 |  |  |  |  | if (/DONE/) | 
| 78 | 0 | 0 |  |  |  |  | { &{$fst->{$state}[2]} ((defined $lexeme) ? $lexeme : "", | 
|  | 0 |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | $blackboard ); | 
| 80 | 0 |  |  |  |  |  | Fault::DebugPrinter->dbg (4," DoneState $state: Exiting"); | 
| 81 | 0 |  |  |  |  |  | $blackboard->{'state'} = $state; | 
| 82 | 0 |  |  |  |  |  | return (@lexlst); | 
| 83 |  |  |  |  |  |  | } | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | # ERR: The string is not a valid Publication Filename Spec | 
| 86 | 0 | 0 |  |  |  |  | if (/ERR/) | 
| 87 | 0 | 0 |  |  |  |  | { &{$fst->{$state}[2]} ((defined $lexeme) ? $lexeme : "", | 
|  | 0 |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | $blackboard ); | 
| 89 | 0 |  |  |  |  |  | Fault::DebugPrinter->dbg (4," ErrorState $state: Failing"); | 
| 90 | 0 |  |  |  |  |  | $blackboard->{'state'} = $state; | 
| 91 | 0 |  |  |  |  |  | return (@lexlst); | 
| 92 |  |  |  |  |  |  | } | 
| 93 |  |  |  |  |  |  | } | 
| 94 |  |  |  |  |  |  | } | 
| 95 |  |  |  |  |  |  | Fault::DebugPrinter->dbg | 
| 96 | 0 |  |  |  |  |  | (4," Nextstate $state: Impossible! How did we escape the while loop???"); | 
| 97 | 0 |  |  |  |  |  | return (@lexlst); | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | #============================================================================= | 
| 101 |  |  |  |  |  |  | #                       Pod Documentation | 
| 102 |  |  |  |  |  |  | #============================================================================= | 
| 103 |  |  |  |  |  |  | # You may extract and format the documentation section with the 'perldoc' cmd. | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | =head1 NAME | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | DMA::FSM - A simple Finite State Machine. | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | use DMA::FSM; | 
| 112 |  |  |  |  |  |  | my $fst       = { see text for format }; | 
| 113 |  |  |  |  |  |  | my (@lexlst)  = ("First", "Second", "Third"); | 
| 114 |  |  |  |  |  |  | my $bb        = {}; | 
| 115 |  |  |  |  |  |  | my @remaining = DMA::FSM::FSM ( $fst, $bb, @lexlst); | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | =head1 Inheritance | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | None. | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | =head1 Description | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | There is a single subroutine named FSM in this module. It will run a FSM | 
| 124 |  |  |  |  |  |  | machine of your choosing. It must contain, and will be started, in state 'S0'. | 
| 125 |  |  |  |  |  |  | When called, lexical analyzer functions from the state  table will be passed | 
| 126 |  |  |  |  |  |  | a user supplied 'blackboard' hash on which they may read, write and share | 
| 127 |  |  |  |  |  |  | results. The arguments to FSM are: | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | 1.Finite State Table | 
| 130 |  |  |  |  |  |  | 2.ptr to a user blackboard hash | 
| 131 |  |  |  |  |  |  | 3.a list of lexemes to be analyzed | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | It returns a list of unused lexemes, if any. If called from within an object, | 
| 134 |  |  |  |  |  |  | it may be useful to use the self pointer for your  blackboard; your lexical | 
| 135 |  |  |  |  |  |  | functions will then be able to execute instance methods as well as access | 
| 136 |  |  |  |  |  |  | ivars (instance variables). | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | The machine is controlled by a state table and it is pretty basic: | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | my $fst = | 
| 141 |  |  |  |  |  |  | {'S0' => ["E0","SAME", \&_getFirstDate,  "S1","TSTL","S2","SAME"], | 
| 142 |  |  |  |  |  |  | 'S1' => ["E1","SAME", \&_getSecondDate, "S2","TSTL","S2","SAME"], | 
| 143 |  |  |  |  |  |  | 'S2' => ["E2","SAME", \&_getFirstBody,  "S3","NEXT","S3","NEXT"], | 
| 144 |  |  |  |  |  |  | 'S3' => ["D0","SAME", \&_getBody,       "S3","NEXT","S3","NEXT"], | 
| 145 |  |  |  |  |  |  | 'D0' => ["D0","DONE", \&_noop,          "","","",""], | 
| 146 |  |  |  |  |  |  | 'E0' => ["E0","FAIL", \&_nullFileName,  "","","",""], | 
| 147 |  |  |  |  |  |  | 'D1' => ["D1","DONE", \&_endsAt1stDate, "","","",""], | 
| 148 |  |  |  |  |  |  | 'D2' => ["D2","DONE", \&_noBody,        "","","",""], | 
| 149 |  |  |  |  |  |  | }; | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | State table records are divided into four parts: | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | * What to do if we don't have any more lexemes (a duple). | 
| 154 |  |  |  |  |  |  | * A lexical analyzer to be called if we do have a lexeme. | 
| 155 |  |  |  |  |  |  | * What to do if the function returns true (a duple). | 
| 156 |  |  |  |  |  |  | * What to do if the function returns false (a duple). | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | The first of the three pairs (0,1) are applied if the state is entered and | 
| 159 |  |  |  |  |  |  | there are no more lexemes; the second pair (3,4) are applied if the specified | 
| 160 |  |  |  |  |  |  | lexical analyzer routine (2) returns true; the third pair (5,6) if it returns | 
| 161 |  |  |  |  |  |  | false. | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | The first item of each pair is the next state and the second is the action | 
| 164 |  |  |  |  |  |  | part, a keyword SAME or NEXT to indicate whether to stay with the same | 
| 165 |  |  |  |  |  |  | lexeme (SAME) or to try to get the next one (NEXT) before executing the next | 
| 166 |  |  |  |  |  |  | state. TSTL means do a NEXT if the current $lexeme is empty, otherwise keep | 
| 167 |  |  |  |  |  |  | using it like SAME. Additional keywords DONE and FAIL are termination | 
| 168 |  |  |  |  |  |  | indicators. Both will stay keep the current lexeme. | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | Internally the state machine is also modal; it starts in 'RUN' state. When a | 
| 171 |  |  |  |  |  |  | new state has an action part of DONE, the mode is changed to 'DONE'. The next | 
| 172 |  |  |  |  |  |  | function to be executed will be in the DONE mode; the state machine will then | 
| 173 |  |  |  |  |  |  | terminate and return any unused lexemes. Similarly, if the action part is | 
| 174 |  |  |  |  |  |  | 'FAIL', the mode becomes 'ERR' and the function of the new state is executed | 
| 175 |  |  |  |  |  |  | in that context, followed by an exit with the list of remaining lexemes. | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | It is up to the user to record any special failure information on their | 
| 178 |  |  |  |  |  |  | blackboard hash. | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | Unreachable states may be null; for instance if a lexical routine always | 
| 181 |  |  |  |  |  |  | absorbs the lexeme it is given, then it may chose to always return true or | 
| 182 |  |  |  |  |  |  | always return false. Thus  the other table duple is unreachable. Likewise, | 
| 183 |  |  |  |  |  |  | an error or done state does no further branching so both the left branch | 
| 184 |  |  |  |  |  |  | (true) and right branch (false) duple are unreachable. | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | A lexical analyzer routine is passed two arguments: the current lexeme and | 
| 187 |  |  |  |  |  |  | a user supplied blackboard hash as noted earlier. The  routine may do any | 
| 188 |  |  |  |  |  |  | tests it wishes and it may read and write anything it wants from the | 
| 189 |  |  |  |  |  |  | blackboard. It returns a list of two values, the firs of which must be true | 
| 190 |  |  |  |  |  |  | or false to differentiate between the two possible next states, a left branch | 
| 191 |  |  |  |  |  |  | or a right branch. | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | The second user return value is either undef or an unused  portion of the | 
| 194 |  |  |  |  |  |  | input lexeme. Thus a lexeme might be  passed to another (or the same) finite | 
| 195 |  |  |  |  |  |  | state machine. | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | For example: | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | sub _GetSecondaryTitle { | 
| 200 |  |  |  |  |  |  | my ($lexeme, $bb) = @_; | 
| 201 |  |  |  |  |  |  | if ($lexeme =~ /^[^A-Z]/) { | 
| 202 |  |  |  |  |  |  | # Left branch, lexeme is still virgin and reusable. | 
| 203 |  |  |  |  |  |  | return (1, $lexeme); | 
| 204 |  |  |  |  |  |  | } | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | $bb->{'secondary_title'} .= $bb->{'del'} . "$lexeme"; | 
| 207 |  |  |  |  |  |  | $bb->{'del'} = "-"; | 
| 208 |  |  |  |  |  |  | # Right branch, lexeme all used up. | 
| 209 |  |  |  |  |  |  | return (0,undef); | 
| 210 |  |  |  |  |  |  | } | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | This may mean extra states in your states diagram to limit states to a binary | 
| 213 |  |  |  |  |  |  | choice of next state. But that shouldn't be too difficult. | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | =head1 Examples | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | use DMA::FSM; | 
| 218 |  |  |  |  |  |  | my $fst       = { see text for format }; | 
| 219 |  |  |  |  |  |  | my (@lexlst)  = ("First", "Second", "Third"); | 
| 220 |  |  |  |  |  |  | my $bb        = {}; | 
| 221 |  |  |  |  |  |  | my @remaining = DMA::FSM::FSM ( $fst, $bb, @lexlst); | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | =head1 Routines | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | =over4 | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | =item B<@remaining = DMA::FSM::FSM ( $fst, $bb, @lexlst)> | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | Run a FSM machine of your choosing. Arguments are a Finite State Table, | 
| 230 |  |  |  |  |  |  | a ptr to blackboard hash and a list of lexemes to be processed by the FSM. | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | It returns a list of unused lexemes, if any. | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | =back4 | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | =head1 KNOWN BUGS | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | See TODO. | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | Fault::DebugPrinter, Fault::ErrorHandler | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | =head1 AUTHOR | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | Dale Amon | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | =cut | 
| 249 |  |  |  |  |  |  |  | 
| 250 |  |  |  |  |  |  | #============================================================================= | 
| 251 |  |  |  |  |  |  | #                                CVS HISTORY | 
| 252 |  |  |  |  |  |  | #============================================================================= | 
| 253 |  |  |  |  |  |  | # $Log: FSM.pm,v $ | 
| 254 |  |  |  |  |  |  | # Revision 1.7  2008-08-28 23:14:03  amon | 
| 255 |  |  |  |  |  |  | # perldoc section regularization. | 
| 256 |  |  |  |  |  |  | # | 
| 257 |  |  |  |  |  |  | # Revision 1.6  2008-08-15 21:47:52  amon | 
| 258 |  |  |  |  |  |  | # Misc documentation and format changes. | 
| 259 |  |  |  |  |  |  | # | 
| 260 |  |  |  |  |  |  | # Revision 1.5  2008-04-11 22:25:23  amon | 
| 261 |  |  |  |  |  |  | # Add blank line after cut. | 
| 262 |  |  |  |  |  |  | # | 
| 263 |  |  |  |  |  |  | # Revision 1.4  2008-04-11 18:56:35  amon | 
| 264 |  |  |  |  |  |  | # Fixed quoting problem with formfeeds. | 
| 265 |  |  |  |  |  |  | # | 
| 266 |  |  |  |  |  |  | # Revision 1.3  2008-04-11 18:39:15  amon | 
| 267 |  |  |  |  |  |  | # Implimented new standard for headers and trailers. | 
| 268 |  |  |  |  |  |  | # | 
| 269 |  |  |  |  |  |  | # Revision 1.2  2008-04-10 15:01:08  amon | 
| 270 |  |  |  |  |  |  | # Added license to headers, removed claim that the documentation section still | 
| 271 |  |  |  |  |  |  | # relates to the old doc file. | 
| 272 |  |  |  |  |  |  | # | 
| 273 |  |  |  |  |  |  | # Revision 1.1.1.1  2004-08-30 23:26:07  amon | 
| 274 |  |  |  |  |  |  | # Dale's library of primitives in Perl | 
| 275 |  |  |  |  |  |  | # | 
| 276 |  |  |  |  |  |  | # 20040821      Dale Amon | 
| 277 |  |  |  |  |  |  | #               Created. Finally, after talking about it for | 
| 278 |  |  |  |  |  |  | #		several years. | 
| 279 |  |  |  |  |  |  | # | 
| 280 |  |  |  |  |  |  | 1; | 
| 281 |  |  |  |  |  |  |  |