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:
The classic languages with their libraries don't support events and states as a first goal.
The execution of state machines regarding the UML-specifications requires some special program flow organization. The automatic code generation from UML-tools produces that necessary code, but writing manually is not fine to do.
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.
.
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.
It should include all headers which are necessary for the code in all state routines.
The static variable definitions at begin are used as arguments for all state routines in the generated code. For the compilation of
the C++ source code of the state machine there are recognized as existing variables inside code fragments in all methods of
state sub classes because they are given global. That's sufficient for compilation check. For the real execution of the generated
code they are given as subroutine arguments in stack. For this example all state routines get the pointer ExampleSimpleData* to the environment class. The pointer is named thiz.
static void entry_Off_ExampleSimpleData(struct ExampleSimpleData_t* thiz, int event)
{ //genStateM: entry StateComposite or StateSimple.
The source of the state machine contains a definition of the class States { ..... };. This class contains a routine void variables() which's values are used for code generation. The name and meaning of the variables depends only on the code generation script's
necessities:
StateSubStruct: Name of the struct created by the state machine generator for the data of this state machine.
StateMethodSuffix: Suffix for all methods. A suffix is necessary for C language to prevent name clashes for short method names.
StateInstance: Expression to access the struct which contains the state machine data. In this example the state instance is accessed in a C environment with a pointer named
thiz (adequate the this-pointer in C++ for the onw data), inside the thiz-instance the state data are contained in a structure state of type State_ExampleSimpleData.
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 ....
The name of the state is the name of the class.
All comments are written in the Java documentation style. They are used for generating a document.
A variable statenr_... defines the number of that state. Because C++-syntax does not allow a simple initialization on the variable definition the
value is written as part of the name. You can write such as statenr_0x28 too.
The entry(), exit() and inState() methods are written with that content, which should be used in the same form in the generated code. Note that the generated
code contains the arguments which are given with as static variables in the state machine description code. It is necessary
to mark the last closing }//. because the parser does not analyse the exact syntax till end, it takes all till }//. simply.
All transitions are methods which starts with the name trans. Following identification characters are necessary to distinguish the methods in the C++ syntax for the check-compilation.
They are used as identification for the transitions for documentation too.
The transition condition is written as expression which is given as default value for the bool cond variable.
A timeout as condition is given by writing (bool time = value).
The text from start of body of the trans...()-method till except switchTo(... is used as action code for the condition.
The last statement inside the trans...()-method should be a switchTo( .... expression which describes the destination state; or more as one switchTo ... expressions for a fork transition.
A state can contain sub classes which presents sub states of a composite state.
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:
zbnfjax/zbnf/StateMCpp.zbnf: ZBNF-syntax for C++ source of a state machine.
zbnfjax:zbnf/StateMCpp.zbnf: ZBNF-syntax for C++ source of a state machine.
zbnfjax:zbnf/StateMJava.zbnf: ZBNF-syntax for Java source of a state machine for the same approach.
zbnfjax:zmake/States.genC1.jzgen: JZcmd generation script for C-code of a state machine.
zbnfjax:zmake/States.genH1.jzgen: JZcmd generation script for the headerfile.
zbnfjax:zmake/States.genDocu.jzgen: JZcmd generation script for a documentation source with topics.
zbnfjax:zmake/States.genHtml.jzgen: JZcmd generation script for a documentation as html file.
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:
examples_Zmake/StateMGen/src/ExampleSimple.state.cpp.html: source in cpp
examples_Zmake/StateMGen/result/exampleSimpleStates.h.html: 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.
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
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
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
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:
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
Topic:.StateMGen_en.gen.
Visit the example source and generated files:
examples_Zmake/StateMGen/src/ExampleSimple.state.cpp.html: source in cpp
examples_Zmake/StateMGen/result/exampleSimpleStates.h.html: 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.