pg-verify 0.1.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 +7 -0
- data/.rspec +3 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +98 -0
- data/README.md +29 -0
- data/Rakefile +62 -0
- data/bin/console +15 -0
- data/bin/pg-verify.rb +18 -0
- data/bin/setup +8 -0
- data/calc.ebnf +21 -0
- data/data/config/pg-verify.yml +66 -0
- data/data/nusmv.sample.smv +179 -0
- data/data/project-template/.gitignore.resource +4 -0
- data/data/project-template/.pg-verify.yml +0 -0
- data/data/project-template/README.md +18 -0
- data/data/project-template/addon/.keep +0 -0
- data/data/project-template/program-graph.rb.resource +103 -0
- data/devpg +5 -0
- data/doc/examples/railroad_crossing.rb +61 -0
- data/doc/examples/train-tree.rb +43 -0
- data/doc/examples/weidezaun.rb +99 -0
- data/doc/examples/weidezaun.txt +29 -0
- data/doc/expose/definition.png +0 -0
- data/doc/expose/diagram.png +0 -0
- data/doc/expose/expose.md +359 -0
- data/doc/expose/validity.png +0 -0
- data/exe/pg-verify +4 -0
- data/integration_tests/ruby_dsl/001_states.rb +10 -0
- data/integration_tests/ruby_dsl/002_transitions.rb +10 -0
- data/integration_tests/ruby_dsl/003_actions.rb +14 -0
- data/integration_tests/ruby_dsl/004_guards.rb +18 -0
- data/integration_tests/ruby_dsl/005_variables.rb +16 -0
- data/integration_tests/ruby_dsl/006_state_variables.rb +26 -0
- data/integration_tests/ruby_dsl/007_variable_initialization.rb +28 -0
- data/integration_tests/ruby_dsl/008_state_initialization.rb +19 -0
- data/integration_tests/ruby_dsl/009_shared_variables.rb +26 -0
- data/integration_tests/ruby_dsl/010_complex_guards.rb +18 -0
- data/integration_tests/ruby_dsl/011_complex_actions.rb +16 -0
- data/integration_tests/ruby_dsl/012_error_components.rb +9 -0
- data/integration_tests/ruby_dsl/013_hazards.rb +25 -0
- data/integration_tests/ruby_dsl/014_tau_transitions.rb +26 -0
- data/integration_tests/ruby_dsl/015_basic_dcca.rb +19 -0
- data/integration_tests/ruby_dsl/016_pressure_tank.rb +146 -0
- data/lib/pg-verify/cli/cli.rb +235 -0
- data/lib/pg-verify/core/cmd_runner.rb +151 -0
- data/lib/pg-verify/core/core.rb +38 -0
- data/lib/pg-verify/core/extensions/array_extensions.rb +11 -0
- data/lib/pg-verify/core/extensions/enumerable_extensions.rb +19 -0
- data/lib/pg-verify/core/extensions/nil_extensions.rb +7 -0
- data/lib/pg-verify/core/extensions/string_extensions.rb +84 -0
- data/lib/pg-verify/core/shell/colorizer.rb +136 -0
- data/lib/pg-verify/core/shell/shell.rb +0 -0
- data/lib/pg-verify/core/util.rb +146 -0
- data/lib/pg-verify/doctor/doctor.rb +180 -0
- data/lib/pg-verify/ebnf_parser/ast.rb +31 -0
- data/lib/pg-verify/ebnf_parser/ebnf_parser.rb +26 -0
- data/lib/pg-verify/ebnf_parser/expression_parser.rb +177 -0
- data/lib/pg-verify/ebnf_parser/expression_parser2.rb +422 -0
- data/lib/pg-verify/ebnf_parser/expressions.ebnf +33 -0
- data/lib/pg-verify/ebnf_parser/expressions.peg +52 -0
- data/lib/pg-verify/ebnf_parser/parser_result.rb +26 -0
- data/lib/pg-verify/interpret/component_context.rb +125 -0
- data/lib/pg-verify/interpret/graph_context.rb +85 -0
- data/lib/pg-verify/interpret/interpret.rb +142 -0
- data/lib/pg-verify/interpret/pg_script.rb +72 -0
- data/lib/pg-verify/interpret/spec/ltl_builder.rb +90 -0
- data/lib/pg-verify/interpret/spec/spec_context.rb +32 -0
- data/lib/pg-verify/interpret/spec/spec_set_context.rb +67 -0
- data/lib/pg-verify/interpret/transition_context.rb +55 -0
- data/lib/pg-verify/model/allocation_set.rb +28 -0
- data/lib/pg-verify/model/assignment.rb +34 -0
- data/lib/pg-verify/model/component.rb +40 -0
- data/lib/pg-verify/model/dcca/hazard.rb +16 -0
- data/lib/pg-verify/model/dcca.rb +67 -0
- data/lib/pg-verify/model/expression.rb +106 -0
- data/lib/pg-verify/model/graph.rb +58 -0
- data/lib/pg-verify/model/model.rb +10 -0
- data/lib/pg-verify/model/parsed_expression.rb +77 -0
- data/lib/pg-verify/model/simulation/trace.rb +43 -0
- data/lib/pg-verify/model/simulation/variable_state.rb +23 -0
- data/lib/pg-verify/model/source_location.rb +45 -0
- data/lib/pg-verify/model/specs/spec.rb +44 -0
- data/lib/pg-verify/model/specs/spec_result.rb +25 -0
- data/lib/pg-verify/model/specs/spec_set.rb +43 -0
- data/lib/pg-verify/model/specs/specification.rb +50 -0
- data/lib/pg-verify/model/transition.rb +41 -0
- data/lib/pg-verify/model/validation/assignment_to_state_variable_validation.rb +26 -0
- data/lib/pg-verify/model/validation/empty_state_set_validation.rb +18 -0
- data/lib/pg-verify/model/validation/errors.rb +119 -0
- data/lib/pg-verify/model/validation/foreign_assignment_validation.rb +30 -0
- data/lib/pg-verify/model/validation/unknown_token_validation.rb +35 -0
- data/lib/pg-verify/model/validation/validation.rb +23 -0
- data/lib/pg-verify/model/variable.rb +47 -0
- data/lib/pg-verify/model/variable_set.rb +84 -0
- data/lib/pg-verify/nusmv/nusmv.rb +23 -0
- data/lib/pg-verify/nusmv/runner.rb +124 -0
- data/lib/pg-verify/puml/puml.rb +23 -0
- data/lib/pg-verify/shell/loading/line_animation.rb +36 -0
- data/lib/pg-verify/shell/loading/loading_animation.rb +80 -0
- data/lib/pg-verify/shell/loading/loading_prompt.rb +43 -0
- data/lib/pg-verify/shell/loading/no_animation.rb +20 -0
- data/lib/pg-verify/shell/shell.rb +30 -0
- data/lib/pg-verify/simulation/simulation.rb +7 -0
- data/lib/pg-verify/simulation/simulator.rb +90 -0
- data/lib/pg-verify/simulation/state.rb +53 -0
- data/lib/pg-verify/transform/hash_transformation.rb +104 -0
- data/lib/pg-verify/transform/nusmv_transformation.rb +261 -0
- data/lib/pg-verify/transform/puml_transformation.rb +89 -0
- data/lib/pg-verify/transform/transform.rb +8 -0
- data/lib/pg-verify/version.rb +5 -0
- data/lib/pg-verify.rb +47 -0
- data/pg-verify.gemspec +38 -0
- data/sig/pg-verify.rbs +4 -0
- 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,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
|