Ruby FSM

January 23rd, 2008 by kowsik

CHSM is a pretty nifty way to model finite state machines in Java or C++. It uses a DSL (Domain Specific Language) with embedded code blocks which is then compiled into the actual source. This FSM in Ruby is an attempt to model something very similar as a DSL.

The FSM described below exists in two levels. At the class level, it’s a set of events and states. The definitions of events and states use a DSL which makes the code incredibly readable and appears pseudo-declarative. At the instance level, the events are translated into methods on the defining class which can be invoked. Much in the spirit of CHSM, the only way to act upon the FSM is using the events which trigger state transitions. Depending on the state, a given event might trigger exceptions because it’s invalid for that state. Finally, you can trivially generate a GraphViz that visually shows the events and states.

Modeling a FSM

The core FSM class is modeled as a Ruby module which can be mixed into the enclosing class like so:

class Vending
    include FSM
    ...
end

Events

Each event has a name and a handle method. The handle method returns true if the event is handled and initiates a state transition. The handle method ends up being an instance method on the enclosing FSM. Also notice that the state definition is effectively a class definition with setters and getters and internal structure.

class Vending
    include FSM

    event :coin do
        attr_reader :value

        handle do |args|
            v = args[:value]
            @value = [ 5, 10, 25 ].member?(v) ? v : nil
        end
    end
end

States

Each state can have optional enter and exit methods that are invoked when the state is entered or exited because of a transition. The first state defined in the FSM also becomes the start state. Transitions from the state are expressed using on and code blocks can be executed for the transitions as well. The state transitions are only taken based on successful event handlers, at which point, the transition code block is invoked. Multiple transitions may exist for a given state.

class Vending
    ...

    attr_accessor :credit

    state :idle do
        enter do |fsm, ev|
            fsm.credit = 0
        end

        on :coin => :collect
    end

    state :collect do
        enter do |fsm, ev|
            fsm.credit += ev.value
        end

        on :coin => :collect

        on :select => :idle do |from, ev, to|
            puts "you selected #{ev.value}"
        end
    end
end

Driving the FSM

As I mentioned before, the only way to interact with the FSM is using the methods that happen to have the same name as the events. Incorrect events when the FSM is in a particular state raise exceptions. The input which causes the events to get fired are typically generated from a socket, stdin or a user-interface.

v = Vending.new
v.start
v.coin :value => 5
v.coin :value => 10
v.select :item => :coke

Visualizing the FSM

Since the meta-data about the events and states are at the class level, querying these and building a dot file is pretty straight forward. Here’s the FSM visualized for the vending machine example:

vending.png

Download FSM

The FSM is released under the BEER license and you can download it here: fsm-01tar.gz. MD5 is 7d8561d43822dae14fb001a71fd62ad6. There are a number of capabilities in CHSM that are not implemented here, specifically clusters. YMMV.

Posted in Ruby, Tools | Permalink | Trackback

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.