fae 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -13
- data/README.md +41 -2
- data/etc/example_intersection_output.png +0 -0
- data/examples/intersection_union_difference.rb +78 -0
- data/lib/fae/finite_automata.rb +81 -1
- data/lib/fae/state.rb +11 -8
- data/lib/fae/version.rb +1 -1
- metadata +13 -11
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
ZDVmMDY1NTE0ZTRiZTFlMzc2YzUyYTViYmI3NDAzYmUxZDM1OTg0MQ==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0f109cfeb573be9efac846742d47aa671c7c1839
|
4
|
+
data.tar.gz: 04c261c86d6381f25b127c254687df95bb43a6be
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
ZjFlMzIzMzk5YmRjNDkzOWMxMzM2ZWVmMjIwZGEyZjRjYTY1MGQxYzkwMWRi
|
11
|
-
MTY4NzdlOTQ3ZDAwZDVlMmEyMWYyZmM5ZTRmZGIzNzAxMmVkMmY=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
MjA0NmJiZTFmYWQ3Njk0OTc0YzRlM2ZlM2YxNDljYjM2NGIyOTA0MDM4MzIy
|
14
|
-
OTA1OTNjMzhhNmZiNDI5NzRkYTA1OGNkYTYxY2YyMGM4NTU0YjhhMjNlYTQy
|
15
|
-
ZjEwZmZhYjE5NTIwNTM2OGQ1OWU5YzJiZTliMzIyNDA5NjQwNjg=
|
6
|
+
metadata.gz: 4a28786b8b6c65b38cf8660d7368749ccf283e211308e06698bc87bc85526fd523f2bea7741295f781c280a9a1078b66c1506752ea524a1f724304c6cbc002c5
|
7
|
+
data.tar.gz: c6369d569cfb2a877ccb3f6c90b37070184e53dd23228cadb292bd20becbb0d896eee936cef2f998e3767a60835f18c35e64e440c958ae6d8fde73e86864e2d1
|
data/README.md
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/fae)
|
4
4
|
|
5
|
-
This is a small Ruby gem that evaluates a Finite Automata State Diagram for validity.
|
5
|
+
This is a small Ruby gem that evaluates a Finite Automata State Diagram for validity, and can generate Finite Automata from set operations of two or more different Finite Automata.
|
6
|
+
|
7
|
+
The gem currently only handles Deterministic Finite Automata.
|
6
8
|
|
7
9
|
## Dependencies
|
8
10
|
|
@@ -16,7 +18,7 @@ Install the gem by running the following command:
|
|
16
18
|
gem install fae
|
17
19
|
```
|
18
20
|
|
19
|
-
## Usage
|
21
|
+
## Evaluation Usage
|
20
22
|
|
21
23
|
The gem comes with an executable, `fae`, and can be run in two different modes, interactive or file.
|
22
24
|
|
@@ -138,6 +140,43 @@ If your state diagram is incorrect, the program will give you feedback about you
|
|
138
140
|
|
139
141
|
This can help you figure out where your diagram is going wrong.
|
140
142
|
|
143
|
+
## Generation Usage
|
144
|
+
|
145
|
+
You can use the gem to generate the union, intersection, or difference of two state diagrams. See the following:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
fa_1 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings where the number of a's is odd")
|
149
|
+
fa_2 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings that include the substring 'bb'")
|
150
|
+
|
151
|
+
# These states were determined by drawing the state diagram.
|
152
|
+
fa_1.add_states([
|
153
|
+
Fae::State.new('A', { :a => 'B', :b => 'A' }, false),
|
154
|
+
Fae::State.new('B', { :a => 'A', :b => 'B' }, true),
|
155
|
+
])
|
156
|
+
|
157
|
+
# These states were determined by drawing the state diagram.
|
158
|
+
fa_2.add_states([
|
159
|
+
Fae::State.new('C', { :a => 'C', :b => 'D' }, false),
|
160
|
+
Fae::State.new('D', { :a => 'C', :b => 'E' }, false),
|
161
|
+
Fae::State.new('E', { :a => 'E', :b => 'E' }, true),
|
162
|
+
])
|
163
|
+
|
164
|
+
# Get the intersection, union, and difference of the two finite automata
|
165
|
+
intersection = fa_1.intersection(fa_2)
|
166
|
+
union = fa_1.union(fa_2)
|
167
|
+
difference = fa_1.difference(fa_2)
|
168
|
+
```
|
169
|
+
|
170
|
+
You can then output the new finite automata to see the states, paths, and accepting states:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
puts intersection
|
174
|
+
```
|
175
|
+
|
176
|
+

|
177
|
+
|
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.
|
179
|
+
|
141
180
|
## Examples
|
142
181
|
|
143
182
|
An example data file and direct API usage can be seen in the [examples directory](https://github.com/caseyscarborough/fae/tree/master/examples).
|
Binary file
|
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This script demonstrates the usage of the fae API and
|
4
|
+
# checks the state diagram for the intersection, union,
|
5
|
+
# and difference of the following two languages:
|
6
|
+
#
|
7
|
+
# - The language of all strings with an odd number of a's
|
8
|
+
# - The language of all strings that include the substring 'bb'
|
9
|
+
#
|
10
|
+
require_relative '../lib/fae'
|
11
|
+
|
12
|
+
CHARACTERS = ['a', 'b']
|
13
|
+
LANGUAGE = Fae::Language.new(CHARACTERS)
|
14
|
+
|
15
|
+
def string_has_odd_number_of_the_letter_a?(string)
|
16
|
+
string.gsub('b', '').length % 2 == 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def string_has_bb_substring?(string)
|
20
|
+
!string.match(/bb/).nil?
|
21
|
+
end
|
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
|
+
fa_1 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings where the number of a's is odd")
|
50
|
+
fa_2 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings that include the substring 'bb'")
|
51
|
+
|
52
|
+
# These states were determined by drawing the state diagram language 1.
|
53
|
+
fa_1.add_states([
|
54
|
+
Fae::State.new('A', { :a => 'B', :b => 'A' }, false),
|
55
|
+
Fae::State.new('B', { :a => 'A', :b => 'B' }, true),
|
56
|
+
])
|
57
|
+
|
58
|
+
# These states were determined by drawing the state diagram for language 2.
|
59
|
+
fa_2.add_states([
|
60
|
+
Fae::State.new('C', { :a => 'C', :b => 'D' }, false),
|
61
|
+
Fae::State.new('D', { :a => 'C', :b => 'E' }, false),
|
62
|
+
Fae::State.new('E', { :a => 'E', :b => 'E' }, true),
|
63
|
+
])
|
64
|
+
|
65
|
+
# Perform the intersection
|
66
|
+
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!
|
69
|
+
|
70
|
+
# Perform the union
|
71
|
+
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!
|
74
|
+
|
75
|
+
# Perform the difference
|
76
|
+
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!
|
data/lib/fae/finite_automata.rb
CHANGED
@@ -5,6 +5,7 @@ module Fae
|
|
5
5
|
# Takes in a language, states, and strings, and checks them
|
6
6
|
# to validate a state diagram.
|
7
7
|
class FiniteAutomata
|
8
|
+
attr_reader :language, :description, :states, :strings
|
8
9
|
|
9
10
|
# Initializes a new instance of the FiniteAutomata.
|
10
11
|
#
|
@@ -27,6 +28,13 @@ module Fae
|
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
31
|
+
# Adds a single string to the strings array.
|
32
|
+
#
|
33
|
+
# @param string [String] the string to add
|
34
|
+
def add_string(string)
|
35
|
+
@strings << string
|
36
|
+
end
|
37
|
+
|
30
38
|
# Adds strings to check against when evaluating.
|
31
39
|
#
|
32
40
|
# @param states [Array] an array of states
|
@@ -53,6 +61,30 @@ module Fae
|
|
53
61
|
return retrieved_state
|
54
62
|
end
|
55
63
|
|
64
|
+
# Generates the finite automata for the intersection
|
65
|
+
# of two different finite automatas.
|
66
|
+
#
|
67
|
+
# @param fa [FiniteAutomata] the finite automata to intersect this one with
|
68
|
+
def intersection(fa)
|
69
|
+
perform_set_operation(:intersection, fa)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Generates the finite automata for the union
|
73
|
+
# of two different finite automatas.
|
74
|
+
#
|
75
|
+
# @param fa [FiniteAutomata] the finite automata to union this one with
|
76
|
+
def union(fa)
|
77
|
+
perform_set_operation(:union, fa)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Generates the finite automata for the difference
|
81
|
+
# of two different finite automatas.
|
82
|
+
#
|
83
|
+
# @param fa [FiniteAutomata] the finite automata to difference this one with
|
84
|
+
def difference(fa)
|
85
|
+
perform_set_operation(:difference, fa)
|
86
|
+
end
|
87
|
+
|
56
88
|
# Runs the evaluation on the finite automata.
|
57
89
|
def evaluate!
|
58
90
|
@invalids = []
|
@@ -84,7 +116,55 @@ module Fae
|
|
84
116
|
puts
|
85
117
|
end
|
86
118
|
|
119
|
+
def to_s
|
120
|
+
output = ""
|
121
|
+
output << "Description: ".colorize(:yellow) + @description
|
122
|
+
output << "\nLanguage: ".colorize(:yellow) + language.characters.to_s
|
123
|
+
output << "\nStates:".colorize(:yellow)
|
124
|
+
@states.each do |state|
|
125
|
+
output << "\n State #{state.name}: ".colorize(:blue)
|
126
|
+
state.paths.keys.each do |key|
|
127
|
+
output << "\n #{'~'.colorize(:yellow)} #{key} #{'->'.colorize(:light_black)} #{state.paths[key]}"
|
128
|
+
end
|
129
|
+
accepting = state.accepting ? "accepting".colorize(:green) : "not accepting".colorize(:red)
|
130
|
+
output << "\n #{'~'.colorize(:yellow)} #{accepting}"
|
131
|
+
end
|
132
|
+
return output
|
133
|
+
end
|
134
|
+
|
87
135
|
private
|
136
|
+
def perform_set_operation(type, fa)
|
137
|
+
if (@language.characters.uniq.sort != fa.language.characters.uniq.sort)
|
138
|
+
puts "Performing the #{type.to_s} requires the languages to be the same.".colorize(:red)
|
139
|
+
return
|
140
|
+
end
|
141
|
+
i_states = []
|
142
|
+
i_strings = []
|
143
|
+
i_description = "the #{type.to_s} of #{@description} and #{fa.description}"
|
144
|
+
i_fa = FiniteAutomata.new(@language, i_description)
|
145
|
+
|
146
|
+
@states.each do |state|
|
147
|
+
fa.states.each do |fa_state|
|
148
|
+
name = state.name + fa_state.name
|
149
|
+
accepting = false
|
150
|
+
paths = {}
|
151
|
+
state.paths.keys.each do |key|
|
152
|
+
path = state.paths[key] + fa_state.paths[key]
|
153
|
+
paths[key] = path
|
154
|
+
end
|
155
|
+
if ((type == :intersection && (state.accepting && fa_state.accepting)) ||
|
156
|
+
(type == :difference && (state.accepting && !fa_state.accepting )) ||
|
157
|
+
(type == :union && (state.accepting || fa_state.accepting)))
|
158
|
+
accepting = true
|
159
|
+
end
|
160
|
+
i_states << State.new(name, paths, accepting)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
i_fa.add_states(i_states)
|
165
|
+
return i_fa
|
166
|
+
end
|
167
|
+
|
88
168
|
def add_state(new_state)
|
89
169
|
valid = true
|
90
170
|
@states.each do |state|
|
@@ -99,7 +179,7 @@ module Fae
|
|
99
179
|
print "#{string.colorize(:blue)}: "
|
100
180
|
if (@language.string_is_valid(string))
|
101
181
|
result = @states.first.evaluate(string, self)
|
102
|
-
if (result[:
|
182
|
+
if (result[:accepting] != string.expected)
|
103
183
|
puts "\u2717".encode("utf-8").colorize(:red)
|
104
184
|
return false
|
105
185
|
else
|
data/lib/fae/state.rb
CHANGED
@@ -2,7 +2,7 @@ module Fae
|
|
2
2
|
|
3
3
|
# A state in the state diagram.
|
4
4
|
class State
|
5
|
-
attr_accessor :name, :
|
5
|
+
attr_accessor :name, :paths, :accepting
|
6
6
|
|
7
7
|
# Creates a new state instance.
|
8
8
|
#
|
@@ -11,24 +11,27 @@ module Fae
|
|
11
11
|
# @param valid [Boolean] whether or not this is an accepting state
|
12
12
|
# @example
|
13
13
|
# State.new('A', { :a => 'B', :b => 'A' }, true)
|
14
|
-
def initialize(name,
|
14
|
+
def initialize(name, paths, accepting)
|
15
15
|
@name = name
|
16
|
-
@
|
17
|
-
@
|
16
|
+
@paths = paths
|
17
|
+
@accepting = accepting
|
18
18
|
end
|
19
19
|
|
20
|
-
# Evaluates a string at this state, and passes
|
20
|
+
# Evaluates a string at this state, and passes the next string to the next state.
|
21
21
|
#
|
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
25
|
if (string.first.empty?)
|
26
|
-
output = @
|
26
|
+
output = @accepting ? "accepting".colorize(:green) : "not accepting".colorize(:red)
|
27
27
|
print "#{@name} (#{output}) "
|
28
|
-
return { :output => output, :
|
28
|
+
return { :output => output, :accepting => @accepting }
|
29
29
|
end
|
30
30
|
print "#{@name} #{'->'.colorize(:light_black)} "
|
31
|
-
|
31
|
+
|
32
|
+
next_state = fa.get_state(paths[string.first.to_sym])
|
33
|
+
next_string = string.shift_left
|
34
|
+
next_state.evaluate(next_string, fa)
|
32
35
|
end
|
33
36
|
end
|
34
37
|
end
|
data/lib/fae/version.rb
CHANGED
metadata
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fae
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Casey Scarborough
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.6'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.6'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: colorize
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
description: ''
|
@@ -60,7 +60,7 @@ executables:
|
|
60
60
|
extensions: []
|
61
61
|
extra_rdoc_files: []
|
62
62
|
files:
|
63
|
-
- .gitignore
|
63
|
+
- ".gitignore"
|
64
64
|
- Gemfile
|
65
65
|
- LICENSE.txt
|
66
66
|
- README.md
|
@@ -69,9 +69,11 @@ files:
|
|
69
69
|
- etc/example_failed_output.png
|
70
70
|
- etc/example_file_mode_output.png
|
71
71
|
- etc/example_interactive_mode_output.png
|
72
|
+
- etc/example_intersection_output.png
|
72
73
|
- etc/example_state_diagram.png
|
73
74
|
- examples/diagrams.yml
|
74
75
|
- examples/example.rb
|
76
|
+
- examples/intersection_union_difference.rb
|
75
77
|
- fae.gemspec
|
76
78
|
- lib/fae.rb
|
77
79
|
- lib/fae/finite_automata.rb
|
@@ -89,12 +91,12 @@ require_paths:
|
|
89
91
|
- lib
|
90
92
|
required_ruby_version: !ruby/object:Gem::Requirement
|
91
93
|
requirements:
|
92
|
-
- -
|
94
|
+
- - ">="
|
93
95
|
- !ruby/object:Gem::Version
|
94
96
|
version: '0'
|
95
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
98
|
requirements:
|
97
|
-
- -
|
99
|
+
- - ">="
|
98
100
|
- !ruby/object:Gem::Version
|
99
101
|
version: '0'
|
100
102
|
requirements: []
|