baby_bots 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/lib/baby_bots.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require 'baby_bots/version'
2
- require 'baby_bots/baby_bot'
3
2
  require 'baby_bots/state'
3
+ require 'baby_bots/baby_bot'
4
+
@@ -1,10 +1,5 @@
1
- # A tiny finite-state automata library.
2
- #
3
- # Author:: Justin Hamilton (mailto:justinanthonyhamilton@gmail.com)
4
- # Copyright :: BSD2 (see LICENSE for more details)
5
-
6
1
  module BabyBots
7
-
2
+
8
3
  # Error to handle attempts to transition to a state that does not exist.
9
4
  class NoSuchStateException < Exception
10
5
  end
@@ -13,24 +8,33 @@ module BabyBots
13
8
  class NoSuchTransitionException < Exception
14
9
  end
15
10
 
16
- # # Error to handle incorrect arity on the post_state methods.
17
- # class PostMethodArgumentError < ArgumentError
18
- # end
19
-
20
11
  # A tiny finite-state automata class.
21
12
  class BabyBot
22
- attr_accessor :curr, :states, :start
13
+ attr_accessor :curr, :start
23
14
 
24
15
  # Accepts an optional hash of states.
25
16
  def initialize(states={})
26
17
  # Hash of state names to state objects
27
18
  @states = {}
28
19
  if !states.empty? then build states end
20
+
21
+ # Add our default error state to provide user-based 'error' handling.
22
+ add_state(ERRSTATE)
23
+
29
24
  # Initial state
30
25
  @start = nil
31
26
  # Current state
32
27
  @curr = nil
33
28
  end
29
+
30
+ # Provides the states, minus the default error state (because it isn't
31
+ # really a state in the true sense). Note that this is NOT a reference
32
+ # to the BabyBot's actual states, but rather a duplicate.
33
+ def states
34
+ new_states = @states.dup
35
+ new_states.delete(ERR)
36
+ return new_states
37
+ end
34
38
 
35
39
  # Adds a new state to the finite-state automata, also accepts
36
40
  # an optional start flag, which will set the supplied state as the initial
@@ -51,6 +55,12 @@ module BabyBots
51
55
  end
52
56
  end
53
57
 
58
+ # Since we implement a default error state, this empty will check
59
+ # if there are any other states aside from that one.
60
+ def empty?
61
+ @states.keys.find_all { |k| k != ERR }.empty?
62
+ end
63
+
54
64
  # Build up this machine's states with a given state table. The format of
55
65
  # this table is assumed to be {state_name => {event1 => transition1, ...}}.
56
66
  # build assumes that the first state provided is the start state, although
@@ -79,6 +89,11 @@ module BabyBots
79
89
  @curr.state
80
90
  end
81
91
 
92
+ # Return the name of the current state
93
+ def state_name
94
+ @curr.state.state
95
+ end
96
+
82
97
  # Process the finite state automata with the given event.
83
98
  # This will first see if the finite state automata has a defined method
84
99
  # named "pre_{current_state}", and if so "cook" the input event with this
@@ -113,23 +128,30 @@ module BabyBots
113
128
  # if the event is nil, and there is no :else clause,
114
129
  # throw an exception
115
130
  if next_state.nil?
116
- raise NoSuchTransitionException,
117
- "No valid transition #{event} for #{@curr.state}"
131
+ transition_error(event, @curr.state)
118
132
  end
119
-
120
- # check if we need to postprocess the event, this will act
133
+
134
+ # first, check if we need to handle an error function for this state,
135
+ # then check if we need to postprocess the event, this will act
121
136
  # as the "return" from any state transition (even self-looping transitions)
122
- if respond_to?("post_#{@curr.state}")
137
+ if next_state == ERRSTATE
138
+ if respond_to?("error_#{@curr.state}")
139
+ ret_val = my_send("error_#{@curr.state}", event)
140
+ else
141
+ raise NoSuchTransitionException,
142
+ "No valid transition #{event} for #{@curr.state}"
143
+ end
144
+ elsif respond_to?("post_#{@curr.state}")
123
145
  ret_val = my_send("post_#{@curr.state}", event)
124
146
  elsif respond_to?("post_cooked_#{curr.state}")
125
147
  ret_val = my_send("post_#{@curr.state}", cooked_event)
126
148
  end
127
149
 
128
150
  # actually transition, and make sure such a transition exists
129
- @curr = next_state
151
+ @curr = next_state unless next_state == ERRSTATE
152
+
130
153
  if @curr.nil?
131
- raise NoSuchStateException,
132
- "No valid state #{@curr} for transition #{event} from #{curr_state}"
154
+ transition_error(event, @curr.state)
133
155
  end
134
156
 
135
157
  return ret_val
@@ -154,8 +176,23 @@ module BabyBots
154
176
  return true
155
177
  end
156
178
 
179
+ # Check if a method call is checking that state name.
180
+ def method_missing(m, *a, &b)
181
+ if m =~ /^([a-zA-Z_]+)\?$/
182
+ state == m.to_s.gsub("?","").to_sym
183
+ else
184
+ raise NoMethodError
185
+ end
186
+ end
187
+
157
188
  private
158
189
 
190
+ # Wrapper for our NoSuchTransitionException
191
+ def transition_error(event, state)
192
+ raise NoSuchTransitionException,
193
+ "No valid transition #{event} for #{state}"
194
+ end
195
+
159
196
  # A wrapper around send, using the arity restrictions assumed by BabyBots.
160
197
  def my_send(method_name, event=nil)
161
198
  m_arity = method(method_name).arity
@@ -2,6 +2,9 @@ module BabyBots
2
2
  # Transition state used to remove events from a transition table.
3
3
  NOWHERE = :nowhere__
4
4
 
5
+ # Defualt Error Transition
6
+ ERR = :err__
7
+
5
8
  # The state contained within the BabyBots Finite State Automata.
6
9
  # States have an event that transitions to a new state. They may also
7
10
  # have an event named :else which will be the transition used by the
@@ -50,4 +53,6 @@ module BabyBots
50
53
  end
51
54
  end
52
55
 
56
+ ERRSTATE = State.new(:err__)
57
+
53
58
  end
@@ -1,4 +1,4 @@
1
1
  module BabyBots
2
2
  # Current development version
3
- VERSION = "0.0.6"
3
+ VERSION = "0.0.7"
4
4
  end
@@ -6,8 +6,25 @@ $run = BabyBots::State.new(:run, {:else => :run})
6
6
 
7
7
 
8
8
  TEST_MACHINE1 = { :loading => {1 => :ready, :else => :loading},
9
- :ready => {1 => :run, :else => :loading},
10
- :run => {:else => :run} }
9
+ :ready => {1 => :run, :else => :loading},
10
+ :run => {:else => :run} }
11
+
12
+ class ERRTEST < BabyBots::BabyBot
13
+ def initialize
14
+ super
15
+ build({ :loading => {1 => :ready, :else => BabyBots::ERR},
16
+ :ready => {0 => :loading, 1 => :run, :else => BabyBots::ERR},
17
+ :run => {:else => :run} })
18
+ end
19
+
20
+ def error_loading
21
+ "Error loading"
22
+ end
23
+
24
+ def error_ready
25
+ "Error ready"
26
+ end
27
+ end
11
28
 
12
29
  class BB < BabyBots::BabyBot
13
30
  def initialize
@@ -52,117 +69,144 @@ end
52
69
 
53
70
 
54
71
  describe BabyBots::BabyBot do
72
+ before(:each) do
73
+ @test = nil
74
+ @test2 = nil
75
+ end
76
+
77
+ after(:each) do
78
+ @test = nil
79
+ @test2 = nil
80
+ end
81
+
55
82
  it "should be able to be initiated with no additional initialization" do
56
- test = BabyBots::BabyBot.new
57
- test.states.should == {}
83
+ @test = BabyBots::BabyBot.new
84
+ @test.empty?.should == true
85
+ @test = nil
58
86
  end
59
87
 
60
88
  it "should be able to be initialized with a state table" do
61
- test = BabyBots::BabyBot.new(TEST_MACHINE1)
62
- test.states.should == {:loading => $loading, :ready => $ready, :run => $run}
89
+ @test = BabyBots::BabyBot.new(TEST_MACHINE1)
90
+ @test.states.should == {:loading => $loading, :ready => $ready, :run => $run}
63
91
  end
64
92
 
65
93
  it "should have states be able to be added using add_state" do
66
- test = BabyBots::BabyBot.new
67
- test.add_state($loading)
68
- test.states.should == {:loading => $loading}
69
- test.add_state($ready)
70
- test.states.should == {:loading => $loading, :ready => $ready}
71
- test.add_state($run)
72
- test.states.should == {:loading => $loading, :ready => $ready, :run => $run}
94
+ @test = BabyBots::BabyBot.new
95
+ @test.add_state($loading)
96
+ @test.states.should == {:loading => $loading}
97
+ @test.add_state($ready)
98
+ @test.states.should == {:loading => $loading, :ready => $ready}
99
+ @test.add_state($run)
100
+ @test.states.should == {:loading => $loading, :ready => $ready, :run => $run}
73
101
  end
74
102
 
75
103
  it "should be able to have states built using the build method" do
76
- test = BabyBots::BabyBot.new
77
- test.build(TEST_MACHINE1)
104
+ @test = BabyBots::BabyBot.new
105
+ @test.build(TEST_MACHINE1)
78
106
  end
79
107
 
80
108
  it "should allow states to be overwritten using build" do
81
- test = BabyBots::BabyBot.new(TEST_MACHINE1)
82
- test.build({ :loading => {1 => :run, 2 => :loading} })
109
+ @test = BabyBots::BabyBot.new(TEST_MACHINE1)
110
+ @test.build({ :loading => {1 => :run, 2 => :loading} })
83
111
 
84
- test2 = BabyBots::BabyBot.new({ :loading => {1 => :run, 2 => :loading},
112
+ @test2 = BabyBots::BabyBot.new({ :loading => {1 => :run, 2 => :loading},
85
113
  :ready => {1 => :run, :else => :loading},
86
114
  :run => {:else => :run} })
87
- test.states.should == test2.states
115
+ @test.states.should == @test2.states
88
116
  end
89
117
 
90
- it "should run the example, with the starting state being loading" do
91
- test = BB.new
92
- test.start.state.should == :loading
118
+ it "should provide state-based error handling" do
119
+ @test = ERRTEST.new
120
+ msg = @test.process(42)
121
+ msg.should == "Error loading"
122
+ @test.state.should == :loading
123
+ @test.process(1)
124
+ @test.state.should == :ready
125
+ msg = @test.process(42)
126
+ msg.should == "Error ready"
127
+ end
128
+
129
+ it "should run the example, with the starting state being 'loading'" do
130
+ @test = BB.new
131
+ @test.start.state.should == :loading
93
132
  end
94
133
 
95
134
  it "should run the example, with the current state being loading" do
96
- test = BB.new
97
- test.state.should == :loading
135
+ @test = BB.new
136
+ @test.state.should == :loading
98
137
  end
99
138
 
100
139
  it "should run the example, iterating from loading to ready on inputs 1" do
101
- test = BB.new
102
- test.process(1)
103
- test.state.should == :ready
140
+ @test = BB.new
141
+ @test.process(1)
142
+ @test.state.should == :ready
104
143
  end
105
144
 
106
145
  it "should run the example, using pre_loading to convert \"1\" to 1" do
107
- test = BB.new
108
- test.process("1")
109
- test.state.should == :ready
146
+ @test = BB.new
147
+ @test.process("1")
148
+ @test.state.should == :ready
110
149
  end
111
150
 
112
151
  it "should run the example, using pre_loading to convert \"1\" to 1" do
113
- test = BB.new
114
- test.process("1")
115
- test.state.should == :ready
152
+ @test = BB.new
153
+ @test.process("1")
154
+ @test.state.should == :ready
116
155
  end
117
156
 
118
157
  it "should run the example, use pre_loading to convert \"1\" to 1, but use post_loading to return \"1\"" do
119
- test = BB.new
120
- final_var = test.process("1")
121
- test.state.should == :ready
158
+ @test = BB.new
159
+ final_var = @test.process("1")
160
+ @test.state.should == :ready
122
161
  final_var.should == "1"
123
162
  end
124
163
 
125
164
  it "should run the example, and be able to be reset using the restart method" do
126
- test = BB.new
127
- test.process(1)
128
- test.state.should == :ready
129
- test.restart
130
- test.state.should == :loading
165
+ @test = BB.new
166
+ @test.process(1)
167
+ @test.state.should == :ready
168
+ @test.restart
169
+ @test.state.should == :loading
131
170
  end
132
171
 
133
172
  it "should run the example, where anything other than \"1\" or \"0\" looping in :ready" do
134
- test = BB.new
135
- test.process(1)
136
- test.state.should == :ready
137
- test.process("banana phone")
138
- test.state.should == :ready
139
- test.process(99)
140
- test.state.should == :ready
141
- test.process
142
- test.state.should == :ready
173
+ @test = BB.new
174
+ @test.process(1)
175
+ @test.state.should == :ready
176
+ @test.process("banana phone")
177
+ @test.state.should == :ready
178
+ @test.process(99)
179
+ @test.state.should == :ready
180
+ @test.process
181
+ @test.state.should == :ready
143
182
  end
144
183
 
145
184
  it "should run the example, lacking a pre_run should not have any ill side-effects" do
146
- test = BB.new
147
- test.process(1)
148
- test.process(1)
149
- ret1 = test.process("chocolate rain")
150
- ret2 = test.process("gournal")
185
+ @test = BB.new
186
+ @test.process(1)
187
+ @test.process(1)
188
+ ret1 = @test.process("chocolate rain")
189
+ ret2 = @test.process("gournal")
151
190
  ret1.should == ret2
152
191
  ret2.should == true
153
192
  end
154
193
 
155
194
  it "should run the example, and not have issue with a zero-arity method" do
156
- test = BB2.new
157
- test.process(1)
158
- test.process(1)
159
- test.state.should == :run
195
+ @test = BB2.new
196
+ @test.process(1)
197
+ @test.process(1)
198
+ @test.state.should == :run
160
199
  end
161
200
 
162
201
  it "should run the example, and raise an ArgumentError on arity error" do
163
- test = BB3.new
164
- lambda{ test.process(1)}.should raise_error ArgumentError
202
+ @test = BB3.new
203
+ lambda{ @test.process(1)}.should raise_error ArgumentError
165
204
  end
166
205
 
206
+ it "should allow the current state to be checked with the message {state_name}?" do
207
+ @test = BB.new
208
+ @test.loading?.should == true
209
+ @test.not_ready?.should == false
210
+ end
167
211
 
168
212
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: baby_bots
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-07 00:00:00.000000000Z
12
+ date: 2012-05-08 00:00:00.000000000Z
13
13
  dependencies: []
14
14
  description: A tiny finite-state automata library.
15
15
  email: justinanthonyhamilton@gmail.com