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