Home
Introduction
Design
FSMs in VB
Event Queues
Data-Driven
Example
Real World
VB6 is Better

The Art of the State:
Implementing FSMs in Visual Basic

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

Key Spinner

© 1998 - 2009 Mark Hurst. All rights reserved.   Updated March 01, 2009