state_machine_checker 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +4 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGELOG.md +0 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +67 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +102 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/lib/state_machine_checker/adapters/state_machines.rb +40 -0
  15. data/lib/state_machine_checker/check_result.rb +40 -0
  16. data/lib/state_machine_checker/ctl/a_f.rb +20 -0
  17. data/lib/state_machine_checker/ctl/a_g.rb +20 -0
  18. data/lib/state_machine_checker/ctl/a_u.rb +23 -0
  19. data/lib/state_machine_checker/ctl/a_x.rb +20 -0
  20. data/lib/state_machine_checker/ctl/and.rb +42 -0
  21. data/lib/state_machine_checker/ctl/api.rb +61 -0
  22. data/lib/state_machine_checker/ctl/atom.rb +62 -0
  23. data/lib/state_machine_checker/ctl/binary_formula.rb +23 -0
  24. data/lib/state_machine_checker/ctl/e_f.rb +34 -0
  25. data/lib/state_machine_checker/ctl/e_g.rb +139 -0
  26. data/lib/state_machine_checker/ctl/e_u.rb +42 -0
  27. data/lib/state_machine_checker/ctl/e_x.rb +40 -0
  28. data/lib/state_machine_checker/ctl/formula.rb +42 -0
  29. data/lib/state_machine_checker/ctl/implication.rb +19 -0
  30. data/lib/state_machine_checker/ctl/not.rb +36 -0
  31. data/lib/state_machine_checker/ctl/or.rb +40 -0
  32. data/lib/state_machine_checker/ctl/unary_operator.rb +22 -0
  33. data/lib/state_machine_checker/finite_state_machine.rb +120 -0
  34. data/lib/state_machine_checker/labeled_machine.rb +51 -0
  35. data/lib/state_machine_checker/labeling.rb +48 -0
  36. data/lib/state_machine_checker/rspec_matchers.rb +60 -0
  37. data/lib/state_machine_checker/state_result.rb +57 -0
  38. data/lib/state_machine_checker/transition.rb +49 -0
  39. data/lib/state_machine_checker/version.rb +3 -0
  40. data/lib/state_machine_checker.rb +43 -0
  41. data/state_machine_checker.gemspec +47 -0
  42. metadata +186 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c5b18f97d28a7f774d19289b199b48d59a9cd470924d60c8d24d8c6e7a19276b
4
+ data.tar.gz: 474ad4475d4363e54430f13988ec0203aa7fad9b1f6810e6cc934077e7fb5a3a
5
+ SHA512:
6
+ metadata.gz: cf8b01a402b35d8957d4a4ad705ed978ce733c2243c846322cd1c596c534cafb7d7551c245c8f5032bdb3dbbbb62ed4c55e4fdcf63508eb71b71025e41b87315
7
+ data.tar.gz: abde78b8b55667d400cb842275e746265d4a1af57aca5c0be5e12fba0f84539ed1e3efea011b583dd75bec301b43535b4f9836cfe9f6a9b16541918ac36d5275
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --format progress
2
+ --warnings
3
+ --color
4
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.0
7
+ before_install: gem install bundler -v 1.17.2
data/CHANGELOG.md ADDED
File without changes
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at chrisstadler@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in state_machine_checker.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,67 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ state_machine_checker (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.0)
10
+ coderay (1.1.2)
11
+ diff-lcs (1.3)
12
+ jaro_winkler (1.5.2)
13
+ method_source (0.9.2)
14
+ parallel (1.14.0)
15
+ parser (2.6.0.0)
16
+ ast (~> 2.4.0)
17
+ powerpack (0.1.2)
18
+ pry (0.12.2)
19
+ coderay (~> 1.1.0)
20
+ method_source (~> 0.9.0)
21
+ psych (3.1.0)
22
+ rainbow (3.0.0)
23
+ rake (10.5.0)
24
+ rspec (3.8.0)
25
+ rspec-core (~> 3.8.0)
26
+ rspec-expectations (~> 3.8.0)
27
+ rspec-mocks (~> 3.8.0)
28
+ rspec-core (3.8.0)
29
+ rspec-support (~> 3.8.0)
30
+ rspec-expectations (3.8.2)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.8.0)
33
+ rspec-mocks (3.8.0)
34
+ diff-lcs (>= 1.2.0, < 2.0)
35
+ rspec-support (~> 3.8.0)
36
+ rspec-support (3.8.0)
37
+ rubocop (0.65.0)
38
+ jaro_winkler (~> 1.5.1)
39
+ parallel (~> 1.10)
40
+ parser (>= 2.5, != 2.5.1.1)
41
+ powerpack (~> 0.1)
42
+ psych (>= 3.1.0)
43
+ rainbow (>= 2.2.2, < 4.0)
44
+ ruby-progressbar (~> 1.7)
45
+ unicode-display_width (~> 1.4.0)
46
+ ruby-progressbar (1.10.0)
47
+ standard (0.0.36)
48
+ rubocop (>= 0.63)
49
+ state_machines (0.5.0)
50
+ unicode-display_width (1.4.1)
51
+ yard (0.9.18)
52
+
53
+ PLATFORMS
54
+ ruby
55
+
56
+ DEPENDENCIES
57
+ bundler (~> 1.17)
58
+ pry
59
+ rake (~> 10.0)
60
+ rspec (~> 3.0)
61
+ standard (~> 0.0.36)
62
+ state_machine_checker!
63
+ state_machines (~> 0.5.0)
64
+ yard (~> 0.9.18)
65
+
66
+ BUNDLED WITH
67
+ 1.17.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Chris Stadler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # State Machine Checker
2
+
3
+ Verify (as in prove) properties of your state machines.
4
+
5
+ Given a definition of a state machine and a [CTL
6
+ formula](https://en.wikipedia.org/wiki/Computation_tree_logic) this library
7
+ performs model checking to verify whether the formula is satisfied by the state
8
+ machine.
9
+
10
+ For example, let's say we have implemented a very simple state machine to
11
+ represent credit card payments:
12
+
13
+ ```rb
14
+ class Payment
15
+ state_machine initial: :checkout do
16
+ event :started_processing do
17
+ transition from: [:checkout], to: :processing
18
+ end
19
+
20
+ event :finished_processing do
21
+ transition from: [:processing], to: :completed
22
+ end
23
+
24
+ event :processing_failed do
25
+ transition from: [:processing], to: :failed
26
+ end
27
+ end
28
+ end
29
+ ```
30
+
31
+ We might want to verify that every `Payment` eventually completes. This property
32
+ can be represented as the CTL Formula `AF completed` — for all paths (`A`)
33
+ eventually (`F`) `completed` is true. `state_machine_checker` includes a DSL for
34
+ writing CTL properties and a `rspec` matcher, so we can write a spec to verify
35
+ this property:
36
+
37
+ ```rb
38
+ it "eventually completes" do
39
+ formula = AF(:completed?)
40
+ expect { Payment.new }.to satisfy(formula)
41
+ end
42
+ ```
43
+
44
+ This spec will fail and give the following message:
45
+
46
+ ```
47
+ 1) Payment eventually completes
48
+ Failure/Error: expect { Payment.new }.to satisfy(formula)
49
+
50
+ Expected state machine for Payment#state to satisfy "AF(completed?)" but it does not.
51
+ Counterexample: [:started_processing, :processing_failed]
52
+ ```
53
+
54
+ A counterexample is given as a series of events: if `started_processing` is
55
+ followed by `processing_failed` then a `Payment` will be in the `failed` state
56
+ and will never reach `completed`.
57
+
58
+ For more examples see [payment_spec.rb](https://github.com/CJStadler/state_machine_checker/blob/master/spec/payment_spec.rb).
59
+
60
+ For a more in depth discussion and implementation details see [the paper](https://github.com/CJStadler/state_machine_checker/blob/master/paper.pdf).
61
+
62
+ ## Limitations
63
+
64
+ - The state machine must be static — once the definition is parsed no states or
65
+ transitions should be added or removed.
66
+ - Atoms should only depend on the current state.
67
+ - Only the [state_machines](https://rubygems.org/gems/state_machines) gem is
68
+ currently supported, but adapters for other state machine gems could be added.
69
+
70
+ ## Installation
71
+
72
+ Add this line to your application's Gemfile:
73
+
74
+ ```ruby
75
+ gem 'state_machine_checker'
76
+ ```
77
+
78
+ And then execute:
79
+
80
+ $ bundle
81
+
82
+ Or install it yourself as:
83
+
84
+ $ gem install state_machine_checker
85
+
86
+ ## Development
87
+
88
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
89
+
90
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
91
+
92
+ ## Contributing
93
+
94
+ Bug reports and pull requests are welcome on GitHub at https://github.com/CJStadler/state_machine_checker. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
95
+
96
+ ## License
97
+
98
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
99
+
100
+ ## Code of Conduct
101
+
102
+ Everyone interacting in the StateMachineChecker project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/CJStadler/state_machine_checker/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "state_machine_checker"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,40 @@
1
+ require_relative "../transition"
2
+
3
+ module StateMachineChecker
4
+ module Adapters
5
+ # An adapter for the state_machines gem.
6
+ class StateMachines
7
+ # @param [StateMachines::Machine] gem_machine
8
+ def initialize(gem_machine)
9
+ @gem_machine = gem_machine
10
+ end
11
+
12
+ def initial_state
13
+ # StateMachines::Machine#initial_state takes an instance as a parameter,
14
+ # even when the initial state is set statically. We are assuming that
15
+ # it is always set statically.
16
+ # TODO: could get this by `gem_machine.states.find(&:initial)`
17
+ gem_machine.instance_variable_get(:@initial_state)
18
+ end
19
+
20
+ def transitions
21
+ gem_machine.events.flat_map { |event|
22
+ event.branches.flat_map { |branch|
23
+ branch.state_requirements.flat_map { |state_requirement|
24
+ froms = state_requirement[:from].values
25
+ tos = state_requirement[:to].values
26
+
27
+ froms.product(tos).map { |from, to|
28
+ Transition.new(from, to, event.name)
29
+ }
30
+ }
31
+ }
32
+ }
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :gem_machine
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ module StateMachineChecker
2
+ # The results of checking whether a given model satisfies a given formula.
3
+ class CheckResult
4
+ # @param [Hash<Symbol, StateResult>] result_hash
5
+ def initialize(result_hash)
6
+ @result_hash = result_hash
7
+ end
8
+
9
+ # The result for a particular state.
10
+ #
11
+ # @param [Symbol] state
12
+ # @return [StateResult]
13
+ def for_state(state)
14
+ result_hash[state]
15
+ end
16
+
17
+ def to_h
18
+ result_hash.clone
19
+ end
20
+
21
+ def union(other)
22
+ map { |state, result| result.or(other.for_state(state)) }
23
+ end
24
+
25
+ def intersection(other)
26
+ map { |state, result| result.and(other.for_state(state)) }
27
+ end
28
+
29
+ def map(&block)
30
+ entries = result_hash.map { |state, result|
31
+ [state, block.yield(state, result)]
32
+ }
33
+ CheckResult.new(Hash[entries])
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :result_hash
39
+ end
40
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "unary_operator"
2
+ require_relative "e_g"
3
+ require_relative "not"
4
+
5
+ module StateMachineChecker
6
+ module CTL
7
+ # The universal eventually operator.
8
+ class AF < UnaryOperator
9
+ # @param [LabeledMachine] model
10
+ # @return [CheckResult]
11
+ def check(model)
12
+ Not.new(CTL::EG.new(Not.new(subformula))).check(model)
13
+ end
14
+
15
+ def to_s
16
+ "AF(#{subformula})"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "unary_operator"
2
+ require_relative "e_f"
3
+ require_relative "not"
4
+
5
+ module StateMachineChecker
6
+ module CTL
7
+ # The universal always operator.
8
+ class AG < UnaryOperator
9
+ # @param [LabeledMachine] model
10
+ # @return [CheckResult]
11
+ def check(model)
12
+ Not.new(CTL::EF.new(Not.new(subformula))).check(model)
13
+ end
14
+
15
+ def to_s
16
+ "AG(#{subformula})"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "binary_formula"
2
+ require_relative "e_g"
3
+ require_relative "e_u"
4
+ require_relative "not"
5
+
6
+ module StateMachineChecker
7
+ module CTL
8
+ # The universal until operator.
9
+ class AU < BinaryFormula
10
+ # @param [LabeledMachine] model
11
+ # @return [CheckResult]
12
+ def check(model)
13
+ Not.new(Not.new(subformula2).EU(Not.new(subformula1.or(subformula2)))
14
+ .or(EG.new(Not.new(subformula2))))
15
+ .check(model)
16
+ end
17
+
18
+ def to_s
19
+ "(#{subformula1}) AU (#{subformula2})"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "unary_operator"
2
+ require_relative "e_x"
3
+ require_relative "not"
4
+
5
+ module StateMachineChecker
6
+ module CTL
7
+ # The universal next operator.
8
+ class AX < UnaryOperator
9
+ # @param [LabeledMachine] model
10
+ # @return [CheckResult]
11
+ def check(model)
12
+ Not.new(CTL::EX.new(Not.new(subformula))).check(model)
13
+ end
14
+
15
+ def to_s
16
+ "AX(#{subformula})"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ require_relative "formula"
2
+
3
+ module StateMachineChecker
4
+ module CTL
5
+ # The logical conjunction of two or more sub-formulae.
6
+ class And < Formula
7
+ # Conjoin several formulae.
8
+ #
9
+ # @param [Enumerator<Formula>] subformulae
10
+ #
11
+ # @example
12
+ # And.new([Atom.new(:even?), Atom.new(:positive?)])
13
+ def initialize(subformulae)
14
+ @subformulae = subformulae
15
+ end
16
+
17
+ # Return an enumerator over the atoms of all sub-formulae.
18
+ #
19
+ # @return [Enumerator<Atom>]
20
+ def atoms
21
+ subformulae.lazy.flat_map(&:atoms)
22
+ end
23
+
24
+ # Check which states of the model are satisfied by all subformulae.
25
+ #
26
+ # @param [LabeledMachine] model
27
+ # @return [CheckResult]
28
+ def check(model)
29
+ sub_results = subformulae.lazy.map { |f| f.check(model) }
30
+ sub_results.reduce(&:intersection)
31
+ end
32
+
33
+ def to_s
34
+ subformulae.map(&:to_s).join(" ∧ ")
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :subformulae
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,61 @@
1
+ require_relative "and"
2
+ require_relative "atom"
3
+ require_relative "a_x"
4
+ require_relative "a_f"
5
+ require_relative "a_g"
6
+ require_relative "a_u"
7
+ require_relative "e_f"
8
+ require_relative "e_x"
9
+ require_relative "e_g"
10
+ require_relative "e_u"
11
+ require_relative "not"
12
+ require_relative "or"
13
+ require_relative "implication"
14
+
15
+ module StateMachineChecker
16
+ module CTL
17
+ module API
18
+ def atom(method_name_or_fn)
19
+ Atom.new(method_name_or_fn)
20
+ end
21
+
22
+ def neg(subformula)
23
+ Not.new(atom_or_formula(subformula))
24
+ end
25
+
26
+ def EF(subformula) # rubocop:disable Naming/MethodName
27
+ CTL::EF.new(atom_or_formula(subformula))
28
+ end
29
+
30
+ def EX(subformula) # rubocop:disable Naming/MethodName
31
+ CTL::EX.new(atom_or_formula(subformula))
32
+ end
33
+
34
+ def EG(subformula) # rubocop:disable Naming/MethodName
35
+ CTL::EG.new(atom_or_formula(subformula))
36
+ end
37
+
38
+ def AF(subformula) # rubocop:disable Naming/MethodName
39
+ CTL::AF.new(atom_or_formula(subformula))
40
+ end
41
+
42
+ def AX(subformula) # rubocop:disable Naming/MethodName
43
+ CTL::AX.new(atom_or_formula(subformula))
44
+ end
45
+
46
+ def AG(subformula) # rubocop:disable Naming/MethodName
47
+ CTL::AG.new(atom_or_formula(subformula))
48
+ end
49
+
50
+ private
51
+
52
+ def atom_or_formula(subformula)
53
+ if subformula.is_a? Formula
54
+ subformula
55
+ else
56
+ atom(subformula)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,62 @@
1
+ require_relative "formula"
2
+ require "state_machine_checker/check_result"
3
+ require "state_machine_checker/state_result"
4
+
5
+ module StateMachineChecker
6
+ module CTL
7
+ # An atomic proposition about a single object.
8
+ class Atom < Formula
9
+ # @param [Symbol, Proc] method_name_or_fn
10
+ #
11
+ # @example
12
+ # Atom.new(:shipped?)
13
+ # Atom.new(->(x) { x.shipped? })
14
+ def initialize(method_name_or_fn)
15
+ @name, @fn = if method_name_or_fn.respond_to?(:call)
16
+ ["atom##{object_id}", method_name_or_fn]
17
+ else
18
+ # Create a function which will send the given method name.
19
+ [method_name_or_fn.to_s, method_name_or_fn.to_proc]
20
+ end
21
+ end
22
+
23
+ # Evaluate the atom on the given instance.
24
+ #
25
+ # @example
26
+ # even_atom = Atom.new(:even?)
27
+ # even_atom.apply(6) #=> true
28
+ # even_atom.apply(7) #=> false
29
+ def apply(instance)
30
+ fn.call(instance)
31
+ end
32
+
33
+ # Returns an enumerator containing only this object, as it is an atom.
34
+ #
35
+ # @return [Enumerator<Atom>]
36
+ def atoms
37
+ [self]
38
+ end
39
+
40
+ # Check which states of the machine are labeled with this atom.
41
+ #
42
+ # @param [LabeledMachine] machine
43
+ # @return [CheckResult]
44
+ def check(machine)
45
+ result = machine.states.each_with_object({}) { |state, h|
46
+ satisfied = machine.labels_for_state(state).include?(self)
47
+ h[state] = StateResult.new(satisfied, [])
48
+ }
49
+
50
+ CheckResult.new(result)
51
+ end
52
+
53
+ def to_s
54
+ name
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :fn, :name
60
+ end
61
+ end
62
+ end