pg-verify 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -20
- data/README.md +1 -1
- data/data/banner.txt +5 -0
- data/data/project-template/README.md +81 -0
- data/doc/examples/vending_machine/checkpoint_1.rb +29 -0
- data/doc/examples/vending_machine/checkpoint_2.rb +47 -0
- data/doc/examples/vending_machine/checkpoint_3.rb +68 -0
- data/doc/examples/vending_machine/checkpoint_4.rb +202 -0
- data/integration_tests/ruby_dsl/001_states.rb +2 -1
- data/integration_tests/ruby_dsl/002_transitions.rb +2 -1
- data/integration_tests/ruby_dsl/017_ctl_specifications.rb +19 -0
- data/integration_tests/ruby_dsl/018_non_atomic_hazard.rb +17 -0
- data/integration_tests/ruby_dsl/019_multiple_actions.rb +21 -0
- data/integration_tests/ruby_dsl/020_vending_machine.rb +188 -0
- data/lib/pg-verify/cli/cli.rb +94 -24
- data/lib/pg-verify/cli/cli_utils.rb +61 -0
- data/lib/pg-verify/interpret/component_context.rb +1 -1
- data/lib/pg-verify/interpret/interpret.rb +1 -1
- data/lib/pg-verify/interpret/pg_script.rb +1 -0
- data/lib/pg-verify/model/parsed_expression.rb +10 -5
- data/lib/pg-verify/model/simulation/trace.rb +15 -4
- data/lib/pg-verify/model/validation/errors.rb +21 -2
- data/lib/pg-verify/nusmv/runner.rb +69 -17
- data/lib/pg-verify/puml/puml.rb +1 -0
- data/lib/pg-verify/transform/hash_transformation.rb +46 -14
- data/lib/pg-verify/transform/nusmv_transformation.rb +4 -3
- data/lib/pg-verify/transform/puml_transformation.rb +27 -8
- data/lib/pg-verify/version.rb +1 -1
- data/pg-verify.gemspec +0 -1
- data/vscript.rb +64 -0
- metadata +14 -18
- data/data/project-template/program-graph.rb.resource +0 -103
- /data/{data/project-template/.pg-verify.yml → lib/pg-verify/puml/runner.rb} +0 -0
@@ -5,9 +5,17 @@ module PgVerify
|
|
5
5
|
class Runner
|
6
6
|
|
7
7
|
def run_specs(program_graph)
|
8
|
-
|
9
|
-
|
8
|
+
transformer = Transform::NuSmvTransformation.new
|
9
|
+
nusmv_s = transformer.transform_graph(program_graph, include_specs: false)
|
10
|
+
commands = [ "go" ]
|
10
11
|
specs = program_graph.specification.flatten()
|
12
|
+
transform_map = specs.each { |spec|
|
13
|
+
transformed_spec_expr = transformer.transform_expression(spec.expression, program_graph.all_variables)
|
14
|
+
cmd = { ltl: "check_ltlspec", ctl: "check_ctlspec" }[spec.expression.predict_type]
|
15
|
+
commands << "#{cmd} -p \"#{transformed_spec_expr}\""
|
16
|
+
}
|
17
|
+
output = eval_nusmv(nusmv_s, commands: commands)
|
18
|
+
|
11
19
|
return parse_spec_results(program_graph, specs, output)
|
12
20
|
end
|
13
21
|
|
@@ -20,8 +28,8 @@ module PgVerify
|
|
20
28
|
nusmv_output.split("\n").each { |line|
|
21
29
|
result = line[/-- specification .* is (true|false)/, 1]
|
22
30
|
if !result.nil?
|
23
|
-
blocks << current_block unless current_block.nil?
|
24
|
-
current_block = block.new(result == "true", [])
|
31
|
+
blocks << current_block unless current_block.nil? # Complete the last block
|
32
|
+
current_block = block.new(result == "true", []) # Start a new block
|
25
33
|
next
|
26
34
|
end
|
27
35
|
current_block.lines << line unless current_block.nil?
|
@@ -41,10 +49,7 @@ module PgVerify
|
|
41
49
|
# variable to the value of that variable in that state
|
42
50
|
def run_simulation(program_graph, steps, random: false)
|
43
51
|
commands = []
|
44
|
-
commands << "
|
45
|
-
commands << "flatten_hierarchy"
|
46
|
-
commands << "encode_variables"
|
47
|
-
commands << "build_model"
|
52
|
+
commands << "go"
|
48
53
|
commands << "pick_state #{random ? '-r' : ''}"
|
49
54
|
commands << "simulate -k #{steps.to_s.to_i} -v #{random ? '-r' : ''}"
|
50
55
|
commands << "quit"
|
@@ -53,12 +58,48 @@ module PgVerify
|
|
53
58
|
return parse_trace(program_graph, output)
|
54
59
|
end
|
55
60
|
|
61
|
+
def run_check!(program_graph)
|
62
|
+
deadlock_state = run_check(program_graph)
|
63
|
+
return if deadlock_state.nil?
|
64
|
+
raise Model::Validation::DeadlockInFSMError.new(program_graph, deadlock_state)
|
65
|
+
end
|
66
|
+
|
67
|
+
def run_check(program_graph)
|
68
|
+
commands = [ "go", "check_fsm", "quit" ]
|
69
|
+
nusmv_s = Transform::NuSmvTransformation.new.transform_graph(program_graph, include_specs: false)
|
70
|
+
output = eval_nusmv(nusmv_s, commands: commands)
|
71
|
+
|
72
|
+
# Return "ok" if the FSM has no deadlocks
|
73
|
+
return nil if output.include?("The transition relation is total: No deadlock state exists")
|
74
|
+
|
75
|
+
# Otherwise compute and return the deadlock state
|
76
|
+
lines = output.split("\n").drop_while { |str|
|
77
|
+
!(str.start_with?("A deadlock state is:") || str.start_with?("successors is:"))
|
78
|
+
}
|
79
|
+
lines = lines[1, lines.length - 1]
|
80
|
+
|
81
|
+
var_state = {}
|
82
|
+
lines.each { |line|
|
83
|
+
key, val = parse_var_assignment_in_trace(line)
|
84
|
+
next if key.nil? || val.nil?
|
85
|
+
var_state[key] = val
|
86
|
+
}
|
87
|
+
|
88
|
+
return var_state
|
89
|
+
end
|
90
|
+
|
56
91
|
def parse_trace(program_graph, nusmv_output)
|
57
92
|
var_states, current_var_state = [], nil
|
58
93
|
|
59
94
|
loop_index = -1
|
60
95
|
|
61
96
|
nusmv_output.split("\n").each { |line|
|
97
|
+
# Mark a loop for the following state
|
98
|
+
if line.include?("Loop starts here")
|
99
|
+
loop_index = var_states.length
|
100
|
+
next
|
101
|
+
end
|
102
|
+
|
62
103
|
# Wait for heading of new state
|
63
104
|
if line.match(/\s*-> State: .+ <-/)
|
64
105
|
# Complete and store the old state if any
|
@@ -76,17 +117,28 @@ module PgVerify
|
|
76
117
|
# Skip lines before the first state
|
77
118
|
next if current_var_state.nil?
|
78
119
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
key_val = line.split("=").map(&:strip)
|
85
|
-
key = key_val[0].gsub("v.V_", "").to_sym
|
86
|
-
val = key_val[1].gsub("L_", "")
|
120
|
+
# Parse the variable state
|
121
|
+
key, val = parse_var_assignment_in_trace(line)
|
122
|
+
next if key.nil? || val.nil?
|
87
123
|
current_var_state[key] = val
|
88
124
|
}
|
89
|
-
|
125
|
+
# Finish up
|
126
|
+
unless current_var_state.nil?
|
127
|
+
missing_keys = var_states.empty? ? [] : var_states[-1].keys - current_var_state.keys
|
128
|
+
missing_keys.each { |key|
|
129
|
+
current_var_state[key] = var_states[-1][key]
|
130
|
+
}
|
131
|
+
var_states << current_var_state
|
132
|
+
end
|
133
|
+
return Model::Trace.new(program_graph, var_states, loop_index: loop_index)
|
134
|
+
end
|
135
|
+
|
136
|
+
def parse_var_assignment_in_trace(line)
|
137
|
+
return nil, nil unless line.include?("=")
|
138
|
+
key_val = line.split("=").map(&:strip)
|
139
|
+
key = key_val[0].gsub("v.V_", "").to_sym
|
140
|
+
val = key_val[1].gsub("L_", "")
|
141
|
+
return key, val
|
90
142
|
end
|
91
143
|
|
92
144
|
def eval_nusmv(nusmv_string, commands: [])
|
data/lib/pg-verify/puml/puml.rb
CHANGED
@@ -15,6 +15,7 @@ module PgVerify
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.convert_file(in_path, out_path)
|
18
|
+
# CALL WITH java -Djava.awt.headless=true -jar addon/plantuml-1.2023.13.jar --help
|
18
19
|
# TODO: The PlantUML jar switches focus to the desktop is if it would
|
19
20
|
# attempt to open a window each time it is invoked.
|
20
21
|
end
|
@@ -4,16 +4,24 @@ module PgVerify
|
|
4
4
|
class HashTransformation
|
5
5
|
|
6
6
|
def transform_graph(graph)
|
7
|
-
|
8
|
-
|
9
|
-
}
|
7
|
+
components = graph.components.map { |c| transform_component(graph, c) }
|
8
|
+
hazards = graph.hazards.map { |h| transform_hazard(h) }
|
9
|
+
sub_hash = {}
|
10
|
+
sub_hash["components"] = components
|
11
|
+
sub_hash["hazards"] = hazards unless hazards.empty?
|
12
|
+
sub_hash["specification"] = transform_specification(graph.specification)
|
13
|
+
return { graph.name.to_s => sub_hash }
|
10
14
|
end
|
11
15
|
|
12
16
|
def parse_graph(hash)
|
13
17
|
name = hash.keys.first.to_sym
|
14
18
|
graph = Model::Graph.new(name)
|
15
|
-
components = hash[hash.keys.first].map { |c| parse_component(graph, c) }
|
19
|
+
components = hash[hash.keys.first]["components"].map { |c| parse_component(graph, c) }
|
20
|
+
hazards = hash[hash.keys.first]["hazards"]&.map { |h| parse_hazard(h) } || []
|
21
|
+
specification = parse_specification(hash[hash.keys.first]["specification"])
|
16
22
|
graph.components = components
|
23
|
+
graph.hazards = hazards
|
24
|
+
graph.specification = specification
|
17
25
|
return graph
|
18
26
|
end
|
19
27
|
|
@@ -22,6 +30,7 @@ module PgVerify
|
|
22
30
|
return { component.name.to_s => {
|
23
31
|
"states" => component.states.map(&:to_s),
|
24
32
|
"variables" => variables.map { |v| transform_variable(v) },
|
33
|
+
"init" => transform_expression(component.init_expression),
|
25
34
|
"transitions" => component.transitions.map { |t| transform_transition(t) },
|
26
35
|
"represents_fault" => component.represents_fault
|
27
36
|
}}
|
@@ -31,13 +40,14 @@ module PgVerify
|
|
31
40
|
name = hash.keys.first
|
32
41
|
states = hash[name]["states"].map(&:to_sym)
|
33
42
|
variables = hash[name]["variables"].map { |v| parse_variable(name, v) }
|
43
|
+
init_expression = parse_expression(hash[name]["init"])
|
34
44
|
transitions = hash[name]["transitions"].map { |t| parse_transition(t) }
|
35
45
|
represents_fault = hash[name]["represents_fault"]
|
36
46
|
|
37
47
|
graph.variables += variables
|
38
48
|
|
39
49
|
return Model::Component.new(name: name.to_sym, states: states,
|
40
|
-
transitions: transitions, represents_fault: represents_fault)
|
50
|
+
transitions: transitions, represents_fault: represents_fault, init_expression: init_expression)
|
41
51
|
end
|
42
52
|
|
43
53
|
def transform_variable(variable)
|
@@ -49,7 +59,7 @@ module PgVerify
|
|
49
59
|
|
50
60
|
def parse_variable(owner_name, hash)
|
51
61
|
name = hash.keys.first
|
52
|
-
range = hash[name]["range"]
|
62
|
+
range = parse_variable_range(hash[name]["range"])
|
53
63
|
init_expression = parse_expression(hash[name]["init"])
|
54
64
|
return Model::Variable.new(name.to_sym, range, owner_name.to_sym, nil, init: init_expression)
|
55
65
|
end
|
@@ -60,9 +70,10 @@ module PgVerify
|
|
60
70
|
end
|
61
71
|
|
62
72
|
def parse_variable_range(range)
|
63
|
-
|
73
|
+
# Matches ranges like 1..12 or -1..23
|
74
|
+
if range.is_a?(String) && range.match(/\A\-?\d+\.\.\-?\d+\z/)
|
64
75
|
first, last = range.split("..")[0], range.split("..")[-1]
|
65
|
-
return Range.new(first, last)
|
76
|
+
return Range.new(first.to_i, last.to_i)
|
66
77
|
end
|
67
78
|
return range
|
68
79
|
end
|
@@ -85,20 +96,41 @@ module PgVerify
|
|
85
96
|
def transform_transition(transition)
|
86
97
|
name = "#{transition.src_state} -> #{transition.tgt_state}"
|
87
98
|
return { name => {
|
88
|
-
"precon" => transition.precon
|
89
|
-
"guard" => transition.guard
|
90
|
-
"action" => transition.action
|
99
|
+
"precon" => transform_expression(transition.precon),
|
100
|
+
"guard" => transform_expression(transition.guard),
|
101
|
+
"action" => transform_expression(transition.action),
|
91
102
|
}}
|
92
103
|
end
|
93
104
|
|
94
105
|
def parse_transition(hash)
|
95
106
|
src_state, tgt_state = hash.keys.first.split("->").map(&:strip).map(&:to_sym)
|
96
|
-
precon = hash.values.first["precon"]
|
97
|
-
guard = hash.values.first["guard"]
|
98
|
-
action = hash.values.first["action"]
|
107
|
+
precon = parse_expression(hash.values.first["precon"])
|
108
|
+
guard = parse_expression(hash.values.first["guard"] )
|
109
|
+
action = parse_expression(hash.values.first["action"])
|
99
110
|
return Model::Transition.new(src_state, tgt_state, precon: precon, guard: guard, action: action)
|
100
111
|
end
|
101
112
|
|
113
|
+
def transform_hazard(hazard)
|
114
|
+
return { "label" => hazard.text, "expression" => hazard.expression }
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse_hazard(hash)
|
118
|
+
return Model::Hazard.new(hash["label"], hash["expression"])
|
119
|
+
end
|
120
|
+
|
121
|
+
def transform_specification(specification)
|
122
|
+
return specification.flatten().map { |spec|
|
123
|
+
{ "label" => spec.text.strip, "expression" => transform_expression(spec.expression) }
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
def parse_specification(array)
|
128
|
+
specs = array.map { |hash|
|
129
|
+
Model::Spec.new(hash["label"], parse_expression(hash["expression"]), nil)
|
130
|
+
}
|
131
|
+
return Model::Specification.new([Model::SpecSet.wrap(specs)])
|
132
|
+
end
|
133
|
+
|
102
134
|
end
|
103
135
|
end
|
104
136
|
end
|
@@ -3,7 +3,7 @@ module PgVerify
|
|
3
3
|
|
4
4
|
class NuSmvTransformation
|
5
5
|
|
6
|
-
def transform_graph(program_graph)
|
6
|
+
def transform_graph(program_graph, include_specs: true)
|
7
7
|
variables = program_graph.all_variables
|
8
8
|
components = program_graph.components
|
9
9
|
|
@@ -11,7 +11,7 @@ module PgVerify
|
|
11
11
|
cmp_s = transform_components(components, variables)
|
12
12
|
main_s = transform_main_module(components)
|
13
13
|
|
14
|
-
specs = transform_specification(program_graph.specification, variables)
|
14
|
+
specs = transform_specification(program_graph.specification, variables) if include_specs
|
15
15
|
|
16
16
|
return "#{var_s}\n\n#{cmp_s}\n\n#{main_s}\n\n#{specs}\n"
|
17
17
|
end
|
@@ -251,7 +251,8 @@ module PgVerify
|
|
251
251
|
def transform_spec(spec, varset)
|
252
252
|
expression_s = "-- #{spec.text}\n"
|
253
253
|
expression_s += "-- Defined in: #{spec.source_location.to_s}\n"
|
254
|
-
|
254
|
+
keyword = { ltl: "LTLSPEC", ctl: "CTLSPEC" }[spec.expression.predict_type]
|
255
|
+
expression_s += keyword + " " + transform_expression(spec.expression, varset)
|
255
256
|
return expression_s
|
256
257
|
end
|
257
258
|
|
@@ -5,13 +5,25 @@ module PgVerify
|
|
5
5
|
|
6
6
|
class PumlTransformation
|
7
7
|
|
8
|
+
attr_accessor :render_labels
|
9
|
+
attr_accessor :render_precons
|
10
|
+
attr_accessor :render_guards
|
11
|
+
attr_accessor :render_actions
|
12
|
+
|
13
|
+
def initialize(render_options = {})
|
14
|
+
@render_labels = render_options[:render_labels].nil? ? true : render_options[:render_labels]
|
15
|
+
@render_precons = render_options[:render_precons].nil? ? true : render_options[:render_precons]
|
16
|
+
@render_guards = render_options[:render_guards].nil? ? true : render_options[:render_guards]
|
17
|
+
@render_actions = render_options[:render_actions].nil? ? true : render_options[:render_actions]
|
18
|
+
end
|
19
|
+
|
8
20
|
def transform_graph(graph, variable_state: nil, only: nil)
|
9
21
|
|
10
22
|
components = graph.components
|
11
23
|
components = components.select { |c| only.map(&:to_s).include?(c.name.to_s) } unless only.nil?
|
12
24
|
|
13
25
|
parts = []
|
14
|
-
parts << components.map { |c| transform_component(graph, c) }.join("\n\n")
|
26
|
+
parts << components.map { |c| transform_component(graph, c, variable_state) }.join("\n\n")
|
15
27
|
parts << transform_variable_state(graph, variable_state) unless variable_state.nil?
|
16
28
|
parts = parts.compact.join("\n\n")
|
17
29
|
return "@startuml Programmgraph\n#{parts}\n@enduml\n"
|
@@ -25,14 +37,14 @@ module PgVerify
|
|
25
37
|
}.join("\n")
|
26
38
|
end
|
27
39
|
|
28
|
-
def transform_component(graph, component)
|
40
|
+
def transform_component(graph, component, variable_state)
|
29
41
|
# Transform component states
|
30
42
|
states_s = component.states.map { |s| transform_state(component, s) }.join("\n")
|
31
43
|
# Transform component transitions
|
32
44
|
trans_s = component.transitions.map { |t| transform_transition(component, t) }.join("\n")
|
33
45
|
# Transform component variables
|
34
46
|
vars = graph.variables.select_by_owner(component.name)
|
35
|
-
vars_s = transform_variables(component, vars)
|
47
|
+
vars_s = transform_variables(component, vars, variable_state)
|
36
48
|
# Transform the initial state
|
37
49
|
initial_s = transform_initial(graph, component)
|
38
50
|
|
@@ -59,10 +71,14 @@ module PgVerify
|
|
59
71
|
end
|
60
72
|
|
61
73
|
def transform_transition(component, transition)
|
62
|
-
|
63
|
-
|
74
|
+
precon = @render_precons ? transition.precon : nil
|
75
|
+
guard = @render_guards ? transition.guard : nil
|
76
|
+
action = @render_actions ? transition.action : nil
|
64
77
|
|
78
|
+
label = [ precon, guard ].map(&:to_s).reject(&:empty?).join(" && ")
|
79
|
+
label += " / " + action.to_s unless action.nil?
|
65
80
|
label = ": #{label}" unless label.strip.empty?
|
81
|
+
label = "" unless @render_labels
|
66
82
|
return "#{component.name}_#{transition.src_state} --> #{component.name}_#{transition.tgt_state} #{label}"
|
67
83
|
end
|
68
84
|
|
@@ -70,9 +86,12 @@ module PgVerify
|
|
70
86
|
str.split("\n").map { |s| "\t#{s}" }.join("\n")
|
71
87
|
end
|
72
88
|
|
73
|
-
def transform_variables(component, variables)
|
74
|
-
return nil if variables.empty?
|
75
|
-
body = variables.map { |
|
89
|
+
def transform_variables(component, variables, variable_state)
|
90
|
+
return nil if variables.empty?
|
91
|
+
body = variables.map { |var|
|
92
|
+
value = variable_state.nil? ? transform_range(var.range) : variable_state[var.name]
|
93
|
+
"#{var.name} => #{value}"
|
94
|
+
}.join("\n")
|
76
95
|
return "map #{component.name}Variables {\n#{body}\n}"
|
77
96
|
end
|
78
97
|
|
data/lib/pg-verify/version.rb
CHANGED
data/pg-verify.gemspec
CHANGED
@@ -30,7 +30,6 @@ Gem::Specification.new do |spec|
|
|
30
30
|
|
31
31
|
# Gem dependencies
|
32
32
|
spec.add_dependency "thor", "~> 1.2.1"
|
33
|
-
spec.add_dependency "ebnf", "~> 2.3.4"
|
34
33
|
spec.add_dependency "rainbow", "~> 3.0.0"
|
35
34
|
spec.add_dependency "config", "~> 4.2.1"
|
36
35
|
spec.add_dependency "plantuml_builder", "~> 0.3.0"
|
data/vscript.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
task :gen_examples do
|
3
|
+
files = Dir["integration_tests/ruby_dsl/*.rb"].select(&:file?)
|
4
|
+
|
5
|
+
target_dir = "examples"
|
6
|
+
FileUtils.rm_rf(target_dir); FileUtils.mkdir(target_dir)
|
7
|
+
|
8
|
+
files.each { |file|
|
9
|
+
json = x "./devpg show json --script #{file}", verbose: false
|
10
|
+
yaml = x "./devpg show yaml --script #{file}", verbose: false
|
11
|
+
json_file = File.join(target_dir, File.basename(file, '.rb') + ".json")
|
12
|
+
yaml_file = File.join(target_dir, File.basename(file, '.rb') + ".yaml")
|
13
|
+
FileUtils.cp(file, File.join(target_dir, File.basename(file)))
|
14
|
+
File.write(json_file, json)
|
15
|
+
File.write(yaml_file, yaml)
|
16
|
+
|
17
|
+
x "tar -czf examples.tar.gz examples/"
|
18
|
+
}
|
19
|
+
FileUtils.rm_rf(target_dir)
|
20
|
+
|
21
|
+
done "Generated examples.tar.gz"
|
22
|
+
end
|
23
|
+
|
24
|
+
task :integration_test do
|
25
|
+
|
26
|
+
work_dir = ".pg-work"
|
27
|
+
FileUtils.rm_rf(work_dir); FileUtils.mkdir(work_dir)
|
28
|
+
|
29
|
+
test_files = Dir["integration_tests/ruby_dsl/*.rb"].sort.select(&:file?)
|
30
|
+
|
31
|
+
|
32
|
+
# Create json files for all tests
|
33
|
+
test_files.each { |source|
|
34
|
+
log "JSON #{File.basename(source)}"
|
35
|
+
target = File.join(work_dir, File.basename(source, '.rb') + ".json")
|
36
|
+
content = x "./devpg show json --script #{source}", verbose: false
|
37
|
+
File.write(target, content)
|
38
|
+
}
|
39
|
+
|
40
|
+
# Create json files for all tests
|
41
|
+
test_files.each { |source|
|
42
|
+
log "YAML #{File.basename(source)}"
|
43
|
+
target = File.join(work_dir, File.basename(source, '.rb') + ".yaml")
|
44
|
+
content = x "./devpg show yaml --script #{source}", verbose: false
|
45
|
+
File.write(target, content)
|
46
|
+
}
|
47
|
+
|
48
|
+
# Run tests for all files
|
49
|
+
test_files.each { |source|
|
50
|
+
args = {
|
51
|
+
"script" => source,
|
52
|
+
"json-file" => File.join(work_dir, File.basename(source, '.rb') + ".json"),
|
53
|
+
}
|
54
|
+
commands = [ "show puml", "test", "dcca", "show png", "simulate" ]
|
55
|
+
args.each { |argument, file|
|
56
|
+
commands.each { |command|
|
57
|
+
log "pgv #{command} #{argument} | #{File.basename(file)}"
|
58
|
+
x "./devpg #{command} --#{argument} #{file}", verbose: false
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
done "Great success!"
|
64
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg-verify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arian Weber
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.2.1
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: ebnf
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 2.3.4
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 2.3.4
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: rainbow
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -97,16 +83,19 @@ files:
|
|
97
83
|
- bin/pg-verify.rb
|
98
84
|
- bin/setup
|
99
85
|
- calc.ebnf
|
86
|
+
- data/banner.txt
|
100
87
|
- data/config/pg-verify.yml
|
101
88
|
- data/nusmv.sample.smv
|
102
89
|
- data/project-template/.gitignore.resource
|
103
|
-
- data/project-template/.pg-verify.yml
|
104
90
|
- data/project-template/README.md
|
105
91
|
- data/project-template/addon/.keep
|
106
|
-
- data/project-template/program-graph.rb.resource
|
107
92
|
- devpg
|
108
93
|
- doc/examples/railroad_crossing.rb
|
109
94
|
- doc/examples/train-tree.rb
|
95
|
+
- doc/examples/vending_machine/checkpoint_1.rb
|
96
|
+
- doc/examples/vending_machine/checkpoint_2.rb
|
97
|
+
- doc/examples/vending_machine/checkpoint_3.rb
|
98
|
+
- doc/examples/vending_machine/checkpoint_4.rb
|
110
99
|
- doc/examples/weidezaun.rb
|
111
100
|
- doc/examples/weidezaun.txt
|
112
101
|
- doc/expose/definition.png
|
@@ -130,8 +119,13 @@ files:
|
|
130
119
|
- integration_tests/ruby_dsl/014_tau_transitions.rb
|
131
120
|
- integration_tests/ruby_dsl/015_basic_dcca.rb
|
132
121
|
- integration_tests/ruby_dsl/016_pressure_tank.rb
|
122
|
+
- integration_tests/ruby_dsl/017_ctl_specifications.rb
|
123
|
+
- integration_tests/ruby_dsl/018_non_atomic_hazard.rb
|
124
|
+
- integration_tests/ruby_dsl/019_multiple_actions.rb
|
125
|
+
- integration_tests/ruby_dsl/020_vending_machine.rb
|
133
126
|
- lib/pg-verify.rb
|
134
127
|
- lib/pg-verify/cli/cli.rb
|
128
|
+
- lib/pg-verify/cli/cli_utils.rb
|
135
129
|
- lib/pg-verify/core/cmd_runner.rb
|
136
130
|
- lib/pg-verify/core/core.rb
|
137
131
|
- lib/pg-verify/core/extensions/array_extensions.rb
|
@@ -185,6 +179,7 @@ files:
|
|
185
179
|
- lib/pg-verify/nusmv/nusmv.rb
|
186
180
|
- lib/pg-verify/nusmv/runner.rb
|
187
181
|
- lib/pg-verify/puml/puml.rb
|
182
|
+
- lib/pg-verify/puml/runner.rb
|
188
183
|
- lib/pg-verify/shell/loading/line_animation.rb
|
189
184
|
- lib/pg-verify/shell/loading/loading_animation.rb
|
190
185
|
- lib/pg-verify/shell/loading/loading_prompt.rb
|
@@ -200,6 +195,7 @@ files:
|
|
200
195
|
- lib/pg-verify/version.rb
|
201
196
|
- pg-verify.gemspec
|
202
197
|
- sig/pg-verify.rbs
|
198
|
+
- vscript.rb
|
203
199
|
homepage: https://github.com/ArianWeber/pg-verify
|
204
200
|
licenses: []
|
205
201
|
metadata:
|
@@ -1,103 +0,0 @@
|
|
1
|
-
####################################################################
|
2
|
-
# Model definition
|
3
|
-
####################################################################
|
4
|
-
|
5
|
-
model :FeelThePain do
|
6
|
-
|
7
|
-
# Define a graph called 'Hand'.
|
8
|
-
graph :Hand do
|
9
|
-
# The hand can be touching the fence or be somewhere else
|
10
|
-
# It starts in 'somewhere' as that state is listed first
|
11
|
-
states :somewhere, :at_fence
|
12
|
-
|
13
|
-
# Transition non-deterministically between those states
|
14
|
-
transition :somewhere => :somewhere
|
15
|
-
transition :somewhere => :at_fence
|
16
|
-
transition :at_fence => :somewhere
|
17
|
-
transition :at_fence => :at_fence
|
18
|
-
end
|
19
|
-
|
20
|
-
# Define another graph called 'PowerSwitch', which also
|
21
|
-
# transitions non-deterministically.
|
22
|
-
graph :PowerSwitch do
|
23
|
-
states :off, :on
|
24
|
-
transition :off => :off
|
25
|
-
transition :off => :on
|
26
|
-
transition :on => :off
|
27
|
-
transition :on => :on
|
28
|
-
end
|
29
|
-
|
30
|
-
graph :Fence do
|
31
|
-
# The fence has no states we are interested in
|
32
|
-
states :exists
|
33
|
-
|
34
|
-
# The fence has a voltage which can go up to 15
|
35
|
-
var :voltage => (0..12), init: 0
|
36
|
-
|
37
|
-
# The voltage increases when the power switch is on
|
38
|
-
transition :exists => :exists do
|
39
|
-
guard "PowerSwitch == on"
|
40
|
-
action "voltage := voltage + 1"
|
41
|
-
end
|
42
|
-
# ..and instantly stops when the switch is off
|
43
|
-
transition :exists => :exists do
|
44
|
-
guard "PowerSwitch == off"
|
45
|
-
action "voltage := 0"
|
46
|
-
end
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
graph :Pain do
|
51
|
-
# We can either be in pain or not
|
52
|
-
states :No, :Yes
|
53
|
-
# Using regular variables with string interpolation
|
54
|
-
pain_threshold = 7
|
55
|
-
transition :No => :Yes do
|
56
|
-
# action "voltage := 2"
|
57
|
-
guard "Hand == at_fence && voltage >= #{pain_threshold}"
|
58
|
-
end
|
59
|
-
transition :Yes => :No do
|
60
|
-
guard "Hand == somewhere || voltage < #{pain_threshold}"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
####################################################################
|
65
|
-
# Validity tests
|
66
|
-
####################################################################
|
67
|
-
|
68
|
-
# Specification of validity characteristics regarding the hand.
|
69
|
-
# The 'specify' block serves as a namespace/container for the contained specifications
|
70
|
-
specify "The Hand" do
|
71
|
-
# Define some simple specs using a description text and an LTL expression
|
72
|
-
it "isn't always touching the fence" => :"F Hand == somewhere"
|
73
|
-
it "isn't always away form the fence" => :"F Hand == at_fence"
|
74
|
-
end
|
75
|
-
|
76
|
-
# Specification of validity characteristics regarding the pain
|
77
|
-
specify "The pain" do
|
78
|
-
# Use a regular LTL Formula as the expression
|
79
|
-
it "is felt at some point" => :"F Pain == Yes"
|
80
|
-
# Use a more declarative syntax. This becomes useful for complex expressions
|
81
|
-
# as LTL patterns can be used very easily
|
82
|
-
it "is always felt at some point" => ltl.globally.exists(:"Pain == Yes")
|
83
|
-
|
84
|
-
# Pattern: 'Universality', range: 'after q'
|
85
|
-
it "is felt after the switch is activated" => ltl.after(:"PowerSwitch == on").exists(:"Pain == Yes")
|
86
|
-
# Pattern: 'Absence', range: 'before q'
|
87
|
-
it "is never felt before the switch is activated" => ltl.before(:"PowerSwitch == on").never(:"Pain == Yes")
|
88
|
-
# Pattern: 'Reaction', range: 'global'
|
89
|
-
it "always reacts to the switch being activated" => ltl.globally.reacts(:"PowerSwitch == on", :"Pain == Yes")
|
90
|
-
|
91
|
-
# Define an assumption. That assumption must be true for all contained specs
|
92
|
-
assuming "the switch is never activated" => :"G PowerSwitch == off" do
|
93
|
-
it "is never felt " => :"G Pain == No"
|
94
|
-
end
|
95
|
-
|
96
|
-
# Assumptions can be nested and used with the declarative syntax.
|
97
|
-
assuming "the switch is activated" => ltl.globally.exists(:"PowerSwitch == No") do
|
98
|
-
assuming "the hand never touches the fence" => :"G Hand != at_fence" do
|
99
|
-
it "is never felt " => :"G Pain == No"
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
File without changes
|