# This is the basis of a visitor to the FSA. # init it with the FSA, then apply transitions to # it to step through the thing. class Visitor attr_reader :current_state attr_accessor :context # fsa to visit def initialize( state, default_context) @current_state = state @context = default_context end # apply the transition to the current state of # the FSA this visitor visits def receive( transition_name ) @current_state = @current_state.receive( transition_name, @context ) end end # Used to construct a Finite State Automata. class FSA attr_reader :start_state, :state_map attr_reader :context def initialize( context={}, &block ) @state_map = {} @context = context[ :context ] instance_eval( &block ) end # Returns a visitor for this FSA def get_visitor Visitor.new( @start_state, @context ) end ########################################## # FSA creation methods ########################################## def state( state_name) @current_state = save_state( state_name ) yield end # Saves a state with the supplied name # Will create it if it doesn't already exist # Will assign it to the start state if the # start state doesn't already exist def save_state(state_name) return @state_map[ state_name ] if @state_map[ state_name ] current_state = State.new( state_name, self ) @state_map[state_name] = current_state @start_state ||= current_state current_state end def event( transition_mapping, &block ) transition_mapping.keys.each do |key| @current_state.add_transition( key, transition_mapping[key], block) end end def before_event( &block ) @current_state.before = block end def after_event( &block ) @current_state.after = block end end # Represents a transition from one state to # another - including an action to perform # if traversed. class Transition attr_reader :next_state, :block def initialize( next_state, block) @next_state = next_state @block = block end end # Represents a state, or node, in an FSA # includes actions to perform on entry and exit, # and the transitions themselves class State attr_accessor :before, :after, :transitions, :sub_fsa attr_reader :state_name def initialize(name, fsa) @state_name = name @transitions = {} @fsa = fsa end # Adds a new transition to this state def add_transition(transition, next_state, block) @transitions[transition] = Transition.new( next_state, block) end # applies an event to this state to determine what # state to enter next. Includes error checking : # if a transition doesn't exist then the error # transition will be invoked def receive( transition_name, context ) transition = @transitions[transition_name] return do_transition( transition, context ) # extract method end # does the actual transition processing def do_transition( transition, context ) # next_state = @fsa.state_map[transition.next_state] # transition.block.call(context) if transition.block # after.call(context) if after # perform after block next_state.before.call(context) if next_state.before # perform before block next_state # end # end