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,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
|