pg-verify 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|