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,43 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
module Shell
|
|
3
|
+
|
|
4
|
+
module LoadingPrompt
|
|
5
|
+
|
|
6
|
+
def self.while_loading(loading_message)
|
|
7
|
+
if Shell.in_pipe? || !Settings.use_animations
|
|
8
|
+
# Do not play animations when piped
|
|
9
|
+
animation = NoAnimation.new(loading_message).start
|
|
10
|
+
else
|
|
11
|
+
animation = LineAnimation.new(loading_message).start
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
start_time = Time.now
|
|
15
|
+
|
|
16
|
+
begin
|
|
17
|
+
result = yield(animation) if block_given?
|
|
18
|
+
delta_time = Time.now - start_time
|
|
19
|
+
rescue StandardError, SignalException, Interrupt, IOError => e
|
|
20
|
+
exception = e
|
|
21
|
+
ensure
|
|
22
|
+
status = exception.nil? ? (result.nil? ? :empty : :success) : :error
|
|
23
|
+
status = result.state if !result.nil? && result.is_a?(LoadingResult)
|
|
24
|
+
animation.stop(status, result.is_a?(LoadingResult) ? result.message : "")
|
|
25
|
+
result = result.data if result.is_a?(LoadingResult)
|
|
26
|
+
raise exception unless exception.nil?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
return result
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class LoadingResult
|
|
33
|
+
attr_accessor :data, :message, :state
|
|
34
|
+
def initialize(data, message, state: :success)
|
|
35
|
+
@data = data
|
|
36
|
+
@message = message
|
|
37
|
+
@state = state
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require_relative 'loading_animation.rb'
|
|
2
|
+
|
|
3
|
+
module PgVerify
|
|
4
|
+
|
|
5
|
+
module Shell
|
|
6
|
+
|
|
7
|
+
class NoAnimation < LoadingAnimation
|
|
8
|
+
|
|
9
|
+
def initialize(loading_message)
|
|
10
|
+
super(loading_message)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def on_anim(passed_time)
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Require all module files
|
|
2
|
+
Dir[File.join(__dir__, "**", '*.rb')].sort.each { |file| require file }
|
|
3
|
+
require 'io/console'
|
|
4
|
+
|
|
5
|
+
module PgVerify
|
|
6
|
+
module Shell
|
|
7
|
+
|
|
8
|
+
def self.in_pipe?()
|
|
9
|
+
!in_tty?()
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.in_tty?()
|
|
13
|
+
$stdout.isatty
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.gen_prompt(level)
|
|
17
|
+
prompt = Settings.prompt.prompt_format % Settings.prompt[level]
|
|
18
|
+
prompt.color(level)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.expand_to_console(left, right)
|
|
22
|
+
return "#{left} (#{right})" unless Settings.allow_right_print
|
|
23
|
+
return "#{left} (#{right})" unless Shell.in_tty?
|
|
24
|
+
width = IO.console.nil? ? 4 : IO.console.winsize[1]
|
|
25
|
+
space = " " * [(width -(Colorizer.uncolorize(left).length + Colorizer.uncolorize(right).length)), 1].max
|
|
26
|
+
return left + space.c_sidenote + right
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
module Simulation
|
|
3
|
+
|
|
4
|
+
class Simulator
|
|
5
|
+
|
|
6
|
+
attr_accessor :graph
|
|
7
|
+
|
|
8
|
+
def initialize(graph)
|
|
9
|
+
@graph = graph
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run(steps: 10, state: State.for_variable_set(@graph.all_variables))
|
|
13
|
+
state_stack = [ state ]
|
|
14
|
+
steps.times {
|
|
15
|
+
component_transitions = find_next_transitions(state)
|
|
16
|
+
state = run_transitions(component_transitions, state)
|
|
17
|
+
state_stack << state
|
|
18
|
+
}
|
|
19
|
+
return state_stack
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns a map from 'component' to 'transition' which should
|
|
23
|
+
# be used for that component based in the state.
|
|
24
|
+
# If there is no matching transition for a component, that
|
|
25
|
+
# component is omitted as to use the "tau-transition".
|
|
26
|
+
def find_next_transitions(state)
|
|
27
|
+
return @graph.components.map { |cmp|
|
|
28
|
+
matching_transitions = cmp.transitions.select { |trans|
|
|
29
|
+
transition_accepted?(trans, cmp, state)
|
|
30
|
+
}
|
|
31
|
+
# Do not change the state if no transitions match
|
|
32
|
+
next if matching_transitions.empty?
|
|
33
|
+
|
|
34
|
+
# TODO: Choose random one using seed
|
|
35
|
+
chosen_transition = matching_transitions.shuffle().first
|
|
36
|
+
[cmp, chosen_transition]
|
|
37
|
+
}.compact.to_h
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Performs the specified transitions on the specified state
|
|
41
|
+
# and returns a new updated state.
|
|
42
|
+
def run_transitions(component_transitions, state)
|
|
43
|
+
new_state = state.clone()
|
|
44
|
+
component_transitions.each { |component, transition|
|
|
45
|
+
# Update the component state
|
|
46
|
+
new_state[component.name] = transition.tgt_state
|
|
47
|
+
|
|
48
|
+
# Perform the action
|
|
49
|
+
next if transition.action.nil?
|
|
50
|
+
parts = transition.action.to_s.split(":=")
|
|
51
|
+
assigned_variable = parts[0].strip
|
|
52
|
+
expression = parts[1].strip
|
|
53
|
+
|
|
54
|
+
new_state[assigned_variable.to_sym] = eval_expression(expression, state)
|
|
55
|
+
}
|
|
56
|
+
return new_state
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def transition_accepted?(transition, component, state)
|
|
60
|
+
# Transition can't be used unless the component is in the required
|
|
61
|
+
# source state.
|
|
62
|
+
return false unless transition.src_state == state[component.name]
|
|
63
|
+
|
|
64
|
+
guard_expression = [ transition.guard, transition.precon ]
|
|
65
|
+
.map(&:to_s).reject(&:empty?).join(" && ")
|
|
66
|
+
|
|
67
|
+
return false if eval_expression(guard_expression, state) == false
|
|
68
|
+
|
|
69
|
+
return true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def eval_expression(expression, state)
|
|
73
|
+
state.names.sort_by(&:length).reverse.each { |var|
|
|
74
|
+
value = state[var]
|
|
75
|
+
expression = expression.gsub(var.to_s, value.to_s)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
expression = Model::ParsedExpression.new(expression, nil)
|
|
79
|
+
words = expression.word_tokens.uniq
|
|
80
|
+
expression = expression.to_s
|
|
81
|
+
words.each { |word|
|
|
82
|
+
expression = expression.gsub(word.to_s, "'#{word}'")
|
|
83
|
+
}
|
|
84
|
+
return eval(expression)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
module Simulation
|
|
3
|
+
|
|
4
|
+
class State
|
|
5
|
+
|
|
6
|
+
attr_accessor :value_map
|
|
7
|
+
|
|
8
|
+
def self.for_variable_set(var_set)
|
|
9
|
+
value_map = var_set.map { |var|
|
|
10
|
+
# TODO: Choose random one when nil
|
|
11
|
+
raise "TODO: Implement this"
|
|
12
|
+
[var, init_val]
|
|
13
|
+
}.to_h
|
|
14
|
+
return self.new(value_map)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(value_map)
|
|
18
|
+
@value_map = value_map
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def [](var)
|
|
22
|
+
var = var.to_sym if var.is_a?(String)
|
|
23
|
+
var = @value_map.keys.detect { |v| v.name == var } if var.is_a?(Symbol)
|
|
24
|
+
return @value_map[var]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def []=(var, value)
|
|
28
|
+
var = var.to_sym if var.is_a?(String)
|
|
29
|
+
var = @value_map.keys.detect { |v| v.name == var } if var.is_a?(Symbol)
|
|
30
|
+
@value_map[var] = value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def variables()
|
|
34
|
+
return @value_map.keys
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def names()
|
|
38
|
+
return @value_map.keys.map(&:name)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def clone()
|
|
42
|
+
self.class.new(value_map.map { |k, v| [k, v] }.to_h)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def to_s()
|
|
46
|
+
var_s = @value_map.keys.map(&:name).join("\n")
|
|
47
|
+
val_s = @value_map.values.join("\n")
|
|
48
|
+
return var_s.line_combine(val_s, separator: " = ")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
module Transform
|
|
3
|
+
|
|
4
|
+
class HashTransformation
|
|
5
|
+
|
|
6
|
+
def transform_graph(graph)
|
|
7
|
+
return { graph.name.to_s =>
|
|
8
|
+
graph.components.map { |c| transform_component(graph, c) }
|
|
9
|
+
}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def parse_graph(hash)
|
|
13
|
+
name = hash.keys.first.to_sym
|
|
14
|
+
graph = Model::Graph.new(name)
|
|
15
|
+
components = hash[hash.keys.first].map { |c| parse_component(graph, c) }
|
|
16
|
+
graph.components = components
|
|
17
|
+
return graph
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def transform_component(graph, component)
|
|
21
|
+
variables = graph.variables.select_by_owner(component)
|
|
22
|
+
return { component.name.to_s => {
|
|
23
|
+
"states" => component.states.map(&:to_s),
|
|
24
|
+
"variables" => variables.map { |v| transform_variable(v) },
|
|
25
|
+
"transitions" => component.transitions.map { |t| transform_transition(t) },
|
|
26
|
+
"represents_fault" => component.represents_fault
|
|
27
|
+
}}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def parse_component(graph, hash)
|
|
31
|
+
name = hash.keys.first
|
|
32
|
+
states = hash[name]["states"].map(&:to_sym)
|
|
33
|
+
variables = hash[name]["variables"].map { |v| parse_variable(name, v) }
|
|
34
|
+
transitions = hash[name]["transitions"].map { |t| parse_transition(t) }
|
|
35
|
+
represents_fault = hash[name]["represents_fault"]
|
|
36
|
+
|
|
37
|
+
graph.variables += variables
|
|
38
|
+
|
|
39
|
+
return Model::Component.new(name: name.to_sym, states: states,
|
|
40
|
+
transitions: transitions, represents_fault: represents_fault)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def transform_variable(variable)
|
|
44
|
+
return { variable.name.to_s => {
|
|
45
|
+
"range" => transform_variable_range(variable.range),
|
|
46
|
+
"init" => transform_expression(variable.init_expression)
|
|
47
|
+
}}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_variable(owner_name, hash)
|
|
51
|
+
name = hash.keys.first
|
|
52
|
+
range = hash[name]["range"]
|
|
53
|
+
init_expression = parse_expression(hash[name]["init"])
|
|
54
|
+
return Model::Variable.new(name.to_sym, range, owner_name.to_sym, nil, init: init_expression)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def transform_variable_range(range)
|
|
58
|
+
return "#{range.first}..#{range.last}"if range.is_a?(Range)
|
|
59
|
+
return range
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def parse_variable_range(range)
|
|
63
|
+
if range.is_a?(String) && range.match(/\A\d+\.\.\d+\z/)
|
|
64
|
+
first, last = range.split("..")[0], range.split("..")[-1]
|
|
65
|
+
return Range.new(first, last)
|
|
66
|
+
end
|
|
67
|
+
return range
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def transform_expression(expression)
|
|
71
|
+
return nil if expression.nil?
|
|
72
|
+
return {
|
|
73
|
+
"string" => expression.expression_string,
|
|
74
|
+
"type" => expression.type.to_s
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def parse_expression(expression)
|
|
79
|
+
return nil if expression.nil?
|
|
80
|
+
string = expression["string"]
|
|
81
|
+
type = expression["type"].to_sym
|
|
82
|
+
return Model::ParsedExpression.new(string, type)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def transform_transition(transition)
|
|
86
|
+
name = "#{transition.src_state} -> #{transition.tgt_state}"
|
|
87
|
+
return { name => {
|
|
88
|
+
"precon" => transition.precon&.to_s,
|
|
89
|
+
"guard" => transition.guard&.to_s,
|
|
90
|
+
"action" => transition.action&.to_s,
|
|
91
|
+
}}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def parse_transition(hash)
|
|
95
|
+
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"]
|
|
99
|
+
return Model::Transition.new(src_state, tgt_state, precon: precon, guard: guard, action: action)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
module Transform
|
|
3
|
+
|
|
4
|
+
class NuSmvTransformation
|
|
5
|
+
|
|
6
|
+
def transform_graph(program_graph)
|
|
7
|
+
variables = program_graph.all_variables
|
|
8
|
+
components = program_graph.components
|
|
9
|
+
|
|
10
|
+
var_s = transform_variables(variables)
|
|
11
|
+
cmp_s = transform_components(components, variables)
|
|
12
|
+
main_s = transform_main_module(components)
|
|
13
|
+
|
|
14
|
+
specs = transform_specification(program_graph.specification, variables)
|
|
15
|
+
|
|
16
|
+
return "#{var_s}\n\n#{cmp_s}\n\n#{main_s}\n\n#{specs}\n"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def transform_variables(varset)
|
|
20
|
+
vars_s = varset.to_a.map { |v| transform_variable(v) }.join("\n")
|
|
21
|
+
return module_string("_VARS", var: vars_s)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def transform_components(components, varset)
|
|
25
|
+
return components.map { |c| transform_component(c, varset) }.join("\n\n")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def transform_variable(variable)
|
|
29
|
+
return "#{transform_varname(variable.name)} : #{transform_range(variable.range)}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def transform_range(range)
|
|
33
|
+
# Transform state variables
|
|
34
|
+
return "{#{range.map { |e| transform_const(e) }.join(", ")}};" if range.is_a?(Array)
|
|
35
|
+
# Transform small integer variables
|
|
36
|
+
return "{#{range.to_a.join(", ")}};" if (range.last - range.first) < 5
|
|
37
|
+
# Transform regular integer variables
|
|
38
|
+
return "#{range.first}..#{range.last};"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def transform_component(component, varset)
|
|
42
|
+
# Name of the component module
|
|
43
|
+
name = transform_module_name(component.name)
|
|
44
|
+
# The component takes all variables v
|
|
45
|
+
name += "(v)"
|
|
46
|
+
|
|
47
|
+
# Grab all variables which this component defines and thus owns
|
|
48
|
+
owned_vars = varset.select_by_owner(component.name).to_a
|
|
49
|
+
|
|
50
|
+
# Create the INIT block expression which must hold initially
|
|
51
|
+
# This is used to initialize variables and decide on the initial
|
|
52
|
+
# state of this component. This can all be randomized by setting
|
|
53
|
+
# the init state to true
|
|
54
|
+
init_expression = transform_init_expression(component, varset, owned_vars)
|
|
55
|
+
|
|
56
|
+
# Transform all transitions which are defined in this component
|
|
57
|
+
trans = component.transitions.map { |transition|
|
|
58
|
+
trans_s = transform_transition(varset, component, transition)
|
|
59
|
+
"( #{trans_s} )"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Create a default transition which is taken whenever no other
|
|
63
|
+
# transitions can.
|
|
64
|
+
tau_transition = []
|
|
65
|
+
# Combine the precondition, guard and source state conditions
|
|
66
|
+
# For each transition of this component
|
|
67
|
+
other_transitions = component.transitions.map { |transition|
|
|
68
|
+
precon_s = transform_expression(transition.precon, varset)
|
|
69
|
+
guard_s = transform_expression(transition.guard, varset)
|
|
70
|
+
|
|
71
|
+
cmp_varname = transform_varname(component.name)
|
|
72
|
+
this_state = transform_const(transition.src_state)
|
|
73
|
+
state_s = "v.#{cmp_varname} = #{this_state}"
|
|
74
|
+
|
|
75
|
+
[ state_s, precon_s, guard_s ].compact.reject(&:empty?).map{ |s| "( #{s} )" }.join(' & ')
|
|
76
|
+
}.compact.reject(&:empty?)
|
|
77
|
+
# Only take the tau transition when all other transitions do not match
|
|
78
|
+
tau_transition << "!(\n\t#{other_transitions.join(" | \n\t")})\n\t" unless other_transitions.empty?
|
|
79
|
+
|
|
80
|
+
# Keep this components state when using the default transition
|
|
81
|
+
# Keep all owned variables equal when using the default transition
|
|
82
|
+
tau_transition += owned_vars.map { |var|
|
|
83
|
+
varname = "v.#{transform_varname(var.name)}"
|
|
84
|
+
"next(#{varname}) = #{varname}"
|
|
85
|
+
}
|
|
86
|
+
# Add the tau transition
|
|
87
|
+
trans << "(#{tau_transition.join(" & ")})"
|
|
88
|
+
|
|
89
|
+
trans = trans.join(" | \n") + ";"
|
|
90
|
+
|
|
91
|
+
return module_string(name, init: init_expression, trans: trans)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def transform_init_expression(component, varset, owned_vars)
|
|
95
|
+
init = owned_vars.map { |var|
|
|
96
|
+
next if var.init_expression.nil?
|
|
97
|
+
transform_expression(var.init_expression, varset)
|
|
98
|
+
}
|
|
99
|
+
init << transform_expression(component.init_expression, varset) unless component.init_expression.nil?
|
|
100
|
+
init = init.compact.join(" & ")
|
|
101
|
+
init = "TRUE" if init.empty?
|
|
102
|
+
return init
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def transform_transition(varset, component, transition)
|
|
106
|
+
expression = []
|
|
107
|
+
|
|
108
|
+
cmp_varname = transform_varname(component.name)
|
|
109
|
+
prev_state = transform_const(transition.src_state)
|
|
110
|
+
expression << "v.#{cmp_varname} = #{prev_state}"
|
|
111
|
+
|
|
112
|
+
next_state = transform_const(transition.tgt_state)
|
|
113
|
+
expression << "next(v.#{cmp_varname}) = #{next_state}"
|
|
114
|
+
|
|
115
|
+
precon_s = transform_expression(transition.precon, varset)
|
|
116
|
+
guard_s = transform_expression(transition.guard, varset)
|
|
117
|
+
|
|
118
|
+
action_s, assigned_vars = transform_assignment(transition.action, component, varset)
|
|
119
|
+
|
|
120
|
+
keep_vars_constant = varset.select_by_owner(component.name).names
|
|
121
|
+
.reject { |v| assigned_vars&.map(&:to_s)&.include?(v.to_s) }
|
|
122
|
+
.reject { |v| v.to_s == component.name.to_s }
|
|
123
|
+
.map { |v| "next(v.#{transform_varname(v)}) = v.#{transform_varname(v)}" }
|
|
124
|
+
.join(' & ')
|
|
125
|
+
|
|
126
|
+
expression << precon_s
|
|
127
|
+
expression << guard_s
|
|
128
|
+
expression << action_s
|
|
129
|
+
expression << keep_vars_constant
|
|
130
|
+
|
|
131
|
+
return expression.compact.reject(&:empty?).map{ |s| "( #{s} )" }.join(' & ')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def transform_assignment(assignment_expression, component, varset)
|
|
135
|
+
return nil if assignment_expression.nil?
|
|
136
|
+
|
|
137
|
+
# Handle composite actions (a := 1 | b := 2) by recursively transforming
|
|
138
|
+
# the individual actions and then combining them
|
|
139
|
+
sub_assignments = assignment_expression.to_s.split("|")
|
|
140
|
+
if sub_assignments.length > 1
|
|
141
|
+
action_s_acc, assigned_vars_acc = [], []
|
|
142
|
+
sub_assignments = sub_assignments.map { |a|
|
|
143
|
+
action_s, assigned_vars = transform_assignment(a, component, varset)
|
|
144
|
+
action_s_acc << action_s
|
|
145
|
+
assigned_vars_acc += assigned_vars
|
|
146
|
+
}
|
|
147
|
+
return action_s_acc.join(" & "), assigned_vars_acc.uniq
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
parts = assignment_expression.to_s.split(":=")
|
|
152
|
+
|
|
153
|
+
assigned_variable = parts[0].strip
|
|
154
|
+
|
|
155
|
+
unless varset.varname?(assigned_variable)
|
|
156
|
+
raise Model::Validation::UnknownVariableError.new(assigned_variable, assignment_expression, varset)
|
|
157
|
+
end
|
|
158
|
+
unless varset[assigned_variable].owner_name == component.name
|
|
159
|
+
raise Model::Validation::ForeignVariableAssignmentError.new(assigned_variable, assignment_expression, varset, component)
|
|
160
|
+
end
|
|
161
|
+
if varset[assigned_variable].state_variable?
|
|
162
|
+
raise Model::Validation::AssignmentToStateVariableError.new(assigned_variable, assignment_expression, varset)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
assigned_variable_s = "next(v.#{transform_varname(assigned_variable)})"
|
|
166
|
+
|
|
167
|
+
expression = parts[1].strip
|
|
168
|
+
expression = transform_expression(Model::ParsedExpression.new(expression, Model::ParsedExpression::TYPE_TERM), varset)
|
|
169
|
+
|
|
170
|
+
assignment_s = "#{assigned_variable_s} = #{expression}"
|
|
171
|
+
|
|
172
|
+
return assignment_s, [assigned_variable]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def transform_expression(expression, varset)
|
|
176
|
+
return nil if expression.nil?
|
|
177
|
+
|
|
178
|
+
variables, constants = [], []
|
|
179
|
+
expression.word_tokens.select { |w|
|
|
180
|
+
if varset.names.include?(w)
|
|
181
|
+
variables << w
|
|
182
|
+
elsif varset.values.include?(w)
|
|
183
|
+
constants << w
|
|
184
|
+
else
|
|
185
|
+
raise Model::Validation::UnknownTokenError.new(w, expression, varset)
|
|
186
|
+
end
|
|
187
|
+
}
|
|
188
|
+
variables, constants = variables.uniq, constants.uniq
|
|
189
|
+
|
|
190
|
+
constants = expression.word_tokens.select { |w| varset.values.include?(w) }.uniq
|
|
191
|
+
|
|
192
|
+
expression_tokens = expression.tokenize
|
|
193
|
+
|
|
194
|
+
variables.each { |v| expression_tokens = expression_tokens.gsub(v.to_s, "v." + transform_varname(v)) }
|
|
195
|
+
constants.each { |c| expression_tokens = expression_tokens.gsub(c.to_s, transform_const(c)) }
|
|
196
|
+
|
|
197
|
+
expression_tokens = expression_tokens.gsub('&&', '&')
|
|
198
|
+
expression_tokens = expression_tokens.gsub('||', '|')
|
|
199
|
+
expression_tokens = expression_tokens.gsub('||', '|')
|
|
200
|
+
expression_tokens = expression_tokens.gsub('==', '=')
|
|
201
|
+
expression_tokens = expression_tokens.gsub('=>', '->')
|
|
202
|
+
expression_tokens = expression_tokens.gsub('<=>', '<->')
|
|
203
|
+
|
|
204
|
+
expression_s = expression_tokens.join(" ")
|
|
205
|
+
|
|
206
|
+
return expression_s
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def transform_main_module(components)
|
|
210
|
+
# Instantiate variable module as v
|
|
211
|
+
var_s = "v : _VARS();\n"
|
|
212
|
+
# Instantiate all component modules, passing in v
|
|
213
|
+
var_s += components.map { |component|
|
|
214
|
+
instance = transform_module_instance_name(component.name)
|
|
215
|
+
modul = transform_module_name(component.name)
|
|
216
|
+
"#{instance} : #{modul}(v);"
|
|
217
|
+
}.join("\n")
|
|
218
|
+
return module_string("main", var: var_s)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def module_string(name, hash = {})
|
|
222
|
+
blocks = [ "MODULE #{name}" ]
|
|
223
|
+
hash.each { |key, val|
|
|
224
|
+
blocks << key.to_s.upcase.indented
|
|
225
|
+
blocks << val.indented(num: 2)
|
|
226
|
+
}
|
|
227
|
+
return blocks.join("\n")
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def transform_varname(name)
|
|
231
|
+
"V_#{name}"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def transform_const(value)
|
|
235
|
+
return value.to_s unless value.is_a?(Symbol) || value.is_a?(String)
|
|
236
|
+
"L_#{value}"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def transform_module_name(name)
|
|
240
|
+
"_P_#{name}"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def transform_module_instance_name(name)
|
|
244
|
+
"p_#{name}"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def transform_specification(specification, varset)
|
|
248
|
+
return specification.flatten().map { |s| transform_spec(s, varset) }.join("\n")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def transform_spec(spec, varset)
|
|
252
|
+
expression_s = "-- #{spec.text}\n"
|
|
253
|
+
expression_s += "-- Defined in: #{spec.source_location.to_s}\n"
|
|
254
|
+
expression_s += "LTLSPEC " + transform_expression(spec.expression, varset)
|
|
255
|
+
return expression_s
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
end
|
|
261
|
+
end
|