fae 0.2.0 → 0.2.1

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: 0f109cfeb573be9efac846742d47aa671c7c1839
4
- data.tar.gz: 04c261c86d6381f25b127c254687df95bb43a6be
3
+ metadata.gz: 38505c6f22203783954989812a41a616301ec8fa
4
+ data.tar.gz: 8e12a0d7a191acf3b7ae804ae583085d601a1980
5
5
  SHA512:
6
- metadata.gz: 4a28786b8b6c65b38cf8660d7368749ccf283e211308e06698bc87bc85526fd523f2bea7741295f781c280a9a1078b66c1506752ea524a1f724304c6cbc002c5
7
- data.tar.gz: c6369d569cfb2a877ccb3f6c90b37070184e53dd23228cadb292bd20becbb0d896eee936cef2f998e3767a60835f18c35e64e440c958ae6d8fde73e86864e2d1
6
+ metadata.gz: 65a2ec14a7d609ddef1c1b96a7f64291c5bb6cfcdbc80a1a79ad68459c6469c85898ba8a592ec1a42364522034ff3d515fca760411c42a3e65afdd465ffa603e
7
+ data.tar.gz: 86cd4a012f81591035ab65a5736470544c7c1a69404a8d6df5e284312ca2cf913d8843830cf4d9d10fb8e8f618824868901c687654bb7fa195ed4fb5694dd823
data/README.md CHANGED
@@ -18,7 +18,7 @@ Install the gem by running the following command:
18
18
  gem install fae
19
19
  ```
20
20
 
21
- ## Evaluation Usage
21
+ ## Evaluating a Diagram
22
22
 
23
23
  The gem comes with an executable, `fae`, and can be run in two different modes, interactive or file.
24
24
 
@@ -99,7 +99,9 @@ Here is an example output:
99
99
 
100
100
  ## Using the API
101
101
 
102
- You can also use the library directly if you'd rather run your own scripts. Here is an example usage:
102
+ You can also use the library directly if you'd rather run your own scripts.
103
+
104
+ ### Basic Usage
103
105
 
104
106
  ```rb
105
107
  require 'fae'
@@ -130,8 +132,42 @@ fa.add_strings([
130
132
 
131
133
  # Run the evaluation
132
134
  fa.evaluate!
135
+
136
+ # Run the evaluation with no output
137
+ valid = fa.evaluate!(true)
138
+ puts "Diagram is correct" if valid
139
+ ```
140
+
141
+ ### Generating Tests for your State Diagram
142
+
143
+ 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.
144
+
145
+ For example, in the language of all strings that have an odd number of a's, the following code would represent a valid string:
146
+
147
+ ```ruby
148
+ # If the value is one when we take away all b's and mod the
149
+ # length by two, then we had an odd number of a's
150
+ string.gsub('b', '').length % 2 == 1
151
+ ```
152
+
153
+ Here is a full example that would test 1000 strings against your diagram:
154
+
155
+ ```ruby
156
+ require 'fae'
157
+
158
+ language = Fae::Language.new(['a', 'b'])
159
+ fa = Fae::FiniteAutomata.new(language, "strings that have an odd number of a's")
160
+
161
+ # Set the valid block
162
+ fa.valid_block = lambda { |string| string.gsub('b', '').length % 2 == 1 }
163
+
164
+ # Generate 1000 strings of length 20 and evaluate:
165
+ fa.generate_strings(1000, 20)
166
+ fa.evaluate!
133
167
  ```
134
168
 
169
+ For more examples, see [`examples/advanced.rb`](https://github.com/caseyscarborough/fae/blob/master/examples/advanced.rb)
170
+
135
171
  ## When Your State Diagram is Incorrect
136
172
 
137
173
  If your state diagram is incorrect, the program will give you feedback about your diagram:
@@ -140,11 +176,13 @@ If your state diagram is incorrect, the program will give you feedback about you
140
176
 
141
177
  This can help you figure out where your diagram is going wrong.
142
178
 
143
- ## Generation Usage
179
+ ## Generating a Diagram
144
180
 
145
181
  You can use the gem to generate the union, intersection, or difference of two state diagrams. See the following:
146
182
 
147
183
  ```ruby
184
+ require 'fae'
185
+
148
186
  fa_1 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings where the number of a's is odd")
149
187
  fa_2 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings that include the substring 'bb'")
150
188
 
@@ -175,7 +213,7 @@ puts intersection
175
213
 
176
214
  ![Example Intersection Output](https://raw.githubusercontent.com/caseyscarborough/fae/master/etc/example_intersection_output.png)
177
215
 
178
- See [`intersection_union_difference.rb`](https://github.com/caseyscarborough/fae/blob/master/examples/intersection_union_difference.rb) for an example of generating the diagram and evaluating them.
216
+ See [`examples/intersection_union_difference.rb`](https://github.com/caseyscarborough/fae/blob/master/examples/intersection_union_difference.rb) for an example of generating the diagram and evaluating them.
179
217
 
180
218
  ## Examples
181
219
 
@@ -0,0 +1,68 @@
1
+ require_relative '../lib/fae'
2
+
3
+ CHARACTERS = ['a', 'b', 'c', 'd']
4
+ LANGUAGE = Fae::Language.new(CHARACTERS)
5
+
6
+ def print_valid_message(valid)
7
+ message = valid ? "VALID".colorize(:green) : "INVALID".colorize(:red)
8
+ puts message
9
+ end
10
+
11
+ def run_validation(fa, block)
12
+ fa.valid_block = block
13
+ fa.generate_strings(100, 20)
14
+
15
+ valid = fa.evaluate!(true)
16
+ print_valid_message(valid)
17
+ end
18
+
19
+ # Language 1
20
+ #
21
+ # The first Finite Automata is the language of all
22
+ # strings that contain the substring 'abcd'.
23
+ #
24
+ fa_1 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings that contain the substring 'abcd'")
25
+
26
+ states = [
27
+ { :name => 'A', :paths => { :a => 'B', :b => 'A', :c => 'A', :d => 'A'}, :accepting => false },
28
+ { :name => 'B', :paths => { :a => 'B', :b => 'C', :c => 'A', :d => 'A'}, :accepting => false },
29
+ { :name => 'C', :paths => { :a => 'B', :b => 'A', :c => 'D', :d => 'A'}, :accepting => false },
30
+ { :name => 'D', :paths => { :a => 'B', :b => 'A', :c => 'A', :d => 'E'}, :accepting => false },
31
+ { :name => 'E', :paths => { :a => 'E', :b => 'E', :c => 'E', :d => 'E'}, :accepting => true },
32
+ ]
33
+ states.each { |s| fa_1.add_state(Fae::State.new(s[:name], s[:paths], s[:accepting])) }
34
+ run_validation(fa_1, lambda { |string| !string.match(/abcd/).nil? })
35
+
36
+ # Language 2
37
+ #
38
+ # The second Finite Automata is the language of all
39
+ # strings that contain more than three c's
40
+ #
41
+ fa_2 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings that contain more than 3 c's")
42
+
43
+ states = [
44
+ { :name => 'F', :paths => { :a => 'F', :b => 'F', :c => 'G', :d => 'F' }, :accepting => false },
45
+ { :name => 'G', :paths => { :a => 'G', :b => 'G', :c => 'H', :d => 'G' }, :accepting => false },
46
+ { :name => 'H', :paths => { :a => 'H', :b => 'H', :c => 'I', :d => 'H' }, :accepting => false },
47
+ { :name => 'I', :paths => { :a => 'I', :b => 'I', :c => 'I', :d => 'I' }, :accepting => true },
48
+ ]
49
+ states.each { |s| fa_2.add_state(Fae::State.new(s[:name], s[:paths], s[:accepting])) }
50
+ run_validation(fa_2, lambda { |string| string.length - 3 >= string.gsub('c', '').length })
51
+
52
+ # Intersection
53
+ 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)
57
+
58
+ # Union
59
+ 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)
63
+
64
+ # Difference
65
+ 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)
File without changes
@@ -6,6 +6,7 @@ module Fae
6
6
  # to validate a state diagram.
7
7
  class FiniteAutomata
8
8
  attr_reader :language, :description, :states, :strings
9
+ attr_accessor :valid_block
9
10
 
10
11
  # Initializes a new instance of the FiniteAutomata.
11
12
  #
@@ -34,6 +35,16 @@ module Fae
34
35
  def add_string(string)
35
36
  @strings << string
36
37
  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
37
48
 
38
49
  # Adds strings to check against when evaluating.
39
50
  #
@@ -86,34 +97,56 @@ module Fae
86
97
  end
87
98
 
88
99
  # Runs the evaluation on the finite automata.
89
- def evaluate!
100
+ def evaluate!(suppress_output=false)
101
+ output = ""
90
102
  @invalids = []
91
103
  if (@states.length == 0)
92
104
  raise "You must add some states to your Finite Automata before checking strings"
93
105
  end
94
106
 
95
- puts """Evaluating strings for #{@description} using language #{@language.characters}".colorize(:yellow)
107
+ output << "Evaluating strings for #{@description} using language #{@language.characters}".colorize(:yellow)
96
108
  @strings.each do |string|
97
- valid = evaluate_string(string)
98
- if (!valid)
109
+ result = evaluate_string(string)
110
+ if (!result[:valid])
99
111
  @invalids << string
100
112
  end
113
+ output << result[:output]
101
114
  end
102
115
 
103
116
  num_invalid = @invalids.length
104
- if (num_invalid > 0)
105
- puts "State diagram may be incorrect for #{@description}".colorize(:red)
106
- puts "\nA total of #{num_invalid} string#{'s' if num_invalid != 1} did not meet your expectations:\n"
117
+ 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"
107
121
 
108
122
  @invalids.each do |string|
109
123
  expected_string = string.expected ? "valid".colorize(:green) : "invalid".colorize(:red)
110
- puts "* You expected the string '#{string.colorize(:blue)}' to be #{expected_string}"
124
+ output << "\n* You expected the string '#{string.colorize(:blue)}' to be #{expected_string}"
111
125
  end
112
- puts "\nIf these expectations are correct, then your state diagram needs revising. Otherwise, you've simply expected the wrong values."
126
+ output << "\n\nIf these expectations are correct, then your state diagram needs revising. Otherwise, you've simply expected the wrong values."
113
127
  else
114
- puts "State diagram is correct.".colorize(:green)
128
+ output << "\nState diagram is correct.\n".colorize(:green)
129
+ end
130
+
131
+ if (!suppress_output)
132
+ puts output
133
+ end
134
+ return valid
135
+ end
136
+
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
+
142
+ strings = []
143
+ number.times do
144
+ string = ""
145
+ length.times { string << @language.characters.sample }
146
+ strings << String.new(string, valid_block.call(string))
115
147
  end
116
- puts
148
+ add_strings(strings)
149
+ strings
117
150
  end
118
151
 
119
152
  def to_s
@@ -165,30 +198,21 @@ module Fae
165
198
  return i_fa
166
199
  end
167
200
 
168
- def add_state(new_state)
169
- valid = true
170
- @states.each do |state|
171
- if (new_state.name == state.name)
172
- raise 'Duplicate state added for Finite Automata'
173
- end
174
- end
175
- @states << new_state
176
- end
177
-
178
201
  def evaluate_string(string)
179
- print "#{string.colorize(:blue)}: "
202
+ output = "\n#{string.colorize(:blue)}: "
180
203
  if (@language.string_is_valid(string))
181
204
  result = @states.first.evaluate(string, self)
205
+ output << result[:output]
182
206
  if (result[:accepting] != string.expected)
183
- puts "\u2717".encode("utf-8").colorize(:red)
184
- return false
207
+ output << "\u2717".encode("utf-8").colorize(:red)
208
+ return { :valid => false, :output => output }
185
209
  else
186
- puts "\u2713".encode("utf-8").colorize(:green)
187
- return true
210
+ output << "\u2713".encode("utf-8").colorize(:green)
211
+ return { :valid => true, :output => output }
188
212
  end
189
213
  else
190
- puts "not valid for language #{@characters}".colorize(:red)
191
- return true
214
+ output << "not valid for language #{@characters}".colorize(:red)
215
+ return { :valid => true, :output => output }
192
216
  end
193
217
  end
194
218
  end
@@ -7,7 +7,7 @@ module Fae
7
7
  # Creates a new language instance.
8
8
  #
9
9
  # @param characters [Array] an array of characters
10
- def initialize(characters)
10
+ def initialize(characters, valid_block=nil)
11
11
  @characters = []
12
12
  @characters = characters.uniq
13
13
  end
@@ -19,7 +19,7 @@ module Fae
19
19
  # Use lookahead to check for valid string
20
20
  regex = "^(?=.*\\D)[#{@characters.join('|')}]+$"
21
21
 
22
- if (string.match /#{regex}/)
22
+ if string.match /#{regex}/
23
23
  return true
24
24
  end
25
25
  return false
@@ -22,16 +22,18 @@ module Fae
22
22
  # @param string [String] the string to evaluate
23
23
  # @param fa [FiniteAutomata] the finite automata that this state belongs to
24
24
  def evaluate(string, fa)
25
+ output = ""
25
26
  if (string.first.empty?)
26
- output = @accepting ? "accepting".colorize(:green) : "not accepting".colorize(:red)
27
- print "#{@name} (#{output}) "
27
+ output << "#{@name} (#{@accepting ? 'accepting'.colorize(:green) : 'not accepting'.colorize(:red)}) "
28
28
  return { :output => output, :accepting => @accepting }
29
29
  end
30
- print "#{@name} #{'->'.colorize(:light_black)} "
30
+ output << "#{@name} #{'->'.colorize(:light_black)} "
31
31
 
32
32
  next_state = fa.get_state(paths[string.first.to_sym])
33
33
  next_string = string.shift_left
34
- next_state.evaluate(next_string, fa)
34
+ result = next_state.evaluate(next_string, fa)
35
+ output << result[:output]
36
+ return { :output => output, :accepting => result[:accepting] }
35
37
  end
36
38
  end
37
39
  end
@@ -1,3 +1,3 @@
1
1
  module Fae
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
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.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Casey Scarborough
@@ -71,9 +71,10 @@ files:
71
71
  - etc/example_interactive_mode_output.png
72
72
  - etc/example_intersection_output.png
73
73
  - etc/example_state_diagram.png
74
+ - examples/advanced.rb
74
75
  - examples/diagrams.yml
75
- - examples/example.rb
76
76
  - examples/intersection_union_difference.rb
77
+ - examples/simple.rb
77
78
  - fae.gemspec
78
79
  - lib/fae.rb
79
80
  - lib/fae/finite_automata.rb