Writing an inline state machine class

When writing the application-specific class that accompanies the grammar for a state machine, you will need to understand the structure of the classes that make up the state machine after parsing and compilation. The base class of every state machine built using the ParseLR library should always be Parsing.FSM. You then inherit your application-specific state machine class from this library base class. Your application-specific class will have any action or guard code that is to be called from the code fragments embedded in the grammar. Lastly, a third level class is inherited from your application-specific class by the parser generator, that contains the code written inline in the grammar description file. Any methods or properties you want your code embedded in the grammar to access must therefore be declared public protected or internal in the application-specific class.

Project references

Any state machine project will need to include three DLLs among the references. These are Parsing.DLL that contains the classes in the namespace Parsing, BooleanLib.dll that contains the helper classes for implementing guard conditions, and ParserGenerator.DLL that contains the classes in the namespace ParserGenerator. Naturally the addition of using statements to the top of source files that refer to members of these DLLs will simplify your source code. If your state machine is to be created as an inline state machine, these references are automatically set up for the autogenerated code built from the grammar. However, any other non-standard DLL references will need to be nominated in the options section using assemblyref statements.

Creating the state machine class

First your source code will need to have an application-specific state machine class that you have written. The actual state machine class already exists as Parsing.FSM in Parsing.DLL, so all you need to ensure is that your application specific parser class inherits from Parsing.FSM.

An example of the structure of your source file containing your application-specific state machine class might be:


using Parsing;

// ... other using statements ...

namespace MyApplication
{
    public class MyFSM : FSM
    {
        // ... Application specific class members here ...
    }
}

Your state machine will represent a single object that acts as the interface to any data structures that are being built or manipulated while the state machine is executing. You would typically include any data members and methods to access them within the application specific state machine class.

Action functions

With any state machine, all the action code that gets executed on state transitions will be called from the code fragments in the grammar that follow each transition description. Any functions or properties called from these code fragments should have already been written and added to the source project for the state machine.

Guard functions

If your grammar requires guard functions to be evaluated for some of the events as they are parsed, these guard functions must also be written in your application-specific state machine class. They will have been listed by name in the guards section of the grammar.

Guard functions take no arguments, but return a boolean result. As for action functions, the guard's result should be calculable from previous state data already stored into or accessible via the properties and fields of the state machine object. This result should be set to true if the event should be accepted at this point and the transition taken, or false if it is not the correct transition to be taken. An example of a guard function named PluralNoun is given in the parser class below:


using Parsing;

namespace MyApplication
{
    public class MyFSM : FSM
    {
        // Application specific class members
        
        public int PluralCount { get; set; }
        
        public void BumpPlural()
        {
            PluralCount++;
        }
        
        public bool PluralNoun()
        {
            return mostRecentWord.ToString().EndsWith("s");
        }
    }
}

As a guide to writing boolean guard functions, try to arrange that each function evaluates a single truth value about the state of the parse or its data. Avoid combining several evaluable boolean items with boolean operators within a single function. The reason for this is that the grammar description allows compound boolean expressions to be constructed using the boolean guard functions and the operators 'and', 'or' and 'not'. By keeping the guard functions as primitive as possible, you can reuse them in different combinations for different guard expressions at different points in the grammar.

Constructors

Your in-line state machine class should either have no constructor, or should be written with a default (parameterless) constructor. If you need to initialise members with values, this should be done via a separate initialisation method, or via property assignments after the FSM instance has been created. The reason for this is that the FSMFactory<TStateMachine> class that is used to parse the grammar description and create instances of the resulting state machine expects the application-specific state machine class (TStateMachine) to have a default constructor. An example of some suitable creation and initialisation code is given below. Note that the FSMFactory<TStateMachine> methods are described elsewhere.


    FSMFactory<MyFSM>.InitializeFromGrammar( ... args ... );
    MyFSM myFSM = FSMFactory<MyFSM>.CreateInstance();
    myFSM.SomeMemberProperty = someValue;