State machine technology, Generator for C and C++

State machine technology, Generator for C and C++

Inhalt


Topic:.stateM_en.

Statemachines are well known both for hardware design (FPGA, hardware logic) and in software. Especially the UML defines statecharts as one of diagram kinds. Statecharts in UML support special features such as parallel states and nested states with defined rules of executing.

In cohesion with statemachines or statecharts in software the Event driven execution is used often. Events are data objects which contains information. Events can be stored in queues and be executed in another thread. In this kind it is a helpfull mechanism for thread interactions.

Both, statemachines respectively statecharts and events are present for software development if an UML-tool is used. For ordinary development of software in statement-oriented languages such as C, C++ or Java, statemachines and events are not used as medium of expressibility in the programming language often. Instead quests and set of boolean or enum-Variables are used to save the state of software. That is because:

Thinking in events and state machines is another approach than procedurale programming. It may be adequate to use that technologies of software. But if that technology is bounded to special tools for UML, it is not used ubiquitously.

This article describes a framework for programming in states and events for Java and C/C++ language for normal line sources without usage of UML-tools but UML-conform. Some aspects of software technology are discussed.


Topic:.stateM_en.stmC.

See article StateMGen

State machines are used in C or C++ often in a simple kind. Therefore they are programmed usually manually with switch - case, setting of the state variable immediately and without specific entry and exit actions. Using of an enum definition of the states is a good idea:

switch(stateVariable) {
  case EnumStateX: {
    if (condition) {
      action();
      stateVariable = EnumStateY;
    }
  } break;
  ....
} //switch

If the requirements for state processing are more complex, this schema is limited. How to write nested states? How to write parallel states? Unique programming of onEntry and onExit? Event handling?

It is the same problem like in Java: The written code should be short and pitty, but the necessities for execution need a verbose execution code. The solution in Java is (see $chapter): using reflection to generate the necesarry data, use it in prefabricated methods of the base (super) classes of the states. This principle does not work in C language. There are not reflection, inheritance (in C++) should not be used.

Therefore another approach is used: Code generation. Therefore a ZBNF parser is used to parse and a JZcmd is used to generate the C file. Both tools are script controlled. The user can change the scripts without study the complex programming of the generater, to adapt to its requirements to the execution code.

Source                               Internal     Prepared                          2. Source
State machine----> ZBNF parser ----> Java data ---Java data----> JZcmdExecuter ---> C-program
(any language)          ^            image                            ^             generated
==============          |                                             |             =========
                   ZBNF syntax                                   JZcmdScript
                   script                                             ^
                   ==========                                         |
                                                                      |
                                                 JZcmd   -------> JZcmd using
                                                 generation       ZBNF parser
                                                 script
                                                 ==========

The source language for the state machine's code can be any language. It is determined and change-able by the ZBNF syntax script. It may be recommended to use a C++ program which contains states as classes and all user-specific code for actions, conditions etc. The C++ state machine source can be compiled to check its syntax. It is not to execute.

The source is parsed and translated to a C-code which should be compiled and linked to execute in the target system. An internal preparation of the parsed state machine's data completes the missing information for state switches. It is the same algorithm which is used for Java state machines with identical behaviour. The form of the generated C-code is tunable by changing the JZcmd generation script.

Follow the pattern for C++ StateMachine Source:


Topic:.StateMGen_en.

.



1 Patten for C++ StateMachine Source, Generated code

Topic:.stateM_en.stmC.pattern.

The user can write and compile this C++ source of the state machine. But that C++ code is not used to run. The compilation is only done to check the syntax with the C++ compiler to detect writing mistakes while editing the source. The translation to the C-sources for the deployment of the state machine is done by a ZBNF parser and JZcmd generator.

The source code in C++ is written in this articel with a background color in yellow:

...The source code...

whereby the generated code is written with a light blue background:

...The generated code in C...

Look for the pattern or visit the example source file examples_Zmake/StateMGen/src/ExampleSimple.state.cpp.html:

#include "ExampleSimpleData.h"
#include <stdio.h>

//Name all arguments of a state trans method as static variable here.
//In generated code there will be given in the argument list.
static struct ExampleSimpleData_t* thiz;
static int event;

//Helper should be defined.
void switchTo(void* ...);

class States
{
 inline void variables() {
   char* StateSubStruct = "State_ExampleSimpleData";
   char* StateMethodSuffix = "_ExampleSimpleData";
   char* StateInstance = "thiz->state";
 }
 /**It is the off-state. This part contains a documentation of the state.
  */
 class Off
 { int statenr_1;
   .....
   .....contains nested states.
 };
 ..... contains all states of the top level.
};

That is the frame of a C++ source for the Statemachine.

static void entry_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event)
{ //genStateM: entry StateComposite or StateSimple.

That class States contains all states of the machine, one state as one inner class definition. Any state has the form:

 /**It is the off-state. This part contains a documentation of the state
  */
 class Off
 { int statenr_1;

   /**Comment for entry-action. Note: If nothing is specified on entry, you can remove the entry method too.*/
   void entry(){
     thiz->work = 0;
   }//.

   /**Comment for exit-action. Note: If nothing is specified on exit, you can remove the exit method too.*/
   void exit(){
   }//.

   /**Comment for this transition. */
   void trans(bool cond = thiz->on_ready){
     thiz->work = 1; switchTo(new Work::Ready());
   }

   /**Comment for the other transition. */
   void trans1(bool cond = thiz->on_cont){ switchTo(new Work::History); }

   /**The method which should be done in the state, if the statemachine is invoked cyclically. */
   void inState(){
     thiz->counter +=1;
   }//.

 };

This is a full example for one state of the statemachine in the chapter Parallel and nesting states ....


2 Generation of code

Topic:.stateM_en.stmC.gen.

The translation of such a state description C++ code to the C state execution code is done by a Java program:

java org.vishia.stateMGen.StateMGen -i:src/exampleSimple.state.cpp -s:../../zbnfjax/zbnf/StateMCpp.zbnf
-d:result/data_Statem.txt -scriptcheck:result/data_script.txt -c:../../zbnfjax/zmake/States.genDocu.jzgen
-y:result/exampleSimple.topic -c:../../zbnfjax/zmake/States.genC1.jzgen -y:result/exampleSimpleStates.c
-c:../../zbnfjax/zmake/States.genH1.jzgen -y:result/exampleSimpleStates.h --rlevel:333 --report:result/log.txt

That command with some arguments controls the generation. All input- and output files are given by the command line arguments. More simple is, using a Zmake script:

TODO

The Java program parses the given source code in C++, composes some coherences between the data and generates the output files. The parsing of the source is script-controlled by the ZBNF parser. The syntax can be changed, another input form (language) can be used instead C++ only with changing the syntax file. The composition of coherences is widely the same like the algorithm for java statemachines, see How does it work - missing data via reflection. The generation of the output is controlled by a JZcmd script. In this script some adaptions can be done to influence the generated code. That is explained in an extra document StateMGen because it is more for system engeneers (administrators of a development process) as for the user programmer which will use state machines. The given scripts with the -c: option in the command line above are the standard scripts used for this example:

The result of the translation are the named C- and Header file and a documentation. The output generated with the standard scripts have the following form. The example shows that code which was generated for the given C++ source code only for this one state of the example. The code is more complex if nested and parallel states are used. Visit the example source and generated files:

//This file was generated by StateMGen - States.genH1
#ifndef __exampleSimpleStates_h__
#define __exampleSimpleStates_h__
/**This struct contains all data which are necessary in the generated code for the state processing. */
typedef struct State_ExampleSimpleData_t
{

 /**Contains the state identifier for nested level with history or parallel states. */
 int statetop;
 ....
} State_ExampleSimpleData;

The state machine needs one or more as one state variable. The standard generation uses a simple integer variable for the state which is set with several values of a define-macro. This form is common used for state programming in C. A maybe better and faster variant is the usage of function pointers because the switch-case-statement is not necessary then. More as one state variable is necessary for parallel states or states with a history. Use this struct to embedd it into the users data.

int stepStates_State_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event);

That is the prototype for the step routine which can be invoked by the application either cyclically or if an event is present in a users queue. Examples for event handling are contained in the example.

#define kOff_State_ExampleSimpleData 1
...

That are the values for any state, which can be evaluated by the application too.

#endif  // __exampleSimpleStates_h__

That is the header file. It contains only public elements which can be used by an application.

The generated C-File:

/*This file is generated from StateMGen.java */

#include "ExampleSimpleData.h"
#include <stdio.h>
#include "exampleSimpleStates.h"

The genrated C-file includes all of the headers which are included in the C++ source.

//all entry-prototypes:
void entry_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event);
...
void exit_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event);
...

The prototypes in the generated C source are necessary because they should be defined before usage.

The entry- and exit method is defined as static (private visible) because they are used only in this source.

static void entry_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event)
{ //genStateM: entry StateComposite or StateSimple.
 thiz->state.statetop = kOff_State_ExampleSimpleData;
 thiz->work = 0;

 #ifdef __DEBUG_entryprintf_States_Fwc__
   printf(" entry Off;\n");
 #endif
}

The entry method contains the association of the state value to the state variable.

INLINE_Fwc void exit_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event)
{
 #ifdef __DEBUG_entryprintf_States_Fwc__
   printf("   exit Off;\n");
 #endif

}

The conditional printf statement is a nuance of the code generation with this script. It may be helpfull for debugging on runtime.

All transitions and the inState-routine in the C++ source are generated into one trans... method with a chain of if(...) in order of the transitions in the C++ source.

INLINE_Fwc int trans_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event)
{ int trans = 0;
 //genStateM: check all conditions of transitions, return on transition with != 0
 if(thiz->on_ready) {
   exit_Off_ExampleSimpleData(thiz, event);
   thiz->work = 1;
   entry_Work_ExampleSimpleData(thiz, event);
   entry_Ready_ExampleSimpleData(thiz, event);
   trans = mTransit_States_Fwc;
 }
 else
 if(thiz->on_cont) {
   exit_Off_ExampleSimpleData(thiz, event);

   entry_Work_ExampleSimpleData(thiz, event);
   trans = mTransit_States_Fwc;
 }
 else
 { //StateMGen: action in state. No transition switched.
   thiz->counter +=1;

 }
 return trans;
}

Last not least a complex stateswitch routine is generated, which contains the switch-case for the states:

int stepStates_State_ExampleSimpleData( struct ExampleSimpleData_t* thiz, int event )
{ int trans = 0;                //set to true if transition has fired.
 int ctSwitchState = 10;     //prevent too many transitions - a endless loop
 do {
   trans &= ~mTransit_States_Fwc;
   switch(thiz->state.statetop) {
              //if the state was entried newly without define an inner state, then the statedefault will be entered now.
              //Note that the default state cannot be entered on entry action because it is unknown in that time
              //whether there will be an entry to a designated state.
     case 0: entry_Off_ExampleSimpleData(thiz, event); //without break, do trans:
     //switch to the current state:
     case kOff_State_ExampleSimpleData: trans |= trans_Off_ExampleSimpleData(thiz, event); break;
     ...
     ...
 } while((trans & mTransit_States_Fwc)    //continue checking transitions if one of the has fired, it is run to completion.
     && thiz->state.statetop !=0  //don't continue if the composite is inactive now.
     && --ctSwitchState >0);     //don't execute too may loops, only a safety check.
 //
 //for all parallel states: switch only if this state is the active one still. Regard a leave of the state from any substate.
 return trans;

}

See article StateMGen for more explaination.


3 Parsing the C++ sources, alternative syntax, storing and evaluating the parse result

Topic:.StateMGen_en.WhyCpp.

Last changed: 2015-07-31

The generation of state machine code is conceived for programming state machines in C or C++ with a conventional Integrated Development Environment (IDE) such as Microsoft Visual Studio or Eclipse CDT. It should not presumed that a special tool for state machines is used.

The state machine's source code, not its execution code, should be written in C++. For the pattern see Chapter: 1 Patten for C++ StateMachine Source, Generated code. The C++ syntax allows compiling the code only for syntax test in the given IDE. With that code no execution machine code is gotten because the source code is incomplete for state machine execution. But the correctness of the source can be simple checked with the C++ compiler. That's important because the bodies for entry(), exit() and the transitions contains any C++ code which is not tested by the Zbnf parser.

There is an complex example in the ZBNF download which uses a Project for the IDE "Microsoft Visual Studio" Version 6.0. The project file may be able to run in a higher version of this tool too or may be converted to another IDE. See examples_Zmake-readme:StateMGen.

The syntax of the state machine's source code depends only from the ZBNF syntax script semantic, for C++ source it is

zbnfjax:zbnf/StateMCpp.zbnf.

For generation with the given script, the semantic of that syntax is essential. This semantic depends on the capability of the class docuSrcJava_Zbnf/org/vishia/stateMGen/StateMGen especially with its inner sub classes. The concept is docuSrcJava_Zbnf/org/vishia/zbnf/ZbnfJavaOutput which is used to store the parser's result. For each semantic identifier of the top level parse result

StateMachine::= ...?semantic>...

a proper public field or set_... or add_... -method to store the parsed information in the top level ZbnfJavaOutput storing class

docuSrcJava_Zbnf/org/vishia/stateMGen/StateMGen.ZbnfResultData

For example all found

#include <* \n?includeLine>

are stored with the method docuSrcJava_Zbnf/org/vishia/stateMGen/StateMGen.ZbnfResultData#set_includeLine(java.lang.String).

All information of components of the parse result are stored with the proper classes. For example

{ <state> }
...
state::=  ...semantic...

creates an instance to store the component state result with the method new_state() inside the super class of ZbnfResultData:

docuSrcJava_Zbnf/org/vishia/stateMGen/StateMGen.ZbnfStateCompositeBase#new_state()

That instance stores all information which have its semantic in the component state. After them the instance is stored and maybe evaluated with the method

docuSrcJava_Zbnf/org/vishia/stateMGen/StateMGen.ZbnfStateCompositeBase#add_state(org.vishia.stateMGen.StateMGen.ZbnfState)

That concept allows changing the syntax only with an alternativ zbnf syntax script. Such an alternative script which defines a more textual syntax is given with the

zbnfjax:zbnf/StateMtxt.zbnf

The semantic is the same. Therefore only the syntax zbnf script need to be changed for another input syntax.

All data from the parsed source of state machine are stored there after parsing. The stored data can be reported in an html file which shows all content of Java instances.

ZBNF/examples_Zmake/StateMGen/result.cmp/data_StatemSrc.html: The parsed data of the example source file examples_Zmake/StateMGen/src/ExampleSimple.state.cpp.html

After parsing that data are prepared:


4 Preparation of parsed data

Topic:.StateMGen_en.prep.

The preparation of the parsed data is necessary because the source code of the state does not contain all necessities to execute the state machine. Especially the correct order of exit- and entry from the current to the desitination state may be more complex and is not contained in the source. Only the code for the exit- and entry-actions itself are defined in the source.

The preparation completes the dependencies between the states with the given information by the parser. It works in the same way which is used for preparing state machines in Java. Therefore the docuSrcJavaPriv_Zbnf/org/vishia/stateMGen/StateMGen.GenStateMachine based on the docuSrcJava_vishiaBase/org/vishia/states/StateMachine. This class is used as super class of a users state machine in Java too.

The parsing process fills an instance of

docuSrcJavaPriv_Zbnf/org/vishia/stateMGen/StateMGen.ZbnfResultData. This class based on the docuSrcJavaPriv_Zbnf/org/vishia/stateMGen/StateMGen.ZbnfStateCompositeBase which presents the main composite state, which is the whole state machine. See the whole content which is filled after parsing:

ZBNF/examples_Zmake/StateMGen/result.cmp/data_StatemSrc.html.

With that data the top level instances of a Java statemachine is created as

docuSrcJavaPriv_Zbnf/org/vishia/stateMGen/StateMGen.GenStateMachine which has the docuSrcJava_vishiaBase/org/vishia/states/StateMachine as its super class.

 void prepareStateData(ZbnfResultData zbnfSrc){
   StateSimple[] aStates = new StateSimple[zbnfSrc.subStates.size()];
   //creates the instance for all prepared data:
   genStm = new GenStateMachine(zbnfSrc, aStates);
   StateComposite stateTop = genStm.stateTop();

Then all states of the parsed result are added to internal lists:

   genStm.rootStates.add(stateTop);
   stateTop.setAuxInfo(new GenStateInfo(null));
   //gather all states and transitions in the parsed data and add it to the prepared data:
   gatherStatesOfComposite(stateTop, stateTop, zbnfSrc);
   gatherAllTransitions();

Last not least the prepare() is invoked:

   //invoke prepare, the same as for Java state machines.
   genStm.prepare();
 }
 ....
   void prepare() {
     topState.prepare();
   }
 ...
   public void prepare() {
     buildStatePathSubstates(null,0);  //for all states recursively
     //the transitions are added already. Don't invoke createTransitionListSubstate(0);
     prepareTransitionsSubstate(0);
   }

The routines buildStatePathSubstates(...) and prepareTransitionsSubstate(0) are invoked for preparing of a Java statemachine too in the constructor of docuSrcJava_vishiaBase/org/vishia/states/StateMachine#StateMachine(java.lang.String, org.vishia.event.EventTimerThread) respectively StateMachine(String name)

As the result the data inside the docuSrcJavaPriv_Zbnf/org/vishia/stateMGen/StateMGen.GenStateMachine are completed correctly to generate the state machine's code. You can see the data content of the class StateMGen.GenStateMachine and all its aggregated classes in the file

ZBNF/examples_Zmake/StateMGen/result.cmp/data_StatemSrc.dst.html


5 Generation of the h- and c-files

Topic:.StateMGen_en.gen.

Visit the example source and generated files:

The jzcmd respectively jzgen script for the headerfile is simple:

To give more overview all parts which are generated texts are marked with :::: and all parts which are statements of JZcmd are not marked. The script starts with

==JZcmd==

Filepath outfile = &sOutfile; ##sOutfile is defined in the java calling environment: path to the out file which is written by <+>...<.+>

main(){
  <+>
:::://This file was generated by StateMGen - States.genH1
::::#ifndef __<&outfile.name()>_h__
::::#define __<&outfile.name()>_h__
::::/**This struct contains all data which are necessary in the generated code for the state processing. */

It writes some constant text in the header file, especially the guards for include. The variables sOutfile and stm are given as script variables from the calling environment of the generator:

 //Java code snippet:
 Writer out = new FileWriter(fOut);
 JZcmdExecuter generator = new JZcmdExecuter(console);
 generator.setScriptVariable("sOutfile", 'S', fOut.getAbsolutePath(), true);
 generator.setScriptVariable("stm", 'O', genStm, true);
 try{
   JZcmd.execute(generator, fileScript, out, console.currdir(), true, fScriptCheck, console);

The next lines in the States.genH1.jzgen generates the typedef struct with all state variables:

::::typedef struct <&stm.zsrcFile.variables.StateSubStruct>_t
::::{
  <.+>
  for(state:stm.rootStates) {
  <+>
::::  /**Contains the state identifier for nested level with history or parallel states. */
::::  int state<&state.stateId>;
::::  int timer<&state.stateId>;
   <.+>
  } //for

It produces in the headerfile:

/**This struct contains all data which are necessary in the generated code for the state processing. */
typedef struct State_ExampleSimpleData_t
{

  /**Contains the state identifier for nested level with history or parallel states. */
  int statetop;
  int timertop;

  /**Contains the state identifier for nested level with history or parallel states. */
  int stateWork;
  int timerWork;

  /**Contains the state identifier for nested level with history or parallel states. */
  int stateActive1;
  int timerActive1;

  /**Contains the state identifier for nested level with history or parallel states. */
  int stateActive2;
  int timerActive2;

} State_ExampleSimpleData;

Then one line defines the prototype for the stepStates... method:

::::int stepStates_<&stm.zsrcFile.variables.StateSubStruct>(<:subtext:stateMethodArguments>);

The arguments are generated by a subroutine which is included as subtext:

sub stateMethodArguments()
{
  for(arg:stm.zsrcFile.statefnargs) { <:><&arg><:hasNext>, <.hasNext><.>; }
}

The produced part of the headerfile in the example looks like:

int stepStates_State_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event);

The rest of the generation script is adequate, one can see the sources.

Generation script for the c-file:

It is some more complex. Therefore only some highlights are shown here.