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,67 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ class DCCA
5
+
6
+ def initialize(graph, runner)
7
+ @graph, @runner = graph, runner
8
+ end
9
+
10
+ def perform()
11
+ @graph.hazards.map { |hazard|
12
+ fault_set = @graph.fault_components.map(&:name)
13
+ powerset = fault_set.powerset
14
+ sets = calc_minimal_critical_sets(powerset, fault_set, hazard)
15
+ [hazard, sets]
16
+ }.to_h
17
+ end
18
+
19
+ def calc_minimal_critical_sets(powerset, fault_set, hazard, level: 0, minimal_critical_sets: [])
20
+ to_check = powerset.select { |set| set.length == level }
21
+ return minimal_critical_sets if to_check.empty?
22
+
23
+ # Determine which of the sets to check can result in the hazard.
24
+ critical = select_critical(to_check, fault_set, hazard)
25
+
26
+ # Remove all super sets of the critical ones, as those must be critical as well,
27
+ # but cannot be minimal.
28
+ powerset = powerset.reject { |set|
29
+ critical.any? { |crit_set| set.subset?(crit_set) }
30
+ }
31
+ # Keep track of the new critical sets
32
+ minimal_critical_sets += critical
33
+
34
+ # Continue with the next level.
35
+ calc_minimal_critical_sets(powerset, fault_set, hazard,
36
+ level: level+1, minimal_critical_sets: minimal_critical_sets)
37
+ end
38
+
39
+ def select_critical(sets, fault_set, hazard)
40
+ specs = sets.map { |set|
41
+ other_errors = fault_set.reject { |err| set.include?(err) }
42
+ only_errors = other_errors.empty? \
43
+ ? "TRUE" \
44
+ : other_errors.map { |err| "#{err} == No" }.join(" && ")
45
+ formula = "!( (#{only_errors}) U (#{hazard.expression}) )"
46
+ formula = ParsedExpression.new(formula, ParsedExpression::TYPE_TL)
47
+ Spec.new("Fault set {#{set.join(', ')}} is safe", formula, nil)
48
+ }
49
+
50
+ spec_set = SpecSet.wrap(specs)
51
+ @graph.specification = Specification.new([spec_set])
52
+
53
+ results = @runner.run_specs(@graph)
54
+
55
+ critical_sets = []
56
+ results.each_with_index { |result, index|
57
+ critical_sets << sets[index] if result.failure?
58
+ }
59
+
60
+ return critical_sets
61
+ end
62
+
63
+
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,106 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ # Class used to represent a mathematical expression
5
+ class Expression
6
+
7
+ ASSIGNMENT_OP = ':='
8
+ MAX_ALLOCS_FOR_CHECK = 1000000
9
+
10
+ # 3 * Position + x
11
+ # >= 4
12
+
13
+ # The entire expression as a string
14
+ attr_accessor :expression_string
15
+
16
+ def initialize(expression_string)
17
+ @expression_string = expression_string
18
+ end
19
+
20
+ # Finds all variables in the specified this expression
21
+ # and returns them as an unique array of symbols ordered by their occource
22
+ def used_variables(expression=@expression_string)
23
+ expression.scan(/[a-zA-Z_][a-zA-Z0-9_]*/).uniq.map(&:to_sym)
24
+ end
25
+
26
+ def assigned_variable()
27
+ var, expression = isolate_assigned_variable()
28
+ return var
29
+ end
30
+
31
+ def term_variables()
32
+ var, expression = isolate_assigned_variable()
33
+ return used_variables(expression)
34
+ end
35
+
36
+ def assignment?()
37
+ return !assigned_variable().nil?
38
+ end
39
+
40
+ # Calcuates an AllocationSet which is populated with all variable allocations
41
+ # which lead to an illegal output value for this expressions variable assignment
42
+ def calc_illegal_allocations(resolved_variables)
43
+
44
+ assigned_var, term = isolate_assigned_variable()
45
+ raise("Term '#{self}' is not a variable assignment!") if assigned_var.nil?
46
+ term_vars = used_variables(term)
47
+
48
+ # Resolve the variables used in this expression to actual variable objects
49
+ assigned_var = resolved_variables.detect {|var| var.name == assigned_var }
50
+ term_vars = term_vars.map { |varname| resolved_variables.detect { |var| varname == var.name } }
51
+
52
+ # Calculate the number of possible allocations, which is the product of the variable range length
53
+ num_combinations = term_vars.map(&:range).map(&:count).reduce(&:*)
54
+
55
+ # Set an upper limit to not calculate some huge range forever
56
+ raise "Search space too large on validation of term '#{self}': #{num_combinations} > #{MAX_ALLOCS_FOR_CHECK}" \
57
+ if num_combinations > MAX_ALLOCS_FOR_CHECK
58
+
59
+ # Calculate all combinations of possible variable assignments based on their range
60
+ allocations = term_vars.map(&:range).map(&:to_a).reduce(&:product).map(&:flatten)
61
+
62
+ # For each combination of variable allocations create an expression by replacing the variable
63
+ # with its value in that allocation
64
+ illegal_allocations = allocations.reject { |alloc|
65
+ values = term_vars.map(&:name).map(&:to_s).zip(alloc).to_h
66
+ expression = term.gsub(/#{term_vars.map(&:name).join("|")}/, values)
67
+
68
+ resolved_value = eval(expression)
69
+ assigned_var.range.include?(resolved_value)
70
+ }
71
+
72
+ puts "#{illegal_allocations}"
73
+
74
+ return AllocationSet.new(term_vars, illegal_allocations)
75
+ end
76
+
77
+ # Only keeps allocations in the specified allocation set which are possible
78
+ # based on this condition
79
+ def filter_allocation_set(allocation_set)
80
+ # Only keep allocations for which this condition evaluates to true
81
+ allocation_set.allocations.select { |alloc|
82
+ values = allocation_set.variables.map(&:name).map(&:to_s).zip(alloc).to_h
83
+ term = @expression_string.gsub(/#{allocation_set.variables.map(&:name).join("|")}/, values)
84
+ result = eval(term)
85
+ raise "Expression #{self} for allocation #{alloc} (#{term}) evaluated to #{result} which is not a boolean" \
86
+ unless result == true || result == false
87
+ result == true
88
+ }
89
+ end
90
+
91
+ # This method splits off a potential assigned variable and returns the variable
92
+ # which is assigned as a symbol and the remaining expression as a string.
93
+ def isolate_assigned_variable()
94
+ var = used_variables().first
95
+ match = @expression_string[/(#{var}\s*#{ASSIGNMENT_OP})/, 1]
96
+ return nil, @expression_string if match.nil?
97
+ return var, @expression_string.gsub(match, '').strip
98
+ end
99
+
100
+ def to_s()
101
+ @expression_string
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,58 @@
1
+
2
+ module PgVerify
3
+ module Model
4
+
5
+ class Graph
6
+
7
+ # The name of this graph
8
+ attr_accessor :name
9
+
10
+ # All Model::Component's which are part of this graph
11
+ attr_accessor :components
12
+
13
+ # A Model::VariableSet of all non-state variables declared by
14
+ # components in this graph
15
+ attr_accessor :variables
16
+
17
+ # The Model::Specification of this graph
18
+ attr_accessor :specification
19
+
20
+ # An array of Model::Hazards for this graph
21
+ attr_accessor :hazards
22
+
23
+ def initialize(name, components: [], variables: VariableSet.new(), specification: Specification.empty(), hazards: [])
24
+ raise "Not a variable set #{variables}" unless variables.is_a?(VariableSet)
25
+ raise "Not a specification #{specification}" unless specification.is_a?(Specification)
26
+ @name = name
27
+ @components, @variables, @specification, @hazards = components, variables, specification, hazards
28
+ end
29
+
30
+ # Returns a list of state variables for each component in this graph
31
+ # Each state variable is named according to the component it represents
32
+ # and has a range consisting of the states of that component
33
+ def state_variables()
34
+ vars = @components.map { |cmp|
35
+ Variable.new(cmp.name, cmp.states, cmp.name, cmp.source_location)
36
+ }
37
+ return VariableSet.new(*vars)
38
+ end
39
+
40
+ # Returns all variables used in this graph including state variables
41
+ def all_variables()
42
+ return state_variables() + @variables
43
+ end
44
+
45
+ def fault_components()
46
+ return @components.select(&:represents_fault?)
47
+ end
48
+
49
+ def validate()
50
+ errors = []
51
+ errors += UnkownVariableValidation.validate(self)
52
+ return errors
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,10 @@
1
+ # Require all module files
2
+ Dir[File.join(__dir__, "**", '*.rb')].sort.each { |file| require file }
3
+
4
+ module PgVerify
5
+ module Model
6
+
7
+ # class ValidationError < PgVerify::Core::Error; end
8
+
9
+ end
10
+ end
@@ -0,0 +1,77 @@
1
+
2
+ module PgVerify
3
+ module Model
4
+
5
+ class ParsedExpression
6
+
7
+ # Guards and preconditions
8
+ TYPE_GUARD = :guard
9
+ # Actions
10
+ TYPE_ACTION = :action
11
+ # Term for the right part of an action
12
+ TYPE_TERM = :term
13
+ # Propositional logic
14
+ TYPE_PL = :pl
15
+ # Temporal logic (LTL or CTL)
16
+ TYPE_TL = :tl
17
+ # Linear temporal logic
18
+ TYPE_LTL = :ltl
19
+ # Computation tree logic
20
+ TYPE_CTL = :ctl
21
+
22
+ TYPES = [ TYPE_GUARD, TYPE_ACTION, TYPE_TERM, TYPE_PL, TYPE_TL, TYPE_LTL, TYPE_CTL ]
23
+
24
+ attr_accessor :expression_string
25
+ attr_accessor :source_location
26
+ attr_accessor :type
27
+
28
+ def initialize(expression_string, type, source_location: nil)
29
+ expression_string = expression_string.to_s if expression_string.is_a?(Symbol)
30
+ raise "Unknown expression type '#{type}'" unless TYPES.include?(type)
31
+ raise "Not a string '#{expression_string}'::#{expression_string.class}" unless expression_string.is_a?(String)
32
+ @expression_string = expression_string
33
+ @source_location = source_location
34
+ @type = type
35
+ end
36
+
37
+ def word_tokens()
38
+ words = expression_string.scan(/[a-zA-Z_][a-zA-Z0-9_]*/).flatten.compact
39
+ words = words.reject { |w| w.match(/\A[GFXRU]+\z/) }
40
+ words = words.reject { |w| w == "TRUE" || w == "FALSE" }
41
+ return words.map(&:to_sym)
42
+ end
43
+
44
+ # Splits the expression string into an array of tokens. e.g:
45
+ # "(a == b) && 3 >= 2" becomes [ "(", "a", "==", "b", ")", "&&", "3", ">=", "2" ]
46
+ # Note that this split method very much a hack at the moment
47
+ def tokenize()
48
+ return expression_string.split(/\s+/).map { |t|
49
+ t.end_with?(")") ? [ t.chop, ")" ] : t
50
+ }.flatten.map { |t|
51
+ t.end_with?("(") ? [ t.chop, "(" ] : t
52
+ }.flatten.map { |t|
53
+ t.start_with?(")") ? [ ")" , t.slice(1..-1)] : t
54
+ }.flatten.map { |t|
55
+ t.start_with?("(") ? [ "(" , t.slice(1..-1)] : t
56
+ }.flatten.reject(&:blank?)
57
+ end
58
+
59
+ def used_variables()
60
+ return expression_string.scan(/[a-zA-Z_][a-zA-Z0-9_]*/).flatten.compact.map(&:to_sym)
61
+ end
62
+
63
+ def to_s()
64
+ @expression_string
65
+ end
66
+
67
+ def assigned_variables()
68
+ raise "Not an action" unless @type == TYPE_ACTION
69
+ return expression_string.split("|").map { |sub_action|
70
+ sub_action.split(":=")[0].strip
71
+ }
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,43 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ class Trace
5
+
6
+ attr_accessor :model
7
+ attr_accessor :states
8
+
9
+ def initialize(model, states)
10
+ @model, @states = model, states
11
+ end
12
+
13
+ def to_s()
14
+ return "No states in trace" if @states.empty?
15
+ # Get all variables (TODO: Bring into sensible order)
16
+ vars = @states.first.keys
17
+ state_vars = @model.state_variables()
18
+
19
+ parts = vars.map { |var|
20
+ var_string = state_vars.varname?(var) ? var.to_s.c_state.c_bold : var.to_s.c_string
21
+ var_string + "\n" + @states.each_with_index.map{ |state, index| value_str(var, state[var], index) }.join("\n")
22
+ }
23
+ str = "Step".c_num.c_bold + "\n" + (0...@states.length).map { |i| "#{i + 1}" } .join("\n")
24
+ parts.each { |part| str = str.line_combine(part, separator: " ") }
25
+
26
+ return str
27
+ end
28
+
29
+ def value_str(key, value, index)
30
+ return value.c_green if [ "On", "Yes", "Active" ].include?(value)
31
+ return value.c_red if [ "Off", "No", "Idle" ].include?(value)
32
+
33
+ settings_color = Settings.trace.colors[value.to_s]
34
+ return value.send(:"c_#{settings_color}") unless settings_color.blank?
35
+
36
+ return "#{value}"
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ class VariableState
5
+
6
+ attr_accessor :value_map
7
+
8
+ def initialize(spec, success, trace)
9
+
10
+ end
11
+
12
+ def success?
13
+ return @success
14
+ end
15
+
16
+ def failure?()
17
+ return !@success
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ class SourceLocation
5
+
6
+ attr_accessor :file
7
+ attr_accessor :line_number
8
+
9
+ def initialize(file, line_number)
10
+ @file, @line_number = file, line_number
11
+ end
12
+
13
+ def render_code_block(num_lines = 2)
14
+ index = @line_number - 1
15
+ # Read lines and add line numbers
16
+ lines = File.read(@file).split("\n").each_with_index.map { |l, i| "#{i.to_s.c_blue}: #{l}" }
17
+
18
+ # Get the line surrounding the error
19
+ start_index = [0, index - num_lines].max
20
+ end_index = [lines.length - 1, index + num_lines].min
21
+ surrounding_lines = lines[start_index..end_index]
22
+ error_line = lines[index]
23
+
24
+ surrounding_lines = [ "...".c_sidenote ] + surrounding_lines + [ "...".c_sidenote ]
25
+
26
+ surrounding_lines = surrounding_lines.map { |l|
27
+ if l == error_line
28
+ l.ljust(surrounding_lines.map(&:length).max) + " < Error".c_error
29
+ else
30
+ l
31
+ end
32
+ }
33
+
34
+ return surrounding_lines.join("\n")
35
+ end
36
+
37
+ def to_s()
38
+ "#{@file}:#{@line_number}"
39
+ end
40
+
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,44 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ # A single specification and a leave in the tree
5
+ class Spec
6
+
7
+ # The text of this spec as a string.
8
+ attr_accessor :text
9
+
10
+ # The LTL/CTL expression of this spec
11
+ attr_accessor :expression
12
+
13
+ # The parent specification set for this node
14
+ attr_accessor :parent
15
+
16
+ attr_accessor :source_location
17
+
18
+ def initialize(text, expression, parent)
19
+ raise "Not a #{Model::ParsedExpression}: #{expression}" unless expression.is_a?(Model::ParsedExpression)
20
+ @text, @expression, @parent = text, expression, parent
21
+ end
22
+
23
+ def parent?
24
+ return !@parent.nil?
25
+ end
26
+
27
+ def linage()
28
+ return parents() + [ self ]
29
+ end
30
+
31
+ def parents()
32
+ array = []
33
+ current = self
34
+ while(current.parent?)
35
+ current = current.parent
36
+ array << current
37
+ end
38
+ return array.reverse()
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ class SpecResult
5
+
6
+ attr_reader :spec
7
+ attr_accessor :success
8
+ attr_accessor :trace
9
+
10
+ def initialize(spec, success, trace)
11
+ @spec, @success, @trace = spec, success, trace
12
+ end
13
+
14
+ def success?
15
+ return @success
16
+ end
17
+
18
+ def failure?()
19
+ return !@success
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ class SpecSet
5
+
6
+ # The text of this spec set as a string.
7
+ attr_accessor :text
8
+
9
+ attr_accessor :assumption
10
+
11
+ # The sub-spec sets contained in this spec set
12
+ attr_accessor :children
13
+
14
+ attr_accessor :parent
15
+
16
+ def self.wrap(specs)
17
+ spec_set = self.new("", nil, nil, nil)
18
+ specs.each { |spec| spec.parent = spec_set }
19
+ spec_set.children = specs
20
+ return spec_set
21
+ end
22
+
23
+ def initialize(text, assumption, parent, children)
24
+ @text, @assumption, @parent, @children = text, assumption, parent, children
25
+ end
26
+
27
+ def parent?
28
+ return !@parent.nil?
29
+ end
30
+
31
+ def get_specs()
32
+ ret = []
33
+ children.each { |child|
34
+ ret << child if child.is_a?(Spec)
35
+ ret += child.get_specs() if child.is_a?(SpecSet)
36
+ }
37
+ return ret
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,50 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ class Specification
5
+
6
+ attr_accessor :spec_sets
7
+
8
+ def self.empty()
9
+ return self.new([])
10
+ end
11
+
12
+ def initialize(spec_sets)
13
+ @spec_sets = spec_sets
14
+ end
15
+
16
+ def get_specs()
17
+ spec_sets.map(&:get_specs).flatten
18
+ end
19
+
20
+ def flatten()
21
+ return get_specs().map { |spec|
22
+ parents = spec.parents
23
+ prefix = parents.map { |spec_set|
24
+ spec_set.assumption.nil? \
25
+ ? spec_set.text \
26
+ : "(assuming #{spec_set.assumption[:text]})" #.c_sidenote
27
+ }.join(" ")
28
+ text = "#{prefix} #{spec.text}"
29
+
30
+ assumption_expression = parents.map { |spec_set|
31
+ next if spec_set.assumption.nil?
32
+ spec_set.assumption[:expression]
33
+ }.compact.join(" && ")
34
+
35
+ expression = spec.expression
36
+ unless assumption_expression.empty?
37
+ expression_string = "( #{assumption_expression} ) => #{spec.expression}"
38
+ expression = Model::ParsedExpression.new(expression_string, Model::ParsedExpression::TYPE_TL)
39
+ expression.source_location = spec.expression.source_location
40
+ end
41
+
42
+ result = Spec.new(text, expression, nil)
43
+ result.source_location = spec.source_location
44
+ result
45
+ }
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ class Transition
5
+
6
+ # The source and target state of this transition as symbols
7
+ attr_accessor :src_state, :tgt_state
8
+
9
+ attr_accessor :guard, :precon, :action
10
+
11
+ def initialize(src_state, tgt_state, guard: nil, action: nil, precon: nil)
12
+ @src_state, @tgt_state = src_state, tgt_state
13
+ @guard, @precon, @action = guard, precon, action
14
+ end
15
+
16
+ def validate!()
17
+ # @actions.each { |action|
18
+ # # Calculate all illegal allocations
19
+ # # An allocation is illegal if it evaluates to a number outside of the range
20
+ # # of the assigned variable
21
+ # illegal_allocations = action.calc_illegal_allocations()
22
+ # # Only keep allocations as illegal if guards do not prevent them
23
+ # illegal_allocations = precon.filter_allocation_set(illegal_allocations)
24
+
25
+ # raise("ValidationError") if illegal_allocations.length != 0
26
+ # }
27
+ end
28
+
29
+
30
+ def to_s()
31
+ label = [ @precon, @guard ].map(&:to_s).reject(&:empty?).join(" && ")
32
+ label += "/ " + @action.to_s unless @action.nil?
33
+
34
+ label = ": #{label}" unless label.strip.empty?
35
+ return "#{@src_state} -> #{@tgt_state} #{label}"
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ module PgVerify
2
+ module Model
3
+ module Validation
4
+
5
+ module AssignmentToStateVariableValidation
6
+
7
+ def self.validate(model)
8
+ errors = []
9
+ varset = model.all_variables()
10
+ actions = model.components.map(&:transitions).flatten.map(&:action).compact
11
+ actions.each { |action|
12
+ action.assigned_variables().each { |var_string|
13
+ var = varset[var_string]
14
+ # Do not handle: Assignment to unknown variable
15
+ next if var.nil?
16
+ next unless var.state_variable?
17
+ errors << AssignmentToStateVariableError.new(var.name, action, varset)
18
+ }
19
+ }
20
+ return errors
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+ end