pg-verify 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +98 -0
  5. data/README.md +29 -0
  6. data/Rakefile +62 -0
  7. data/bin/console +15 -0
  8. data/bin/pg-verify.rb +18 -0
  9. data/bin/setup +8 -0
  10. data/calc.ebnf +21 -0
  11. data/data/config/pg-verify.yml +66 -0
  12. data/data/nusmv.sample.smv +179 -0
  13. data/data/project-template/.gitignore.resource +4 -0
  14. data/data/project-template/.pg-verify.yml +0 -0
  15. data/data/project-template/README.md +18 -0
  16. data/data/project-template/addon/.keep +0 -0
  17. data/data/project-template/program-graph.rb.resource +103 -0
  18. data/devpg +5 -0
  19. data/doc/examples/railroad_crossing.rb +61 -0
  20. data/doc/examples/train-tree.rb +43 -0
  21. data/doc/examples/weidezaun.rb +99 -0
  22. data/doc/examples/weidezaun.txt +29 -0
  23. data/doc/expose/definition.png +0 -0
  24. data/doc/expose/diagram.png +0 -0
  25. data/doc/expose/expose.md +359 -0
  26. data/doc/expose/validity.png +0 -0
  27. data/exe/pg-verify +4 -0
  28. data/integration_tests/ruby_dsl/001_states.rb +10 -0
  29. data/integration_tests/ruby_dsl/002_transitions.rb +10 -0
  30. data/integration_tests/ruby_dsl/003_actions.rb +14 -0
  31. data/integration_tests/ruby_dsl/004_guards.rb +18 -0
  32. data/integration_tests/ruby_dsl/005_variables.rb +16 -0
  33. data/integration_tests/ruby_dsl/006_state_variables.rb +26 -0
  34. data/integration_tests/ruby_dsl/007_variable_initialization.rb +28 -0
  35. data/integration_tests/ruby_dsl/008_state_initialization.rb +19 -0
  36. data/integration_tests/ruby_dsl/009_shared_variables.rb +26 -0
  37. data/integration_tests/ruby_dsl/010_complex_guards.rb +18 -0
  38. data/integration_tests/ruby_dsl/011_complex_actions.rb +16 -0
  39. data/integration_tests/ruby_dsl/012_error_components.rb +9 -0
  40. data/integration_tests/ruby_dsl/013_hazards.rb +25 -0
  41. data/integration_tests/ruby_dsl/014_tau_transitions.rb +26 -0
  42. data/integration_tests/ruby_dsl/015_basic_dcca.rb +19 -0
  43. data/integration_tests/ruby_dsl/016_pressure_tank.rb +146 -0
  44. data/lib/pg-verify/cli/cli.rb +235 -0
  45. data/lib/pg-verify/core/cmd_runner.rb +151 -0
  46. data/lib/pg-verify/core/core.rb +38 -0
  47. data/lib/pg-verify/core/extensions/array_extensions.rb +11 -0
  48. data/lib/pg-verify/core/extensions/enumerable_extensions.rb +19 -0
  49. data/lib/pg-verify/core/extensions/nil_extensions.rb +7 -0
  50. data/lib/pg-verify/core/extensions/string_extensions.rb +84 -0
  51. data/lib/pg-verify/core/shell/colorizer.rb +136 -0
  52. data/lib/pg-verify/core/shell/shell.rb +0 -0
  53. data/lib/pg-verify/core/util.rb +146 -0
  54. data/lib/pg-verify/doctor/doctor.rb +180 -0
  55. data/lib/pg-verify/ebnf_parser/ast.rb +31 -0
  56. data/lib/pg-verify/ebnf_parser/ebnf_parser.rb +26 -0
  57. data/lib/pg-verify/ebnf_parser/expression_parser.rb +177 -0
  58. data/lib/pg-verify/ebnf_parser/expression_parser2.rb +422 -0
  59. data/lib/pg-verify/ebnf_parser/expressions.ebnf +33 -0
  60. data/lib/pg-verify/ebnf_parser/expressions.peg +52 -0
  61. data/lib/pg-verify/ebnf_parser/parser_result.rb +26 -0
  62. data/lib/pg-verify/interpret/component_context.rb +125 -0
  63. data/lib/pg-verify/interpret/graph_context.rb +85 -0
  64. data/lib/pg-verify/interpret/interpret.rb +142 -0
  65. data/lib/pg-verify/interpret/pg_script.rb +72 -0
  66. data/lib/pg-verify/interpret/spec/ltl_builder.rb +90 -0
  67. data/lib/pg-verify/interpret/spec/spec_context.rb +32 -0
  68. data/lib/pg-verify/interpret/spec/spec_set_context.rb +67 -0
  69. data/lib/pg-verify/interpret/transition_context.rb +55 -0
  70. data/lib/pg-verify/model/allocation_set.rb +28 -0
  71. data/lib/pg-verify/model/assignment.rb +34 -0
  72. data/lib/pg-verify/model/component.rb +40 -0
  73. data/lib/pg-verify/model/dcca/hazard.rb +16 -0
  74. data/lib/pg-verify/model/dcca.rb +67 -0
  75. data/lib/pg-verify/model/expression.rb +106 -0
  76. data/lib/pg-verify/model/graph.rb +58 -0
  77. data/lib/pg-verify/model/model.rb +10 -0
  78. data/lib/pg-verify/model/parsed_expression.rb +77 -0
  79. data/lib/pg-verify/model/simulation/trace.rb +43 -0
  80. data/lib/pg-verify/model/simulation/variable_state.rb +23 -0
  81. data/lib/pg-verify/model/source_location.rb +45 -0
  82. data/lib/pg-verify/model/specs/spec.rb +44 -0
  83. data/lib/pg-verify/model/specs/spec_result.rb +25 -0
  84. data/lib/pg-verify/model/specs/spec_set.rb +43 -0
  85. data/lib/pg-verify/model/specs/specification.rb +50 -0
  86. data/lib/pg-verify/model/transition.rb +41 -0
  87. data/lib/pg-verify/model/validation/assignment_to_state_variable_validation.rb +26 -0
  88. data/lib/pg-verify/model/validation/empty_state_set_validation.rb +18 -0
  89. data/lib/pg-verify/model/validation/errors.rb +119 -0
  90. data/lib/pg-verify/model/validation/foreign_assignment_validation.rb +30 -0
  91. data/lib/pg-verify/model/validation/unknown_token_validation.rb +35 -0
  92. data/lib/pg-verify/model/validation/validation.rb +23 -0
  93. data/lib/pg-verify/model/variable.rb +47 -0
  94. data/lib/pg-verify/model/variable_set.rb +84 -0
  95. data/lib/pg-verify/nusmv/nusmv.rb +23 -0
  96. data/lib/pg-verify/nusmv/runner.rb +124 -0
  97. data/lib/pg-verify/puml/puml.rb +23 -0
  98. data/lib/pg-verify/shell/loading/line_animation.rb +36 -0
  99. data/lib/pg-verify/shell/loading/loading_animation.rb +80 -0
  100. data/lib/pg-verify/shell/loading/loading_prompt.rb +43 -0
  101. data/lib/pg-verify/shell/loading/no_animation.rb +20 -0
  102. data/lib/pg-verify/shell/shell.rb +30 -0
  103. data/lib/pg-verify/simulation/simulation.rb +7 -0
  104. data/lib/pg-verify/simulation/simulator.rb +90 -0
  105. data/lib/pg-verify/simulation/state.rb +53 -0
  106. data/lib/pg-verify/transform/hash_transformation.rb +104 -0
  107. data/lib/pg-verify/transform/nusmv_transformation.rb +261 -0
  108. data/lib/pg-verify/transform/puml_transformation.rb +89 -0
  109. data/lib/pg-verify/transform/transform.rb +8 -0
  110. data/lib/pg-verify/version.rb +5 -0
  111. data/lib/pg-verify.rb +47 -0
  112. data/pg-verify.gemspec +38 -0
  113. data/sig/pg-verify.rbs +4 -0
  114. metadata +226 -0
@@ -0,0 +1,26 @@
1
+ model :TestAction do
2
+ graph :Engine do
3
+ states :running, :stopped
4
+ end
5
+ graph :Car do
6
+ states :accelerating, :decelerating
7
+
8
+ transition :accelerating => :decelerating do
9
+ guard "Engine == stopped"
10
+ end
11
+ transition :decelerating => :decelerating do
12
+ guard "Engine == stopped"
13
+ end
14
+ transition :accelerating => :accelerating do
15
+ guard "Engine == running"
16
+ end
17
+ transition :decelerating => :accelerating do
18
+ guard "Engine == running"
19
+ end
20
+
21
+ end
22
+ specify "The car" do
23
+ it "accelerates when the engine is running" => :"G (Engine == running => X Car == accelerating)"
24
+ it "decelerates when the engine isn't running" => :"G (Engine != running => X Car == decelerating)"
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ model :TestAction do
2
+ graph :Test do
3
+ state :idle
4
+ var a: (0..1), init: 0
5
+ var b: (0..1), init: "b == 0"
6
+ var c: (-1..2), init: "c > 0 && c < 2"
7
+ var d: (0..10), init: "d == c + c + c"
8
+
9
+ var u: (0..10)
10
+ init "u == 2"
11
+
12
+ var x: (0..10), init: "x >= 5 && x <= 7"
13
+ var y: (0..10), init: "y = x + 1"
14
+ var z: (0..1)
15
+ end
16
+ specify "The variable" do
17
+ it "a is initialized to 0" => :"a == 0"
18
+ it "b is initialized to 0" => :"b == 0"
19
+ it "c is initialized to 1" => :"c == 1"
20
+ it "d is initialized to 3" => :"d == 3"
21
+
22
+ it "u is initialized to 2" => :"u == 2"
23
+
24
+ it "x is initialized to either 5, 6 or 7" => :"x == 5 || x == 6 || x == 7"
25
+ it "y is initialized to either 6, 7 or 8" => :"y == 6 || y == 7 || y == 8"
26
+ it "z is either 0 or 1" => :"z == 0 || z == 1"
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ model :TestAction do
2
+
3
+ graph :GraphZero do
4
+ states :a, :b, :c
5
+ end
6
+ graph :GraphOne do
7
+ states :a, :b, :c
8
+ init "GraphOne == a || GraphOne == b"
9
+ end
10
+ graph :GraphTwo do
11
+ states :a, :b, :c, init: :a
12
+ end
13
+
14
+ specify "The graph" do
15
+ it "GraphZero starts in a or b or c" => :"GraphOne == a || GraphOne == b || GraphOne == c"
16
+ it "GraphOne starts in a or b" => :"GraphOne == a || GraphOne == b"
17
+ it "GraphTwo starts in a" => :"GraphTwo == a"
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ model :TestAction do
2
+
3
+ graph :Car do
4
+ var distance_to_wall: (0..10), init: 0
5
+
6
+ states :DrivingRelentlessly
7
+
8
+ transition :DrivingRelentlessly => :DrivingRelentlessly do
9
+ precon "distance_to_wall > 0"
10
+ action "distance_to_wall := distance_to_wall - 1"
11
+ end
12
+ end
13
+ graph :CrashDetector do
14
+ states :no_crash, :crash, init: :no_crash
15
+
16
+ transition :no_crash => :crash do
17
+ guard "distance_to_wall == 0"
18
+ end
19
+
20
+ end
21
+
22
+ specify "The car" do
23
+ it "starts out driving" => :"CrashDetector == no_crash && Car == DrivingRelentlessly"
24
+ it "crashes horribly" => :"F CrashDetector == crash"
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ model :TestAction do
2
+ graph :Test do
3
+ var a: (0..10), init: 0
4
+ var b: (0..10), init: 10
5
+
6
+ states :initial, :second ,init: :initial
7
+ transition :initial => :second do
8
+ guard "b > 5 && a + 1 == 1 && a != b && Other == second"
9
+ end
10
+ end
11
+ graph :Other do
12
+ states :initial, :second, init: :initial
13
+ transition :initial => :second
14
+ end
15
+ specify "The Test" do
16
+ it "does reach the second state" => :"X X Test == second"
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ model :TestAction do
2
+ graph :Test do
3
+ var a: (0..11), init: 0
4
+ var b: (0..100), init: 0
5
+
6
+ states :go
7
+ transition :go => :go do
8
+ guard "a < 11"
9
+ action "a := a + 1 | b := a * a"
10
+ end
11
+ end
12
+ specify "The variable" do
13
+ it "a reaches 10" => :"F a == 10"
14
+ it "b reaches 100" => :"F b == 100"
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ model :Test do
2
+
3
+ transient error :TransientError
4
+ persistent error :PersistentError
5
+
6
+ specify "The persistent error" do
7
+ it "Stays persistent" => :"G ( PersistentError == Yes => ( ! F PersistentError == No ) )"
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ model :Test do
2
+
3
+ persistent error :BreakFailure
4
+
5
+ graph :Car do
6
+ states :driving
7
+ var distance_to_wall: (0..10), init: 9
8
+
9
+ transition :driving => :driving do
10
+ precon "distance_to_wall > 0"
11
+ guard "distance_to_wall > 5 || BreakFailure == Yes"
12
+ action "distance_to_wall := distance_to_wall - 1"
13
+ end
14
+
15
+ end
16
+
17
+ # Expected Cut Sets: { BreakFailure }
18
+ hazard "The car crashes" => :"distance_to_wall == 0"
19
+
20
+ specify "The car" do
21
+ assuming "the breaks don't fail" => :"G BreakFailure = No" do
22
+ it "does not crash" => :"G distance_to_wall > 0"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ model :Test do
2
+
3
+ graph :Graph1 do
4
+ var counter: (0..5), init: 0
5
+ states :counting
6
+ transition :counting => :counting do
7
+ precon "counter < 5"
8
+ action "counter := counter + 1"
9
+ end
10
+ end
11
+
12
+ graph :Graph2 do
13
+ states :idle
14
+ end
15
+
16
+ specify "The counter" do
17
+ it "Goes to 0" => :"counter == 0"
18
+ it "Goes to 1" => :"X counter == 1"
19
+ it "Goes to 2" => :"X X counter == 2"
20
+ it "Goes to 3" => :"X X X counter == 3"
21
+ it "Goes to 4" => :"X X X X counter == 4"
22
+ it "Goes to 5" => :"X X X X X counter == 5"
23
+ it "Stays at 5" => :"X X X X X X X X counter == 5"
24
+ it "reaches 5" => :"F counter == 5"
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ model :TestTransitions do
2
+
3
+ transient error :Err1
4
+ transient error :Err2
5
+ transient error :Err3
6
+ transient error :Err4
7
+ transient error :Err5
8
+
9
+ graph :OhNo do
10
+ states :Ok, :Bad, init: :Ok
11
+ transition :Ok => :Bad do
12
+ guard "(Err1 == Yes && Err2 == Yes && Err3 == Yes ) || (Err4 == Yes && Err5 == Yes)"
13
+ end
14
+ end
15
+
16
+ # Expected Cut Sets: { Err1, Err2, Err3 } { Err4, Err5 }
17
+ hazard "Bad" => :"OhNo == Bad"
18
+
19
+ end
@@ -0,0 +1,146 @@
1
+ model :PressureTank do
2
+
3
+ transient error :SwitchOperatedBadly
4
+ persistent error :SensorDead
5
+ # Relay is jammed if it does not open when wanted
6
+ persistent error :Relay1Jammed
7
+ persistent error :Relay2Jammed
8
+
9
+ # S1
10
+ graph :Switch do
11
+ states :Start, :Closed, :Open, init: :Start
12
+
13
+ # Randomly close at some point
14
+ transition :Start => :Start
15
+ transition :Start => :Closed
16
+
17
+ # Open right after closing (unless error)
18
+ transition :Closed => :Open do
19
+ guard "SwitchOperatedBadly == No"
20
+ end
21
+
22
+ # Close again if error
23
+ transition :Open => :Closed do
24
+ guard "SwitchOperatedBadly == Yes"
25
+ end
26
+ end
27
+
28
+ # T
29
+ graph :Timer do
30
+ states :Closed, :Open, :Countdown, init: :Closed
31
+
32
+ var time: (0..60), init: 0
33
+
34
+ countdown_requirement = "Sensor == Closed && ( Switch == Closed || Relay2 == Closed )"
35
+
36
+ transition :Closed => :Countdown do
37
+ guard countdown_requirement
38
+ end
39
+ transition :Countdown => :Countdown do
40
+ guard "#{countdown_requirement} && time < 60"
41
+ action "time := time + 1"
42
+ end
43
+
44
+ transition :Countdown => :Open do
45
+ guard "#{countdown_requirement} && time = 60"
46
+ action "time := 0"
47
+ end
48
+
49
+ transition :Open => :Closed
50
+
51
+ end
52
+
53
+ # D
54
+ graph :Tank do
55
+ states :Functional, :Raptured, init: :Functional
56
+ var pressure: (0..70), init: 0
57
+
58
+ transition :Functional => :Functional do
59
+ guard "Motor == On"
60
+ action "pressure := pressure + 1"
61
+ end
62
+ transition :Functional => :Functional do
63
+ guard "Motor == Off"
64
+ action "pressure := 0"
65
+ end
66
+ transition :Functional => :Raptured do
67
+ guard "Motor == On && pressure > 65"
68
+ action "pressure := 0"
69
+ end
70
+ end
71
+
72
+ # M
73
+ graph :Motor do
74
+ states :Off, :On, init: :Off
75
+
76
+ # Motor turns on/off if relay 2 delivers power or doesn't
77
+ transition :Off => :On do
78
+ guard "Relay2 == Closed"
79
+ end
80
+ transition :On => :Off do
81
+ guard "Relay2 == Open"
82
+ end
83
+ end
84
+
85
+ # S
86
+ graph :Sensor do
87
+ states :Closed, :Open, init: :Closed
88
+
89
+ # Trigger when tank is full (unless dead)
90
+ transition :Closed => :Open do
91
+ guard "pressure >= 60 && SensorDead == No"
92
+ end
93
+ # Stop triggering when tank is empty
94
+ transition :Open => :Closed do
95
+ guard "pressure < 60"
96
+ end
97
+ end
98
+
99
+ # K1
100
+ graph :Relay1 do
101
+ states :Closed, :Open, init: :Open
102
+
103
+ transition :Closed => :Open do
104
+ guard "Timer == Open && Relay1Jammed == No"
105
+ end
106
+ transition :Open => :Closed do
107
+ guard "Timer != Open && Switch == Closed"
108
+ end
109
+ end
110
+
111
+ # K2
112
+ graph :Relay2 do
113
+ states :Closed, :Open, init: :Open
114
+
115
+ close_requirement = "Switch == Closed || ( Relay1 == Closed && Sensor == Closed )"
116
+
117
+ transition :Open => :Closed do
118
+ guard close_requirement
119
+ end
120
+
121
+ transition :Closed => :Open do
122
+ guard "! ( #{close_requirement} ) && Relay2Jammed == No"
123
+ end
124
+
125
+ end
126
+
127
+ specify "The tank" do
128
+ assuming no_errors do
129
+ assuming "the switch is pressed" => :"F Switch == Closed" do
130
+ it "will be pressured" => :"F pressure == 60"
131
+ it "will be depressured again" => :"F (pressure == 60 && F pressure == 0)"
132
+ end
133
+ it "does not rapture" => :"! F Tank == Raptured"
134
+ end
135
+ end
136
+
137
+ specify "The switch" do
138
+ assuming "no fault" => :"G ( SwitchOperatedBadly == No )" do
139
+ it "is only pressed once" => :"G ( Switch == Closed => X (! F Switch == Closed ))"
140
+ end
141
+ end
142
+
143
+ # Expected Cut Sets: { SwitchOperatedBadly } { Relay2Jammed } { SensorDead, Relay1Jammed }
144
+ hazard "The tank raptures" => :"Tank == Raptured"
145
+
146
+ end
@@ -0,0 +1,235 @@
1
+ # Require all module files
2
+ Dir[File.join(__dir__, '*.rb')].sort.each { |file| require file }
3
+
4
+ require 'thor'
5
+ require 'plantuml_builder'
6
+
7
+ module PgVerify
8
+ module Cli
9
+
10
+ class ShowCommand < Thor
11
+
12
+ desc "puml", "Shows the model in PlantUML format"
13
+ method_option :only, :type => :array, repeatable: true
14
+ method_option :hide, :type => :array, repeatable: true
15
+ method_option :script, :type => :string
16
+ def puml()
17
+ script_file = options[:script] || Settings.ruby_dsl.default_script_name
18
+ models = Interpret::PgScript.new.interpret(script_file)
19
+ models.each { |model|
20
+ components = self.class.select_components(options[:only], options[:hide], model)
21
+ puml = Transform::PumlTransformation.new.transform_graph(model, only: components)
22
+ puts puml
23
+ }
24
+ end
25
+
26
+ desc "png", "Shows the model as a PNG image"
27
+ method_option :only, :type => :array, repeatable: true
28
+ method_option :hide, :type => :array, repeatable: true
29
+ method_option :script, :type => :string
30
+ def png()
31
+ script_file = options[:script] || Settings.ruby_dsl.default_script_name
32
+ models = Interpret::PgScript.new.interpret(script_file)
33
+
34
+ models.each { |model|
35
+ components = self.class.select_components(options[:only], options[:hide], model)
36
+ puml = Transform::PumlTransformation.new.transform_graph(model, only: components)
37
+ png = PlantumlBuilder::Formats::PNG.new(puml).load
38
+ out_name = File.basename(script_file, '.*')
39
+ out_name += "-" + model.name.to_s.gsub(/\W+/, '_').downcase + ".png"
40
+ out_path = File.expand_path(out_name, Settings.outdir)
41
+ FileUtils.mkdir_p(Settings.outdir)
42
+ File.binwrite(out_path, png)
43
+ puts "Wrote #{out_path.c_file}"
44
+ }
45
+ end
46
+
47
+ desc "yaml", "Shows the model in YAML format"
48
+ method_option :script, :type => :string
49
+ def yaml()
50
+ script_file = options[:script] || Settings.ruby_dsl.default_script_name
51
+ models = Interpret::PgScript.new.interpret(script_file)
52
+
53
+ models.each { |model|
54
+ hash = Transform::HashTransformation.new.transform_graph(model)
55
+ puts hash.to_yaml
56
+ }
57
+ end
58
+
59
+ desc "json", "Shows the model in Json format"
60
+ method_option :script, :type => :string
61
+ def json()
62
+ script_file = options[:script] || Settings.ruby_dsl.default_script_name
63
+ models = Interpret::PgScript.new.interpret(script_file)
64
+
65
+ models.each { |model|
66
+ hash = Transform::HashTransformation.new.transform_graph(model)
67
+ puts JSON.pretty_generate(hash)
68
+ }
69
+ end
70
+
71
+ desc "nusmv", "Shows the model in NuSMV format"
72
+ method_option :script, :type => :string
73
+ def nusmv()
74
+ script_file = options[:script] || Settings.ruby_dsl.default_script_name
75
+ models = Interpret::PgScript.new.interpret(script_file)
76
+
77
+ models.each { |model|
78
+ nusmv = Transform::NuSmvTransformation.new.transform_graph(model)
79
+ puts nusmv
80
+ }
81
+ end
82
+
83
+ def self.select_components(only_arg, hide_arg, model)
84
+ only = (only_arg || []).flatten.map(&:to_s).map(&:downcase)
85
+ hide = (hide_arg || []).flatten.map(&:to_s).map(&:downcase)
86
+ components = model.components.map(&:name)
87
+ components = components.select { |c| only.include?(c.to_s.downcase) } unless only.empty?
88
+ components = components.reject { |c| hide.include?(c.to_s.downcase) } unless hide.empty?
89
+ return components
90
+ end
91
+
92
+ end
93
+
94
+ class BaseCommand < Thor
95
+
96
+ desc "test", "Test the model specifications"
97
+ method_option :script, :type => :string
98
+ def test()
99
+ script_file = options[:script] || Settings.ruby_dsl.default_script_name
100
+ models = Interpret::PgScript.new.interpret(script_file)
101
+
102
+ models.each { |model|
103
+ results = Shell::LoadingPrompt.while_loading("Running specifications") {
104
+ NuSMV::Runner.new().run_specs(model)
105
+ }
106
+
107
+ results.each { |result|
108
+ stat_string = result.success ? "PASSED".c_success : "FAILED".c_error
109
+ puts "[ #{stat_string} ] #{result.spec.text}"
110
+ puts " #{result.spec.expression.to_s.c_blue}"
111
+ unless result.success
112
+ puts "Here is the trace:".c_red
113
+ trace_s = result.trace.to_s.indented(str: " >> ".c_red)
114
+ puts trace_s + "\n"
115
+ end
116
+ }
117
+ }
118
+ end
119
+
120
+ desc "dcca", "Run the automatic DCCA for hazards of the model"
121
+ method_option :script, :type => :string
122
+ def dcca()
123
+ script_file = options[:script] || Settings.ruby_dsl.default_script_name
124
+ models = Interpret::PgScript.new.interpret(script_file)
125
+
126
+ models.each { |model|
127
+ dcca = Model::DCCA.new(model, NuSMV::Runner.new)
128
+ result = Shell::LoadingPrompt.while_loading("Calculating DCCA for #{model.name.to_s.c_string}") {
129
+ dcca.perform()
130
+ }
131
+ result.each { |hazard, crit_sets|
132
+ s = crit_sets.length == 1 ? "" : "s"
133
+ message = "Hazard #{hazard.text.to_s.c_string} (#{hazard.expression.to_s.c_blue}) "
134
+ message += "has #{crit_sets.length.to_s.c_num} minimal critical cut set#{s}:"
135
+ puts message
136
+ crit_sets.each { |set|
137
+ puts "\t{ #{set.map(&:to_s).map(&:c_blue).join(', ')} }"
138
+ }
139
+ }
140
+ }
141
+ end
142
+
143
+ desc "simulate", "Simulate the model and save each step as an image"
144
+ method_option :script, :type => :string
145
+ method_option :steps, :type => :numeric, default: 10
146
+ method_option :force, :type => :numeric, default: 10
147
+ method_option :random, :type => :boolean, default: false
148
+ method_option :png, :type => :boolean, default: false
149
+ def simulate()
150
+ script_file = options[:script] || Settings.ruby_dsl.default_script_name
151
+ models = Interpret::PgScript.new.interpret(script_file)
152
+ runner = NuSMV::Runner.new
153
+
154
+ models.each { |model|
155
+ trace = Shell::LoadingPrompt.while_loading("Simulating model #{model.name.to_s.c_string}") {
156
+ runner.run_simulation(model, options[:steps], random: options[:random])
157
+ }
158
+
159
+ # Print the trace
160
+ puts trace.to_s
161
+
162
+ next unless options[:png]
163
+
164
+ # Prepare output dir
165
+ out_dir = File.expand_path("simulate-" + model.name.to_s.gsub(/\W+/, '_').downcase, Settings.outdir)
166
+ FileUtils.mkdir_p(out_dir)
167
+
168
+ # Generate images
169
+ Shell::LoadingPrompt.while_loading("Rendering states") { |printer|
170
+ trace.states.each_with_index { |variable_state, index|
171
+ printer.printl("Step #{index + 1}/#{trace.states.length}")
172
+ puml = Transform::PumlTransformation.new.transform_graph(model, variable_state: variable_state)
173
+ png = PlantumlBuilder::Formats::PNG.new(puml).load
174
+ out_path = File.expand_path("step-#{index}.png", out_dir)
175
+ File.binwrite(out_path, png)
176
+ }
177
+ }
178
+ puts "Wrote #{trace.states.length.to_s.c_num} files to #{out_dir.c_file}"
179
+ }
180
+
181
+ end
182
+
183
+ desc "init", "Initialize a new pg-verify project"
184
+ method_option :directory, :type => :string
185
+ def init()
186
+
187
+ target = options[:directory].blank? ? Dir.pwd() : File.expand_path(options[:directory])
188
+ if Dir.exist?(target) && Dir.entries(target).size > 2
189
+ puts "The target directory #{target.c_file} isn't empty!"
190
+ exit 1
191
+ end
192
+
193
+ # Create target dir
194
+ FileUtils.mkdir_p(target)
195
+
196
+ # Copy files to target
197
+ template_dir = File.join(PgVerify.root, "data", "project-template")
198
+ files = Dir.glob(File.join(template_dir, '**', '*'), File::FNM_DOTMATCH).select { |f| File.file?(f) }
199
+ files.each { |f|
200
+ target_file = File.join(target, f.sub(template_dir, ""))
201
+ target_file = target_file.gsub(".resource", "")
202
+ FileUtils.mkdir_p(File.dirname(target_file))
203
+ FileUtils.cp(f, target_file) unless File.basename(f) == ".keep"
204
+ }
205
+ # Copy the actual default config into the project as that
206
+ # will contain all keys and should be commented
207
+ FileUtils.cp(File.join(PgVerify.root, "data", "config", "pg-verify.yml"), File.join(target, ".pg-verify.yml"))
208
+
209
+ # Initialize git project
210
+ Dir.chdir(target) {
211
+ Core::CMDRunner.run_cmd("git init")
212
+ Core::CMDRunner.run_cmd("git add .")
213
+ Core::CMDRunner.run_cmd("git commit -m \"Initial Commit\"")
214
+ }
215
+
216
+ puts "Successfully initialized project at #{target.c_file}!"
217
+ puts "You can read the #{'README.md'.c_file} to get started."
218
+ puts "Run #{'pg-verify doctor'.c_blue} and follow the instructions to set up your environment!"
219
+ end
220
+
221
+ desc "doctor", "Check for common problems"
222
+ def doctor()
223
+ Doctor.check()
224
+ end
225
+
226
+ desc "show", "Show the program graph multiple different ways"
227
+ subcommand 'show', ShowCommand
228
+
229
+ # Make Thor exit with non-0 in case of errors
230
+ def self.exit_on_failure?(); true end
231
+
232
+ end
233
+
234
+ end
235
+ end