If we want to carry an FSM through to the bitter end, we can implement it directly as program code. This requires a leap of faith
because the code can often appear long-winded. In spite of this, if we're taking the trouble to implement the FSM, we'll gain much more by sticking rigorously to the mechanism without being tempted to introduce
shortcuts, particularly in trying to avoid repetition of code. Recall that we're using an FSM to formalise
the design of the GUI, and for a complex GUI the direct translation to code pays dividends by virtually eliminating the need for debugging. By introducing shortcuts, not only do we lose this integrity, but we also make the code harder to read.
Building an FSM with code is a straightforward affair that can be abstracted in a simple conditional statement: If we're HERE and THIS happens Then do THAT
and GoTo THERE The only thing we have to keep track of is the current state, and most of our effort will be concerned with the mechanics of processing events and invoking the action procedures. We
can build an FSM in any language that supports conditional statements, so let's start by looking at an implementation that can be adapted to any version of Visual Basic. For this example, we will implement
the C comment stripper described earlier and build it into a simple application using the form shown in Figure 7. The application displays the text as we type, minus any C-style comments. We'll drive the
FSM in real time – that is, the events will be caused directly by our keypresses, and the states and events will be displayed in the other boxes on the form.
Figure 7: The comment stripper FSM program The first thing we need is a state, which can be represented as a
simple integer. It doesn't matter what data type we choose for the state, since there is no concept of ordering. The only requirement is that the states be unique. In real life, we'll
usually want to define constants for the states and events. In this example, however, we're not going to use event constants because it's convenient to represent events with the ASCII codes
generated by the keypresses. Here's how to define the states: Private Const S_OUTSIDE = 1 Private Const S_STARTING = 2 Private Const S_INSIDE = 3 Private Const S_ENDING = 4
Public State As Integer
If you refer back to the FSM tables for the comment stripper, you'll see that there are 12 different combinations of state and event, so our conditional logic needs to guide us along 12
different paths through the code. To implement this with simple conditional statements, we have the choice of using If-Then-ElseIf or Select Case statements; for this example, we'll
arbitrarily choose the latter. To decode one particular path, the code will contain a fragment such as this: Select Case nState
Case S_OUTSIDE:
Select Case nEvent Case Asc("/")
nState = S_STARTING Case Asc("*")
txtOutBox.Text = txtOutBox.Text & Chr$(nEvent)
nState = S_OUTSIDE Case Else
txtOutBox.Text = txtOutBox.Text & Chr$(nEvent)
nState = S_OUTSIDE End Select
Case S_STARTING:
... End Select You can see that each of the 12 cells in the FSM tables has a piece of code inside a pair of nested Select Case
statements. The State and Event tables are combined here, so the last statement in each case assigns a new value to nState (which we'll assume is a reference parameter). The rest of the code for each decoded
state/event pair depends on what we want this particular implementation of the comment stripper to do – in fact, we're just going to add the text to the text box or not, so the actions
here are simple. In practice, the code will usually be more manageable if we divide it up so that each state has its own function. Thus, the example above becomes something like this: Select Case nState
Case S_OUTSIDE DoStateOUTSIDE(nState, nEvent) Case S_STARTING DoStateSTARTING(nState, nEvent) ... End Select
Sub DoStateOUTSIDE(ByVal nEvent As Integer, _ ByRef nState As Integer)
Select Case nEvent Case Asc("/") nState = S_STARTING
Case Asc("*"): txtOutBox.Text = txtOutBox.Text & Chr$(nEvent)
nState = S_OUTSIDE Case Else
txtOutBox.Text = txtOutBox.Text & Chr$(nEvent) nState = S_OUTSIDE
End Select End Sub Now we have the state variable and the logic for decoding the state/event pairs, and all we need is a source of events. In this
example, we'll trap keypresses by setting the KeyPreview property of the form and generating an event for each keypress. All we need to do now is feed the events to the FSM by calling a
function that contains the decoding logic (let's call it DoFSM). The keypress event handler looks something like this: Private Sub Form_KeyPress(KeyAscii As Integer)
Call DoFSM(State, KeyAscii) KeyAscii = 0 ' Throw away the keypress End Sub In this example, the event codes and the real-world events that
map onto them are one and the same – hence, the 'action' code in each DoState routine can get the ASCII codes directly from the nEvent parameter. Most applications don't have such coupling, and
we would need to arrange for any such real-world data to be buffered somewhere if we wanted the action routines to have access to it. Consider, for example, the Unix tool YACC (yet
another compiler-compiler), which builds table-driven parsers that process sequences of tokens read from an input stream. A parser generated by YACC gets its tokens by successive calls to a C function named yylex()
, which is the direct equivalent of the KeyPress event handler. The yylex() function returns a numeric token, equivalent to the nEvent parameter, but it also copies the
full text of the actual word it recognised into a global variable named yytext. This variable is available to any code in the YACC-generated program.
The only element missing from the FSM program is something to initialise the state variable. Recall that one state of the FSM is always designated the start state, so we need a line of code
to assign that to the state variable before we start generating events: State = S_OUTSIDE This can go in the Form_Load event of the comment stripper program. Next... |