Skip to content

FSMLang/FSMLang

Repository files navigation

FSMLang

Philosophy

FSMLang is designed to allow design work in the problem space of finite state machines without the encumbrances of any particular implementation language. Thus, FSMLang is implemented as a "pre-processor," generating code in any desired general programming language to implement the described finite state machine. FSMLang allows effort to be focused on the definition of events, states, and transitions; indeed, though the action to be taken in any particular event/state intersection is declarable (of course), the actual definition of that action is treated as a detail which falls outside the scope of FSMLang. Moreover, the mechanisms for collecting or determining events are also outside the language scope. FSMLang creates an object or objects in a target programming language which, when inserted into the larger program structure will invoke the correct actions and make the correct transitions for the events handed it.

(Though it is said, "any desired general programming language," implementation of FSMLang output in languages other than C is an exercise currently left to the reader.)

The created state machine contains a single state variable, which should not be manipulated by any user-written function. This variable is maintained on the heap, not on the machine's function call stack. This means that the machine must not be called recursively; neither from within any action function, nor from separate threads of execution. The keyword reentrant can be used to designate machines which are called from different execution threads. Macros will be inserted at the beginning and end of the state machine function which must be defined to properly protect the machine from re-entrance.

Top of page

Example: Simple State Machine

As an example, consider a simple communications protocol which specifies that an acknowledgement must be received for each message before another may be sent. The sender of messages in such a protocol must have at least two states: In the first state, which will be called IDLE, the sender has sent no message. In the second, which will be called AWAITING_ACK, the sender has sent a message and is awaiting an acknowledgement. The events which this automaton will see are SEND_MESSAGE, the request by a client entity for a message to be sent, and ACK, the receipt of the acknowledgment from the peer automaton.

The valid actions, then, are to send a message if one is requested, and the automaton is in the IDLE state,and to simply return to the IDLE state if an ACK is received while in the AWAITING_ACK state. Sending a message requires a transition to the AWAITING_ACK state. The receipt of an acknowledgement while in the IDLE state represents a protocol error which may be safely ignored. A request to send a message while in the AWAITING_ ACK state, however, must result in the message being queued for later transmission.

Using FSMLang, this machine can be described this way:

/**
   This machine manages communications using a "stop and wait" protocol.
      Only one message is allowed to be outstanding.
*/
machine simpleCommunicator
{

  state IDLE,
        AWAITING_ACK;

  event SEND_MESSAGE,
        ACK;

  /**
   Transmit message to the peer.
  */
  action sendMessage[SEND_MESSAGE,IDLE] transition AWAITING_ACK;

  /**
   Place message into queue.
  */
  action queueMessage[SEND_MESSAGE,AWAITING_ACK];

  /**
   Check queue for messages; if found pop from queue and return SEND_MESSAGE.
   If no message is found in the queue return noEvent.
  */
  action checkQueue[ACK,AWAITING_ACK] transition IDLE;

  /* these lines are informational; they affect the html output,
      but do not affect any code generated.
  */
  queueMessage returns noEvent;
  sendMessage  returns noEvent;
  checkQueue   returns SEND_MESSAGE, noEvent;

}

(When no transition is specified, the machine remains in the state it was in when the event occurred. And, a comma separated list of events or states enclosed within parentheses may be used in place of any single event or state designation; in which case, the action specified will be taken in the "cross product" of the two (event and/or state) vectors so described.)

This is the header file created

/**
	simpleCommunicator.h

	This file automatically generated by FSMLang
*/

#ifndef _SIMPLECOMMUNICATOR_H_
#define _SIMPLECOMMUNICATOR_H_

#ifdef SIMPLE_COMMUNICATOR_DEBUG
#include <stdio.h>
#include <stdlib.h>
#endif

#define FSM_VERSION "1.26"

#define RUN_STATE_MACHINE(A,B) \
	((*(A)->fsm)((A),(B)))

#ifndef INIT_FSM_DATA
#define INIT_FSM_DATA {0}
#endif
#define DECLARE_SIMPLE_COMMUNICATOR_MACHINE(A) \
SIMPLE_COMMUNICATOR (A) =\
{\
	simpleCommunicator_IDLE,\
	&simpleCommunicator_action_array,\
	simpleCommunicatorFSM\
};\
SIMPLE_COMMUNICATOR *p##A = &A;

/* Event naming convenience macros. */
#undef THIS
#define THIS(A) simpleCommunicator_##A
typedef enum {
	 simpleCommunicator_SEND_MESSAGE
	, simpleCommunicator_ACK
	, simpleCommunicator_noEvent
	, simpleCommunicator_numEvents
} SIMPLE_COMMUNICATOR_EVENT;

#ifdef SIMPLE_COMMUNICATOR_DEBUG
extern char *SIMPLE_COMMUNICATOR_EVENT_NAMES[];
#endif

typedef enum {
	 simpleCommunicator_IDLE
	, simpleCommunicator_AWAITING_ACK
	, simpleCommunicator_numStates
} SIMPLE_COMMUNICATOR_STATE;

#ifdef SIMPLE_COMMUNICATOR_DEBUG
extern char *SIMPLE_COMMUNICATOR_STATE_NAMES[];
#endif

typedef struct _simpleCommunicator_struct_ SIMPLE_COMMUNICATOR, *pSIMPLE_COMMUNICATOR;
extern SIMPLE_COMMUNICATOR simpleCommunicator;

extern pSIMPLE_COMMUNICATOR psimpleCommunicator;

typedef SIMPLE_COMMUNICATOR_EVENT (*SIMPLE_COMMUNICATOR_ACTION_FN)(pSIMPLE_COMMUNICATOR);

typedef void (*SIMPLE_COMMUNICATOR_FSM)(pSIMPLE_COMMUNICATOR,SIMPLE_COMMUNICATOR_EVENT);

void simpleCommunicatorFSM(pSIMPLE_COMMUNICATOR,SIMPLE_COMMUNICATOR_EVENT);

typedef struct _simpleCommunicator_action_trans_struct_ {
	SIMPLE_COMMUNICATOR_ACTION_FN	action;
	SIMPLE_COMMUNICATOR_STATE	transition;
} SIMPLE_COMMUNICATOR_ACTION_TRANS, *pSIMPLE_COMMUNICATOR_ACTION_TRANS;

extern const SIMPLE_COMMUNICATOR_ACTION_TRANS simpleCommunicator_action_array[simpleCommunicator_numEvents][simpleCommunicator_numStates];

struct _simpleCommunicator_struct_ {
	SIMPLE_COMMUNICATOR_STATE					state;
	SIMPLE_COMMUNICATOR_ACTION_TRANS const	(*actionArray)[simpleCommunicator_numEvents][simpleCommunicator_numStates];
	SIMPLE_COMMUNICATOR_FSM						fsm;
};

SIMPLE_COMMUNICATOR_EVENT simpleCommunicator_sendMessage(pSIMPLE_COMMUNICATOR);
SIMPLE_COMMUNICATOR_EVENT simpleCommunicator_queueMessage(pSIMPLE_COMMUNICATOR);
SIMPLE_COMMUNICATOR_EVENT simpleCommunicator_checkQueue(pSIMPLE_COMMUNICATOR);
SIMPLE_COMMUNICATOR_EVENT simpleCommunicator_noAction(pSIMPLE_COMMUNICATOR);



#endif

This is the source file created

/**
	simpleCommunicator.c

	This file automatically generated by FSMLang
*/

#include "simpleCommunicator.h"

const SIMPLE_COMMUNICATOR_ACTION_TRANS simpleCommunicator_action_array[simpleCommunicator_numEvents][simpleCommunicator_numStates] =
{
	{
		/* -- SEND_MESSAGE -- */

		/* -- IDLE -- */	{simpleCommunicator_sendMessage,simpleCommunicator_AWAITING_ACK}
		/* -- AWAITING_ACK -- */	, {simpleCommunicator_queueMessage,simpleCommunicator_AWAITING_ACK}
	},
	{
		/* -- ACK -- */

		/* -- IDLE -- */	{simpleCommunicator_noAction, simpleCommunicator_IDLE}
		/* -- AWAITING_ACK -- */	, {simpleCommunicator_checkQueue,simpleCommunicator_IDLE}
	},
};
SIMPLE_COMMUNICATOR simpleCommunicator = {
	simpleCommunicator_IDLE,
	&simpleCommunicator_action_array,
	simpleCommunicatorFSM
};

pSIMPLE_COMMUNICATOR psimpleCommunicator = &simpleCommunicator;

void simpleCommunicatorFSM(pSIMPLE_COMMUNICATOR pfsm, SIMPLE_COMMUNICATOR_EVENT event)
{
/* writeOriginalFSM */
	SIMPLE_COMMUNICATOR_EVENT new_e;

	SIMPLE_COMMUNICATOR_EVENT e = event;

	while (e != simpleCommunicator_noEvent) {

#ifdef SIMPLE_COMMUNICATOR_DEBUG
DBG_PRINTF("event: %s; state: %s"
,SIMPLE_COMMUNICATOR_EVENT_NAMES[e]
,SIMPLE_COMMUNICATOR_STATE_NAMES[pfsm->state]
);
#endif

		new_e = ((* (*pfsm->actionArray)[e][pfsm->state].action)(pfsm));

		pfsm->state = (*pfsm->actionArray)[e][pfsm->state].transition;

		e = new_e;

	} 

}

SIMPLE_COMMUNICATOR_EVENT __attribute__((weak)) simpleCommunicator_sendMessage(pSIMPLE_COMMUNICATOR pfsm)
{
	DBG_PRINTF("weak: simpleCommunicator_sendMessage");
	return THIS(noEvent);
}

SIMPLE_COMMUNICATOR_EVENT __attribute__((weak)) simpleCommunicator_queueMessage(pSIMPLE_COMMUNICATOR pfsm)
{
	DBG_PRINTF("weak: simpleCommunicator_queueMessage");
	return THIS(noEvent);
}

SIMPLE_COMMUNICATOR_EVENT __attribute__((weak)) simpleCommunicator_checkQueue(pSIMPLE_COMMUNICATOR pfsm)
{
	DBG_PRINTF("weak: simpleCommunicator_checkQueue");
	return THIS(noEvent);
}

SIMPLE_COMMUNICATOR_EVENT __attribute__((weak)) simpleCommunicator_noAction(pSIMPLE_COMMUNICATOR pfsm)
{
	DBG_PRINTF("weak: simpleCommunicator_noAction");
	return simpleCommunicator_noEvent;
}


#ifdef SIMPLE_COMMUNICATOR_DEBUG
char *SIMPLE_COMMUNICATOR_EVENT_NAMES[] = {
	 "simpleCommunicator_SEND_MESSAGE"
	, "simpleCommunicator_ACK"
	, "simpleCommunicator_noEvent"
	, "simpleCommunicator_numEvents"
};

char *SIMPLE_COMMUNICATOR_STATE_NAMES[] = {
	 "simpleCommunicator_IDLE"
	,"simpleCommunicator_AWAITING_ACK"
};

#endif

By default, action functions return events, and the main state machine function loops until an action indicates that there are no further events.

The action functions themselves are not generated; instead, they are written by the machine implementor. This allows FSMLang to remain focused on the state machine, rather than the details of the implementation of its purpose for existing.

Top of page

Hierarchical

A more complex example shows how FSMLang treats hierarchical state machines. We reuse the Simple Communicator from the first example, but add the requirement to establish a session with the peer before any message is sent. One way to do this is to have a top-level state machine with two sub state machines: One machine to establish the session; the other to send the messages, again with the requirement that only one message can be in transit at any time.

The fsm code describes the machines in this way. Note the similarity of the sendMessage sub machine to the simpleCommunicator, above.

/**
   
This machine manages communications using a "stop and wait" protocol.
      Only one message is allowed to be outstanding.
	 



	 
Before any message can be exchanged, however, a session must be established
      with the peer.  Establishing a connection requires several exchanges to
      authenticate.  The connection will remain active as long as messages
      continue to be exchanged with a minimum frequency.
   


*/
machine hsmCommunicator {

	state IDLE,
        ESTABLISHING_SESSION,
        IN_SESSION;

	event SEND_MESSAGE,
        SESSION_ESTABLISHED,
        SESSION_TIMEOUT;

	/**
		Establish a connection with the peer.
  */
	machine establishSession {

		state IDLE,
					AWAITING_RESPONSE;

		event ESTABLISH_SESSION_REQUEST,
 					STEP0_RESPONSE,
					STEP1_RESPONSE;

		/**
			Start the session establishment process.
		*/
		action sendStep0Message[ESTABLISH_SESSION_REQUEST, IDLE] transition AWAITING_RESPONSE;

		/**
			Continue session establishment
		*/
		action sendStep1Message[STEP0_RESPONSE, AWAITING_RESPONSE];

		/**
			Notify parent that session is established.
		*/
		action notifyParent[STEP1_RESPONSE, AWAITING_RESPONSE] transition IDLE;

	  /* these lines are informational; they affect the html output,
	      but do not affect any C code generated.
		*/
		sendStep0Message returns noEvent;
		sendStep1Message returns noEvent;
		notifyParent     returns parent::SESSION_ESTABLISHED;

	}

	machine sendMessage {

		state	IDLE,
 				IN_SESSION,
				AWAITING_ACK;

		event	SEND_MESSAGE,
				ACK,
				SESSION_ESTABLISHED,
				SESSION_TIMEOUT;

		/**
			Transmit message to the peer.
		*/
		action	sendMessage[SEND_MESSAGE,IN_SESSION] transition AWAITING_ACK;

		/**
			Place message into queue.
		*/
		action	queueMessage[SEND_MESSAGE,(IDLE, AWAITING_ACK)];

		/**
			Check queue for messages; if found pop from queue and return SEND_MESSAGE.
			If no message is found in the queue return noEvent.
		*/
		action	checkQueue[ACK,AWAITING_ACK]          transition IN_SESSION,
		         checkQueue[SESSION_ESTABLISHED, IDLE] transition IN_SESSION;


		transition [SESSION_TIMEOUT, (IN_SESSION, AWAITING_ACK)] IDLE;

		/* these lines are informational; they affect the html output,
			but do not affect any C code generated.
		*/
		queueMessage returns noEvent;
		sendMessage  returns noEvent;
		checkQueue   returns SEND_MESSAGE, noEvent;

	}

	/* these are actions of the top level machine */

	/**
		Start the session establishment process by activating the establishSession machine.
	*/
	action startSessionEstablishment[SEND_MESSAGE, IDLE] transition ESTABLISHING_SESSION;

	/**
		Start the session timer and notify the sendMessage machine that the session is
		 established.
	*/
	action completeSessionStart[SESSION_ESTABLISHED, ESTABLISHING_SESSION] transition IN_SESSION;

	/**
		Notify the sendMessage machine that the session has timed-out.
	*/
	action notifySessionTimeout[SESSION_TIMEOUT, (ESTABLISHING_SESSION, IN_SESSION)] transition IDLE;

	/**
		Extend the session timer and pass the message to be sent to the sendMessage machine.
	*/
	action requestMessageTransmission[SEND_MESSAGE, (ESTABLISHING_SESSION, IN_SESSION)];


	/* these lines are informational; they affect the html output,
	    but do not affect any C code generated.
	*/
	startSessionEstablishment   returns SEND_MESSAGE;
	completeSessionStart        returns sendMessage::SESSION_ESTABLISHED;
	notifySessionTimeout        returns sendMessage::SESSION_TIMEOUT;
	requestMessageTransmission  returns sendMessage::SEND_MESSAGE;

}

FSMLang produces one header/source file pair, or one html file for each machine it encounters. The top-level machine's event enumeration contains the events of all machines so that any event can be passed to the machine. Each sub-machine has its own enumeration, but these enumerations do not start at 0. Rather, they are arranged so that each noEvent event has the same numeric value as the corresponding event in the top-level enumeration. In this way, sub-machines are easily selected based on their numeric event range.

The FSM function of a sub-machine returns a top-level machine event, providing the mechanism by which the sub-machines communicate with the top-level machine. The return value of the sub-machine FSM function is the event returned by a sub-machine action function when that event is not a sub-machine event. When the sub-machine action returns the sub-machine's noEvent that event is translated into the top-level machine's noEvent.

FSMLang does not provide for sub-machines to indicate that their action functions return an event from another sub-machine. Rather, sub-machine actions can return events from their own machine, or from their parent. This is by design. The top-level machine should provide actions when necessary to bridge between the activation of different sub-machines.

However, it is possible for a sub-machine to designate any event as being "from the parent" (event parent::e1, for example). The name, of course, must be that of an event actually declared in the parent. Moreover, by also giving the name of a data translation function (e.g. event parent::e1 data translator dt1) When this is done, FSMLang generates code for the parent to call the submachine when that event occurs. If a data translator is given, it will be called before the sub-machine is invoked. More than one sub-machine can share a parent event; the machines will be called in order. The loop will be exited when a machine returns anything other than noEvent, so that the parent can then handle that event.

It is possible, that the parent would want to inhibit the operation of the sub-machines. state s1 inhibits submachines will do just that; no submachines will be run, unless shared events exist and the parent handles those events in that state.

The html file created for the top-level machine contains a list of sub-machines. The list is hyper-linked to the html file for the respective machine.

At the moment, only one level of sub-machine is supported. This limitation may be removed in later releases.

The -ts flag should always be used to generate code due the way the event enumerations are generated for sub-machines. This limitation, too, may be removed in later releases.

These are the header files created

/**
	hsmCommunicator.h

	This file automatically generated by FSMLang
*/

#ifndef _HSMCOMMUNICATOR_H_
#define _HSMCOMMUNICATOR_H_

#ifdef HSM_COMMUNICATOR_DEBUG
#include <stdio.h>
#include <stdlib.h>
#endif

#define FSM_VERSION "1.26"

#define RUN_STATE_MACHINE(A,B) \
	((*(A)->fsm)((A),(B)))

#ifndef INIT_FSM_DATA
#define INIT_FSM_DATA {0}
#endif
#define DECLARE_HSM_COMMUNICATOR_MACHINE(A) \
HSM_COMMUNICATOR (A) =\
{\
	hsmCommunicator_IDLE,\
	&hsmCommunicator_action_array,\
	hsmCommunicatorFSM\
};\
HSM_COMMUNICATOR *p##A = &A;

/* Event naming convenience macros. */
#undef THIS
#define THIS(A) hsmCommunicator_##A
#undef HSM_COMMUNICATOR
#define HSM_COMMUNICATOR(A) hsmCommunicator_##A
#undef ESTABLISH_SESSION
#define ESTABLISH_SESSION(A) hsmCommunicator_establishSession_##A
#undef SEND_MESSAGE
#define SEND_MESSAGE(A) hsmCommunicator_sendMessage_##A

typedef enum {
	 hsmCommunicator_SEND_MESSAGE
	, hsmCommunicator_SESSION_ESTABLISHED
	, hsmCommunicator_SESSION_TIMEOUT
	, hsmCommunicator_noEvent
	, hsmCommunicator_numEvents
	, hsmCommunicator_establishSession_ESTABLISH_SESSION_REQUEST
	, hsmCommunicator_establishSession_STEP0_RESPONSE
	, hsmCommunicator_establishSession_STEP1_RESPONSE
	, hsmCommunicator_establishSession_noEvent
	, hsmCommunicator_sendMessage_SEND_MESSAGE
	, hsmCommunicator_sendMessage_ACK
	, hsmCommunicator_sendMessage_SESSION_ESTABLISHED
	, hsmCommunicator_sendMessage_SESSION_TIMEOUT
	, hsmCommunicator_sendMessage_noEvent
} HSM_COMMUNICATOR_EVENT;

#ifdef HSM_COMMUNICATOR_DEBUG
extern char *HSM_COMMUNICATOR_EVENT_NAMES[];
#endif

typedef enum {
	 hsmCommunicator_IDLE
	, hsmCommunicator_ESTABLISHING_SESSION
	, hsmCommunicator_IN_SESSION
	, hsmCommunicator_numStates
} HSM_COMMUNICATOR_STATE;

#ifdef HSM_COMMUNICATOR_DEBUG
extern char *HSM_COMMUNICATOR_STATE_NAMES[];
#endif

typedef struct _hsmCommunicator_struct_ HSM_COMMUNICATOR, *pHSM_COMMUNICATOR;
extern HSM_COMMUNICATOR hsmCommunicator;

extern pHSM_COMMUNICATOR phsmCommunicator;

typedef HSM_COMMUNICATOR_EVENT (*HSM_COMMUNICATOR_ACTION_FN)(pHSM_COMMUNICATOR);

typedef void (*HSM_COMMUNICATOR_FSM)(pHSM_COMMUNICATOR,HSM_COMMUNICATOR_EVENT);

void hsmCommunicatorFSM(pHSM_COMMUNICATOR,HSM_COMMUNICATOR_EVENT);

/* Sub Machine Declarations */

/* enumerate sub-machines */
typedef enum {
	 hsmCommunicator_establishSession
	, hsmCommunicator_firstSubMachine = hsmCommunicator_establishSession
	,  hsmCommunicator_sendMessage
	, hsmCommunicator_numSubMachines
} HSM_COMMUNICATOR_SUB_MACHINES;

typedef HSM_COMMUNICATOR_EVENT (*HSM_COMMUNICATOR_SUB_MACHINE_FN)(HSM_COMMUNICATOR_EVENT);
typedef struct _hsmCommunicator_sub_fsm_if_ HSM_COMMUNICATOR_SUB_FSM_IF, *pHSM_COMMUNICATOR_SUB_FSM_IF;
struct _hsmCommunicator_sub_fsm_if_
{
	HSM_COMMUNICATOR_EVENT                first_event;
	HSM_COMMUNICATOR_EVENT                last_event;
	HSM_COMMUNICATOR_SUB_MACHINE_FN       subFSM;
};

extern pHSM_COMMUNICATOR_SUB_FSM_IF hsmCommunicator_sub_fsm_if_array[hsmCommunicator_numSubMachines];

extern HSM_COMMUNICATOR_SUB_FSM_IF establishSession_sub_fsm_if;
extern HSM_COMMUNICATOR_SUB_FSM_IF sendMessage_sub_fsm_if;

typedef struct _hsmCommunicator_action_trans_struct_ {
	HSM_COMMUNICATOR_ACTION_FN	action;
	HSM_COMMUNICATOR_STATE	transition;
} HSM_COMMUNICATOR_ACTION_TRANS, *pHSM_COMMUNICATOR_ACTION_TRANS;

extern const HSM_COMMUNICATOR_ACTION_TRANS hsmCommunicator_action_array[hsmCommunicator_numEvents][hsmCommunicator_numStates];

struct _hsmCommunicator_struct_ {
	HSM_COMMUNICATOR_STATE					state;
	HSM_COMMUNICATOR_ACTION_TRANS const	(*actionArray)[hsmCommunicator_numEvents][hsmCommunicator_numStates];
	pHSM_COMMUNICATOR_SUB_FSM_IF	(*subMachineArray)[hsmCommunicator_numSubMachines];
	HSM_COMMUNICATOR_FSM						fsm;
};

HSM_COMMUNICATOR_EVENT hsmCommunicator_startSessionEstablishment(pHSM_COMMUNICATOR);
HSM_COMMUNICATOR_EVENT hsmCommunicator_completeSessionStart(pHSM_COMMUNICATOR);
HSM_COMMUNICATOR_EVENT hsmCommunicator_notifySessionTimeout(pHSM_COMMUNICATOR);
HSM_COMMUNICATOR_EVENT hsmCommunicator_requestMessageTransmission(pHSM_COMMUNICATOR);
HSM_COMMUNICATOR_EVENT hsmCommunicator_noAction(pHSM_COMMUNICATOR);



#endif
/**
	establishSession.h

	This file automatically generated by FSMLang
*/

#ifndef _ESTABLISHSESSION_H_
#define _ESTABLISHSESSION_H_

#include "hsmCommunicator.h"

#ifdef ESTABLISH_SESSION_DEBUG
#include <stdio.h>
#include <stdlib.h>
#endif

#define DECLARE_ESTABLISH_SESSION_MACHINE(A) \
ESTABLISH_SESSION A =\
{\
	establishSession_IDLE,\
	&establishSession_action_array,\
	establishSessionFSM\
};\
ESTABLISH_SESSION *p##A = &(A);

/*
	sub-machine events are included in the top-level machine event enumeration.
	These macros set the appropriate names for events from THIS machine
	and those from the PARENT machine.

	They may be turned off as needed.
*/
#ifndef NO_EVENT_CONVENIENCE_MACROS
#undef THIS
#define THIS(A) hsmCommunicator_establishSession_##A
#define PARENT(A) hsmCommunicator_##A
#endif

#ifdef ESTABLISH_SESSION_DEBUG
extern char *ESTABLISH_SESSION_EVENT_NAMES[];
#endif

typedef enum {
	 establishSession_IDLE
	, establishSession_AWAITING_RESPONSE
	, establishSession_numStates
} ESTABLISH_SESSION_STATE;

#ifdef ESTABLISH_SESSION_DEBUG
extern char *ESTABLISH_SESSION_STATE_NAMES[];
#endif

typedef struct _establishSession_struct_ ESTABLISH_SESSION, *pESTABLISH_SESSION;
extern ESTABLISH_SESSION establishSession;

extern pESTABLISH_SESSION pestablishSession;

typedef HSM_COMMUNICATOR_EVENT (*ESTABLISH_SESSION_ACTION_FN)(pESTABLISH_SESSION);

typedef HSM_COMMUNICATOR_EVENT (*ESTABLISH_SESSION_FSM)(pESTABLISH_SESSION,HSM_COMMUNICATOR_EVENT);

HSM_COMMUNICATOR_EVENT establishSessionFSM(pESTABLISH_SESSION,HSM_COMMUNICATOR_EVENT);

typedef enum { establishSession_numEvents = 3} ESTABLISH_SESSION_EVENTS;
typedef struct _establishSession_action_trans_struct_ {
	ESTABLISH_SESSION_ACTION_FN	action;
	ESTABLISH_SESSION_STATE	transition;
} ESTABLISH_SESSION_ACTION_TRANS, *pESTABLISH_SESSION_ACTION_TRANS;

extern const ESTABLISH_SESSION_ACTION_TRANS establishSession_action_array[establishSession_numEvents][establishSession_numStates];

struct _establishSession_struct_ {
	ESTABLISH_SESSION_STATE					state;
	ESTABLISH_SESSION_ACTION_TRANS const	(*actionArray)[establishSession_numEvents][establishSession_numStates];
	ESTABLISH_SESSION_FSM						fsm;
};

HSM_COMMUNICATOR_EVENT establishSession_sendStep0Message(pESTABLISH_SESSION);
HSM_COMMUNICATOR_EVENT establishSession_sendStep1Message(pESTABLISH_SESSION);
HSM_COMMUNICATOR_EVENT establishSession_notifyParent(pESTABLISH_SESSION);
HSM_COMMUNICATOR_EVENT establishSession_noAction(pESTABLISH_SESSION);


#endif
/**
	sendMessage.h

	This file automatically generated by FSMLang
*/

#ifndef _SENDMESSAGE_H_
#define _SENDMESSAGE_H_

#include "hsmCommunicator.h"

#ifdef SEND_MESSAGE_DEBUG
#include <stdio.h>
#include <stdlib.h>
#endif

#define DECLARE_SEND_MESSAGE_MACHINE(A) \
SEND_MESSAGE A =\
{\
	sendMessage_IDLE,\
	&sendMessage_action_array,\
	sendMessageFSM\
};\
SEND_MESSAGE *p##A = &(A);

/*
	sub-machine events are included in the top-level machine event enumeration.
	These macros set the appropriate names for events from THIS machine
	and those from the PARENT machine.

	They may be turned off as needed.
*/
#ifndef NO_EVENT_CONVENIENCE_MACROS
#undef THIS
#define THIS(A) hsmCommunicator_sendMessage_##A
#define PARENT(A) hsmCommunicator_##A
#endif

#ifdef SEND_MESSAGE_DEBUG
extern char *SEND_MESSAGE_EVENT_NAMES[];
#endif

typedef enum {
	 sendMessage_IDLE
	, sendMessage_IN_SESSION
	, sendMessage_AWAITING_ACK
	, sendMessage_numStates
} SEND_MESSAGE_STATE;

#ifdef SEND_MESSAGE_DEBUG
extern char *SEND_MESSAGE_STATE_NAMES[];
#endif

typedef struct _sendMessage_struct_ SEND_MESSAGE, *pSEND_MESSAGE;
extern SEND_MESSAGE sendMessage;

extern pSEND_MESSAGE psendMessage;

typedef HSM_COMMUNICATOR_EVENT (*SEND_MESSAGE_ACTION_FN)(pSEND_MESSAGE);

typedef HSM_COMMUNICATOR_EVENT (*SEND_MESSAGE_FSM)(pSEND_MESSAGE,HSM_COMMUNICATOR_EVENT);

HSM_COMMUNICATOR_EVENT sendMessageFSM(pSEND_MESSAGE,HSM_COMMUNICATOR_EVENT);

typedef enum { sendMessage_numEvents = 4} SEND_MESSAGE_EVENTS;
typedef struct _sendMessage_action_trans_struct_ {
	SEND_MESSAGE_ACTION_FN	action;
	SEND_MESSAGE_STATE	transition;
} SEND_MESSAGE_ACTION_TRANS, *pSEND_MESSAGE_ACTION_TRANS;

extern const SEND_MESSAGE_ACTION_TRANS sendMessage_action_array[sendMessage_numEvents][sendMessage_numStates];

struct _sendMessage_struct_ {
	SEND_MESSAGE_STATE					state;
	SEND_MESSAGE_ACTION_TRANS const	(*actionArray)[sendMessage_numEvents][sendMessage_numStates];
	SEND_MESSAGE_FSM						fsm;
};

HSM_COMMUNICATOR_EVENT sendMessage_sendMessage(pSEND_MESSAGE);
HSM_COMMUNICATOR_EVENT sendMessage_queueMessage(pSEND_MESSAGE);
HSM_COMMUNICATOR_EVENT sendMessage_checkQueue(pSEND_MESSAGE);
HSM_COMMUNICATOR_EVENT sendMessage_noAction(pSEND_MESSAGE);


#endif

These are the source files created

/**
	hsmCommunicator.c

	This file automatically generated by FSMLang
*/

#include "hsmCommunicator.h"

const HSM_COMMUNICATOR_ACTION_TRANS hsmCommunicator_action_array[hsmCommunicator_numEvents][hsmCommunicator_numStates] =
{
	{
		/* -- SEND_MESSAGE -- */

		/* -- IDLE -- */	{hsmCommunicator_startSessionEstablishment,hsmCommunicator_ESTABLISHING_SESSION}
		/* -- ESTABLISHING_SESSION -- */	, {hsmCommunicator_requestMessageTransmission,hsmCommunicator_ESTABLISHING_SESSION}
		/* -- IN_SESSION -- */	, {hsmCommunicator_requestMessageTransmission,hsmCommunicator_IN_SESSION}
	},
	{
		/* -- SESSION_ESTABLISHED -- */

		/* -- IDLE -- */	{hsmCommunicator_noAction, hsmCommunicator_IDLE}
		/* -- ESTABLISHING_SESSION -- */	, {hsmCommunicator_completeSessionStart,hsmCommunicator_IN_SESSION}
		/* -- IN_SESSION -- */	, {hsmCommunicator_noAction, hsmCommunicator_IN_SESSION}
	},
	{
		/* -- SESSION_TIMEOUT -- */

		/* -- IDLE -- */	{hsmCommunicator_noAction, hsmCommunicator_IDLE}
		/* -- ESTABLISHING_SESSION -- */	, {hsmCommunicator_notifySessionTimeout,hsmCommunicator_IDLE}
		/* -- IN_SESSION -- */	, {hsmCommunicator_notifySessionTimeout,hsmCommunicator_IDLE}
	},
};

pHSM_COMMUNICATOR_SUB_FSM_IF hsmCommunicator_sub_fsm_if_array[hsmCommunicator_numSubMachines] =
{
	&establishSession_sub_fsm_if
	, &sendMessage_sub_fsm_if
};

HSM_COMMUNICATOR hsmCommunicator = {
	hsmCommunicator_IDLE,
	&hsmCommunicator_action_array,
	&hsmCommunicator_sub_fsm_if_array,
	hsmCommunicatorFSM
};

pHSM_COMMUNICATOR phsmCommunicator = &hsmCommunicator;

static HSM_COMMUNICATOR_EVENT findAndRunSubMachine(pHSM_COMMUNICATOR, HSM_COMMUNICATOR_EVENT);

void hsmCommunicatorFSM(pHSM_COMMUNICATOR pfsm, HSM_COMMUNICATOR_EVENT event)
{
/* writeOriginalFSM */
	HSM_COMMUNICATOR_EVENT new_e;

	HSM_COMMUNICATOR_EVENT e = event;

	while (e != hsmCommunicator_noEvent) {

#ifdef HSM_COMMUNICATOR_DEBUG
DBG_PRINTF("event: %s; state: %s"
,HSM_COMMUNICATOR_EVENT_NAMES[e]
,HSM_COMMUNICATOR_STATE_NAMES[pfsm->state]
);
#endif

		if (e < hsmCommunicator_noEvent)
		{

			new_e = ((* (*pfsm->actionArray)[e][pfsm->state].action)(pfsm));

			pfsm->state = (*pfsm->actionArray)[e][pfsm->state].transition;

			e = new_e;

		} 
		else
		{
			e = findAndRunSubMachine(pfsm, e);
		}

	}

}


static HSM_COMMUNICATOR_EVENT findAndRunSubMachine(pHSM_COMMUNICATOR pfsm, HSM_COMMUNICATOR_EVENT e)
{
	for (HSM_COMMUNICATOR_SUB_MACHINES machineIterator = hsmCommunicator_firstSubMachine;
	     machineIterator < hsmCommunicator_numSubMachines;
	     machineIterator++
	    )
	{
			if (
			   ((*pfsm->subMachineArray)[machineIterator]->first_event <= e)
			   && ((*pfsm->subMachineArray)[machineIterator]->last_event >= e)
			    )
			{
				return ((*(*pfsm->subMachineArray)[machineIterator]->subFSM)(e));
			}
	}

	return hsmCommunicator_noEvent;

}

HSM_COMMUNICATOR_EVENT __attribute__((weak)) hsmCommunicator_startSessionEstablishment(pHSM_COMMUNICATOR pfsm)
{
	DBG_PRINTF("weak: hsmCommunicator_startSessionEstablishment");
	return THIS(noEvent);
}

HSM_COMMUNICATOR_EVENT __attribute__((weak)) hsmCommunicator_completeSessionStart(pHSM_COMMUNICATOR pfsm)
{
	DBG_PRINTF("weak: hsmCommunicator_completeSessionStart");
	return THIS(noEvent);
}

HSM_COMMUNICATOR_EVENT __attribute__((weak)) hsmCommunicator_notifySessionTimeout(pHSM_COMMUNICATOR pfsm)
{
	DBG_PRINTF("weak: hsmCommunicator_notifySessionTimeout");
	return THIS(noEvent);
}

HSM_COMMUNICATOR_EVENT __attribute__((weak)) hsmCommunicator_requestMessageTransmission(pHSM_COMMUNICATOR pfsm)
{
	DBG_PRINTF("weak: hsmCommunicator_requestMessageTransmission");
	return THIS(noEvent);
}

HSM_COMMUNICATOR_EVENT __attribute__((weak)) hsmCommunicator_noAction(pHSM_COMMUNICATOR pfsm)
{
	DBG_PRINTF("weak: hsmCommunicator_noAction");
	return hsmCommunicator_noEvent;
}


#ifdef HSM_COMMUNICATOR_DEBUG
char *HSM_COMMUNICATOR_EVENT_NAMES[] = {
	 "hsmCommunicator_SEND_MESSAGE"
	, "hsmCommunicator_SESSION_ESTABLISHED"
	, "hsmCommunicator_SESSION_TIMEOUT"
	, "hsmCommunicator_noEvent"
	, "hsmCommunicator_numEvents"
	, "hsmCommunicator_establishSession_ESTABLISH_SESSION_REQUEST"
	, "hsmCommunicator_establishSession_STEP0_RESPONSE"
	, "hsmCommunicator_establishSession_STEP1_RESPONSE"
	, "hsmCommunicator_establishSession_noEvent"
	, "hsmCommunicator_sendMessage_SEND_MESSAGE"
	, "hsmCommunicator_sendMessage_ACK"
	, "hsmCommunicator_sendMessage_SESSION_ESTABLISHED"
	, "hsmCommunicator_sendMessage_SESSION_TIMEOUT"
	, "hsmCommunicator_sendMessage_noEvent"
};

char *HSM_COMMUNICATOR_STATE_NAMES[] = {
	 "hsmCommunicator_IDLE"
	,"hsmCommunicator_ESTABLISHING_SESSION"
	,"hsmCommunicator_IN_SESSION"
};

#endif
/**
	establishSession.c

	This file automatically generated by FSMLang
*/

#include "establishSession.h"


const ESTABLISH_SESSION_ACTION_TRANS establishSession_action_array[establishSession_numEvents][establishSession_numStates] =
{
	{
		/* -- ESTABLISH_SESSION_REQUEST -- */

		/* -- IDLE -- */	{establishSession_sendStep0Message,establishSession_AWAITING_RESPONSE}
		/* -- AWAITING_RESPONSE -- */	, {establishSession_noAction, establishSession_AWAITING_RESPONSE}
	},
	{
		/* -- STEP0_RESPONSE -- */

		/* -- IDLE -- */	{establishSession_noAction, establishSession_IDLE}
		/* -- AWAITING_RESPONSE -- */	, {establishSession_sendStep1Message,establishSession_AWAITING_RESPONSE}
	},
	{
		/* -- STEP1_RESPONSE -- */

		/* -- IDLE -- */	{establishSession_noAction, establishSession_IDLE}
		/* -- AWAITING_RESPONSE -- */	, {establishSession_notifyParent,establishSession_IDLE}
	},
};
HSM_COMMUNICATOR_EVENT establishSession_sub_machine_fn(HSM_COMMUNICATOR_EVENT e)
{
	return establishSessionFSM(pestablishSession, e);
}


HSM_COMMUNICATOR_SUB_FSM_IF establishSession_sub_fsm_if =
{
		.subFSM = establishSession_sub_machine_fn
	, .first_event = hsmCommunicator_establishSession_ESTABLISH_SESSION_REQUEST
	, .last_event = hsmCommunicator_establishSession_STEP1_RESPONSE
};

ESTABLISH_SESSION establishSession = {
	establishSession_IDLE,
	&establishSession_action_array,
	establishSessionFSM
};

pESTABLISH_SESSION pestablishSession = &establishSession;

HSM_COMMUNICATOR_EVENT establishSessionFSM(pESTABLISH_SESSION pfsm, HSM_COMMUNICATOR_EVENT event)
{
/* writeOriginalSubFSM */
	HSM_COMMUNICATOR_EVENT new_e;

	HSM_COMMUNICATOR_EVENT e = event;

	while (
		(e != THIS(noEvent))
		&& (e >= THIS(ESTABLISH_SESSION_REQUEST))
	)
	{

#ifdef ESTABLISH_SESSION_DEBUG
DBG_PRINTF("event: %s; state: %s"
,ESTABLISH_SESSION_EVENT_NAMES[e - THIS(ESTABLISH_SESSION_REQUEST)]
,ESTABLISH_SESSION_STATE_NAMES[pfsm->state]
);
#endif

		new_e = ((* (*pfsm->actionArray)[e - THIS(ESTABLISH_SESSION_REQUEST)][pfsm->state].action)(pfsm));

		pfsm->state = (*pfsm->actionArray)[e - THIS(ESTABLISH_SESSION_REQUEST)][pfsm->state].transition;

		e = new_e;

	} 
	return e == THIS(noEvent) ? PARENT(noEvent) : e;

}

HSM_COMMUNICATOR_EVENT __attribute__((weak)) establishSession_sendStep0Message(pESTABLISH_SESSION pfsm)
{
	DBG_PRINTF("weak: establishSession_sendStep0Message");
	return THIS(noEvent);
}

HSM_COMMUNICATOR_EVENT __attribute__((weak)) establishSession_sendStep1Message(pESTABLISH_SESSION pfsm)
{
	DBG_PRINTF("weak: establishSession_sendStep1Message");
	return THIS(noEvent);
}

HSM_COMMUNICATOR_EVENT __attribute__((weak)) establishSession_notifyParent(pESTABLISH_SESSION pfsm)
{
	DBG_PRINTF("weak: establishSession_notifyParent");
	return THIS(noEvent);
}


#ifdef ESTABLISH_SESSION_DEBUG
char *ESTABLISH_SESSION_EVENT_NAMES[] = {
	 "establishSession_ESTABLISH_SESSION_REQUEST"
	, "establishSession_STEP0_RESPONSE"
	, "establishSession_STEP1_RESPONSE"
	, "establishSession_noEvent"
	, "establishSession_numEvents"
};

char *ESTABLISH_SESSION_STATE_NAMES[] = {
	 "establishSession_IDLE"
	,"establishSession_AWAITING_RESPONSE"
};

#endif
/**
	sendMessage.c

	This file automatically generated by FSMLang
*/

#include "sendMessage.h"


const SEND_MESSAGE_ACTION_TRANS sendMessage_action_array[sendMessage_numEvents][sendMessage_numStates] =
{
	{
		/* -- SEND_MESSAGE -- */

		/* -- IDLE -- */	{sendMessage_queueMessage,sendMessage_IDLE}
		/* -- IN_SESSION -- */	, {sendMessage_sendMessage,sendMessage_AWAITING_ACK}
		/* -- AWAITING_ACK -- */	, {sendMessage_queueMessage,sendMessage_AWAITING_ACK}
	},
	{
		/* -- ACK -- */

		/* -- IDLE -- */	{sendMessage_noAction, sendMessage_IDLE}
		/* -- IN_SESSION -- */	, {sendMessage_noAction, sendMessage_IN_SESSION}
		/* -- AWAITING_ACK -- */	, {sendMessage_checkQueue,sendMessage_IN_SESSION}
	},
	{
		/* -- SESSION_ESTABLISHED -- */

		/* -- IDLE -- */	{sendMessage_checkQueue,sendMessage_IN_SESSION}
		/* -- IN_SESSION -- */	, {sendMessage_noAction, sendMessage_IN_SESSION}
		/* -- AWAITING_ACK -- */	, {sendMessage_noAction, sendMessage_AWAITING_ACK}
	},
	{
		/* -- SESSION_TIMEOUT -- */

		/* -- IDLE -- */	{sendMessage_noAction, sendMessage_IDLE}
		/* -- IN_SESSION -- */	, {sendMessage_noAction,sendMessage_IDLE}
		/* -- AWAITING_ACK -- */	, {sendMessage_noAction,sendMessage_IDLE}
	},
};
HSM_COMMUNICATOR_EVENT sendMessage_sub_machine_fn(HSM_COMMUNICATOR_EVENT e)
{
	return sendMessageFSM(psendMessage, e);
}


HSM_COMMUNICATOR_SUB_FSM_IF sendMessage_sub_fsm_if =
{
		.subFSM = sendMessage_sub_machine_fn
	, .first_event = hsmCommunicator_sendMessage_SEND_MESSAGE
	, .last_event = hsmCommunicator_sendMessage_SESSION_TIMEOUT
};

SEND_MESSAGE sendMessage = {
	sendMessage_IDLE,
	&sendMessage_action_array,
	sendMessageFSM
};

pSEND_MESSAGE psendMessage = &sendMessage;

HSM_COMMUNICATOR_EVENT sendMessageFSM(pSEND_MESSAGE pfsm, HSM_COMMUNICATOR_EVENT event)
{
/* writeOriginalSubFSM */
	HSM_COMMUNICATOR_EVENT new_e;

	HSM_COMMUNICATOR_EVENT e = event;

	while (
		(e != THIS(noEvent))
		&& (e >= THIS(SEND_MESSAGE))
	)
	{

#ifdef SEND_MESSAGE_DEBUG
DBG_PRINTF("event: %s; state: %s"
,SEND_MESSAGE_EVENT_NAMES[e - THIS(SEND_MESSAGE)]
,SEND_MESSAGE_STATE_NAMES[pfsm->state]
);
#endif

		new_e = ((* (*pfsm->actionArray)[e - THIS(SEND_MESSAGE)][pfsm->state].action)(pfsm));

		pfsm->state = (*pfsm->actionArray)[e - THIS(SEND_MESSAGE)][pfsm->state].transition;

		e = new_e;

	} 
	return e == THIS(noEvent) ? PARENT(noEvent) : e;

}

HSM_COMMUNICATOR_EVENT __attribute__((weak)) sendMessage_sendMessage(pSEND_MESSAGE pfsm)
{
	DBG_PRINTF("weak: sendMessage_sendMessage");
	return THIS(noEvent);
}

HSM_COMMUNICATOR_EVENT __attribute__((weak)) sendMessage_queueMessage(pSEND_MESSAGE pfsm)
{
	DBG_PRINTF("weak: sendMessage_queueMessage");
	return THIS(noEvent);
}

HSM_COMMUNICATOR_EVENT __attribute__((weak)) sendMessage_checkQueue(pSEND_MESSAGE pfsm)
{
	DBG_PRINTF("weak: sendMessage_checkQueue");
	return THIS(noEvent);
}


#ifdef SEND_MESSAGE_DEBUG
char *SEND_MESSAGE_EVENT_NAMES[] = {
	 "sendMessage_SEND_MESSAGE"
	, "sendMessage_ACK"
	, "sendMessage_SESSION_ESTABLISHED"
	, "sendMessage_SESSION_TIMEOUT"
	, "sendMessage_noEvent"
	, "sendMessage_numEvents"
};

char *SEND_MESSAGE_STATE_NAMES[] = {
	 "sendMessage_IDLE"
	,"sendMessage_IN_SESSION"
	,"sendMessage_AWAITING_ACK"
};

#endif

Note that the -tp option was used to create PlantUML output, which was then processed by PlantUML to produce an SVG image. The html was created using the --include-svg-img=true option to include the image in the output.

An unrealized goal of the FSMLang effort is to optimize the output machine for speed and size based on an analysis of the event-state matrix. There are command-line switches which force the creation of a compact table, or the use of switch statements instead of a table, but these are manual. One should be able to make those decisions based on the density of the event-state matrix. It may also be possible, using matrix transformations to make some part of the matrix very dense, then use a hybrid table/switch approach in the code.

Top of page

State Entry and Exit Functions

Entry and exit functions may be added to states. For example,

state some_state on entry prepare_some_state;

adds a named entry function to some_state.

Similarly,

state some_state on exit tidy_up_some_state;

adds a named exit function to some_state.

When entry or exit functions exist, code will be generated to call them appropriately. Names given in the .fsm file are prepended with the name of the machine. "Anonymous" entry or exit functions can be declared simply by ommiting the names. In this case names will be generated using the pattern <machine_name>onEntryTo<state_name> for entry functions, and similarly, using onExitFrom for exit functions. When weak function generation is not disabled, weak versions of these anonymous functions are created.

Because they are powerful, entry and exit functions can be mis-used. As the names chosen here suggest, they should be used only to prepare a state or to leave it in a tidy condition. They should not be used as a substitue for the creation of sub-machines, or well-thought out action sequences.

Top of page

Event Data

Events may be declared to have associated data. For example:

event data_packet_arrived data {
   unsigned length;
   uint8_t  *packet;
   };

When any event is declared with data, FSMLang shifts the event declaration from a simple enumeration to a structure containing the event enumeration and the union of the event data structures. This structure becomes the method for passing events in from the outside world into the state machine.

Continuing the example, event data_packet_arrived will cause the declaration of this structure for the event's data:

typedef struct _<machine_name>_data_packet_arrived_ {
   unsigned length;
   uint8t_t *packet;
} <MACHINE_NAME>_DATA_PACKET_ARRIVED_DATA, *p<MACHINE_NAME>_DATA_PACKET_ARRIVED_DATA;

The event data union is declared:

typedef union {
   <MACHINE_NAME>_DATA_PACKET_ARRIVED_DATA data_packet_arrived_data;
   .
   .
   .
} <MACHINE_NAME>_EVENT_DATA, *p<MACHINE_NAME>_EVENT_DATA;

Finally, the machine's event enumeration will be named <MACHINE_NAME>_EVENT_ENUM, and a structure, containing the event enumeration and the event data union will be created:

typedef struct {
   <MACHINE_NAME>_EVENT_ENUM event;
   <MACHINE_NAME>_EVENT_DATA event_data;
} <MACHINE_NAME>_EVENT, *p<MACHINE_NAME>_EVENT;

As indicated above, this structure is used to communicated external events to the state machine. It is not used to communicate events internally. Internally, events are communicated only through the event enumeration, as is done by machines not having events with data. Thus, action functions are declared as returning <MACHINE_NAME>_EVENT_ENUM, rather than <MACHINE_NAME>_EVENT. Because of this, the data communicated by the (external) event must be moved into the machine's data structure, in order to be visible to the action functions.

The movement of data is done through a invocation of a data translator. The translater can be named in the machine description:

event data_packet_arrived data translator copy_data_packet {
   unsigned length;
   uint8t_t *packet;
};

Otherwise, a translator function will be declared as <machine_name>translate<event_name>_data.

In either case, the function arguments are first, a pointer to the machine structure; and second, a pointer to the event data.

Top of page

Using the State Machine

To use the state machine in C code, include the generated header file, and use the provided RUN_STATE_MACHINE macro to call the state machine function with the desired event. Unless prohibited on the FSMLang command line, the generated header file provides a pointer to an instance of the machine, p. This is the first argument to the macro. The second is the event name as found in the machine's event enumeration.

Using the Simple Communicator as an example, this line runs the machine with the ACK event:

    RUN_STATE_MACHINE(psimpleCommunicator,simpleCommunicator_ACK);

This would be placed in the function which receives the message. Since such functions are often IRSs, it must be remembered that the machine is not reentrant. Invocations of the machine outside of this ISR context must be properly protected from this interrupt. The needed protection can be facilitated through the use of the reentrant keyword to modify the machine declaration. Or, it may be done by simply wraping the invocation with interrupt protection.

With the 1.40 release, a function is provided which can be used to invoke the singleton state machine normally included in the generated code. The function is declared as

    run_<machine_name>(<p><MACHINE_NAME>_EVENT);

For the simpleCommunicator, this would be:

    run_simpleCommunicator(SIMPLE_COMMUNICATOR_EVENT);

Top of page

Commenting

As shown in the examples above, "document comments" can be added to the FSMLang file. In addition to illuminating the FSMLang file itself, these comments are used in the HTML and PLANTUML output. Release 1.40 brings a slight modification to how document comments are processed.

The most visible change is that document comments appearing before action or transition matrices now adhere to the matrix. Formerly, such comments would adhere either to the action, or to the first event in the matrix, in the case of a transition only matrix. (This last circumstance was not really desireable, of course.) By adhering to the matrix, the comment addresses the involved event and state vectors (along with any associated transition), rather than the action. Previously, there was no way to make such a comment.

With this change, the comments are now used differently in the HTML output, and used for the first time in the plantuml output.

In the HTML, the comments are placed in the relevant cell of the event/state table. If the matrix is a cross-product of non-trivial event and state vectors, each cell in that product will contain the comment. If this is not desired, the vectors can be broken up into trivial vectors (i.e. individual event, state pairs), each being given the appropriate comment.

In the plantuml, these comments are used as notes on link, appearing next to the line linking two states by the transition.

HTML formatting can be controlled by providing an alternate style sheet (--css-content-filename= ). To provide independence to the HTML output, the stylesheet can be included directly into the HTML document (--css-content-internal=true).

The generated plantuml can be altered in several ways: The machine name can be set as the diagram title ( --add-plantuml-title=true). Strings may be given which will be placed after the opening @plantuml, but before any FSMLang generated content (--add-plantuml-prefix-string=). And, whole files can be named which will be similarly placed (--add-plantuml-prefix-file=). The last two options can be used any number of times; the text or files will be added in the order given; but all strings will be added before any files. Finally, --add-plantuml-legend=true will add a legend, which, by default, will contain the names and associated document commnents for all events, states, and, actions of the machine. Any of these three may be excluded by --exclude-[events|states|actions]-from-plantuml-legend=true.

Top of page

Making the FSMLang Executable

The source is in a Git repository at https://github.com/FSMLang/FSMLang.

Four targets are provided to support Linux (RH 6.5 has been tested, but may now need some maintenance), Cygwin (on Windows 10), and, MinGW and MinGWsa (also on Windows 10). MinGW uses the MinGW compiler, but expects to be built in a Cygwin shell, while MinGWsa works for a stand-alone MinGW installation (but, use the bash shell to do the build). To make the executable for any of these, simply type make , spelling the name of the target as given in the previous sentence. Appending ".test" to any target name will execute the full test suite. Appending ".clean" will do as expected.

simpleCommunicator.fsm is at the top of the tree; it is as shown in this page.

There are irrelevant files at the top of the tree. Ignore them. (Identifying these is an exercise also left to the reader.)

Top of page

Command Syntax

Usage : fsm [-tc|s|h|p] [-o outfile] [-s] filename, where filename ends with '.fsm'
	 and where 'c' gets you c code output based on an event/state table,
	 's' gets you c code output with individual state functions using switch constructions,
	 and 'h' gets you html output
	 and 'p' gets you PlantUML output
	-i0 inhibits the creation of a machine instance
		any other argument to 'i' allows the creation of an instance;
		this is the default
	-c will create a more compact event/state table when -tc is used
		with machines having actions which return states
	-s prints some useful statistics and exits
	-o  <outfile> will use <outfile> as the filename for the top-level machine output.
		Any sub-machines will be put into files based on the sub-machine names.
	--generate-weak-fns=false suppresses the generation of weak function stubs.
	--force-generation-of-event-passing-actions forces the generation of actions which pass events
		when weak function generation is disabled..
		The generated functions are not weak.
	--core-logging-only=true suppresses the generation of debug log messages in all but the core FSM function.
	--generate-run-function<=true|false> enables or supresses the generation of a run
		function to replace the RUN_STATE_MACHINE machro.
		The default is to generate the macro; the option argument is optional, if not given, "true" is assumed.
	--include-svg-img=true adds <img/> tag referencing <filename>.svg to include an image at the top of the web page.
	--css-content-internal=true puts the CSS directly into the html.
	--css-content-filename=<filename> uses the named file for the css citation, or
		for the content copy.
	--add-plantuml-title=<*true|false> adds the machine name as a title to the plantuml.
	--add-plantuml-legend=<*center|left|right|top|*bottm> adds a legend to the plantuml.
		Center, bottom are the defaults.  Horizontal and vertial parameters can be added in a quoted string.
		Center is a horizontal parameter.
		By default, event, state, and action lists are included in the legend, and event descriptions are removed
		from the body of the diagram.
	--exclude-states-from-plantuml-legend=<*true|false> excludes state information from the plantuml legend.
		When excluded from legend, state comments are included in the diagram body.
	--exclude-events-from-plantuml-legend=<*true|false> excludes event information from the plantuml legend.
	--exclude-actions-from-plantuml-legend=<*true|false> excludes action information from the plantuml legend.
	--add-machine-name adds the machine name when using the --short-debug-names option
	--add-event-cross-reference<=true|false> adds a cross-reference list as a comment block
		in front of the machine event enumeration. Omitting the optional argument is equivalent
		to specifying "true"
	--add-plantuml-prefix-string=<text> will add the specified text to the plantuml output before
		any generated output.  This option can be specified multiple times; all text will be
		added in the order given
		for the content copy.
	--add-plantuml-prefix-file=<text> will add the text in the specified file
		to the plantuml output before any generated output.
		This option can be specified multiple times; all text will be
		added in the order given
		for the content copy.
	-v prints the version and exits

The default is to generate C code using an action table.

Top of page

Basic Language Syntax

The example machine above illustrates the major language features and syntax. There are five basic keywords: machine, state, event, action, transition. A sixth keyword, returns, was shown in the example in its use as an informational element for actions, but is not further discussed here. Identifiers follow the C rules, as do declarations, which may be comma seperated, as in the state and event declarations above, or set seperately delimited by semi-colons as are all of the action declarations. State and event declarations may be interspersed, but all must come before the first action declaration. Naming scope is within a given machine; that is, on the one hand, all states and events must be declared within a machine in order to be used by the machine, and on the other, different machines may use the same names for states and events because they will apply only within that machine definition.

The action declaration is :

action action_identifier[event_vector,state_vector];

Or,

action action_identifier[event_vector,state_vector] transition state_identifier;

Where event_vector is

event_identifier

or,

(event_identifier_list)

with event_identifier_list being

event_identifier

or

event_identifier, event_identifier_list.

The analogous definition holds for state_vectors.

It is also possible to declare an actionless transition:

transition[event_vector, state_vector] state_identifier;

Top of page

More examples

There are many files with the .fsm extension included in the distribution, most found under the test directory. All illustrate valid constructions. Moreover, the tests starting with full_test create executables with action functions supplied. The effects of the use of other keywords not discussed here can also be seen, such as:

designating a machine to use action functions which return a state instead of an event giving a machine some data designating header files to be included by the generated header (perhaps needed by the data given to the machine) designating a function to call on every machine transition transitioning via functions that return states (with machines where actions return events). assigning values from external enums to the internal event enumerations (this should be used only when forcing the generation of switch statements for state handlers). Also found in the examples is some new functionality involving complex events. The html side of this works, but a full test has not yet been created. This may be of interest to ThreadX fans, since the idea of the complex event comes from a working system running under ThreadX where some events are messages taken from a queue, and can themselves contain enumerations which further refine the event. For example a message to control a subsystem might contain an enumeration indicating whether to activate or deactivate the system. In this way, the actual real events can be tracked in FSMLang (subsystem.activate being an event distinct from subsystem.deactivate).

Finally, the test makefiles show how to add .fsm targets to a makefile.

Top of page