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