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.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +98 -0
  5. data/README.md +29 -0
  6. data/Rakefile +62 -0
  7. data/bin/console +15 -0
  8. data/bin/pg-verify.rb +18 -0
  9. data/bin/setup +8 -0
  10. data/calc.ebnf +21 -0
  11. data/data/config/pg-verify.yml +66 -0
  12. data/data/nusmv.sample.smv +179 -0
  13. data/data/project-template/.gitignore.resource +4 -0
  14. data/data/project-template/.pg-verify.yml +0 -0
  15. data/data/project-template/README.md +18 -0
  16. data/data/project-template/addon/.keep +0 -0
  17. data/data/project-template/program-graph.rb.resource +103 -0
  18. data/devpg +5 -0
  19. data/doc/examples/railroad_crossing.rb +61 -0
  20. data/doc/examples/train-tree.rb +43 -0
  21. data/doc/examples/weidezaun.rb +99 -0
  22. data/doc/examples/weidezaun.txt +29 -0
  23. data/doc/expose/definition.png +0 -0
  24. data/doc/expose/diagram.png +0 -0
  25. data/doc/expose/expose.md +359 -0
  26. data/doc/expose/validity.png +0 -0
  27. data/exe/pg-verify +4 -0
  28. data/integration_tests/ruby_dsl/001_states.rb +10 -0
  29. data/integration_tests/ruby_dsl/002_transitions.rb +10 -0
  30. data/integration_tests/ruby_dsl/003_actions.rb +14 -0
  31. data/integration_tests/ruby_dsl/004_guards.rb +18 -0
  32. data/integration_tests/ruby_dsl/005_variables.rb +16 -0
  33. data/integration_tests/ruby_dsl/006_state_variables.rb +26 -0
  34. data/integration_tests/ruby_dsl/007_variable_initialization.rb +28 -0
  35. data/integration_tests/ruby_dsl/008_state_initialization.rb +19 -0
  36. data/integration_tests/ruby_dsl/009_shared_variables.rb +26 -0
  37. data/integration_tests/ruby_dsl/010_complex_guards.rb +18 -0
  38. data/integration_tests/ruby_dsl/011_complex_actions.rb +16 -0
  39. data/integration_tests/ruby_dsl/012_error_components.rb +9 -0
  40. data/integration_tests/ruby_dsl/013_hazards.rb +25 -0
  41. data/integration_tests/ruby_dsl/014_tau_transitions.rb +26 -0
  42. data/integration_tests/ruby_dsl/015_basic_dcca.rb +19 -0
  43. data/integration_tests/ruby_dsl/016_pressure_tank.rb +146 -0
  44. data/lib/pg-verify/cli/cli.rb +235 -0
  45. data/lib/pg-verify/core/cmd_runner.rb +151 -0
  46. data/lib/pg-verify/core/core.rb +38 -0
  47. data/lib/pg-verify/core/extensions/array_extensions.rb +11 -0
  48. data/lib/pg-verify/core/extensions/enumerable_extensions.rb +19 -0
  49. data/lib/pg-verify/core/extensions/nil_extensions.rb +7 -0
  50. data/lib/pg-verify/core/extensions/string_extensions.rb +84 -0
  51. data/lib/pg-verify/core/shell/colorizer.rb +136 -0
  52. data/lib/pg-verify/core/shell/shell.rb +0 -0
  53. data/lib/pg-verify/core/util.rb +146 -0
  54. data/lib/pg-verify/doctor/doctor.rb +180 -0
  55. data/lib/pg-verify/ebnf_parser/ast.rb +31 -0
  56. data/lib/pg-verify/ebnf_parser/ebnf_parser.rb +26 -0
  57. data/lib/pg-verify/ebnf_parser/expression_parser.rb +177 -0
  58. data/lib/pg-verify/ebnf_parser/expression_parser2.rb +422 -0
  59. data/lib/pg-verify/ebnf_parser/expressions.ebnf +33 -0
  60. data/lib/pg-verify/ebnf_parser/expressions.peg +52 -0
  61. data/lib/pg-verify/ebnf_parser/parser_result.rb +26 -0
  62. data/lib/pg-verify/interpret/component_context.rb +125 -0
  63. data/lib/pg-verify/interpret/graph_context.rb +85 -0
  64. data/lib/pg-verify/interpret/interpret.rb +142 -0
  65. data/lib/pg-verify/interpret/pg_script.rb +72 -0
  66. data/lib/pg-verify/interpret/spec/ltl_builder.rb +90 -0
  67. data/lib/pg-verify/interpret/spec/spec_context.rb +32 -0
  68. data/lib/pg-verify/interpret/spec/spec_set_context.rb +67 -0
  69. data/lib/pg-verify/interpret/transition_context.rb +55 -0
  70. data/lib/pg-verify/model/allocation_set.rb +28 -0
  71. data/lib/pg-verify/model/assignment.rb +34 -0
  72. data/lib/pg-verify/model/component.rb +40 -0
  73. data/lib/pg-verify/model/dcca/hazard.rb +16 -0
  74. data/lib/pg-verify/model/dcca.rb +67 -0
  75. data/lib/pg-verify/model/expression.rb +106 -0
  76. data/lib/pg-verify/model/graph.rb +58 -0
  77. data/lib/pg-verify/model/model.rb +10 -0
  78. data/lib/pg-verify/model/parsed_expression.rb +77 -0
  79. data/lib/pg-verify/model/simulation/trace.rb +43 -0
  80. data/lib/pg-verify/model/simulation/variable_state.rb +23 -0
  81. data/lib/pg-verify/model/source_location.rb +45 -0
  82. data/lib/pg-verify/model/specs/spec.rb +44 -0
  83. data/lib/pg-verify/model/specs/spec_result.rb +25 -0
  84. data/lib/pg-verify/model/specs/spec_set.rb +43 -0
  85. data/lib/pg-verify/model/specs/specification.rb +50 -0
  86. data/lib/pg-verify/model/transition.rb +41 -0
  87. data/lib/pg-verify/model/validation/assignment_to_state_variable_validation.rb +26 -0
  88. data/lib/pg-verify/model/validation/empty_state_set_validation.rb +18 -0
  89. data/lib/pg-verify/model/validation/errors.rb +119 -0
  90. data/lib/pg-verify/model/validation/foreign_assignment_validation.rb +30 -0
  91. data/lib/pg-verify/model/validation/unknown_token_validation.rb +35 -0
  92. data/lib/pg-verify/model/validation/validation.rb +23 -0
  93. data/lib/pg-verify/model/variable.rb +47 -0
  94. data/lib/pg-verify/model/variable_set.rb +84 -0
  95. data/lib/pg-verify/nusmv/nusmv.rb +23 -0
  96. data/lib/pg-verify/nusmv/runner.rb +124 -0
  97. data/lib/pg-verify/puml/puml.rb +23 -0
  98. data/lib/pg-verify/shell/loading/line_animation.rb +36 -0
  99. data/lib/pg-verify/shell/loading/loading_animation.rb +80 -0
  100. data/lib/pg-verify/shell/loading/loading_prompt.rb +43 -0
  101. data/lib/pg-verify/shell/loading/no_animation.rb +20 -0
  102. data/lib/pg-verify/shell/shell.rb +30 -0
  103. data/lib/pg-verify/simulation/simulation.rb +7 -0
  104. data/lib/pg-verify/simulation/simulator.rb +90 -0
  105. data/lib/pg-verify/simulation/state.rb +53 -0
  106. data/lib/pg-verify/transform/hash_transformation.rb +104 -0
  107. data/lib/pg-verify/transform/nusmv_transformation.rb +261 -0
  108. data/lib/pg-verify/transform/puml_transformation.rb +89 -0
  109. data/lib/pg-verify/transform/transform.rb +8 -0
  110. data/lib/pg-verify/version.rb +5 -0
  111. data/lib/pg-verify.rb +47 -0
  112. data/pg-verify.gemspec +38 -0
  113. data/sig/pg-verify.rbs +4 -0
  114. 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,7 @@
1
+ # Require all module files
2
+ Dir[File.join(__dir__, "**", '*.rb')].sort.each { |file| require file }
3
+
4
+ module PgVerify
5
+ module Simulation
6
+ end
7
+ 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