pg-verify 0.1.0

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