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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +98 -0
- data/README.md +29 -0
- data/Rakefile +62 -0
- data/bin/console +15 -0
- data/bin/pg-verify.rb +18 -0
- data/bin/setup +8 -0
- data/calc.ebnf +21 -0
- data/data/config/pg-verify.yml +66 -0
- data/data/nusmv.sample.smv +179 -0
- data/data/project-template/.gitignore.resource +4 -0
- data/data/project-template/.pg-verify.yml +0 -0
- data/data/project-template/README.md +18 -0
- data/data/project-template/addon/.keep +0 -0
- data/data/project-template/program-graph.rb.resource +103 -0
- data/devpg +5 -0
- data/doc/examples/railroad_crossing.rb +61 -0
- data/doc/examples/train-tree.rb +43 -0
- data/doc/examples/weidezaun.rb +99 -0
- data/doc/examples/weidezaun.txt +29 -0
- data/doc/expose/definition.png +0 -0
- data/doc/expose/diagram.png +0 -0
- data/doc/expose/expose.md +359 -0
- data/doc/expose/validity.png +0 -0
- data/exe/pg-verify +4 -0
- data/integration_tests/ruby_dsl/001_states.rb +10 -0
- data/integration_tests/ruby_dsl/002_transitions.rb +10 -0
- data/integration_tests/ruby_dsl/003_actions.rb +14 -0
- data/integration_tests/ruby_dsl/004_guards.rb +18 -0
- data/integration_tests/ruby_dsl/005_variables.rb +16 -0
- data/integration_tests/ruby_dsl/006_state_variables.rb +26 -0
- data/integration_tests/ruby_dsl/007_variable_initialization.rb +28 -0
- data/integration_tests/ruby_dsl/008_state_initialization.rb +19 -0
- data/integration_tests/ruby_dsl/009_shared_variables.rb +26 -0
- data/integration_tests/ruby_dsl/010_complex_guards.rb +18 -0
- data/integration_tests/ruby_dsl/011_complex_actions.rb +16 -0
- data/integration_tests/ruby_dsl/012_error_components.rb +9 -0
- data/integration_tests/ruby_dsl/013_hazards.rb +25 -0
- data/integration_tests/ruby_dsl/014_tau_transitions.rb +26 -0
- data/integration_tests/ruby_dsl/015_basic_dcca.rb +19 -0
- data/integration_tests/ruby_dsl/016_pressure_tank.rb +146 -0
- data/lib/pg-verify/cli/cli.rb +235 -0
- data/lib/pg-verify/core/cmd_runner.rb +151 -0
- data/lib/pg-verify/core/core.rb +38 -0
- data/lib/pg-verify/core/extensions/array_extensions.rb +11 -0
- data/lib/pg-verify/core/extensions/enumerable_extensions.rb +19 -0
- data/lib/pg-verify/core/extensions/nil_extensions.rb +7 -0
- data/lib/pg-verify/core/extensions/string_extensions.rb +84 -0
- data/lib/pg-verify/core/shell/colorizer.rb +136 -0
- data/lib/pg-verify/core/shell/shell.rb +0 -0
- data/lib/pg-verify/core/util.rb +146 -0
- data/lib/pg-verify/doctor/doctor.rb +180 -0
- data/lib/pg-verify/ebnf_parser/ast.rb +31 -0
- data/lib/pg-verify/ebnf_parser/ebnf_parser.rb +26 -0
- data/lib/pg-verify/ebnf_parser/expression_parser.rb +177 -0
- data/lib/pg-verify/ebnf_parser/expression_parser2.rb +422 -0
- data/lib/pg-verify/ebnf_parser/expressions.ebnf +33 -0
- data/lib/pg-verify/ebnf_parser/expressions.peg +52 -0
- data/lib/pg-verify/ebnf_parser/parser_result.rb +26 -0
- data/lib/pg-verify/interpret/component_context.rb +125 -0
- data/lib/pg-verify/interpret/graph_context.rb +85 -0
- data/lib/pg-verify/interpret/interpret.rb +142 -0
- data/lib/pg-verify/interpret/pg_script.rb +72 -0
- data/lib/pg-verify/interpret/spec/ltl_builder.rb +90 -0
- data/lib/pg-verify/interpret/spec/spec_context.rb +32 -0
- data/lib/pg-verify/interpret/spec/spec_set_context.rb +67 -0
- data/lib/pg-verify/interpret/transition_context.rb +55 -0
- data/lib/pg-verify/model/allocation_set.rb +28 -0
- data/lib/pg-verify/model/assignment.rb +34 -0
- data/lib/pg-verify/model/component.rb +40 -0
- data/lib/pg-verify/model/dcca/hazard.rb +16 -0
- data/lib/pg-verify/model/dcca.rb +67 -0
- data/lib/pg-verify/model/expression.rb +106 -0
- data/lib/pg-verify/model/graph.rb +58 -0
- data/lib/pg-verify/model/model.rb +10 -0
- data/lib/pg-verify/model/parsed_expression.rb +77 -0
- data/lib/pg-verify/model/simulation/trace.rb +43 -0
- data/lib/pg-verify/model/simulation/variable_state.rb +23 -0
- data/lib/pg-verify/model/source_location.rb +45 -0
- data/lib/pg-verify/model/specs/spec.rb +44 -0
- data/lib/pg-verify/model/specs/spec_result.rb +25 -0
- data/lib/pg-verify/model/specs/spec_set.rb +43 -0
- data/lib/pg-verify/model/specs/specification.rb +50 -0
- data/lib/pg-verify/model/transition.rb +41 -0
- data/lib/pg-verify/model/validation/assignment_to_state_variable_validation.rb +26 -0
- data/lib/pg-verify/model/validation/empty_state_set_validation.rb +18 -0
- data/lib/pg-verify/model/validation/errors.rb +119 -0
- data/lib/pg-verify/model/validation/foreign_assignment_validation.rb +30 -0
- data/lib/pg-verify/model/validation/unknown_token_validation.rb +35 -0
- data/lib/pg-verify/model/validation/validation.rb +23 -0
- data/lib/pg-verify/model/variable.rb +47 -0
- data/lib/pg-verify/model/variable_set.rb +84 -0
- data/lib/pg-verify/nusmv/nusmv.rb +23 -0
- data/lib/pg-verify/nusmv/runner.rb +124 -0
- data/lib/pg-verify/puml/puml.rb +23 -0
- data/lib/pg-verify/shell/loading/line_animation.rb +36 -0
- data/lib/pg-verify/shell/loading/loading_animation.rb +80 -0
- data/lib/pg-verify/shell/loading/loading_prompt.rb +43 -0
- data/lib/pg-verify/shell/loading/no_animation.rb +20 -0
- data/lib/pg-verify/shell/shell.rb +30 -0
- data/lib/pg-verify/simulation/simulation.rb +7 -0
- data/lib/pg-verify/simulation/simulator.rb +90 -0
- data/lib/pg-verify/simulation/state.rb +53 -0
- data/lib/pg-verify/transform/hash_transformation.rb +104 -0
- data/lib/pg-verify/transform/nusmv_transformation.rb +261 -0
- data/lib/pg-verify/transform/puml_transformation.rb +89 -0
- data/lib/pg-verify/transform/transform.rb +8 -0
- data/lib/pg-verify/version.rb +5 -0
- data/lib/pg-verify.rb +47 -0
- data/pg-verify.gemspec +38 -0
- data/sig/pg-verify.rbs +4 -0
- metadata +226 -0
|
@@ -0,0 +1,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,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
|