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
|
|
|
|
|
|
|
|