testfsm : shows one technique for creating FSMs in C++

Download testfsm.zip

Synopsis:

fsm.h
MyFsm.cpp
Myfsm.h
testfsm.cpp
whitefsm.cpp
WhiteFsm.h


fsm.h

Synopsis
#pragma once
#include <windows.h>
#include <crtdbg.h>
#include "stdio.h"

//-------------------------------------------------------
//-- This class implements base functionality for an fsm.
//-- It is assumed that that an action is performed when a transition occurs
//-- StateId: Each state in the fsm have a unique id associated with it
//--    It is the user's responsibility to ensure that the id's are unique
//--
//-- Input Stimuli: the fsm consumes stimuli provided by the user. The stimulus
//--    is passed into the run() method. eg. input stimuli could be a stream of 
//--    characters read from a file or a series of objects
//--
//-- EventId: every input stimuli has an event id associated with it. It is
//--    the user's responsibility to map the correct event id to a given 
//--    input stimulus.
//--
//--    One or more input stimuli can map to the same event id, that is an 
//--    input stimulus does not have to map uniquely to an event id
//--
//--    A given input stimulus can map to different event id's, that is an
//--    input stimulus does not have to map to the same event id in all states. 
//--    In other words, the mapping can be context sensitive depending on the 
//--    current state. Each state has a map() function that maps all possible 
//--    input stimuli into an event id
//--
//--  Example: a simple three state fsm that toggles back and forth based
//--    on whether the current char is a blank or not. It removes all but
//--    the first blank. Once a 0 char is read, it goes to a done state and
//--    stays there.
//--    The .h looks like this:
//--         #pragma once
//--  
//--         #include "fsm.h"
//--  
//--         class WhiteFSM : public FSM<char>
//--           {
//--           char m_char; //required by the action routines
//--    
//--           protected:
//--             void dumpchar();
//--
//--         public:
//--             WhiteFSM() {}
//--             ~WhiteFSM() {}
//--      
//--             void run(char c);
//--             void init();
//--         } ;
//--  
//--    The .cpp looks like this
//--
//--          #include "whitefsm.h"
//--
//--          typedef enum _event
//--            {
//--            e_anychar = 1,
//--              e_blankchar,
//--              e_ateof
//--            } EVENTID;
//--
//--          const struct State_base : public State<char>
//--            {
//--            State_base(StateId_t id, bool final) : State<char>(id, final){}
//--            EventId_t map(char c)
//--              {
//--              if (c == 0) return e_ateof;
//--              if (c == ' ') return e_blankchar;
//--              return e_anychar;
//--              }
//--            } ;
//--
//--          const State_base s_start(1, false);
//--const State_base s_blanks(2, false);
//--const State_base s_done(3, true);
//--
//--void WhiteFSM::dumpchar()
//--  {
//--  printf("%c", m_char);
//--  }
//--
//--void WhiteFSM::run(char c)
//--  {
//--  m_char = c;
//--  FSM<char>::run(c);
//--  }
//--
//--void WhiteFSM::init()
//--  {
//--  //curstate event  Action nextstate
//--  Add(&s_start,  e_ateof,      0,                        &s_done);
//--  Add(&s_start,  e_anychar,    (PACTIONFUNC) dumpchar,   &s_start);
//--  Add(&s_start,  e_blankchar,  (PACTIONFUNC) dumpchar,   &s_blanks);
//--  
//--  Add(&s_blanks, e_blankchar,  0,                        &s_blanks);
//--  Add(&s_blanks, e_anychar,    (PACTIONFUNC) dumpchar,   &s_start);
//--  Add(&s_blanks, e_ateof,      0,                        &s_done);
//--  
//--  Add(&s_done,   e_anychar,    0,                        &s_done);
//--  
//--  SetCurrentState(&s_start);
//--  }
//--
//--
//--
//--
//--
//--
//--
//--
//--
//--
//--
//--
//--
//--
//--
//--
//--
//--
//--




















































//--
//-------------------------------------------------------

//-------------------------------------------------------
//every state and event class has a unique id associated with it
typedef  long StateId_t;   //the enumuration that every state maps to
typedef  long EventId_t;   //the enumeration that every event maps to

//------------------------------------------------------
//the base state class
template <class STIMULUS>
class State
  {
  public:
    // id is a unique identifier for each state class
    // final indicates if this state is a final state or not
    State(StateId_t id, bool final) : m_id(id), m_bFinal(final) {}
    virtual ~State() {}
    
    //returns true if the state is a final state
    virtual bool      isFinal()  {return m_bFinal;}

    //returns the unique id for the state class
    virtual StateId_t map()      {return m_id;}

    //for a given input stimulus, returns an id that is used by the fsm to cause a transition
    //each state can map similar input stimuli differently if they want
    virtual EventId_t map(STIMULUS) = 0;
    
  protected:
    StateId_t m_id;
    bool      m_bFinal;
    
  private:
    State() {}   // no default ctor
    State(const State& ) {} //no copy ctor
  } ;

//------------------------------------------------------
//the base fsm class
template <class STIMULUS>
class FSM 
  {
  public:
    //return true if the fsm is in a final state
    bool isFinal() {return m_sCurrentState->isFinal();}

  protected:
    //this is the prototype for every action function in the fsm table
    typedef void (FSM<STIMULUS>::*PACTIONFUNC) ();
    typedef State<STIMULUS> State_t;

    //you must derive this FSM class to use it
    FSM()  : m_iMaxStateTableIndex(0), 
             m_sCurrentState(0),
             m_lCurrentStateId(-1)
      {}
    virtual ~FSM() {}

    //Sets the current state to the given parameter.
    void SetCurrentState(const State_t* s) 
      {
      m_sCurrentState = const_cast<State_t*>(s);
      m_lCurrentStateId = m_sCurrentState->map();
      }

    //adds a state to the fsm
    //s1 is the current state
    //ev is the event id to transition on
    //s2 is the next state to go to if the fsm is in s1 and ev comes in
    //f is the action to take when the transition occurs
    void Add(const State_t* s1, EventId_t ev, PACTIONFUNC f, const State_t* s2)
      {
      _ASSERTE(s1);
      _ASSERTE(s2);
      _ASSERTE(ev);
      m_table[m_iMaxStateTableIndex].curstate    = const_cast<State_t*>(s1);
      m_table[m_iMaxStateTableIndex].curstateid  = m_table[m_iMaxStateTableIndex].curstate->map();
      m_table[m_iMaxStateTableIndex].eventid     = ev;
      m_table[m_iMaxStateTableIndex].fn          = f;
      m_table[m_iMaxStateTableIndex].nextstate   = const_cast<State_t*>(s2);
      m_table[m_iMaxStateTableIndex].nextstateid = m_table[m_iMaxStateTableIndex].nextstate->map();
      m_iMaxStateTableIndex++;
      }
    
    //run the fsm given the input stimulus
    void run(STIMULUS stim)
      {
      //first map the input stimulus into an event id
      EventId_t ev = m_sCurrentState->map(stim);
      
      //search the table for the event id and current state
      int i;
      for(i = 0; i < m_iMaxStateTableIndex; i++)
        {
        //if we've found the event id and current state
        if (m_table[i].curstateid == m_lCurrentStateId && m_table[i].eventid == ev)
          {
          //...and there is an action to perform
          if (m_table[i].fn)
            (this->*m_table[i].fn) (); //do the action
          
          //set the fsm to the next state
          m_lCurrentStateId = m_table[i].nextstateid;
          m_sCurrentState = m_table[i].nextstate;
          _ASSERTE(m_sCurrentState);
          _ASSERTE(m_lCurrentStateId == m_sCurrentState->map());
          break;
          }
        }
      
      //assert if the eventid/current state combo is not in the fsm
      _ASSERTE(i < m_iMaxStateTableIndex || "unknown transition from current state with event");
      }
    
  private:
    struct _table
      {
      State_t*    curstate;
      StateId_t   curstateid;
      EventId_t   eventid;
      PACTIONFUNC fn;
      State_t*    nextstate;
      StateId_t   nextstateid;
      } m_table[20];
    
    StateId_t  m_lCurrentStateId;
    State_t*   m_sCurrentState;
    int        m_iMaxStateTableIndex;
  } ;


MyFsm.cpp

Synopsis
#pragma once

#include "myfsm.h"

//these are all of the possible event id's
//all input stimuli map to these id's
typedef enum _event
  {
  e_any = 1,
    e_slash,
    e_star,
    e_eof,
    e_newline
  } EVENTID;

//Declare all the states we're going to use

//------------------------------------------------------------
//This is a common class just to keep things simpler for the map() function
class CommonState : public State<char>
  {
  protected:
    //this is used just to ensure that the state id's are unique
    typedef enum _stateid
      {
      e_start = 1,
        e_slash1,
        e_slash2,
        e_star1,
        e_star2,
        e_error,
        e_done
      };
    
  public:
    CommonState(StateId_t id, bool final) : State<char>(id, final){}
    virtual ~CommonState() {}
    StateId_t map(char c)
      {
      if (c == 0) return e_eof;
      return e_any;
      }
  } ;

//------------------------------------------------------------
class StartState : public CommonState
  {
  public:
    StartState() : CommonState(e_start, false) {}
    StateId_t map(char c)
      {
      if (c == '/')  return e_slash;
      return CommonState::map(c);
      }
  }  const s_StartState;

//------------------------------------------------------------
const class Slash1State : public CommonState
  {
  public : 
    Slash1State() : CommonState(e_slash1, false) {}
    StateId_t map(char c)
      {
      if (c == '/')  return e_slash;
      if (c == '*')  return e_star;
      
      return CommonState::map(c);
      }
  } s_Slash1State;

//------------------------------------------------------------
const class Slash2State : public CommonState
  {
  public : 
    Slash2State() : CommonState(e_slash2, false) {}
    StateId_t map(char c)
      {
      if (c == '\n')  return e_newline;
      
      return CommonState::map(c);
      }
  } s_Slash2State;

//------------------------------------------------------------
const class Star1State : public CommonState
  {
  public : 
    Star1State() : CommonState(e_star1, false) {}
    StateId_t map(char c)
      {
      if (c == '*')  return e_star;
      
      return CommonState::map(c);
      }
  } s_Star1State;

//------------------------------------------------------------
const class Star2State : public CommonState
  {
  public : 
    Star2State() : CommonState(e_star2, false) {}
    StateId_t map(char c)
      {
      if (c == '/')  return e_slash;
      
      return CommonState::map(c);
      }
  } s_Star2State;

//------------------------------------------------------------
const class ErrorState : public CommonState
  {
  public : 
    ErrorState() : CommonState(e_error, true) {}
    StateId_t map(char c)
      {
      return e_any;
      }
  } s_ErrorState;

//------------------------------------------------------------
const class DoneState : public CommonState
  {
  public : 
    DoneState() : CommonState(e_done, true) {}
  } s_DoneState;


//------------------------------------------------------------
typedef FSM<char> charFSM;

inline void MyFSM::CommentColor()
  {
  SetConsoleTextAttribute(m_hconsole, FOREGROUND_GREEN);
  }

inline void MyFSM::NormalColor()
  {
  SetConsoleTextAttribute(m_hconsole, FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE);
  }

MyFSM::MyFSM()
  {
  m_hconsole = GetStdHandle(STD_OUTPUT_HANDLE);
  }

MyFSM::~MyFSM()
  {}

void MyFSM::run(char c)
  {
  m_char = c;
  charFSM::run(c);
  }

//Actions
//dump the current char
void  MyFSM::dumpchar(void)
  {
  NormalColor();
  printf("%c", m_char);
  }

//we're all done;
void MyFSM::alldone()
  {
  NormalColor();
  printf("<--done\n");
  }

void MyFSM::dumpslashchar()
  {
  NormalColor();
  printf("/%c", m_char);
  }

void  MyFSM::dumpslashcomment()
  {
  CommentColor();
  printf("/%c", m_char);
  }

void  MyFSM::dumpcomment()
  {
  CommentColor();
  printf("%c", m_char);
  }
void  MyFSM::dumpcommentnewline()
  {
  CommentColor();
  printf("\n");
  }

void MyFSM::dumperror()
  {
  NormalColor();
  printf("error: \n");
  }

void MyFSM::init()
  {
  //curstate event  Action nextstate
    Add(&s_StartState,  e_eof,     (PACTIONFUNC) &MyFSM::alldone,       &s_DoneState);
  Add(&s_StartState,  e_any,     (PACTIONFUNC) &MyFSM::dumpchar,      &s_StartState);
  Add(&s_StartState,  e_slash,   0,                   &s_Slash1State);
  
  Add(&s_Slash1State, e_any,     (PACTIONFUNC) &MyFSM::dumpslashchar, &s_StartState);
  Add(&s_Slash1State, e_slash,   (PACTIONFUNC) &MyFSM::dumpslashcomment,   &s_Slash2State);
  Add(&s_Slash1State, e_star,    (PACTIONFUNC) &MyFSM::dumpslashcomment,   &s_Star1State);
  
  Add(&s_Slash2State, e_any,     (PACTIONFUNC) &MyFSM::dumpcomment,   &s_Slash2State);
  Add(&s_Slash2State, e_newline, (PACTIONFUNC) &MyFSM::dumpcommentnewline,   &s_StartState);
  Add(&s_Slash2State, e_eof,     (PACTIONFUNC) &MyFSM::dumperror,     &s_ErrorState);
  
  Add(&s_Star1State,  e_any,     (PACTIONFUNC) &MyFSM::dumpcomment,   &s_Star1State);
  Add(&s_Star1State,  e_star,    (PACTIONFUNC) &MyFSM::dumpcomment,   &s_Star2State);
  
  Add(&s_Star2State,  e_any,     (PACTIONFUNC) &MyFSM::dumpcomment,   &s_Star1State);
  Add(&s_Star2State,  e_slash,   (PACTIONFUNC) &MyFSM::dumpcomment,   &s_StartState);
  
  Add(&s_ErrorState,  e_any,     0,             &s_DoneState);
  
  SetCurrentState(&s_StartState);
  }

Myfsm.h

Synopsis
#pragma once

#include "fsm.h"

class MyFSM : public FSM<char>
  {
  HANDLE m_hconsole;
  char m_char;

  protected:
    inline void CommentColor();
    inline void NormalColor();

    //Action functions
    void dumpchar();
    void alldone();
    void dumpslashchar();
    void dumpslashcomment();
    void dumpcomment();
    void dumpcommentnewline();
    void dumperror();
    
  public:
    MyFSM();
    ~MyFSM();
    
    void run(char c);
    void init();
  } ;

testfsm.cpp

Synopsis
#include <conio.h>
#include "MyFsm.h"
#include "WhiteFsm.h"

void main()
{
  MyFSM fsm;

  fsm.init();

  char s[] = "a/* is */ a //comment\na /*comment*/";
  int i;
  for(i = 0; !fsm.isFinal() ;i++)
    {
    fsm.run(s[i]);
    }

  WhiteFSM fsm2;

  fsm2.init();

  char t[] = "a       b      c d    e\n";
  for(i = 0; !fsm2.isFinal() ;i++)
    {
    fsm2.run(t[i]);
    }

//  _getche();
}

whitefsm.cpp

Synopsis
#pragma once

#include "whitefsm.h"

typedef enum _event
  {
  e_anychar = 1,
    e_blankchar,
    e_ateof
  } EVENTID;

const struct State_base : public State<char>
  {
  State_base(StateId_t id, bool final) : State<char>(id, final){}
  EventId_t map(char c)
    {
    if (c == 0) return e_ateof;
    if (c == ' ') return e_blankchar;
    return e_anychar;
    }
  } ;

const State_base s_start(1, false);
const State_base s_blanks(2, false);
const State_base s_done(3, true);

void WhiteFSM::dumpchar()
  {
  printf("%c", m_char);
  }

void WhiteFSM::run(char c)
  {
  m_char = c;
  FSM<char>::run(c);
  }

void WhiteFSM::init()
  {
  //curstate event  Action nextstate
  Add(&s_start,  e_ateof,      0,                        &s_done);
  Add(&s_start,  e_anychar,    (PACTIONFUNC) &WhiteFSM::dumpchar,   &s_start);
  Add(&s_start,  e_blankchar,  (PACTIONFUNC) &WhiteFSM::dumpchar,   &s_blanks);
  
  Add(&s_blanks, e_blankchar,  0,                        &s_blanks);
  Add(&s_blanks, e_anychar,    (PACTIONFUNC) &WhiteFSM::dumpchar,   &s_start);
  Add(&s_blanks, e_ateof,      0,                        &s_done);
  
  Add(&s_done,   e_anychar,    0,                        &s_done);
  
  SetCurrentState(&s_start);
  }

WhiteFsm.h

Synopsis
#pragma once

#include "fsm.h"

class WhiteFSM : public FSM<char>
  {
  char m_char;
  
  protected:
    void dumpchar();

  public:
    WhiteFSM() {}
    ~WhiteFSM() {}
    
    void run(char c);
    void init();
  } ;






Contact me about content on this page using john_web-at-arrizza-dot-com
For Web Master or site problems contact: webadmin-at-arrizza-dot-com
Copyright John Arrizza (c) 2001-2010