Skip to content
/ fgen Public

Note: this is just an old code dump, I have no idea if this state machine code generator still works. Both Perl and C++ are very different these days. This code was first created about 1999 and went through many iterations to about 2005. I still think it's probably a good approach to state machine generation.

Notifications You must be signed in to change notification settings

ToddHoff/fgen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fgen

Generate Framework Code from System Specifications

Contents

  1. Description
    1. What fgen Is
    2. What fgen Is Not
  2. Synopsis
    1. Setup
    2. Command Line Invocation
  3. Syntax
    1. fgen's BNF
    2. Configuration BNF
    3. State Machine BNF
    4. Source Specification Files
    5. # Comment Marker
    6. \ Line Extender
  4. Configurable Attributes - CFG Block
  5. State Machine - SM and IN Blocks
    1. Defined
    2. Motivating Example of All the Work You can Save
    3. Files Produced
    4. SM Definition (SM block)
    5. State Definition (IN block)
    6. Implementing the State Machine
    7. State Machine and Agent Example
  6. Internals - fgen internals
  7. Bugs
  8. Files


What fgen Is

fgen takes a system specification and generates code based on the specification. The idea is a programmer can make a simple description of something and then generate a lot of infrastructure type code based on the specification. fgen also makes it easy to add new system specifications, extensions to existing specifications, and different code generators for specifications.

Systems can be anything, but they usually map to something relatively complex where automatic code generation can provide a big productivity win. State machines are one such area where a state machine can be simply specified and a lot of code generated.

What fgen Is Not

fgen is not a perfect compiler implementing a perfect grammar. It is a quick and simple way to describe things and generate code from the description. That's all. Please don't be too dissapointed :-)


Synopsis

fgen automatically generates code from simple system specifications. Currently fgen understands the following system specifications:

Fgen is designed to make it very easy to add new system specifications and parsers.

Setup

Install Perl

Fgen is a perl script which requires Perl5 to be installed on your system.

Path Setup

Fgen should be in your system's search path. Add the directory fgen is in to your system's PATH environment variable. Environment variables are set using Start/Setting/System/Environment.

Command Line Invocation

perl fgen.pl files...

perl

Perl is a call to the perl program that should be installed. If perl is not in your path then the full path to perl must be specified on the command line.

fgen.pl

fgen.pl is the program that reads your specifications and generates code. If it is not in the current directory then the full path to fgen.pl must be specfied.

files

fgen takes multiple files on its command line which means specifications can be placed in several files and then processed together. All files are first parsed and then code generated which means if an attribute is redefined in a later file the definition of the later file is used. If a the file is not local then the full path to the file must be specified.

fgen pays no attention to file extensions so you can organize files however you want. Suggested file extensions are:

  1. .sm - state machine specification files

Example:

% perl D:\main\sw\tools\fgen\fgen.pl ImageDownload.sm

In the example the ++ state machine specified in ImageDownload.sm is generated. Perl is assumed to be in the path. ImageDownload.sm is assumed to be local.


Syntax

fgen's syntax is very simple, it consists of:

  • Blank line separated blocks. Lines must be separated by lines contaning new-lines only. Even spaces on the "blank" line can confuse the parser.
  • Where each block consists of one or more new-line separated lines.
  • Where each line consists of attributes and their values.
  • Where attributes are separated by spaces.
  • Blocks begin with a block type key word and a block name. The rest of lines in the block describe the system.

All system specifications follow this basic syntax. Each system must come up with a way to map the system to this syntax. Attributes can be anything and values can be anything so it's not really difficult. Go here to see an example.

fgen's BNF

(file)        ::= (specs)*
(specs)       ::= (block) | (cfg) | (sm) | (msg)

(block)       ::= (block_start) 
                  (block_entry)+
                  (blank_line)
(block_start) ::= (block_type) (ws) (block_name) (nl)
(block_type)  ::= (id) // a keyword identifying the block type
(block_name)  ::= (id) // name of the block
(block_entry) ::= (block_line) (nl)
(block_line)  ::= whatever grammar required for the block type

(dir_path)    ::= "(path)" | <(path)>
(inc_list)     ::= INC (ws) dir_path [, (ws) dir_path]*
(path)        ::= (id) // a relative or full path to a directory
(value)       ::= (id)
(id)          ::= a string with no embedded spaces
(nl)          ::= a carriage return
(blank_line)  ::= a blank line
(ws)          ::= white space
(number)      ::= a string of digits
(lighteraff)  ::= any series of characters
(boolean)     ::= 0 | 1

CFG Block BNF

The CFG block allows the specification of general attributes used in the generation of code. For example, the attribute DEBUG_LEVEL specified the default debug level to use when generating debug statements. You may have a lot of configuration attributes are almost none.

(cfg)         ::= (cfg_start)
                  (cfg_entry)+ 
                  (blank_line)
(cfg_start)   ::= CFG (ws) (id) (nl)
(cfg_entry)   ::= (cfg_attr) (cfg_value) (nl)
(cfg_attr)    ::= (id)  // ID from the configurable attributes list
(cfg_value)   ::= whatever the value should be for the attribute

Any attribute not in the configurable attribute list is automatically added even though it's not "official." This allows new parsers to have their own configurable attributes. All configurable attributes are in the associative array $::CFG{$attribute}. A small example:

CFG Remote Peer State Machine
   DEBUG            1
   DRIVERS          lngen.pl
   INC              "Project/LnTypes.h"
   IGNORE_SAME_ENTRY_EXIT_TRANSITION 0

State Machine BNF

A state machine is made up of SM block and one or more IN blocks. The SM block defines the state machine in general and the IN blocks define the events, state transitions, and the actions to take.

SM Block

The state machine block defines attributes about the state machine. It is like a CFG block for the state machine.

(sm)          ::= (sm_start) 
                  (start_state) 
                  (sm_entry)+ 
                  (blank_line)
                  (in)+

(sm_start)    ::= SM (ws) (sm_name) (nl)
(sm_name)     ::= (id) // name of the state machine
(sm_entry)    ::= (case_err) | (inc_list) | (timer) | (is_protect)
                  | (state_type) | (sm_module) | (method) | (doc)
                  | (on_doc) | (on_args) | (do_args) | (do_doc) | (attr) | (debug)
				  | (derive) | (gen_as_string) | (gen_inject_event) 
				  | (gen_notify_call) | (gen_eforwarder)
(start_state) ::= START (ws) (id) (nl)
(case_err)    ::= CASE_ERR (ws) (lighteraff) (nl)
(is_protect)  ::= IS_PROTECT (ws) (boolean) (nl)
(state_type)  ::= STATE_TYPE (ws) (id) (nl)
(sm_module)   ::= IS_MODULE (ws) (boolean) (nl)
(doc)         ::= DOC (ws) (text) (nl)
(on_doc)      ::= ON_DOC (text) (nl)
(do_doc)      ::= DO_DOC (ws) (action) (ws) (text) (nl)
(on_args)     ::= ON_ARGS (ws) (event_name) (ws) ARGS (ws) (args) (nl)
(do_args)     ::= DO_ARGS (ws) (action_name) (ws) ARGS (ws) (args) (nl)
(method)      ::= METHOD (ws) (var_type) , (method_body) (nl)
(debug)       ::= DEBUG_LEVEL (ws) (level) 
(attr)        ::= ATTRIBUTE (ws) (var_type) , (var_name), (var_value) (nl)
(var_type)    ::= A type designation for a method attribute.
(var_name)    ::= Name of the attribute.
(var_value)   ::= Value for an attribute.
(text)        ::= A string of any length. The \ character can be used to
                  break a string across lines.
(level)       ::= A debug level approriate to the code generator.
(event_name)  ::= The name of an event.
(action_name) ::= The name of an action.
(derive)      ::= DERIVE (ws) (dclasses)
(dclasses)    ::= The classes to derive the state machine from. It's
                  a standard C++ derivation specification (public X).
(gen_as_string) ::= GEN_AS_STRING (ws) (0|1)
(gen_inject_event) ::= GEN_INJECT_EVENT (ws) (0|1)
(gen_notify_call) ::= GEN_NOTIFY_CALL (ws) (0|1)
(gen_eforwarder) ::= GEN_EFORWARDER (ws) (0|1)
(args)        ::= (var_type) (var_name) [, (var_type) (var_name)]

A small example is:

SM RemotePeerSm
   START            IDLE
   METHOD           virtual int, gt()
   ATTRIBUTE        int, mVal, 0
   ON_ARGS HealthCheckOk ARGS int x, int y
   DO_ARGS RepairOm ARGS int x, int y
   DOC This class remotely peers with a peer. \
More text of great use.

IN Block

The IN block defines what happens when an event is generate when the state machine is in a given state. A state machine can have any number of IN blocks. You'll have one IN block for every event that transitions the state machine.

(in)          ::= (in_start) 
                  (which_sm)* (which_in)* (timer)* (transition)+ 
                  [(on_entry)] [(on_exit)]
                  (blank_line)

(which_sm)    ::= SM (ws) (which_state) [, (ws) (which_state)]* (nl)
(which_state) ::= (state_name) | CURRENT | GLOBAL

(which_in)    ::= IN [(doc)] | (in_list) 
(doc)         ::= DOC text
(in_list)     ::= (ws) (state_name) [, (ws) (state_name)]* (nl)

(in_start)    ::= IN (ws) (state_name) [, (state_name]+ (nl)
(transition)  ::= [NEXT | FNEXT] (ws) (state_name) (ws) ON (event) (ws) 
                  [(onerr) (ws)] [(if) (ws)] [(do) (ws)] (nl)
(do)          ::= DO (ws) (action) (ws) [(timer)][, (do)] |
                  DO (ws) (action) | (timer_stop) [, (do)]
(if)          ::= IF (ws) (test)
(onerr)       ::= ONERR (ws) PREVSTATE | (state_name) | (invoke) 
(timer_stop)  ::= TIMER_STOP (ws) (timer_name)
(timer)       ::= TIMED_BY (ws) (timer_name) 
                  PERIOD (dmsecs)[(imsecs)]
                  [ACTION TRUE|FALSE]
(key)         ::= identifier of one of the predefined configurable values
(dmsecs)      ::= number of delay milli-seconds | (method_name()).
                  Delay is the delay before the timer first fires.
(imsecs)      ::= number of interval milli-seconds | (method_name())
(method_name) ::= name of a method to call to the attribute value.
(test)        ::= a method returning an integer

(timer_name)  ::= (id)   // name of a timer
(action)      ::= (id)   // name of an action 
(event)       ::= (id)   // name of an event 
(state_name)  ::= (id)   // name of a state
(invoke)      ::= (id)() // function/method/macro name with parens
(on_entry)    ::= ON_ENTRY (do)
(on_exit)     ::= ON_EXIT (do)

A small example:

IN IDLE     
   DOC This state means we are rich.
   ON_ENTRY DO TIMER_STOP HealthTimer
   ON_EXIT  DO RepairOm  TIMED_BY HealthTimer PERIOD  3.4
   NEXT KNOWN      ON PeerStateMsgRcvd                                       \
      DO NewState TIMED_BY MsgTimer PERIOD GetNormalMsgToVal()
   NEXT FAILED     ON PeerFailed                                             \
      DO TIMER_STOP MsgTimer,                                                \
      DO FailedState

IN FAILED
   DOC This state means we have lost all our money.
   NEXT IDLE       ON PeerNotFailed
   NEXT KNOWN      ON PeerStateMsgRcvd IF IsPeerNotFailed()                  \

# Comment Marker

Lines beginning with # are comments.

\ Line Extender

Lines span more than one line by putting a </I> at the end of the line.


Configurable Attributes - CFG Block

The CFG block allows the specification of general attributes used in the generation of code. For example, the attribute DEBUG_LEVEL specified the default debug level to use when generating debug statements. You may have a lot of configuration attributes are almost none.

The BNF is in section CFG BNF.

A small example:

CFG Remote Peer State Machine
   DEBUG            1
   DRIVERS          lngen.pl
   INC              "Project/LnTypes.h"
Attribute Default Meaning
DEBUG 0 Set the debug level:
  • 0 means off
  • 1 means trace
  • 2 dumps parser data structures and more detailed info
CCEXT cpp C++ extension to use for C++ source files.
TIMER_CLASS Timer Name of the timer class to use when generating timers.
TIMER_INC "Osencap/LnTTimer.h" Path to use in the include statement to include the timer class.
TIMER_IS_LNOBJECT 0 Controls if code is generated to use Destroy() to delete the timer. It should be used when timers are dirived from LnObjec.t
TIMER_FUNC virtual int HandleTimer() Method invoked on timer callbacks.
TIMER_SET Start Method of the timer object to start a timer. This method must accept the argument:
  • int msecs - the number of milli-seconds between timer firings.
TIMER_CANCEL Cancel Method of the timer object that cancels a timer.
MUTEX_CLASS LnMutex Name of the mutex class.
MUTEX_INC "Osencap/LnMutex.h" Path to use in the include statement to include the mutex class.
ERR_TYPE LnStatus The type of the error return.
NO_ERR LN_OK Value use to indicate no error occurred.
MSG_CLASS Msg Name of the message class to use.
INC none Automically includes the specified include file in every generated class definition. For example:

        INC "Util/Log.h"
        
SEARCH_PATH fgen's directory SEARCH_PATH is comma separated list of paths of where to so search drivers, specified with the DRIVERS attribute. By default, the directory where fgen is located is added to the search path. For example:

        SEARCH_PATH /h/bin, /h2/someplace
        
DRIVERS none DRIVERS is a comma separated list of paths to drivers. Each driver is expected to generate appropriate code based on the specification. Paths should be full paths unless the module will be found in the perl library search path (the @INC variable). For example:

        DRIVERS /h/fgen.pl, yourfgen.pl
        
OVERWRITE 1 OVERWRITE controls if generated files overwrite existing files. Options:

  • 1 - means overwrite
  • 0 - means don't overwrite
GEN_ASSERT 0 GEN_ASSERT tells the code generator to generate assertions. By default code is generally generated without asserts.
  • 1 - means generate assert statements
  • 0 - means don't generate assert statements
GEN_DEBUG 1 GEN_DEBUG tells the code generator to generate debug statements where it thinks they would make sense.
  • 1 - means generate debug statements
  • 0 - means don't generate debug statements
DEBUG_LEVEL level Output::LEVEL_L2 The default debug level to use when creating debug statements. It can be any valid level.
  <P> 
  For example:

  <PRE>
  CFG Test
     DEBUG_LEVEL Output::LEVEL_L2
  </PRE>
IGNORE_SAME_ENTRY_EXIT_TRANSITION 0 Decides if ON_ENTRY and ON_EXIT behaviours are called when transition to the same state. Applications may have reasons for not wanting these behaviours called on same state transitions.
  • 1 - do not call exit and entry routines when transitioning to the same state.
  • 0 - always call exit and entry routines. The default.

See an example .


State Machine - SM and IN Blocks

The easiest way to understand how the state machine specification works is to take a look at an example.

A state machine is made up of SM block and one or more IN blocks. The SM block defines the state machine in general and the IN blocks define the events, state transitions, and the actions to take.

A small example is:

CFG Remote Peer State Machine
   DEBUG            1
   DRIVERS          lngen.pl
   INC              "Project/LnTypes.h"

SM RemotePeerSm
   START            IDLE
   METHOD           virtual int, GetNormalMsgToVal()
   ATTRIBUTE        int, mVal, 0
   ON_ARGS HealthCheckOk ARGS int x, int y
   DO_ARGS RepairOm ARGS int x, int y
   DOC This class remotely peers with a peer. \
More text of great use.
   ON_DOC PeerStateMsgRcvd This event means a message was received.
   DO_DOC NewState This action does something. 

IN IDLE     
   DOC This state means we are rich. \
Let me tell you what i do all day.
   ON_ENTRY DO TIMER_STOP HealthTimer
   ON_EXIT  DO RepairOm  TIMED_BY HealthTimer PERIOD  3.4
   NEXT KNOWN      ON PeerStateMsgRcvd                                       \
      DO NewState TIMED_BY MsgTimer PERIOD GetNormalMsgToVal()
   NEXT FAILED     ON PeerFailed                                             \
      DO TIMER_STOP MsgTimer,                                                \
      DO FailedState

IN FAILED
   DOC This state means we have lost all our money.
   NEXT IDLE       ON PeerNotFailed

For the more technically minded state machine's are defined by BNF .

Defined

A state machine consists of:

  • list of states that can be entered
  • events causing movement from one state to another
  • actions performed when a state transition occurs

fgen allows the above to be encoded simply and also provides some value-add with the addition of timers. Operations can be timed. Timer objects are automatically generated and managed.

A state machine is defined by two blocks:

  • SM defines the overall state machine. There is one SM block per state machine.
  • IN defines each state in the state machine. There is one IN block per state in the state machine.

Multiple state machines can be defined in the same file. By default states are associated with the current state machine which means the state machine definition (SM block) they are lexically below in the file.

Using the SM attribute of an IN block which defines state a state may be associate with one or more state machines.

Motivating Example of All the Work You can Save

This example shows a fairly complicated state machine for maintaining the state of a remote peer entity. Notice how the state machine uses timers. The timers are automatically generated and managed. All the code implementing the state machine is generated. All you have to do is specify the state machine.

To use the state machine you just derive from it, implement the actions, and call the event methods when they occur. This tool can save a considerable amount of effort, especially when a state machine is changing a lot. Agents and state machines combined together are especially powerful. An agent can define its state machine using fgen and then inherit from it. Messages arriving to an agent are events that move the state machine through its states. The actions are implemented by the agent to do the agent's work.

# NAME:
#   RemotePeer.sm - state machine for Remote Peer stste
#
# SYNOPSIS:
#   fgen.pl RemotePeer.sm
#
# DESCRIPTION:
#   This file describes an example state machine for maintaining the
#   state of a remote peer entity.
#
#   Running fgen on this file will create the C++ class implementing 
#   the state machine.
#
# SEE ALSO: fgen
#
# TYPE: SPECIFICATION
#########################################################################

CFG Remote Peer State Machine
   DEBUG            1
   DRIVERS          lngen.pl
   INC              "Project/LnTypes.h"

SM RemotePeerSm
   START            IDLE
   METHOD           virtual int, gt()
   METHOD           virtual bool, IsPeerFailed()
   METHOD           virtual bool, IsPeerNotFailed()
   METHOD           virtual int, GetNormalMsgToVal()
   ATTRIBUTE        int, mVal, 0
   ON_ARGS          HealthCheckOk ARGS int x, int y
   DO_ARGS          RepairOm ARGS int x, int y
   IS_MODULE        1

IN IDLE     
   ON_ENTRY DO TIMER_STOP HealthTimer
   ON_EXIT  DO RepairOm  TIMED_BY HealthTimer PERIOD  3.4
   NEXT KNOWN      ON PeerStateMsgRcvd                                       \
      DO NewState TIMED_BY MsgTimer PERIOD GetNormalMsgToVal()
   NEXT FAILED     ON PeerFailed                                             \
      DO TIMER_STOP MsgTimer,                                                \
      DO FailedState
   NEXT FAILED     ON Entry IF IsPeerFailed() DO FailedState
   NEXT IDLE       ON Entry                                                  \
      DO UnknownState TIMED_BY MsgTimer PERIOD GetNormalMsgToVal()
   NEXT OVERDUE    ON MsgTimerFire                                           \
      DO Nothing TIMED_BY MsgTimer PERIOD getOverdueMsgToVal()

IN FAILED
   NEXT IDLE       ON PeerNotFailed
   NEXT KNOWN      ON PeerStateMsgRcvd IF IsPeerNotFailed()                  \
      DO NewState TIMED_BY MsgTimer PERIOD GetNormalMsgToVal()

IN KNOWN
   NEXT KNOWN      ON PeerStateMsgRcvd                                       \
      DO CheckForNewState TIMED_BY MsgTimer PERIOD GetNormalMsgToVal()
   NEXT FAILED     ON Entry IF IsPeerFailed() DO FailedState
   NEXT IDLE       ON Entry                                                  \
      DO UnknownState TIMED_BY MsgTimer PERIOD GetNormalMsgToVal()
   NEXT FAILED     ON PeerFailed                                             \
      DO TIMER_STOP MsgTimer,                                                \
      DO FailedState
   NEXT OVERDUE    ON MsgTimerFire                                           \
      DO Nothing TIMED_BY MsgTimer PERIOD getOverdueMsgToVal()

IN OVERDUE
   NEXT KNOWN      ON PeerStateMsgRcvd                                       \
      DO CheckForNewState TIMED_BY MsgTimer PERIOD GetNormalMsgToVal()
   NEXT FAILED     ON PeerFailed                                             \
      DO TIMER_STOP MsgTimer,                                                \
      DO FailedState
   NEXT FAILED    ON MsgTimerFire                                           \
      DO FailedState

Files Produced

The following files are produced:

  • A state machine header file. The filename is based on the value of attribute SM in the SM block start.
  • A state machine source file. The filename is based on the value of attribute SM. The extension is defined by attribute CCEXT.

SM Definition (SM block)

A state machine block starts when the keyword SM is seen at the beginning of a line. State machine blocks describe attributes about a state machine. Other blocks describe each state in the state machine.

State Machine Start Block

Attribute Meaning
SM name This is block start for the SM block. name is the name of the state machine. The generated source files are named after SM. Attributes for SM are defined in their own table .

   For example:
   <PRE>
   SM DownloadSm
   </PRE>

State Machine Attributes

Attribute Default Meaning
START state required Initial state of the machine. A required field for state machines.

For example:

      SM Test
         START IDLE
      
INC inc_list NA A list of files to include in the statema machine header. Usually files are included to resolve any user defined types.
  For example:
  <PRE>
  SM Test
     INC  "A.h", "B.h"
  </PRE>
IS_PROTECT 1 | 0 0, no Mutex If IS_PROTECT is set to 1 then all state machine transitions are protected by a mutex. The default is no mutex protection.

  For example:
  <PRE>
  SM Test
     IS_PROTECT 1
  </PRE>
ATTRIBUTE var_type, var_name, var_value 0 ATTRIBUTE specifies a variable to make part of the state machine class. For each attribute in a state machine there will be a corresponding attribute in the state machine class definition. The variable will have type var_type and variable name var_name. The variable will be initialized in the constructor to value var_value. This allows the state machine to be better separated from its derived class.
  For example:
  <PRE>
  SM Test
     ATTRIBUTE int, mVal, 0
  </PRE>
ON_ARGS event_name ARGS (args) ON_ARGS allows an event to be called with arguments. The arguments will be passed to any actions defined as taking parameters (see DO_ARGS). This allows, for example, a message to be passed to an action directly without the message having to be stored in a object variable.
  For example:
  <PRE>
  SM Test
     ON_ARGS HealthCheckOk ARGS int x, int y
  </PRE>

  The HealthCheckOk method will be defined as taking argument "int x" and 
  "int y".
DO_ARGS action_name ARGS (args) DO_ARGS allows an action to be called with arguments. Arguments will be passed from an event to the action. The variable names have to be the same in the event and in the action.
  For example:
  <PRE>
  SM Test
    ON_ARGS HealthCheckOk ARGS int x, int y
    DO_ARGS RepairOm ARGS int x, int y

  IN OM_FAILED
     NEXT OM_PRESENT ON HealthCheckOk                 \
        DO RepairOm                                   \
           TIMED_BY HealthTimer PERIOD  3.4                              
  </PRE>

  In this example the values passed to HealthCheckOk will be passed
  in turn to RepairOm.
STATE_TYPE id State STATE_TYPE allows you to replace a state machine's automatically generated state enumeration with your own preexisting state type. You must make sure the enum labels in your state machine match the labels in your enum. An INC statement must be used to to include a file that will resolve the enum type.

  For example:
  <PRE>
  SM Test
     STATE_TYPE       OperationStateType
  </PRE>
TIMED_BY NA A timer may be defined that is not used explicitly by any transition. Code for the timer is generated. Such timers must be manually managed by the programmer using the start and stop timer methods. Please see TIMED_BY for more details on the the syntax.

  Timers defined in the state machine block do not generate an action
  method in the state machine by default. This is because it might conflict
  with an event used in an <I>ON</I> clause in a transition. To make
  TIMED_BY generate an action method use the <I>ACTION</I> clause and set 
  it to true. When you do this the method <I>timer_name</I> + <I>Fire</I>
  is added to the state machine's interface and will be called whenever
  the timer fires. WARNING: you cannot then use <I>timer_name</I> +
  <I>Fire</I> event in a transition <I>ON</I> clause or the state machine
  won't compile. <P>

  For example:
  <PRE>
  SM Test
     TIMED_BY TestTimer PERIOD 10 ACTION TRUE
  </PRE>
IS_MODULE boolean 0, not a module A state machine can be made part of a module when the IS_MODULE keyword is set to 1. When set the state machines constructor will take a module pointer as an argument. Debug macros are passed the module pointer.
METHOD type, body NA User defined methods can be easily added into the state machine and used as timer value accessors or if tests. The method may be virtual or completely inline.

type is the return type of the method.
body body is method name and parameter list. It can be just the method name or a complete inlined method.

  <P> 
  For example:

  <PRE>
  SM Test
     METHOD int,  GetTimer(void) { return 5; } 
     METHOD virtual int,  AnotherTimer(void)
  </PRE>
DOC text NA Documentation for the state machine class. It is included in a comment block. text is a string of any length. The \ character can be used to break comments across lines.

For example:

  <PRE>
  SM Test
     DOC Some example documentation. \

This is broken on another line.

ON_DOC event text NA Documentation for an event. It is included in a comment block for the event. event is the name of an event in the state machine. text is a string of any length. The \ character can be used to break comments across lines.

For example:

  <PRE>
  SM Test
     ON_DOC Event1 Some example documentation. \

This is broken on another line. ON_DOC Event2 Some example documentation.
This is broken on another line.

DO_DOC action text NA Documentation for an action used an a DO clause. It is included in a comment block for the action. action is the name of the action used in a DO clause. text is a string of any length. The \ character can be used to break comments across lines.

For example:

  <PRE>
  SM Test
     DO_DOC Do1 Some example documentation. \

This is broken on another line. ON_DOC Do2 Some example documentation.
This is broken on another line.

DEBUG_LEVEL level Output::LEVEL_L2 The debug level to use when creating debug statements for the state machine. It can be any valid level. It defaults to the level specified in CFG.
  <P> 
  For example:

  <PRE>
  SM Test
     DEBUG_LEVEL Output::LEVEL_L2
  </PRE>
CASE_ERR code D(XMOD, XLVL, \"UNHANDLED: EVENT=event STATE=\" << CurrentState() <<\"\\n\");\n\t\trc= LN_FAIL;\n"; Code fragment to use in the default case of a state switch for an event. The string event in the fragment is replaced with the current event name. The string XMOD is replace with the name of the module. The string XLVL is replaced with the current debug level.

  <I>"</I> and <I>\n</I> must be quoted as this 
  line is outputted via a print statement.
DERIVE (ws) (dclasses) None. This directive specifies classes the state machine should derive from. It allows state machines to have base classes.

   For example:

  <PRE>
  SM Test
     DERIVE public BaseClass, AnotherClass
  </PRE>
GEN_AS_STRING (ws) (0|1) 0 Causes the method CurrentStateName to be generated. This method returns the current state as a string value. The value 0 means don't generate the method. This is the default. The value 1 means generate the method.
  <PRE>
  SM Test
     GEN_AS_STRING 1
  </PRE>
  <P>
GEN_EFORWARDER (ws) (0|1) 0 Causes the pure virtual method FwdEvent to be generated. It also causes GEN_INJECT_EVENT to be defined. The InjectEvent method instead of calling events directly, creates an Action object that when its Doit method is invoked, causes the correct state machine event method to be invoked. The Action object is passed to FwdEvent for forwading to a thread context that will invoke the Doit method in its thread context. The Doit method calls the correct event method and thus causes state changes in the state machine. State machine actions invoked because of the event will be executed in the thread context.
  <P>
  The value <I>0</I> means don't generate the method. This is the default. 
  The value <I>1</I> means generate the method.

  <P>
  <PRE>

  SM Test
     GEN_EFORWARDER 1
  </PRE>
  <P>
GEN_NOTIFY_CALL (ws) (0|1) 0 Causes the pure virtual method SmChangedEvent to be generated. This method is called by the state machine whenever the state machine changes state. Derived class are exected to implement this method and do whatever is appropriate when the state changes.
  <P>
  The value <I>0</I> means don't generate the method. This is the default. 
  The value <I>1</I> means generate the method.

  <P>
  <PRE>

  SM Test
     GEN_NOTIFY_CALL 1
  </PRE>
  <P>
GEN_INJECT_EVENT (ws) (0|1) 0 Causes the method InjectEvent to be generated. This method can be used to trigger an event using only the event name. Normally an event is triggered by calling the method corresponding to the event. InjectEvent will take the event name and invoke the correct event method. No arguments can be passed to events when this option is used.
  <P>
  The value <I>0</I> means don't generate the method. This is the default. 
  The value <I>1</I> means generate the method.

  <P>
  <PRE>

  SM Test
     GEN_INJECT_EVENT 1
  </PRE>
  <P>

See an example SM block.

State Definition (IN block)

Each state is defined by a state block which includes:

  • the name of the state
  • one line each for each state the current state can transition to
  • a blank line for separating state blocks

State Start Block

Attribute Meaning
IN state_name [, state_name]+ This is the block start for the state definition. state_name is the name of the state. An IN statement may have multiple state names which means the transitions will be executed if any of the specified states are the current state.

  After <I>block start</I> what follows is a number of lines defining the 
  states that can be transitioned to from this current state. Attributes
  describing a transition are defined in their own 
  <A HREF="#stateentries"> table </A>. <P>

  For example:
  <PRE>
     IN IDLE
  </PRE>
or
  <PRE>
     IN IDLE, BLACKOUT, WORLD_ENDS
  </PRE>

State Entries

Attribute Meaning
SM which_sm SM specifies which state machines a state is associated with. which_sm is a comma separated list of state machines names for which the state definition should be apart.

  If <B>no</B> <I>SM</I> attribute is specified then the state is associated
  with the current state machine. The current state machine is the
  state mechine the state definition is lexigraphically under. <P>

  If <B>an</B> <I>SM</I> attribute is specified then the state is associated
  with only the specified state machine names in <I>which_sm</I>. 
  The token <I>CURRENT</I> is a predefined value which means include this state 
  for the current state machine as well as any other state machines defined
  on <I>which_sm</I>. <P>

  More than one <I>SM</I> attribute may be defined per state. <P> 

  One common use for <I>SM</I> is the ability to define a common set
  of states that should be in a set of state machines. <P>

  The state machine name <I>GLOBAL</I> is a predefined special state machine
  name. When a state is associated with state machine <I>GLOBAL</I> it 
  makes all the state's transitions available for inclusion any other state 
  in any other state machine. See the <I>IN</I>attribute in this table for
  more information.

  For example:
  <PRE>
  SM DownloadSm

or SM TestSm, CURRENT or SM GLOBAL or do not specify one

IN which_in IN specifies the global state definitions to merge into this one. which_in is a comma separated list of state names in the global state machine that are to be merged into this one. See attribute SM in this table for more information.

  Globally available states allow a common set of transitions to be
  defined once and then reused many times in other states. If every state
  must respond to a power off event, for example, then the transitions for
  this event can be defined once and reused in all other states. 

  For example:
  <PRE>
  IN Common, MoreCommon
  </PRE>
NEXT state_name Specifies the next state to transition to when the specified event occurs. NEXT is defined in its own table .
TIMED_BY A timer may be defined that is not used explicitly by any transition. Code for the timer is generated. Such timers must be manually managed by the programmer using the start and stop timer methods. Please see TIMED_BY for more details on the the syntax.

State Transitions

Attribute Meaning
NEXT state_name Specifies the next state to transition to when the specified event occurs. The next state must exist in the state machine which means it must have its own definition.

  For example:
  <PRE>
     NEXT INFO
  </PRE>
FNEXT state_name FNEXT stands for forced next and is the same as NEXT except the state is ALWAYS set to state_name. FNEXT is used when an IF clause is present as an IF clause makes the state transition conditional. Sometimes we want the state to always change but have the action only be executed when the IF condition is true.

  For example:
  <PRE>
     FNEXT ADMIN_STATE_OFFLINE ON MoveOffline  IF IsGoOffline DO GoOffline
  </PRE>
ON event Specifies the event that must occur to cause the state transition. A method is created in the state machine class named directly after event. When the event occurs the event's method must be called to trigger the state transitions. The state machine can't know when events occur, code using the the state machine must interpret events.

  When timers are used an event is created named after the timer: 
  the <I>timer name</I> + <I>Fire</I>.
  You can make use of the timer events in the ON specification.<P>

  For example:
  <PRE>
  NEXT INFO     ON AcqSuccess

or NEXT INFO ON YourTimerNameFire

ONERR PREVSTATE | (state_name) | (invoke) ONERR specifies what should happen when an action called during a state transition fails. As there is no one prefered approach to handling action failures several options are provided. Remember, a state machine's state has been set to target state of a transition before any actions are called. So by default when an action returns an error the state transition exits immediately remaining in the current state.

  Your options for dealing with action failures are: <P> 

  If you want the state machine to revert back to the state before
  this current transition then use the keyword PREVSTATE. <P>

  If you want a function/method/macro called then specify a name
  with "()" in it, for example: dosomething(). It's up to you to
  make sure the call is resolved somehow. <P>

  If you want the state machine to enter another state then specify
  the state name. Of course the state specified must exist in the
  state machine. <P>


  For example:
  <PRE>
  NEXT INFO     ON AcqSuccess  ONERR PREVSTATE
or
  NEXT INFO     ON AcqSuccess  ONERR EndWorld()
or
  NEXT INFO     ON AcqSuccess  ONERR IDLE
  </PRE>
</TD>
IF test The IF clause places a conditional on the state transition. The test method is implemented by a class. test is called before state transition happens. If test returns 0 then the transition will not happen. Any non-zero value will cause the transition to occur.

  For example:
  <PRE>
  NEXT INFO     ON AcqSuccess IF IsStable

or NEXT INFO ON YourTimerNameFire IF IsStable

DO action The action to perform on the state transition. More than one DO clause can be specified by comma separating them. A pure virtual function is defined for each action in the state machine. Classes are expected to derive from the state machine class and implement the actions. See the generated header files for the actions to implement. Actions are called as events arrive and trigger state transitions.

   For example:
   <PRE>
   NEXT INFO    ON AcqSuccess   DO SendDnldInfoReq
or
   NEXT INFO    ON AcqSuccess   DO SendDnldInfoReq, \
                                DO NothingMuch
   </PRE>
</TD>
ON_ENTRY do ON_ENTRY allows actions to be performed every time a state is entered. Actions are specified using the DO clause. Actions are performed before transition actions are performed.
   For example:
   <PRE>
   ON_ENTRY     DO TIMER_STOP HealthTimer
 or
   ON_ENTRY     DO SendDnldInfoReq, \
                DO NothingMuch
   </PRE>
</TD>
ON_EXIT do ON_EXIT allows actions to be performed every time a state is exited. Actions are specified using the DO clause. Actions are performed before transition actions are performed.
   For example:
   <PRE>
   ON_EXIT     DO TIMER_STOP HealthTimer
 or
   ON_EXIT     DO SendDnldInfoReq, \
                DO NothingMuch
   </PRE>
</TD>
TIMED_BY timer_name PERIOD (dmsecs)[.(imsecs)] Creates a timer to time the action specified in the DO clause. Timers not created in a state transition context should ignore the DO clause explanations.

   A timer object is automatically created using the name 
   <I>timer_name</I> as the class name. <P>

   Timers fire at a specified period defined by the <I>PERIOD</I> attribute. 
   The period description needed depends on the type of timer. 
   The <I>PERIOD</I> clause must be defined for every <I>TIMED_BY</I> 
   clause.
   This allows using the same timer with different periods depending on the
   context.

   One shot timers need to specify the <I>dmsecs</I> parts of 
   <I>PERIOD</I>, which is the delay milli-seconds. The delay is 
   the delay before the timer first fires. <P>

   Cyclic timers need to specify dmsecs and <I>imsecs</I>.
   The interval between timer firings is specified 
   using <I>imsecs</I> which is the interval milli-seconds.
  
   Don't confuse delay and interval times. Delay is the time before the
   first time a timer fires. Interval is the period between firings after
   the first timer fires. <P>
   
   The same timer can be used by multiple <I>DO</I> clauses, but only 
   one can use it at a time. <P>

   Times in the <I>PERIOD</I> attribute can be specified using a 
   simple number like <I>10</I> or a method call like <I>GetSecs()</I>. 
   The method is expected to return the value of the parameter. You are 
   expected to provide an
   a method and implementation using the <I>ATTRIBUTE</I> and
   <I>METHOD</I> features. <P>

   When a timer fires it generates the event <I>timer</I> + <I>Fire</I>. So
   a timer named <I>Retry</I> would generate the event <I>RetryFire</I>
   when the timer expires. <P>

   Timers causes the following code to be generated:
   <UL>
   <LI> a timer class
   <LI> start and stop methods for each timer
   </UL><P>

   For example:
   <PRE>
   NEXT INFO  ON Success  DO SendInfoReq \
      TIMED_BY InfoReqTimer PERIOD 5
or
   NEXT INFO  ON Success  DO SendInfoReq \
      TIMED_BY InfoReqTimer PERIOD GetMsecs()
or
   SM LedSm
      START        IDLE
      TIMED_BY     FlashTimer PERIOD 0.10 
   </PRE>
 </TD>
DOC text NA Documentation for the state. It is included in a comment block for the state. text is a string of any length. The \ character can be used to break comments across lines.

For example:

  <PRE>
  IN Test
     DOC Some example documentation. \

This is broken on another line.

Implementing the State Machine

Generated Files

From a state machine specification a state machine header file and implementation file are generated. The header file is divided in to sections.

  1. Timer Classes
    A class is generated for each timer specified.
  2. State Machine Class
    Next is the state machine class. In the state machine class:
    1. State Enum
      An enum for every state in your state machine.
    2. Event Methods
      A method for every event driving your state machine. Events are generated from the ON clauses in your state machine.
    3. Action Methods
      A pure virtual function for every action in your state machine. Actions are generated from the DO clauses in your state machine. Events cause action methods to be called.
    4. Timer Methods
      A start and stop method is generated for every timer. This allows you to stop and start the timer in your state machine actions.

Derive a Class

State machines are driven by events. As events come into your system, from messages, interrupts, or some other source, you must invoke the corresponding event method in the state machine object. Events are coded to call the action methods you specified and change states appropriately.

The action methods are virtual. You must derive a class from the state machine class and implement the action methods. Its in the action methods that the behaviour of the state machine is coded.


Bugs

  • The error handling could be improved. A lot errors just don't generate good code.


Files

  • *.sm - state machine specification file

About

Note: this is just an old code dump, I have no idea if this state machine code generator still works. Both Perl and C++ are very different these days. This code was first created about 1999 and went through many iterations to about 2005. I still think it's probably a good approach to state machine generation.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published