Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Build simple rules-based state machines in Perl
Perl

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
lib/FSA
t
Build.PL
Changes
MANIFEST
MANIFEST.SKIP
META.yml
Makefile.PL
README

README

Name
    FSA::Rules - Build simple rules-based state machines in Perl

Synopsis
      my $fsa = FSA::Rules->new(
         ping => {
             do => sub {
                 print "ping!\n";
                 my $state = shift;
                 $state->result('pong');
                 $state->machine->{count}++;
             },
             rules => [
                 game_over => sub { shift->machine->{count} >= 20 },
                 pong      => sub { shift->result eq 'pong' },
             ],
         },

         pong => {
             do => sub { print "pong!\n" },
             rules => [ ping => 1, ], # always goes back to ping
         },
         game_over => { do => sub { print "Game Over\n" } }
      );

      $fsa->start;
      $fsa->switch until $fsa->at('game_over');

Description
    This class implements a simple state machine pattern, allowing you to
    quickly build rules-based state machines in Perl. As a simple
    implementation of a powerful concept, it differs slightly from an ideal
    DFA model in that it does not enforce a single possible switch from one
    state to another. Rather, it short circuits the evaluation of the rules
    for such switches, so that the first rule to return a true value will
    trigger its switch and no other switch rules will be checked. (But see
    the `strict' attribute and parameter to `new()'.) It differs from an NFA
    model in that it offers no back-tracking. But in truth, you can use it
    to build a state machine that adheres to either model--hence the more
    generic FSA moniker.

    FSA::Rules uses named states so that it's easy to tell what state you're
    in and what state you want to go to. Each state may optionally define
    actions that are triggered upon entering the state, after entering the
    state, and upon exiting the state. They may also define rules for
    switching to other states, and these rules may specify the execution of
    switch-specific actions. All actions are defined in terms of anonymous
    subroutines that should expect an FSA::State object itself to be passed
    as the sole argument.

    FSA::Rules objects and the FSA::State objects that make them up are all
    implemented as empty hash references. This design allows the action
    subroutines to use the FSA::State object passed as the sole argument, as
    well as the FSA::Rules object available via its `machine()' method, to
    stash data for other states to access, without the possibility of
    interfering with the state or the state machine itself.

  Serialization

    As of version 0.24, FSA::Rules supports serialization by Storable 2.05
    and later. In other words, FSA::Rules can function as a persistent state
    machine.

    However, FSA::Rules stores data outside of FSA::Rules objects, in
    private data structures inside the FSA::Rules module itself. Therefore,
    unless you want to clone your FSA::Rules object, you must let it fall
    out of scope after you serialize it, so that its data will be cleared
    from memory. Otherwise, if you freeze and thaw an FSA::Rules object in a
    single process without `undef'ing the original, there will be *two*
    copies of the object stored by FSA::Rules.

    So how does it work? Because the rules are defined as code references,
    you must use Storable 2.05 or later and set its `$Deparse' and `$Eval'
    variables to true values:

      use Storable qw(freeze thaw);

      local $Storable::Deparse = 1;
      local $Storable::Eval    = 1;

      my $frozen = freeze($fsa);
      $fsa = thaw($frozen);

    The only caveat is that, while Storable can serialize code references,
    it doesn't properly reference closure variables. So if your rules code
    references are closures, you'll have to serialize the data that they
    refer to yourself.

Class Interface
  Constructor

    new

      my $fsa = FSA::Rules->new(
          foo_state => { ... },
          bar_state => { ... },
      );

      $fsa = FSA::Rules->new(
          \%params,
          foo_state => { ... },
          bar_state => { ... },
      );

    Constructs and returns a new FSA::Rules object. An optional first
    argument is a hash reference that may contain one or more of these keys:

    start
        Causes the `start()' method to be called on the machine before
        returning it.

    done
        A value to which to set the `done' attribute.

    strict
        A value to which to set the `strict' attribute.

    state_class
        The name of the class to use for state objects. Defaults to
        "FSA::State". Use this parameter if you want to use a subclass of
        FSA::State.

    All other parameters define the state table, where each key is the name
    of a state and the following hash reference defines the state, its
    actions, and its switch rules. These state specifications will be
    converted to FSA::State objects available via the `states()' method. The
    first state parameter is considered to be the start state; call the
    `start()' method to automatically enter that state.

    The supported keys in the state definition hash references are:

    label
          label => 'Do we have a username?',
          label => 'Create a new user',

        A label for the state. It might be the question that is being asked
        within the state (think decsision tree), the answer to which
        determins which rule will trigger the switch to the next state. Or
        it might merely describe what's happening in the state.

    on_enter
          on_enter => sub { ... }
          on_enter => [ sub {... }, sub { ... } ]

        Optional. A code reference or array reference of code references.
        These will be executed when entering the state, after any switch
        actions defined by the `rules' of the previous state. The FSA::State
        for which the `on_enter' actions are defined will be passed to each
        code reference as the sole argument.

    do
          do => sub { ... }
          do => [ sub {... }, sub { ... } ]

        Optional. A code reference or array reference of code references.
        These are the actions to be taken while in the state, and will
        execute after any `on_enter' actions. The FSA::State object for
        which the `do' actions are defined will be passed to each code
        reference as the sole argument.

    on_exit
          on_exit => sub { ... }
          on_exit => [ sub {... }, sub { ... } ]

        Optional. A code reference or array reference of code references.
        These will be executed when exiting the state, before any switch
        actions (defined by `rules'). The FSA::State object for which the
        `on_exit' actions are defined will be passed to each code reference
        as the sole argument.

    rules
        Optional. The rules for switching from the state to other states.
        This is an array reference but shaped like a hash. The keys are the
        names of the states to consider moving to, while the values are the
        rules for switching to that state. The rules will be executed in the
        order specified in the array reference, and *they will
        short-circuit* unless the `strict' attribute has been set to a true
        value. So for the sake of efficiency it's worthwhile to specify the
        switch rules most likely to evaluate to true before those more
        likely to evaluate to false.

        Rules themselves are best specified as hash references with the
        following keys:

        rule
            A code reference or value that will be evaluated to determine
            whether to switch to the specified state. The value must be true
            or the code reference must return a true value to trigger the
            switch to the new state, and false not to switch to the new
            state. When executed, it will be passed the FSA::State object
            for the state for which the rules were defined, along with any
            other arguments passed to `try_switch()' or `switch()'--the
            methods that execute the rule code references. These arguments
            may be inputs that are specifically tested to determine whether
            to switch states. To be polite, rules should not transform the
            passed values if they're returning false, as other rules may
            need to evaluate them (unless you're building some sort of
            chaining rules--but those aren't really rules, are they?).

        message
            An optional message that will be added to the current state when
            the rule specified by the `rule' parameter evaluates to true.
            The message will also be used to label switches in the output of
            the `graph()' method.

        action
            A code reference or an array reference of code references to be
            executed during the switch, after the `on_exit' actions have
            been executed in the current state, but before the `on_enter'
            actions execute in the new state. Two arguments will be passed
            to these code references: the FSA::State object for the state
            for which they were defined, and the FSA::State object for the
            new state (which will not yet be the current state).

        A couple of examples:

          rules => [
              foo => {
                  rule => 1
              },
              bar => {
                  rule    => \&goto_bar,
                  message => 'Have we got a bar?',
              },
              yow => {
                  rule    => \&goto_yow,
                  message => 'Yow!',
                  action  => [ \&action_one, \&action_two],
              }
          ]

        A rule may also simply be a code reference or value that will be
        evaluated when FSA::Rules is determining whether to switch to the
        new state. You might want just specify a value or code reference if
        you don't need a message label or switch actions to be executed. For
        example, this `rules' specification:

          rules => [
              foo => 1
          ]

        Is equivalent to this `rules' specification:

          rules => [
              foo => { rule => 1 }
          ]

        And finally, you can specify a rule as an array reference. In this
        case, the first item in the array will be evaluated to determine
        whether to switch to the new state, and any other items must be code
        references that will be executed during the switch. For example,
        this `rules' specification:

          rules => [
              yow => [ \&check_yow, \&action_one, \&action_two ]
          ]

        Is equivalent to this `rules' specification:

          rules => [
              yow => {
                  rule   =>  \&check_yow,
                  action => [ \&action_one, \&action_two ],
              }
          ]

Instance Interface
  Instance Methods

    start

      my $state = $fsa->start;

    Starts the state machine by setting the state to the first state defined
    in the call to `new()'. If the machine is already in a state, an
    exception will be thrown. Returns the start state FSA::State object.

    at

      $fsa->switch until $fsa->at('game_over');

    Requires a statename. Returns false if the current machine state does
    not match the name. Otherwise, it returns the state.

    curr_state

      my $curr_state = $fsa->curr_state;
      $fsa->curr_state($curr_state);

    Get or set the current FSA::State object. Pass a state name or object to
    set the state. Setting a new state will cause the `on_exit' actions of
    the current state to be executed, if there is a current state, and then
    execute the `on_enter' and `do' actions of the new state. Returns the
    new FSA::State object when setting the current state.

    state

    Deprecated alias for `curr_state()'. This method will issue a warning
    and will be removed in a future version of FSA::Rules. Use
    `curr_state()', instead.

    prev_state

      my $prev_state = $fsa->prev_state;

    Returns the FSA::State object representing the previous state. This is
    useful in states where you need to know what state you came from, and
    can be very useful in "fail" states.

    states

      my @states = $fsa->states;
      my $states = $fsa->states;
      my $state  = $fsa->states($state_name);
      @states    = $fsa->states(@state_names);
      $states    = $fsa->states(@state_names);

    Called with no arguments, this method returns a list or array reference
    of all of the FSA::State objects that represent the states defined in
    the state machine. When called with a single state name, it returns the
    FSA::State object object for that state. When called with more than one
    state name arguments, it returns a list or array reference of those
    states.

    If called with any state names that did not exist in the original
    definition of the state machine, this method will `croak()'.

    try_switch

      my $state = $fsa->try_switch;
      $state = $fsa->try_switch(@inputs);

    Checks the switch rules of the current state and switches to the first
    new state for which a rule returns a true value. The evaluation of
    switch rules short-circuits to switch to the first state for which a
    rule evaluates to a true value unless the `strict' attribute is set to a
    true value.

    If <strict> is set to a true value, *all* rules will be evaluated, and
    if more than one returns a true statement, an exception will be thrown.
    This approach guarntees that every attempt to switch from one state to
    another will have one and only one possible destination state to which
    to switch, thus satisfying the DFA pattern.

    All arguments passed to `try_switch' will be passed to the switch rule
    code references as inputs. If a switch rule evaluates to true and there
    are additional switch actions for that rule, these actions will be
    executed after the `on_exit' actions of the current state (if there is
    one) but before the `on_enter' actions of the new state. They will be
    passed the current state object and the new state object as arguments.

    Returns the FSA::State object representing the state to which it
    switched and `undef' if it cannot switch to another state.

    switch

      my $state = eval { $fsa->switch(@inputs) };
      print "No can do" if $@;

    The fatal form of `try_switch()'. This method attempts to switch states
    and returns the FSA::State object on success and throws an exception on
    failure.

    done

      my $done = $fsa->done;
      $fsa->done($done);
      $fsa->done( sub {...} );

    Get or set a value to indicate whether the engine is done running. Or
    set it to a code reference to have that code reference called each time
    `done()' is called without arguments and have *its* return value
    returned. A code reference should expect the FSA::Rules object passed in
    as its only argument. Note that this varies from the pattern for state
    actions, which should expect the relevant FSA::State object to be passed
    as the argument. Call the `curr_state()' method on th FSA::Rules object
    if you want the current state in your `done' code reference.

    This method can be useful for checking to see if your state engine is
    done running, and calling `switch()' when it isn't. States can set it to
    a true value when they consider processing complete, or you can use a
    code reference that evaluates "done-ness" itself. Something like this:

      my $fsa = FSA::Rules->new(
          foo => {
              do    => { $_[0]->machine->done(1) if ++$_[0]->{count} >= 5 },
              rules => [ foo => 1 ],
          }
      );

    Or this:

      my $fsa = FSA::Rules->new(
          foo => {
              do    => { ++shift->machine->{count} },
              rules => [ foo => 1 ],
          }
      );
      $fsa->done( sub { shift->{count} >= 5 });

    Then you can just run the state engine, checking `done()' to find out
    when it's, uh, done.

      $fsa->start;
      $fsa->switch until $fsa->done;

    Although you could just use the `run()' method if you wanted to do that.

    Note that `done' will be reset to `undef' by a call to `reset()' when
    it's not a code reference. If it *is* a code reference, you need to be
    sure to write it in such a way that it knows that things have been reset
    (by examining states, for example, all of which will have been removed
    by `reset()').

    strict

      my $strict = $fsa->strict;
      $fsa->strict(1);

    Get or set the `strict' attribute of the state machine. When set to
    true, the strict attribute disallows the short-circuiting of rules and
    allows a transfer if only one rule returns a true value. If more than
    one rule evaluates to true, an exception will be thrown.

    run

      $fsa->run;

    This method starts the FSA::Rules engine (if it hasn't already been set
    to a state) by calling `start()', and then calls the `switch()' method
    repeatedly until `done()' returns a true value. In other words, it's a
    convenient shortcut for:

        $fsa->start unless $self->curr_state;
        $fsa->switch until $self->done;

    But be careful when calling this method. If you have no failed switches
    between states and the states never set the `done' attribute to a true
    value, then this method will never die or return, but run forever. So
    plan carefully!

    Returns the FSA::Rules object.

    reset

      $fsa->reset;

    The `reset()' method clears the stack and notes, sets the current state
    to `undef', and sets `done' to `undef' (unless `done' is a code
    reference). Also clears any temporary data stored directly in the
    machine hash reference and the state hash references. Use this method
    when you want to reuse your state machine. Returns the DFA::Rules
    object.

      my $fsa = FSA::Rules->new(@state_machine);
      $fsa->done(sub {$done});
      $fsa->run;
      # do a bunch of stuff
      $fsa->{miscellaneous} = 42;
      $fsa->reset->run;
      # $fsa->{miscellaneous} does not exist

    notes

      $fsa->notes($key => $value);
      my $val = $fsa->notes($key);
      my $notes = $fsa->notes;

    The `notes()' method provides a place to store arbitrary data in the
    state machine, just in case you're not comfortable using the FSA::Rules
    object itself, which is an empty hash. Any data stored here persists for
    the lifetime of the state machine or until `reset()' is called.

    Conceptually, `notes()' contains a hash of key-value pairs.

    `$fsa->notes($key => $value)' stores a new entry in this hash.
    `$fsa->notes->($key)' returns a previously stored value. `$fsa->notes',
    called without arguments, returns a reference to the entire hash of
    key-value pairs.

    Returns the FSA::Rules object when setting a note value.

    last_message

      my $message = $fsa->last_message;
      $message = $fsa->last_message($state_name);

    Returns the last message of the current state. Pass in the name of a
    state to get the last message for that state, instead.

    last_result

      my $result = $fsa->last_result;
      $result = $fsa->last_result($state_name);

    Returns the last result of the current state. Pass in the name of a
    state to get the last result for that state, instead.

    stack

      my $stack = $fsa->stack;

    Returns an array reference of all states the machine has been in since
    it was created or since `reset()' was last called, beginning with the
    first state and ending with the current state. No state name will be
    added to the stack until the machine has entered that state. This method
    is useful for debugging.

    raw_stacktrace

      my $stacktrace = $fsa->raw_stacktrace;

    Similar to `stack()', this method returns an array reference of the
    states that the machine has been in. Each state is an array reference
    with two elements. The first element is the name of the state and the
    second element is a hash reference with two keys, "result" and
    "message". These are set to the values (if used) set by the `result()'
    and `message()' methods on the corresponding FSA::State objects.

    A sample state:

      [
          some_state,
          {
              result  => 7,
              message => 'A human readable message'
          }
      ]

    stacktrace

      my $trace = $fsa->stacktrace;

    Similar to `raw_stacktrace', except that the `result's and `message's
    are output in a human readable format with nicely formatted data (using
    Data::Dumper). Functionally there is no difference from
    `raw_stacktrace()' unless your states are storing references in their
    `result's or `message's

    For example, if your state machine ran for only three states, the output
    may resemble the following:

      State: foo
      {
        message => 'some message',
        result => 'a'
      }

      State: bar
      {
        message => 'another message',
        result => [0, 1, 2]
      }

      State: bar
      {
        message => 'and yet another message',
        result => 2
      }

    graph

      my $graph_viz = $fsa->graph(@graph_viz_args);
      $graph_viz = $fsa->graph(\%params, @graph_viz_args);

    Constructs and returns a GraphViz object useful for generating graphical
    representations of the complete rules engine. The parameters to
    `graph()' are all those supported by the GraphViz constructor; consult
    the GraphViz documentation for details.

    Each node in the graph represents a single state. The label for each
    node in the graph will be either the state label or if there is no
    lable, the state name.

    Each edge in the graph represents a rule that defines the relationship
    between two states. If a rule is specified as a hash reference, the
    `message' key will be used as the edge label; otherwise the label will
    be blank.

    An optional hash reference of parameters may be passed as the first
    argument to `graph()'. The supported parameters are:

    with_state_name
        This parameter, if set to true, prepends the name of the state and
        two newlines to the label for each node. If a state has no label,
        then the state name is simply used, regardless. Defaults to false.

    wrap_nodes
    wrap_node_labels
        This parameter, if set to true, will wrap the node label text. This
        can be useful if the label is long. The line length is determined by
        the `wrap_length' parameter. Defaults to false.

    wrap_edge_labels
    wrap_labels
        This parameter, if set to true, will wrap the edge text. This can be
        useful if the rule message is long. The line length is determined by
        the `wrap_length' parameter. Defaults to false `wrap_labels' is
        deprecated and will be removed in a future version.

    text_wrap
    wrap_length
        The line length to use for wrapping text when `wrap_nodes' or
        `wrap_labels' is set to true. `text_wrap' is deprecated and will be
        removed in a future version. Defaults to 25.

    node_params
        A hash reference of parameters to be passed to the GraphViz
        `add_node()' method when seting up a state as a node. Only the
        `label' parameter will be ignored. See the
        `GraphViz|GraphViz/"add_node"' documentation for the list of
        supported paramters.

    edge_params
        A hash reference of parameters to be passed to the GraphViz
        `add_node()' method when seting up a state as a node. See the
        `GraphViz|GraphViz/"add_edge"' documentation for the list of
        supported paramters.

    Note: If either `GraphViz' or `Text::Wrap' is not available on your
    system, `graph()' will simply will warn and return.

    DESTROY

    This method cleans up an FSA::Rules object's internal data when it is
    released from memory. In general, you don't have to worry about the
    `DESTROY()' method unless you're subclassing FSA::Rules. In that case,
    if you implement youre own `DESTROY()' method, just be sure to call
    `SUPER::DESTROY()' to preven memory leaks.

FSA::State Interface
    FSA::State objects represent individual states in a state machine. They
    are passed as the first argument to state actions, where their methods
    can be called to handle various parts of the processing, set up messages
    and results, or access the state machine object itself.

    Like FSA::Rules objects, FSA::State objects are empty hashes, so you can
    feel free to stash data in them. But note that each state object is
    independent of all others, so if you want to stash data for other states
    to access, you'll likely have to stash it in the state machine object
    (in its hash implementation or via the `notes()' method), or retrieve
    other states from the state machine using its `states()' method and then
    access their hash data directly.

  Constructor

    new

      my $state = FSA::State->new;

    Constructs and returns a new FSA::State object. Not intended to be
    called directly, but by FSA::Rules.

  Instance Methods

    name

      my $name = $state->name;

    Returns the name of the state.

    label

      my $label = $state->label;

    Returns the label of the state.

    machine

      my $machine = $state->machine;

    Returns the FSA::Rules object for which the state was defined.

    result

      my $fsa = FSA::Rules->new(
        # ...
        some_state => {
            do => sub {
                my $state = shift;
                # Do stuff...
                $state->result(1); # We're done!
            },
            rules => [
                bad  => sub { ! shift->result },
                good => sub {   shift->result },
            ]
        },
        # ...
      );

    This is a useful method to store results on a per-state basis. Anything
    can be stored in the result slot. Each time the state is entered, it
    gets a new result slot. Call `result()' without arguments in a scalar
    context to get the current result; call it without arguments in an array
    context to get all of the reults for the state for each time it has been
    entered into, from first to last. The contents of each result slot can
    also be viewed in a `stacktrace' or `raw_stacktrace'.

    message

      my $fsa = FSA::Rules->new(
        # ...
        some_state => {
            do => sub {
                my $state = shift;
                # Do stuff...
                $state->message('hello ', $ENV{USER});
            },
            rules => [
                bad  => sub { ! shift->message },
                good => sub {   shift->message },
            ]
        },
        # ...
      );

    This is a useful method to store messages on a per-state basis. Anything
    can be stored in the message slot. Each time the state is entered, it
    gets a new message slot. Call `message()' without arguments in a scalar
    context to get the current message; call it without arguments in an
    array context to get all of the messages for the state for each time it
    has been entered into, from first to last. The contents of each message
    slot can also be viewed in a `stacktrace' or `raw_stacktrace'.

    prev_state

      my $prev = $state->prev_state;

    A shortcut for `$state->machine->prev_state'.

    done

      my $done = $state->done;
      $state->done($done);

    A shortcut for `$state->machine->done'. Note that, unlike `message' and
    `result', done-ness is stored machine-wide, rather than state-wide.
    You'll generally call it on the state object when you want to tell the
    machine that processing is complete.

    notes

      my $notes = $state->notes;
      $state->notes($notes);

    A shortcut for `$state->machine->notes'. Note that, unlike `message' and
    `result', notes are stored machine-wide, rather than state-wide. It is
    therefore probably the most convenient way to stash data for other
    states to access.

    enter

    Executes all of the `on_enter' actions. Called by FSA::Rules's
    `curr_state()' method, and not intended to be called directly.

    do

    Executes all of the `do' actions. Called by FSA::Rules's `curr_state()'
    method, and not intended to be called directly.

    exit

    Executes all of the `on_exit' actions. Called by FSA::Rules's
    `curr_state()' method, and not intended to be called directly.

    DESTROY

    This method cleans up an FSA::State object's internal data when it is
    released from memory. In general, you don't have to worry about the
    `DESTROY()' method unless you're subclassing FSA::State. In that case,
    if you implement youre own `DESTROY()' method, just be sure to call
    `SUPER::DESTROY()' to preven memory leaks.

To Do
    Factor FSA::Class into a separate file.
Bugs
    Please send bug reports to <bug-fsa-rules@rt.cpan.org>.

Authors
    David Wheeler <david@kineticode.com> Curtis "Ovid" Poe
    <eop_divo_sitruc@yahoo.com> (reverse the name to email him)

Copyright and License
    Copyright (c) 2004-2006 Kineticode, Inc. All Rights Reserved.

    This module is free software; you can redistribute it and/or modify it
    under the same terms as Perl itself.

Something went wrong with that request. Please try again.