File Coverage

blib/lib/DMA/FSM.pm
Criterion Covered Total %
statement 9 67 13.4
branch 0 32 0.0
condition n/a
subroutine 3 4 75.0
pod 1 1 100.0
total 13 104 12.5


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