Preprocessor for State-Machines in C

State Machines In Software

While state machines are well known in the hardware domain, they find only rare use in the software world. Usually they are modelled with one variable, which can take a certain set of values, and checking the actual value in some sort of a case-construct and executing the corresponding instructions. The overhead is significant, especially when the subroutines (or functions) are very short (as found in real time control) and the state machine is called very often. The assignment of a new value to the variable has to be done explicitely by the programmer with the right value. Although someone will probably define the possible values as named constants, it is still easy to make mistakes in the form of assigning a wrong (out of the allowed range) value to this variable. So, in C it would something like this:

#define flip 1
#define flop 2

int flipflop_state;

switch (flipflop_state) {
   case flip:
        do_flip_action;
        if (condition_to_toggle == true) flipflop_state = flop;
        break;

   case flop:
        do_flop_action;
        if (condition_to_toggle == true) flipflop_state = flip;
        break;

   default:
        do_action_that_never_should_happen;
        break;
}
As can be seen in this simple example of a flip-flop, it is very easy to make a dangerous error. No compiler on earth forbids (or even warns about it) the programmer to assign an illegal value (e.g. 1234) to the state machine variable. Moreover the out-of-bounds-check (the default:-case) is mandatory here. Wouldn't it be nice when the computer could do some work to make errors of this kind impossible? With the further advantage that the out-of-bounds-check could be eliminated? Or eliminating the costly checks at all?

The last two points can easily be solved by a skilled programmer with the use of a pointer to a function, that is pointing to the actual function to be executed in the present state of the machine. Changing the state is done by pointing the pointer to another function. This concept executes much, much faster than the example above. But the drawback is this: Either you verify before each call via the pointer that it really points to a legal address or you risk crashing the application (when an illegal value was assigned to the pointer). And question one is still open. This is where the state-machine-preprocessor comes in.

A New Approach to State Machines in C

The concept is simple: We introduce 4 new keywords that manage the task. Let us rewrite the example of above and make it a working one:

#include < stdio.h>              /* delete the space after the < ! Is in for the browser only */

#SEQUENCE void flipflop()       /* define the state machine and give it a name */

#PRESENT flop                   /* define the first state, like a normal function */
{
    int c;
         printf("Current state is FLOP - Hit < f > to switch state!\n");
         if (getchar() == 'f') #NEW flip        /* check condition to transit to other state */
}

#PRESENT flip                   /* define the other state */
{
    int c;
         printf("Current state is FLIP - Hit < f > to switch state!\n");
         if (getchar() == 'f') #NEW flop
}

void init_flipflop (void) { #NEW flop } /* define a funtion to set the machine to a known state! */

#ENDSEQUENCE                    /* end of the state machine definition */

void main()                     /* let us try it out */
{
    init_flipflop();            /* set machine to a known state BEFORE the 1.st time use */
    while (1==1)                /* in this example we loop forever */
        {
                getchar();      /* to "eat" the input terminator, for aesthetic reasons only */
                flipflop();     /* call the state machine */
        }

}

What do the new keywords do? Let us start by grouping them into two categories: #SEQUENCE and #ENDSEQUENCE belong to the first category and #PRESENT and #NEW go into the second. The rules for the keywords of the first category are:

The rules for the second category are these: Note:

What Are The Advantages?

There are some. First and most, it is impossible to make fatal errors. The state machine can assume only defined states. Second, the preprocessor copies the type and parameter declaration of the state machine to every state. This enforces all states to have the same template. The compiler can thus verify that you made no programming errors. And last, but not least, you do not have to care about any internals of your state machine. You code the function of it and leave the checks to the preprocessor and the compiler.

How to Use The Preprocessor

The DOS version is called with the name of the input file to process. It creates 2 output files, whose names are derived from the input file name by substituting the extension with .c and .smh, respectively. The .smh is included by the .c file. The C file is ready to be processed by any C compiler that conforms to ANSI standards. The input file is unmodified. Please note, that due to this mechanism it is not possible to give the input file an extension of either .c or .smh. If you omit the extension the default of .smc is used.

What Will The Output Files Look Like?

For the curious of you that want to see the result without trying out the preprocessor, here are the listings of the two output files:

The C File

#include < stdio.h>              /* delete the space after the < ! Is in for the browser only */

#include "FF.SMH"      /* define the state machine and give it a name */

void _flipflop_flop_()                   /* define the first state, like a normal function */
{
    int c;
         printf("Current state is FLOP - Hit < f > to switch state!\n");
         if (getchar() == 'f') _flipflop_ = _flipflop_flip_ ;        /* check condition to transit to other state */
}

void _flipflop_flip_()                   /* define the other state */
{
    int c;
         printf("Current state is FLIP - Hit < f > to switch state!\n");
         if (getchar() == 'f') _flipflop_ = _flipflop_flop_ ;
}

void init_flipflop (void) { _flipflop_ = _flipflop_flop_ ; } /* define a funtion to set the machine to a known state! */

/* End of state machine flipflop */                     /* end of the state machine definition */

void main()                     /* let us try it out */
{
    init_flipflop();            /* set machine to a known state BEFORE the 1.st time use */
    while (1==1)                /* in this example we loop forever */
        {
                getchar();      /* to "eat" the input terminator, for aesthetic reasons only */
                flipflop();     /* call the state machine */
        }

}

As you can see, it is mostly a copy of the input file. But some changes are noteworthy. Your declaration of your state machine is replaced by an #include of the new header file. All the #PRESENTs are gone. But the state names are changed. They have the name of the state machine prepended. And they start and end with an underscore. This is a common method to avoid name clashing. Finally the #ENDSEQUENCE left no trace at all.

The Header File

void (*_flipflop_)();
#define flipflop()  (*_flipflop_)()
void _flipflop_flop_() ;
void _flipflop_flip_() ;

You see the declaration of the state machine and all the states here. And a simple definition to make the state machine like you intended to call it. That is all. The reason, why this header file is needed is simple: It is easier for the preprocessor to first insert the #include into the C file and then later, when all states are defined, to write the header file.

Customizing The Error Messages

If you want to customize the error messages, you need Turbo Pascal 6.0 or a similar compiler. Copy either msg_ger.pas or msg_eng.pas to a name of your choice. Then edit your copy, compile it an run it. A new smproc.msg file will be created, overwriting any existing one.