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 +4 -4
- data/README.md +42 -4
- data/examples/advanced.rb +68 -0
- data/examples/{example.rb → simple.rb} +0 -0
- data/lib/fae/finite_automata.rb +52 -28
- data/lib/fae/language.rb +2 -2
- data/lib/fae/state.rb +6 -4
- data/lib/fae/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38505c6f22203783954989812a41a616301ec8fa
|
4
|
+
data.tar.gz: 8e12a0d7a191acf3b7ae804ae583085d601a1980
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
##
|
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.
|
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
|
-
##
|
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
|
data/lib/fae/finite_automata.rb
CHANGED
@@ -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
|
-
|
107
|
+
output << "Evaluating strings for #{@description} using language #{@language.characters}".colorize(:yellow)
|
96
108
|
@strings.each do |string|
|
97
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
124
|
+
output << "\n* You expected the string '#{string.colorize(:blue)}' to be #{expected_string}"
|
111
125
|
end
|
112
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
184
|
-
return false
|
207
|
+
output << "\u2717".encode("utf-8").colorize(:red)
|
208
|
+
return { :valid => false, :output => output }
|
185
209
|
else
|
186
|
-
|
187
|
-
return true
|
210
|
+
output << "\u2713".encode("utf-8").colorize(:green)
|
211
|
+
return { :valid => true, :output => output }
|
188
212
|
end
|
189
213
|
else
|
190
|
-
|
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
|
data/lib/fae/language.rb
CHANGED
@@ -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
|
22
|
+
if string.match /#{regex}/
|
23
23
|
return true
|
24
24
|
end
|
25
25
|
return false
|
data/lib/fae/state.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
data/lib/fae/version.rb
CHANGED
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.
|
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
|