The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

FSM::Simple - Flexible Perl implementation of Finite State Machine.

SYNOPSIS

    use FSM::Simple;
    
    my $machine = FSM::Simple->new();
    
    $machine->add_state(name => 'init',      sub => \&init);
    $machine->add_state(name => 'make_cake', sub => \&make_cake);
    $machine->add_state(name => 'eat_cake',  sub => \&eat_cake);
    $machine->add_state(name => 'clean',     sub => \&clean);
    $machine->add_state(name => 'stop',      sub => \&stop);
    
    $machine->add_trans(from => 'init',      to => 'make_cake', exp_val => 'makeCake');
    $machine->add_trans(from => 'make_cake', to => 'eat_cake',  exp_val => 1);
    $machine->add_trans(from => 'eat_cake',  to => 'clean',     exp_val => 'good');
    $machine->add_trans(from => 'eat_cake',  to => 'make_cake', exp_val => 'bad');
    $machine->add_trans(from => 'clean',     to => 'stop',      exp_val => 'foo');
    $machine->add_trans(from => 'clean',     to => 'stop',      exp_val => 'done');
    
    $machine->run();
    
    sub init {
        my $rh_args = shift;
        print "Let's make a cake\n";
        
        # Prepare ingredients.
        $rh_args->{flour}  = 2;    # kg
        $rh_args->{water}  = 0.5;  # liter
        $rh_args->{leaven} = 0.1;  # kg
        
        $rh_args->{returned_value} = 'makeCake';
        return $rh_args;
    }
    
    sub make_cake {
        my $rh_args = shift;
        print "I am making a cake\n";
    
        # Do somethink with ingredients
        # from $rh_arhs
        # and put the cake into $rh_args
        $rh_args->{cake} = '%%%';
    
        $rh_args->{returned_value} = 1;
        return $rh_args;
    }
    
    sub eat_cake {
        my $rh_args = shift;
        print "I am eating a cake\n";
        
        # Eat the cake from $rh_args->{cake}
        
        # If the cake is tasty then return 'good' otherwise 'bad'.
        srand;
        if (rand(1000) < 400) {
            $rh_args->{returned_value} = 'good';
        }
        else {
            $rh_args->{returned_value} = 'bad';
        }
        
        return $rh_args;
    }
    
    sub clean {
        my $rh_args = shift;
        print "I am cleaning the kitchen\n";
    
        $rh_args->{returned_value} = 'done';
        return $rh_args;
    }
    
    sub stop {
        my $rh_args = shift;
        print "Stop machine\n";
    
        $rh_args->{returned_value} = undef; # stop condition!!!
        return $rh_args;
    }

Example of output:

  Let's make a cake
  I am making a cake
  I am eating a cake
  I am cleaning the kitchen
  Stop machine

DESCRIPTION

This module contains a class with simple Finite State Machine (FSM) implementation. This is tiny layer for better control of flow in your code. You can create your own subs and add these to the FSM object. Next you can define transitions between those subs with expected returned value.

Each of your sub should return hash reference like this:

  {
     returned_value => 'must be SCALAR!',
     # your data or data from previous sub
  }

which contains required key 'returned_value' and other keys if you want. Value of this key must be a SCALAR or undef. It will be taken into consideration in decision which state (sub) should be next. FSM will stop when returned_value equals undef.

METHODS

$fsm = FSM::Simple->new( )
$fsm = FSM::Simple->new( trans_history => 1 )

Simple constructor of FSM. In this method you can turn on transitions history.

$fsm->add_state(name => 'state1', sub => \&sub_for_state1)

Add new state to the machine with name and sub reference to run. This sub has to return hash reference with pair returned_value => 'some SCALAR' . First state will be as initial state. You can change initial state by 'init_state' method.

$fsm->add_trans( from => 'state1', to => 'state2', exp_val => 'some val' );

Add new transition between state1 and state2. state2 will be run when state1 returns hash reference with pair returned_value => 'some val'

$fsm->init_state
$fsm->init_state( 'state1' )

Get and set name of initial state.

$fsm->trans_history_on

Turn on transitions history.

$fsm->trans_history_off

Turn off transitions history.

$fsm->run

Main method to run machine. FSM will stop if some state returns pair returned_value => undef .

$fsm->trans_history

Get array reference with states which were running or empty array reference when tranisions history was turned off.

$fsm->clean_trans_history

Reset transitions history.

$fsm->trans_stats

After run $fsm->run this method will return hash reference with pairs: state_name => counter (how many times this state was run)

$fsm->clear_trans_stats

Reset values of stats counters.

$fsm->trans_array

Get defined transitions as reference of array of hashes with keys: { from => 'state1', to => 'state2', returned_value => 'some val' }

$fsm->generate_graphviz_code( )
$fsm->generate_graphviz_code( size => 12 )
$fsm->generate_graphviz_code( name => 'name_of_graph' )
$fsm->generate_graphviz_code( name => 'name_of_graph', size => 12 )

You can generate code for Graphviz to show nice picture with states and transition. Optionally you can set name of graph and/or size of states (default is 8). In this code you will find some help how you can generate nice graph.

Example of graphviz code:

  digraph name_of_graph {
    rankdir=LR;
    size="8";
    node [shape = doublecircle]; initial_state;
    node [shape = circle];
    eat_cake_state -> make_cake_state [ label = "bad" ];
    make_cake_state -> eat_cake_state [ label = "1" ];
    clean_state -> stop_state [ label = "foo" ];
    eat_cake_state -> clean_state [ label = "good" ];
    friends_state -> eat_cake_state [ label = "thx" ];
    eat_cake_state -> friends_state [ label = "very good" ];
    initial_state -> make_cake_state [ label = "makeCake" ];

    // Install graphviz if required.
    // Write this code to (e.g.) fsm.dot and run:
    // dot -T png fsm.dot -o fsm.png
  }

TEST COVERAGE

The result of test coverage:

  ---------------------------- ------ ------ ------ ------ ------ ------ ------
  File                           stmt   bran   cond    sub    pod   time  total
  ---------------------------- ------ ------ ------ ------ ------ ------ ------
  ...-Simple/lib/FSM/Simple.pm  100.0   97.1  100.0  100.0    9.1    3.4   93.6
  add_state.t                    89.2    n/a    n/a  100.0    n/a   18.0   92.0
  add_trans.t                    95.1    n/a    n/a  100.0    n/a   17.4   96.5
  complex_tests.t               100.0  100.0    n/a  100.0    n/a   23.6  100.0
  init_state.t                  100.0    n/a    n/a  100.0    n/a   20.5  100.0
  run.t                         100.0    n/a    n/a  100.0    n/a   17.1  100.0
  Total                          98.3   97.4  100.0  100.0    9.1  100.0   96.7
  ---------------------------- ------ ------ ------ ------ ------ ------ ------

This code was also checked by podchecker, B::Lint and Perl::Critic.

BUGS

Please contanct with me if you will find some bug or you have any questions/suggestions.

SEE ALSO

DMA::FSM, Set::FA::Element, FLAT

AUTHOR

Pawel Koscielny, <koscielny.pawel@gmail.com>

COPYRIGHT AND LICENSE

Copyright (C) 2012 by Pawel Koscielny

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version of Perl 5 you may have available.