fae 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 38505c6f22203783954989812a41a616301ec8fa
4
- data.tar.gz: 8e12a0d7a191acf3b7ae804ae583085d601a1980
3
+ metadata.gz: a353faa342e9405b48cfa833f5069cf5161dc7e4
4
+ data.tar.gz: 67eb919d556ffc2d0dbe6ad08e4f6ac1a703fbe3
5
5
  SHA512:
6
- metadata.gz: 65a2ec14a7d609ddef1c1b96a7f64291c5bb6cfcdbc80a1a79ad68459c6469c85898ba8a592ec1a42364522034ff3d515fca760411c42a3e65afdd465ffa603e
7
- data.tar.gz: 86cd4a012f81591035ab65a5736470544c7c1a69404a8d6df5e284312ca2cf913d8843830cf4d9d10fb8e8f618824868901c687654bb7fa195ed4fb5694dd823
6
+ metadata.gz: 467117d43bb395ad9c0dd1fe975014edc1a57420dc683bbd8ddf98f0f70115b115d841d7cdaf63185dfc85c909840b5b05e5836a08dedf872a8e6e3c6202808a
7
+ data.tar.gz: 6dd10a31af3524a001c508f0177ee2dabdc9980fe246e0a1393960699d4712ceed8ead1bcf8027202c22fde4ca3d36bec1ac9477a64a231a1f1314709c0f114f
data/README.md CHANGED
@@ -138,6 +138,20 @@ valid = fa.evaluate!(true)
138
138
  puts "Diagram is correct" if valid
139
139
  ```
140
140
 
141
+ The add and evaluate methods can be chained also:
142
+
143
+ ```ruby
144
+ states = [
145
+ # Some states...
146
+ ]
147
+
148
+ strings = [
149
+ # Some strings...
150
+ ]
151
+
152
+ fa.add_states(states).add_strings(strings).evaluate!
153
+ ```
154
+
141
155
  ### Generating Tests for your State Diagram
142
156
 
143
157
  If you really want to test your diagram and don't want to have to come up with all of your valid or invalid strings, you can generate valid and invalid strings by setting the `valid_block` as a lambda returning a boolean expression on your Finite Automata that represents a valid string in the language.
@@ -166,6 +180,12 @@ fa.generate_strings(1000, 20)
166
180
  fa.evaluate!
167
181
  ```
168
182
 
183
+ These can be chained also:
184
+
185
+ ```ruby
186
+ fa.generate_strings(1000, 20).evaluate!
187
+ ```
188
+
169
189
  For more examples, see [`examples/advanced.rb`](https://github.com/caseyscarborough/fae/blob/master/examples/advanced.rb)
170
190
 
171
191
  ## When Your State Diagram is Incorrect
@@ -3,6 +3,17 @@ require_relative '../lib/fae'
3
3
  CHARACTERS = ['a', 'b', 'c', 'd']
4
4
  LANGUAGE = Fae::Language.new(CHARACTERS)
5
5
 
6
+ # Patch the string class to make valid lambdas easier to read.
7
+ class String
8
+ def has_at_least_three_cs?
9
+ self.length - 3 >= self.gsub('c', '').length
10
+ end
11
+
12
+ def has_abcd_substring?
13
+ !self.match(/abcd/).nil?
14
+ end
15
+ end
16
+
6
17
  def print_valid_message(valid)
7
18
  message = valid ? "VALID".colorize(:green) : "INVALID".colorize(:red)
8
19
  puts message
@@ -10,9 +21,7 @@ end
10
21
 
11
22
  def run_validation(fa, block)
12
23
  fa.valid_block = block
13
- fa.generate_strings(100, 20)
14
-
15
- valid = fa.evaluate!(true)
24
+ valid = fa.generate_strings(100, 20).evaluate!(true)
16
25
  print_valid_message(valid)
17
26
  end
18
27
 
@@ -51,18 +60,18 @@ run_validation(fa_2, lambda { |string| string.length - 3 >= string.gsub('c', '')
51
60
 
52
61
  # Intersection
53
62
  intersection = fa_1.intersection(fa_2)
54
- run_validation(intersection, lambda do |string|
55
- (string.length - 3 >= string.gsub('c', '').length) && !string.match(/abcd/).nil?
56
- end)
63
+ run_validation(
64
+ intersection, lambda { |s| s.has_abcd_substring? && s.has_at_least_three_cs? }
65
+ )
57
66
 
58
67
  # Union
59
68
  union = fa_1.union(fa_2)
60
- run_validation(union, lambda do |string|
61
- (string.length - 3 >= string.gsub('c', '').length) || !string.match(/abcd/).nil?
62
- end)
69
+ run_validation(
70
+ union, lambda { |s| s.has_abcd_substring? || s.has_at_least_three_cs? }
71
+ )
63
72
 
64
73
  # Difference
65
74
  difference = fa_1.difference(fa_2)
66
- run_validation(difference, lambda do |string|
67
- !string.match(/abcd/).nil? && !(string.length - 3 >= string.gsub('c', '').length)
68
- end)
75
+ run_validation(
76
+ difference, lambda { |s| s.has_abcd_substring? && !s.has_at_least_three_cs? }
77
+ )
@@ -20,32 +20,6 @@ def string_has_bb_substring?(string)
20
20
  !string.match(/bb/).nil?
21
21
  end
22
22
 
23
- def valid_for_intersection?(string)
24
- string_has_odd_number_of_the_letter_a?(string) && string_has_bb_substring?(string)
25
- end
26
-
27
- def valid_for_union?(string)
28
- string_has_odd_number_of_the_letter_a?(string) || string_has_bb_substring?(string)
29
- end
30
-
31
- def valid_for_difference?(string)
32
- string_has_odd_number_of_the_letter_a?(string) && !string_has_bb_substring?(string)
33
- end
34
-
35
- # Returns 100 random strings in the language, 50 of length 15 and 50 of length 5.
36
- def get_random_strings
37
- generate_string = lambda do |num|
38
- string = ""
39
- num.times { string << CHARACTERS.sample }
40
- string
41
- end
42
-
43
- strings = []
44
- 50.times { strings << generate_string.call(15) }
45
- 50.times { strings << generate_string.call(5) }
46
- strings
47
- end
48
-
49
23
  fa_1 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings where the number of a's is odd")
50
24
  fa_2 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings that include the substring 'bb'")
51
25
 
@@ -64,15 +38,21 @@ fa_2.add_states([
64
38
 
65
39
  # Perform the intersection
66
40
  intersection = fa_1.intersection(fa_2)
67
- get_random_strings.each { |s| intersection.add_string(String.new(s, valid_for_intersection?(s))) }
68
- intersection.evaluate!
41
+ intersection.valid_block = lambda do |string|
42
+ string_has_odd_number_of_the_letter_a?(string) && string_has_bb_substring?(string)
43
+ end
44
+ intersection.generate_strings(100, 10).evaluate!
69
45
 
70
46
  # Perform the union
71
47
  union = fa_1.union(fa_2)
72
- get_random_strings.each { |s| union.add_string(String.new(s, valid_for_union?(s))) }
73
- union.evaluate!
48
+ union.valid_block = lambda do |string|
49
+ string_has_odd_number_of_the_letter_a?(string) || string_has_bb_substring?(string)
50
+ end
51
+ union.generate_strings(100, 10).evaluate!
74
52
 
75
53
  # Perform the difference
76
54
  difference = fa_1.difference(fa_2)
77
- get_random_strings.each { |s| difference.add_string(String.new(s, valid_for_difference?(s))) }
78
- union.evaluate!
55
+ difference.valid_block = lambda do |string|
56
+ string_has_odd_number_of_the_letter_a?(string) && !string_has_bb_substring?(string)
57
+ end
58
+ difference.generate_strings(100, 10).evaluate!
data/lib/fae.rb CHANGED
@@ -2,6 +2,7 @@ require 'colorize'
2
2
 
3
3
  # Fae classes
4
4
  require_relative 'string'
5
+ require_relative 'fae/exceptions'
5
6
  require_relative 'fae/language'
6
7
  require_relative 'fae/state'
7
8
  require_relative 'fae/finite_automata'
@@ -0,0 +1,24 @@
1
+ module Fae
2
+ # Generic exception for the Fae library.
3
+ class FaeException < StandardError; end
4
+
5
+ # Raised when Finite Automata doesn't have a valid block
6
+ # and strings are trying to be generated
7
+ class MissingValidBlockException < FaeException; end
8
+
9
+ # Raised when a state couldn't be found in a diagram,
10
+ # which should never happen.
11
+ class StateNotFoundException < FaeException; end
12
+
13
+ # Raised when a duplicate state is trying to be added
14
+ # to a FiniteAutomata.
15
+ class DuplicateStateException < FaeException; end
16
+
17
+ # Raised when trying to evaluate a FiniteAutomata
18
+ # with no states.
19
+ class EmptyStatesException < FaeException; end
20
+
21
+ # Raised when trying to perform a set operation on
22
+ # FiniteAutomata with two different languages.
23
+ class LanguageMismatchException < FaeException; end
24
+ end
@@ -24,9 +24,8 @@ module Fae
24
24
  #
25
25
  # @param strings [Array] an array of strings
26
26
  def add_strings(strings)
27
- strings.each do |string|
28
- @strings << string
29
- end
27
+ strings.each { |string| @strings << string }
28
+ self
30
29
  end
31
30
 
32
31
  # Adds a single string to the strings array.
@@ -35,24 +34,24 @@ module Fae
35
34
  def add_string(string)
36
35
  @strings << string
37
36
  end
38
-
39
- def add_state(new_state)
40
- valid = true
41
- @states.each do |state|
42
- if (new_state.name == state.name)
43
- raise 'Duplicate state added for Finite Automata'
44
- end
45
- end
46
- @states << new_state
47
- end
48
37
 
49
38
  # Adds strings to check against when evaluating.
50
39
  #
51
40
  # @param states [Array] an array of states
52
41
  def add_states(states)
53
- states.each do |state|
54
- add_state(state)
42
+ states.each { |state| add_state(state) }
43
+ self
44
+ end
45
+
46
+ # Adds a single state to the states array.
47
+ #
48
+ # @param new_state [State] the state to add.
49
+ def add_state(new_state)
50
+ valid = true
51
+ @states.each do |state|
52
+ raise Fae::DuplicateStateException, 'Duplicate state added for Finite Automata' if new_state.name == state.name
55
53
  end
54
+ @states << new_state
56
55
  end
57
56
 
58
57
  # Retrieves a state from this finite automata by name.
@@ -67,9 +66,9 @@ module Fae
67
66
  end
68
67
  end
69
68
  if (retrieved_state.nil?)
70
- raise "State #{name} was not found in this Finite Automata. Ensure that all states have outputs for #{@language.characters}"
69
+ raise Fae::StateNotFoundException, "State #{name} was not found in this Finite Automata. Ensure that all states have outputs for #{@language.characters}"
71
70
  end
72
- return retrieved_state
71
+ retrieved_state
73
72
  end
74
73
 
75
74
  # Generates the finite automata for the intersection
@@ -98,32 +97,28 @@ module Fae
98
97
 
99
98
  # Runs the evaluation on the finite automata.
100
99
  def evaluate!(suppress_output=false)
101
- output = ""
100
+ raise Fae::EmptyStatesException, 'You must add some states to your Finite Automata before checking strings' if @states.length == 0
101
+
102
+ output = "Evaluating strings for #{@description} using language #{@language.characters}".colorize(:yellow)
102
103
  @invalids = []
103
- if (@states.length == 0)
104
- raise "You must add some states to your Finite Automata before checking strings"
105
- end
106
104
 
107
- output << "Evaluating strings for #{@description} using language #{@language.characters}".colorize(:yellow)
108
105
  @strings.each do |string|
109
106
  result = evaluate_string(string)
110
- if (!result[:valid])
111
- @invalids << string
112
- end
113
- output << result[:output]
107
+ @invalids << string if !result[:valid]
108
+ output << result[:output]
114
109
  end
115
110
 
116
111
  num_invalid = @invalids.length
117
112
  valid = num_invalid == 0
118
- if (!valid)
119
- output << "\nState diagram may be incorrect for #{@description}".colorize(:red)
120
- output << "\n\nA total of #{num_invalid} string#{'s' if num_invalid != 1} did not meet your expectations:\n"
113
+ if !valid
114
+ output << "\nState diagram may be incorrect for #{@description}\n".colorize(:red)
115
+ output << "\nA total of #{num_invalid} string#{'s' if num_invalid != 1} did not meet your expectations:\n\n"
121
116
 
122
117
  @invalids.each do |string|
123
118
  expected_string = string.expected ? "valid".colorize(:green) : "invalid".colorize(:red)
124
- output << "\n* You expected the string '#{string.colorize(:blue)}' to be #{expected_string}"
119
+ output << "* You expected the string '#{string.colorize(:blue)}' to be #{expected_string}\n"
125
120
  end
126
- output << "\n\nIf these expectations are correct, then your state diagram needs revising. Otherwise, you've simply expected the wrong values."
121
+ output << "\nIf these expectations are correct, then your state diagram needs revising. Otherwise, you've simply expected the wrong values."
127
122
  else
128
123
  output << "\nState diagram is correct.\n".colorize(:green)
129
124
  end
@@ -131,27 +126,29 @@ module Fae
131
126
  if (!suppress_output)
132
127
  puts output
133
128
  end
134
- return valid
129
+ valid
135
130
  end
136
131
 
132
+ # Generates strings for the finite automata and adds them
133
+ # to the strings array.
134
+ #
135
+ # @param number [Integer] number of strings
136
+ # @param length [Integer] length of each string
137
137
  def generate_strings(number, length)
138
- if valid_block.nil?
139
- raise 'You must set the valid block for the Finite Automata to generate strings'
140
- end
141
-
138
+ raise Fae::MissingValidBlockException, 'You must set the valid block for the Finite Automata to generate strings' if valid_block.nil?
139
+
142
140
  strings = []
143
141
  number.times do
144
142
  string = ""
145
143
  length.times { string << @language.characters.sample }
146
144
  strings << String.new(string, valid_block.call(string))
147
145
  end
148
- add_strings(strings)
149
- strings
146
+ self.add_strings(strings)
147
+ self
150
148
  end
151
149
 
152
150
  def to_s
153
- output = ""
154
- output << "Description: ".colorize(:yellow) + @description
151
+ output = "Description: ".colorize(:yellow) + @description
155
152
  output << "\nLanguage: ".colorize(:yellow) + language.characters.to_s
156
153
  output << "\nStates:".colorize(:yellow)
157
154
  @states.each do |state|
@@ -162,19 +159,19 @@ module Fae
162
159
  accepting = state.accepting ? "accepting".colorize(:green) : "not accepting".colorize(:red)
163
160
  output << "\n #{'~'.colorize(:yellow)} #{accepting}"
164
161
  end
165
- return output
162
+ output
166
163
  end
167
164
 
168
165
  private
169
166
  def perform_set_operation(type, fa)
170
- if (@language.characters.uniq.sort != fa.language.characters.uniq.sort)
171
- puts "Performing the #{type.to_s} requires the languages to be the same.".colorize(:red)
172
- return
167
+ if @language.characters.uniq.sort != fa.language.characters.uniq.sort
168
+ raise LanguageMismatchException, "Performing the #{type.to_s} requires the languages to be the same.".colorize(:red)
173
169
  end
174
- i_states = []
175
- i_strings = []
176
- i_description = "the #{type.to_s} of #{@description} and #{fa.description}"
177
- i_fa = FiniteAutomata.new(@language, i_description)
170
+
171
+ states = []
172
+ strings = []
173
+ description = "the #{type.to_s} of #{@description} and #{fa.description}"
174
+ new_fa = FiniteAutomata.new(@language, description)
178
175
 
179
176
  @states.each do |state|
180
177
  fa.states.each do |fa_state|
@@ -190,30 +187,30 @@ module Fae
190
187
  (type == :union && (state.accepting || fa_state.accepting)))
191
188
  accepting = true
192
189
  end
193
- i_states << State.new(name, paths, accepting)
190
+ states << State.new(name, paths, accepting)
194
191
  end
195
192
  end
196
193
 
197
- i_fa.add_states(i_states)
198
- return i_fa
194
+ new_fa.add_states(states)
195
+ new_fa
199
196
  end
200
197
 
201
198
  def evaluate_string(string)
199
+ valid = true
202
200
  output = "\n#{string.colorize(:blue)}: "
203
201
  if (@language.string_is_valid(string))
204
202
  result = @states.first.evaluate(string, self)
205
203
  output << result[:output]
206
204
  if (result[:accepting] != string.expected)
207
205
  output << "\u2717".encode("utf-8").colorize(:red)
208
- return { :valid => false, :output => output }
206
+ valid = false
209
207
  else
210
208
  output << "\u2713".encode("utf-8").colorize(:green)
211
- return { :valid => true, :output => output }
212
209
  end
213
210
  else
214
211
  output << "not valid for language #{@characters}".colorize(:red)
215
- return { :valid => true, :output => output }
216
212
  end
213
+ return { :valid => valid, :output => output }
217
214
  end
218
215
  end
219
216
  end
@@ -17,12 +17,7 @@ module Fae
17
17
  # @param string [String] the string to check
18
18
  def string_is_valid(string)
19
19
  # Use lookahead to check for valid string
20
- regex = "^(?=.*\\D)[#{@characters.join('|')}]+$"
21
-
22
- if string.match /#{regex}/
23
- return true
24
- end
25
- return false
20
+ string.match "^(?=.*\\D)[#{@characters.join('|')}]+$"
26
21
  end
27
22
 
28
23
  # Adds a character to the language.
@@ -1,3 +1,3 @@
1
1
  module Fae
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fae
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Casey Scarborough
@@ -77,6 +77,7 @@ files:
77
77
  - examples/simple.rb
78
78
  - fae.gemspec
79
79
  - lib/fae.rb
80
+ - lib/fae/exceptions.rb
80
81
  - lib/fae/finite_automata.rb
81
82
  - lib/fae/language.rb
82
83
  - lib/fae/state.rb