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