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