fae 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f0f89598b59524e013062bb117f47fcd1889716a
4
- data.tar.gz: b9a9397367d130f47918553a63344e7e73fb4fa1
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OTQ4YmUyNTU0ODY2YjViODVkNjFkY2FmMzY4ZDM0NmVhZTUyMGFlYQ==
5
+ data.tar.gz: !binary |-
6
+ ZDVmMDY1NTE0ZTRiZTFlMzc2YzUyYTViYmI3NDAzYmUxZDM1OTg0MQ==
5
7
  SHA512:
6
- metadata.gz: ed9dff23f912d6156c0bd0d81e220ab95da5a44361883303ed56d1f5d009169b332ff3662b8b40b2285d45406302e65d7a6fba052a9249280d6b21f8c85e49b7
7
- data.tar.gz: 2d20b2843097ee222bd03a73288fcba66f240a4b76324299efad7144979f8816fa77b14fa9c9efe2152b9c7bacb9d4299fc237ea69623b9b4525ab94917969af
8
+ metadata.gz: !binary |-
9
+ OThiOTUyNTk5MDc2NzBkNzkyZjk3ZGM2M2VkNWJmOTkxNDEyZWI0MTZjNzkz
10
+ ZjFlMzIzMzk5YmRjNDkzOWMxMzM2ZWVmMjIwZGEyZjRjYTY1MGQxYzkwMWRi
11
+ MTY4NzdlOTQ3ZDAwZDVlMmEyMWYyZmM5ZTRmZGIzNzAxMmVkMmY=
12
+ data.tar.gz: !binary |-
13
+ MjA0NmJiZTFmYWQ3Njk0OTc0YzRlM2ZlM2YxNDljYjM2NGIyOTA0MDM4MzIy
14
+ OTA1OTNjMzhhNmZiNDI5NzRkYTA1OGNkYTYxY2YyMGM4NTU0YjhhMjNlYTQy
15
+ ZjEwZmZhYjE5NTIwNTM2OGQ1OWU5YzJiZTliMzIyNDA5NjQwNjg=
data/README.md CHANGED
@@ -1,28 +1,152 @@
1
1
  # Fae
2
2
 
3
- TODO: Write a gem description
3
+ [![Gem Version](https://badge.fury.io/rb/fae.svg)](http://badge.fury.io/rb/fae)
4
+
5
+ This is a small Ruby gem that evaluates a Finite Automata State Diagram for validity.
6
+
7
+ ## Dependencies
8
+
9
+ * Ruby 1.9.3 or greater
4
10
 
5
11
  ## Installation
6
12
 
7
- Add this line to your application's Gemfile:
13
+ Install the gem by running the following command:
8
14
 
9
- gem 'fae'
15
+ ```bash
16
+ gem install fae
17
+ ```
10
18
 
11
- And then execute:
19
+ ## Usage
12
20
 
13
- $ bundle
21
+ The gem comes with an executable, `fae`, and can be run in two different modes, interactive or file.
14
22
 
15
- Or install it yourself as:
23
+ ### File Mode
16
24
 
17
- $ gem install fae
25
+ You can load a finite automata state diagram from a file, by creating it in the following format:
18
26
 
19
- ## Usage
27
+ ```yaml
28
+ - description: the language with an odd number of a's
29
+ # Comma-separated list of letters in your language
30
+ language: a
31
+ states:
32
+ # This is the state name, then the paths each letter would take you.
33
+ # This should be in "letter -> State, letter -> State" format.
34
+ A: a -> B
35
+ # If the state is accepting, you should add accepting to the end
36
+ B: a -> A, accepting
37
+ # Strings to evaluate when checking the state diagram
38
+ strings:
39
+ # In the format "name: valid or invalid"
40
+ a: valid
41
+ aa: invalid
42
+ aaa: valid
43
+ aaaa: invalid
44
+ ```
45
+
46
+ Then run the executable with the `-f` flag:
47
+
48
+ ```bash
49
+ fae -f diagram.yml
50
+ ```
51
+
52
+ For example, take the following state diagram, which is the language `a,b` and described by `the language of all strings in which the number of a's is even`:
53
+
54
+ ![Example State Diagram](https://raw.githubusercontent.com/caseyscarborough/fae/master/etc/example_state_diagram.png)
55
+
56
+ The data file for this diagram would look like this (`example.yml`):
57
+
58
+ ```yaml
59
+ - description: the language of all strings in which the number of a's is even
60
+ language: a, b
61
+ states:
62
+ A: a -> B, b -> A, accepting
63
+ B: a -> A, b -> B
64
+ strings:
65
+ a: invalid
66
+ aa: valid
67
+ aaa: invalid
68
+ baba: valid
69
+ bababa: invalid
70
+ bab: invalid
71
+ # As many other string combinations you'd like to test
72
+ ```
73
+
74
+ You can then run:
75
+
76
+ ```bash
77
+ fae -f example.yml
78
+ ```
79
+
80
+ And the output would be the following:
81
+
82
+ ![Example Output](https://raw.githubusercontent.com/caseyscarborough/fae/master/etc/example_file_mode_output.png)
83
+
84
+ ## Interactive Mode
85
+
86
+ Interactive mode is exactly what it sounds like, in which it will ask you questions about your diagram and evaluate it for you. This method is convenient, but if you'd like to check it again you will have to go back through the entire process.
87
+
88
+ You can start interactive mode by executing:
89
+
90
+ ```bash
91
+ fae -i
92
+ ```
93
+
94
+ Here is an example output:
95
+
96
+ ![Example Output](https://raw.githubusercontent.com/caseyscarborough/fae/master/etc/example_interactive_mode_output.png)
97
+
98
+ ## Using the API
99
+
100
+ You can also use the library directly if you'd rather run your own scripts. Here is an example usage:
101
+
102
+ ```rb
103
+ require 'fae'
104
+
105
+ characters = ['a', 'b']
106
+ language = Fae::Language.new(characters)
107
+
108
+ # A Finite Automata is created with a language and description.
109
+ fa = Fae::FiniteAutomata.new(language, "The language of all strings containing at least two a's")
110
+
111
+ fa.add_states([
112
+ # A new state is created with a name, a Hash of paths,
113
+ # and whether or not it is accepting. The hash of paths
114
+ # takes the letter as a key, and the next state as its value.
115
+ Fae::State.new('A', { :a => 'B', :b => 'A' }, false),
116
+ Fae::State.new('B', { :a => 'C', :b => 'B' }, false),
117
+ Fae::State.new('C', { :a => 'C', :b => 'C' }, true)
118
+ ])
119
+
120
+ fa.add_strings([
121
+ # Strings are added with their value, and whether or
122
+ # not they should be valid for this Finite Automata.
123
+ String.new("a", false),
124
+ String.new("aa", true),
125
+ String.new("ba", false),
126
+ # ... as many strings as you'd like to test
127
+ ])
128
+
129
+ # Run the evaluation
130
+ fa.evaluate!
131
+ ```
132
+
133
+ ## When Your State Diagram is Incorrect
134
+
135
+ If your state diagram is incorrect, the program will give you feedback about your diagram:
136
+
137
+ ![](https://raw.githubusercontent.com/caseyscarborough/fae/master/etc/example_failed_output.png)
138
+
139
+ This can help you figure out where your diagram is going wrong.
140
+
141
+ ## Examples
142
+
143
+ An example data file and direct API usage can be seen in the [examples directory](https://github.com/caseyscarborough/fae/tree/master/examples).
20
144
 
21
- TODO: Write usage instructions here
145
+ The examples in the data file are taken from the Chapter 2 exercises in [Introduction to Languages and the Theory of Computation](http://www.amazon.com/Introduction-Languages-Theory-Computation-Martin/dp/0073191469).
22
146
 
23
147
  ## Contributing
24
148
 
25
- 1. Fork it ( https://github.com/[my-github-username]/fae/fork )
149
+ 1. Fork it ( https://github.com/caseyscarborough/fae/fork )
26
150
  2. Create your feature branch (`git checkout -b my-new-feature`)
27
151
  3. Commit your changes (`git commit -am 'Add some feature'`)
28
152
  4. Push to the branch (`git push origin my-new-feature`)
data/bin/fae CHANGED
@@ -7,7 +7,12 @@ options = {}
7
7
  OptionParser.new do |opt|
8
8
  opt.banner = "Usage: fae [options]"
9
9
 
10
- opt.on('-h', '--help', 'View this help menu') { puts opt; exit 0 }
10
+ help = lambda do |code|
11
+ puts opt
12
+ exit code
13
+ end
14
+
15
+ opt.on('-h', '--help', 'View this help menu') { help.call(0) }
11
16
  opt.on('-f FILENAME', '--file FILENAME', 'Evaluate a data file') { |o| options[:file] = o }
12
17
  opt.on('-v', '--version', 'Display the version number') { puts Fae::VERSION; exit 0 }
13
18
  opt.on('-i', '--interactive', 'Start interactive check') { options[:interactive] = true }
@@ -16,17 +21,20 @@ OptionParser.new do |opt|
16
21
  opt.parse!
17
22
  rescue OptionParser::InvalidOption => e
18
23
  puts e.message
19
- puts opt
20
- exit 1
24
+ help.call(1)
21
25
  rescue OptionParser::MissingArgument => e
22
26
  puts e.message
23
- puts opt
24
- exit 1
27
+ help.call(1)
28
+ end
29
+
30
+ if (!options[:file] && !options[:interactive])
31
+ puts "You must specify an argument."
32
+ help.call(1)
25
33
  end
26
34
  end
27
35
 
28
36
  if (options[:file])
29
- Fae.load_file(options[:file])
30
- else
31
- Fae.interactive
37
+ Fae.file_mode(options[:file])
38
+ elsif (options[:interactive])
39
+ Fae.interactive_mode
32
40
  end
Binary file
Binary file
Binary file
@@ -0,0 +1,47 @@
1
+ - description: 2.1d - the language of all strings that begin or end with "aa" or "bb"
2
+ language: a, b
3
+ states:
4
+ A: a -> B, b -> D
5
+ B: a -> C, b -> G
6
+ C: a -> C, b -> C, accepting
7
+ D: a -> F, b -> E
8
+ E: a -> E, b -> E, accepting
9
+ F: a -> H, b -> G
10
+ G: a -> F, b -> I
11
+ H: b -> G, a -> H, accepting
12
+ I: a -> F, b -> I, accepting
13
+ strings:
14
+ a: invalid
15
+ aa: valid
16
+ aaa: valid
17
+ aaabbbbbba: valid
18
+ aabbabba: valid
19
+ bbabba: valid
20
+ babbbab: invalid
21
+ babbbabb: valid
22
+ abbabbba: invalid
23
+ - description: 2.1f - the language of all strings with an even number of a's
24
+ language: a, b
25
+ states:
26
+ A: a -> B, b -> A, accepting
27
+ B: a -> A, b -> B
28
+ strings:
29
+ a: invalid
30
+ aa: valid
31
+ aaa: invalid
32
+ aaabbbbbbbba: valid
33
+ - description: 2.1h - the language of all strings containing no more than one occurrence of the string "aa"
34
+ language: a, b
35
+ states:
36
+ A: a -> B, b -> A, accepting
37
+ B: a -> C, b -> A, accepting
38
+ C: a -> E, b -> D, accepting
39
+ D: a -> F, b -> D, accepting
40
+ E: a -> E, b -> E
41
+ F: a -> E, b -> D, accepting
42
+ strings:
43
+ a: valid
44
+ aa: valid
45
+ aaa: invalid
46
+ aaabbbbbbbba: invalid
47
+ aabbabbba: valid
@@ -0,0 +1,28 @@
1
+ require_relative '../lib/fae'
2
+
3
+ characters = ['a', 'b']
4
+ language = Fae::Language.new(characters)
5
+
6
+ # A Finite Automata is created with a language and description.
7
+ fa = Fae::FiniteAutomata.new(language, "The language of all strings containing at least two a's")
8
+
9
+ fa.add_states([
10
+ # A new state is created with a name, a Hash of paths,
11
+ # and whether or not it is accepting. The hash of paths
12
+ # takes the letter as a key, and the next state as its value.
13
+ Fae::State.new('A', { :a => 'B', :b => 'A' }, false),
14
+ Fae::State.new('B', { :a => 'C', :b => 'B' }, false),
15
+ Fae::State.new('C', { :a => 'C', :b => 'C' }, true)
16
+ ])
17
+
18
+ fa.add_strings([
19
+ # Strings are added with their value, and whether or
20
+ # not they should be valid for this Finite Automata.
21
+ String.new("a", false),
22
+ String.new("aa", true),
23
+ String.new("ba", false),
24
+ # ... as many strings as you'd like to test
25
+ ])
26
+
27
+ # Run the evaluation
28
+ fa.evaluate!
data/fae.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["caseyscarborough@gmail.com"]
11
11
  spec.summary = %q{Evaluates Finite Automata State Diagrams for correctness.}
12
12
  spec.description = %q{}
13
- spec.homepage = ""
13
+ spec.homepage = "https://github.com/caseyscarborough/fae"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
data/lib/fae.rb CHANGED
@@ -10,8 +10,7 @@ require_relative 'fae/version'
10
10
  module Fae
11
11
 
12
12
  class << self
13
-
14
- def load_file(filename)
13
+ def file_mode(filename)
15
14
  diagrams = nil
16
15
  begin
17
16
  diagrams = YAML.load_file(filename)
@@ -55,7 +54,7 @@ module Fae
55
54
  end
56
55
  end
57
56
 
58
- def interactive
57
+ def interactive_mode
59
58
  states = []
60
59
  strings = []
61
60
 
@@ -1,5 +1,15 @@
1
1
  module Fae
2
+
3
+ # The main class that drives the Finite Automata evaluation.
4
+ #
5
+ # Takes in a language, states, and strings, and checks them
6
+ # to validate a state diagram.
2
7
  class FiniteAutomata
8
+
9
+ # Initializes a new instance of the FiniteAutomata.
10
+ #
11
+ # @param language [Language] a language instance
12
+ # @param description [String] the description of the finite automata
3
13
  def initialize(language, description)
4
14
  @states = []
5
15
  @strings = []
@@ -8,18 +18,27 @@ module Fae
8
18
  @description = description
9
19
  end
10
20
 
21
+ # Adds strings to check against when evaluating.
22
+ #
23
+ # @param strings [Array] an array of strings
11
24
  def add_strings(strings)
12
25
  strings.each do |string|
13
26
  @strings << string
14
27
  end
15
28
  end
16
29
 
30
+ # Adds strings to check against when evaluating.
31
+ #
32
+ # @param states [Array] an array of states
17
33
  def add_states(states)
18
34
  states.each do |state|
19
35
  add_state(state)
20
36
  end
21
37
  end
22
38
 
39
+ # Retrieves a state from this finite automata by name.
40
+ #
41
+ # @param name [String] the name of the state to find
23
42
  def get_state(name)
24
43
  retrieved_state = nil
25
44
  @states.each do |state|
@@ -34,6 +53,7 @@ module Fae
34
53
  return retrieved_state
35
54
  end
36
55
 
56
+ # Runs the evaluation on the finite automata.
37
57
  def evaluate!
38
58
  @invalids = []
39
59
  if (@states.length == 0)
data/lib/fae/language.rb CHANGED
@@ -1,12 +1,20 @@
1
1
  module Fae
2
+
3
+ # A language described by any number of characters
2
4
  class Language
3
5
  attr_accessor :characters
4
6
 
7
+ # Creates a new language instance.
8
+ #
9
+ # @param characters [Array] an array of characters
5
10
  def initialize(characters)
6
11
  @characters = []
7
12
  @characters = characters.uniq
8
13
  end
9
14
 
15
+ # Checks if a string is valid for this language.
16
+ #
17
+ # @param string [String] the string to check
10
18
  def string_is_valid(string)
11
19
  # Use lookahead to check for valid string
12
20
  regex = "^(?=.*\\D)[#{@characters.join('|')}]+$"
@@ -17,6 +25,9 @@ module Fae
17
25
  return false
18
26
  end
19
27
 
28
+ # Adds a character to the language.
29
+ #
30
+ # @param char [String] the character to add
20
31
  def add_character(char)
21
32
  @characters << char
22
33
  end
data/lib/fae/state.rb CHANGED
@@ -1,18 +1,31 @@
1
1
  module Fae
2
+
3
+ # A state in the state diagram.
2
4
  class State
3
5
  attr_accessor :name, :next_states, :valid
4
6
 
7
+ # Creates a new state instance.
8
+ #
9
+ # @param name [String] the state name, i.e.: "A"
10
+ # @param next_states [Hash] a hash of next states from this state
11
+ # @param valid [Boolean] whether or not this is an accepting state
12
+ # @example
13
+ # State.new('A', { :a => 'B', :b => 'A' }, true)
5
14
  def initialize(name, next_states, valid)
6
15
  @name = name
7
16
  @next_states = next_states
8
17
  @valid = valid
9
18
  end
10
19
 
20
+ # Evaluates a string at this state, and passes it to the next state.
21
+ #
22
+ # @param string [String] the string to evaluate
23
+ # @param fa [FiniteAutomata] the finite automata that this state belongs to
11
24
  def evaluate(string, fa)
12
25
  if (string.first.empty?)
13
- output = valid ? "valid".colorize(:green) : "invalid".colorize(:red)
26
+ output = @valid ? "valid".colorize(:green) : "invalid".colorize(:red)
14
27
  print "#{@name} (#{output}) "
15
- return { :output => output, :valid => valid }
28
+ return { :output => output, :valid => @valid }
16
29
  end
17
30
  print "#{@name} #{'->'.colorize(:light_black)} "
18
31
  fa.get_state(next_states[string.first.to_sym]).evaluate(string.shift_left, fa)
data/lib/fae/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Fae
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.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.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Casey Scarborough
@@ -14,42 +14,42 @@ dependencies:
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,12 +60,18 @@ 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
67
67
  - Rakefile
68
68
  - bin/fae
69
+ - etc/example_failed_output.png
70
+ - etc/example_file_mode_output.png
71
+ - etc/example_interactive_mode_output.png
72
+ - etc/example_state_diagram.png
73
+ - examples/diagrams.yml
74
+ - examples/example.rb
69
75
  - fae.gemspec
70
76
  - lib/fae.rb
71
77
  - lib/fae/finite_automata.rb
@@ -73,7 +79,7 @@ files:
73
79
  - lib/fae/state.rb
74
80
  - lib/fae/version.rb
75
81
  - lib/string.rb
76
- homepage: ''
82
+ homepage: https://github.com/caseyscarborough/fae
77
83
  licenses:
78
84
  - MIT
79
85
  metadata: {}
@@ -83,12 +89,12 @@ require_paths:
83
89
  - lib
84
90
  required_ruby_version: !ruby/object:Gem::Requirement
85
91
  requirements:
86
- - - ">="
92
+ - - ! '>='
87
93
  - !ruby/object:Gem::Version
88
94
  version: '0'
89
95
  required_rubygems_version: !ruby/object:Gem::Requirement
90
96
  requirements:
91
- - - ">="
97
+ - - ! '>='
92
98
  - !ruby/object:Gem::Version
93
99
  version: '0'
94
100
  requirements: []