Class: BabyBots::BabyBot

Inherits:
Object
  • Object
show all
Defined in:
lib/baby_bots/baby_bot.rb

Overview

A tiny finite-state automata class.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(states = {}) ⇒ BabyBot

Accepts an optional hash of states.



25
26
27
28
29
30
31
32
33
# File 'lib/baby_bots/baby_bot.rb', line 25

def initialize(states={})
  # Hash of state names to state objects
  @states = {}
  if !states.empty? then build states end
  # Initial state
  @start = nil
  # Current state
  @curr = nil
end

Instance Attribute Details

#currObject

Returns the value of attribute curr.



22
23
24
# File 'lib/baby_bots/baby_bot.rb', line 22

def curr
  @curr
end

#startObject

Returns the value of attribute start.



22
23
24
# File 'lib/baby_bots/baby_bot.rb', line 22

def start
  @start
end

#statesObject

Returns the value of attribute states.



22
23
24
# File 'lib/baby_bots/baby_bot.rb', line 22

def states
  @states
end

Instance Method Details

#==(another_baby) ⇒ Object

Equality is based on if all the states are the same, and if the machines are currently in the same state.



145
146
147
148
149
150
151
152
153
154
155
# File 'lib/baby_bots/baby_bot.rb', line 145

def ==(another_baby)
  if @curr != another_baby.curr
    return false
  end

  @states.keys.each do |k|
    if @states[k] != another_baby.states[k] then return false end 
  end

  return true
end

#add_state(state, start = nil) ⇒ Object

Adds a new state to the finite-state automata, also accepts an optional start flag, which will set the supplied state as the initial state. Note that this machine may only have one start state. Additionally, adding the first start state will set @curr to be this state, after this has been done @curr will remain on whatever state it is currently residing on, and must be reset using the restart method.



41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/baby_bots/baby_bot.rb', line 41

def add_state(state, start=nil)
  # key on state names to the actual state object
  @states[state.state] = state

  # if this is a start state
  if start
    @start = state

    # only set the current state to the start state if @curr is nil.
    if @curr.nil? then @curr = @start end
  end
end

#build(table, no_first = false) ⇒ Object

Build up this machine’s states with a given state table. The format of this table is assumed to be => {event1 => transition1, …}. build assumes that the first state provided is the start state, although the optional no_first parameter may be set to override this.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/baby_bots/baby_bot.rb', line 58

def build(table, no_first=false)
  first_state = !no_first

  # iterate through each provided state table entries
  table.each do |state, state_table|
    temp_state = State.new(state)

    # iterate through the current state table, adding events => transition
    state_table.each do |event, transition|
      temp_state.add_transition(event, transition)
    end

    # finally, add the state to the machine, and since we've already
    # added a start state, clear the first_state flag
    add_state(temp_state, first_state)
    first_state = false
  end
end

#process(event = nil) ⇒ Object

Process the finite state automata with the given event. This will first see if the finite state automata has a defined method named “pre_current_state”, and if so “cook” the input event with this method. Process will then use the cooked input if available, or the raw input if there is none to compute the transition. Before actually transitioning, it will then send the raw output to a method of “post_current_state”, which will be the return value. If a method named “post_cook_current_state” is supplied, it will send the cooked event to this method instead of using “post_current_state”.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/baby_bots/baby_bot.rb', line 91

def process(event=nil)
  # get the current state
  curr_state = @curr

  # check if we need to preprocess the event
  if respond_to?("pre_#{@curr.state}")
    cooked_event = my_send("pre_#{@curr.state}", event)
  end

  # calculate the next state
  if !cooked_event.nil?
    next_state = @states[curr.table[cooked_event]]
  else
    next_state = @states[curr.table[event]]
  end

  # if there is no such transition, see if there is an a translation
  # known as else, and use that as the next state
  if next_state.nil?
    next_state = @states[curr.table[:else]]
  end
  
  # if the event is nil, and there is no :else clause,
  # throw an exception
  if next_state.nil?
    raise NoSuchTransitionException,
    "No valid transition #{event} for #{@curr.state}"
  end

  # check if we need to postprocess the event, this will act
  # as the "return" from any state transition (even self-looping transitions)
  if respond_to?("post_#{@curr.state}")
    ret_val = my_send("post_#{@curr.state}", event)
  elsif respond_to?("post_cooked_#{curr.state}")
    ret_val = my_send("post_#{@curr.state}", cooked_event)
  end
  
  # actually transition, and make sure such a transition exists
  @curr = next_state
  if @curr.nil?
    raise NoSuchStateException,
    "No valid state #{@curr} for transition #{event} from #{curr_state}"
  end

  return ret_val
end

#restartObject

Restart the current state to be the start state.



139
140
141
# File 'lib/baby_bots/baby_bot.rb', line 139

def restart
  @curr = @start
end

#stateObject

Return the current state’s actual state.



78
79
80
# File 'lib/baby_bots/baby_bot.rb', line 78

def state
  @curr.state
end