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,85 @@
|
|
|
1
|
+
|
|
2
|
+
module PgVerify
|
|
3
|
+
module Interpret
|
|
4
|
+
|
|
5
|
+
class GraphContext
|
|
6
|
+
|
|
7
|
+
attr_accessor :parent_script
|
|
8
|
+
# The list of currently declared components
|
|
9
|
+
attr_accessor :components
|
|
10
|
+
attr_accessor :specs
|
|
11
|
+
attr_accessor :hazards
|
|
12
|
+
# The name of this graph
|
|
13
|
+
attr_accessor :name
|
|
14
|
+
|
|
15
|
+
def initialize(name, parent_script)
|
|
16
|
+
@parent_script = parent_script
|
|
17
|
+
@name = name
|
|
18
|
+
@components, @specs, @hazards = [], [], []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# DSL method for declaring a new component in this graph
|
|
22
|
+
def graph(name, &blk)
|
|
23
|
+
raise InvalidDSL_graph.new("Name '#{name}' is neither a symbol nor string") unless name.is_a?(Symbol) || name.is_a?(String)
|
|
24
|
+
cmp = ComponentContext.new(name, self)
|
|
25
|
+
cmp.instance_eval(&blk)
|
|
26
|
+
@components << cmp
|
|
27
|
+
return cmp
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def transient(name)
|
|
31
|
+
cmp = graph(name) do
|
|
32
|
+
all_states = [ :No, :Yes ]
|
|
33
|
+
states(*all_states)
|
|
34
|
+
all_states.product(all_states).each { |s1, s2| transition({ s1 => s2}) }
|
|
35
|
+
end
|
|
36
|
+
cmp.represents_fault = true
|
|
37
|
+
return cmp
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def persistent(name)
|
|
41
|
+
cmp = graph(name) do
|
|
42
|
+
states(:No, :Yes)
|
|
43
|
+
transition :No => :No
|
|
44
|
+
transition :No => :Yes
|
|
45
|
+
end
|
|
46
|
+
cmp.represents_fault = true
|
|
47
|
+
return cmp
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def disable_error(name)
|
|
51
|
+
cmp = @components.find { |c| c.name == name.to_sym }
|
|
52
|
+
raise "No such component: #{name}" if cmp.nil?
|
|
53
|
+
raise "Not an error component: #{name}" unless cmp.represents_fault
|
|
54
|
+
cmp.transition_list = [ ]
|
|
55
|
+
cmp.init_expressions = [ "#{name} == No" ]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def error(name)
|
|
59
|
+
return name
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# DSL method for declaring a new specification in this graph
|
|
63
|
+
def specify(text, &blk)
|
|
64
|
+
specset = SpecSetContext.new(text, self, nil)
|
|
65
|
+
specset.instance_eval(&blk)
|
|
66
|
+
@specs << specset
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def hazard(hash)
|
|
70
|
+
text = hash.keys.first
|
|
71
|
+
expression = hash[text]
|
|
72
|
+
@hazards << Model::Hazard.new(text, expression)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def to_model()
|
|
76
|
+
components = @components.map(&:to_model)
|
|
77
|
+
variables = Model::VariableSet.new(*@components.map(&:owned_variables).flatten())
|
|
78
|
+
specification = Model::Specification.new(@specs.map { |s| s.to_model(nil) })
|
|
79
|
+
Model::Graph.new(@name, components: components, variables: variables, specification: specification, hazards: @hazards)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Require all module files
|
|
2
|
+
Dir[File.join(__dir__, "**", '*.rb')].sort.each { |file| require file }
|
|
3
|
+
|
|
4
|
+
module PgVerify
|
|
5
|
+
|
|
6
|
+
module Interpret
|
|
7
|
+
|
|
8
|
+
class InterpretError < PgVerify::Core::Error
|
|
9
|
+
attr_accessor :script_file
|
|
10
|
+
attr_accessor :line_number
|
|
11
|
+
attr_accessor :cause
|
|
12
|
+
|
|
13
|
+
def initialize(script_file, line_number, cause)
|
|
14
|
+
@script_file, @line_number, @cause = script_file, line_number, cause
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def formatted()
|
|
18
|
+
title = "Error while interpreting script at #{@script_file}:#{@line_number}"
|
|
19
|
+
cause_str = format_cause()
|
|
20
|
+
|
|
21
|
+
sloc = Model::SourceLocation.new(@script_file, @line_number)
|
|
22
|
+
code_block = sloc.render_code_block
|
|
23
|
+
|
|
24
|
+
body = "#{code_block}\n\n#{cause_str}"
|
|
25
|
+
return title, body
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def format_cause()
|
|
29
|
+
return "#{cause}" unless cause.is_a?(Core::Error)
|
|
30
|
+
return cause.to_formatted()
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class InvalidDSL_var < PgVerify::Core::Error
|
|
36
|
+
def initialize(message)
|
|
37
|
+
@message = message
|
|
38
|
+
end
|
|
39
|
+
def formatted()
|
|
40
|
+
hint = [ "Define variables like this:" ]
|
|
41
|
+
hint << "var <name>: <range> [, init: <condition>]"
|
|
42
|
+
hint << " where:"
|
|
43
|
+
hint << " <name> is the name of the variable"
|
|
44
|
+
hint << " <range> is a range of integers like (0..15) or an array of symbols like [ :yes, :no ]"
|
|
45
|
+
hint << " <condition> is either a value in that range or an expression"
|
|
46
|
+
return @message, nil, hint.join("\n")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class InvalidDSL_state < PgVerify::Core::Error
|
|
51
|
+
def initialize(message)
|
|
52
|
+
@message = message
|
|
53
|
+
end
|
|
54
|
+
def formatted()
|
|
55
|
+
hint = [ "Define states like this:" ]
|
|
56
|
+
hint << "states <states>"
|
|
57
|
+
hint << " where:"
|
|
58
|
+
hint << " <states> is an array of symbols like :on, :off"
|
|
59
|
+
return @message, nil, hint.join("\n")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class InvalidDSL_graph < PgVerify::Core::Error
|
|
64
|
+
def initialize(message)
|
|
65
|
+
@message = message
|
|
66
|
+
end
|
|
67
|
+
def formatted()
|
|
68
|
+
hint = [ "Define graphs like this:" ]
|
|
69
|
+
hint << "graph <name> do ... end"
|
|
70
|
+
return @message, nil, hint.join("\n")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class InvalidDSL_transition < PgVerify::Core::Error
|
|
75
|
+
def initialize(message)
|
|
76
|
+
@message = message
|
|
77
|
+
end
|
|
78
|
+
def formatted()
|
|
79
|
+
hint = [ "Define transitions like this:" ]
|
|
80
|
+
hint << "transition <pre-state> => <post-state> do ... end"
|
|
81
|
+
hint << " where"
|
|
82
|
+
hint << " <pre-state> & <post-state> are states in this graph, like :on or :off"
|
|
83
|
+
return @message, nil, hint.join("\n")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class InvalidDSL_expression < PgVerify::Core::Error
|
|
88
|
+
def initialize(type, message)
|
|
89
|
+
@type, @message = type, message
|
|
90
|
+
end
|
|
91
|
+
def formatted()
|
|
92
|
+
hint = [ "Define #{@type}s like this:" ]
|
|
93
|
+
hint << "#{@type} <expression>"
|
|
94
|
+
hint << " where"
|
|
95
|
+
hint << " <expression> is a valid expression as a string or symbol"
|
|
96
|
+
return @message, nil, hint.join("\n")
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class InvalidDSL_model < PgVerify::Core::Error
|
|
101
|
+
def initialize(type, message)
|
|
102
|
+
@type, @message = type, message
|
|
103
|
+
end
|
|
104
|
+
def formatted()
|
|
105
|
+
hint = [ "Define models like this:" ]
|
|
106
|
+
hint << "model <name> do ... end"
|
|
107
|
+
hint << " where"
|
|
108
|
+
hint << " <name> is a string or symbol"
|
|
109
|
+
return @message, nil, hint.join("\n")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
class NoSuchStateError < PgVerify::Core::Error
|
|
114
|
+
def initialize(state, component)
|
|
115
|
+
@state, @component = state, component
|
|
116
|
+
end
|
|
117
|
+
def formatted()
|
|
118
|
+
title = "No such state #{@state} in component #{@component.name}"
|
|
119
|
+
body = "The component #{@component.name.to_s.c_cmp} does not contain a state called #{@state.to_s.c_state}.\n"
|
|
120
|
+
body += "Available states are: #{@component.states.map(&:to_s).map(&:c_state).join(', ')}"
|
|
121
|
+
hint = "Make sure to define states before defining transitions."
|
|
122
|
+
return title, body, hint
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
class NoSuchScriptError < Core::Error
|
|
127
|
+
def initialize(script_path)
|
|
128
|
+
@script_path = script_path
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def formatted()
|
|
132
|
+
title = "Could not find script at #{@script_path}"
|
|
133
|
+
body = "No script file at #{@script_path.c_error}!"
|
|
134
|
+
hint = "Make sure to create a program graph script at \n#{File.expand_path(@script_path)}"
|
|
135
|
+
return title, body, hint
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
|
|
2
|
+
module PgVerify
|
|
3
|
+
module Interpret
|
|
4
|
+
|
|
5
|
+
class PgScript
|
|
6
|
+
|
|
7
|
+
# List of models which are defined in this script
|
|
8
|
+
attr_accessor :models
|
|
9
|
+
# The full path to the source script file
|
|
10
|
+
attr_accessor :script_file
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def initialize()
|
|
14
|
+
@models = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def interpret(file, validate: true)
|
|
18
|
+
file = File.expand_path(file)
|
|
19
|
+
raise NoSuchScriptError.new(file) unless File.file?(file)
|
|
20
|
+
@script_file ||= file
|
|
21
|
+
|
|
22
|
+
begin
|
|
23
|
+
Dir.chdir(File.dirname(file)) { eval(File.read(file), get_binding(), file) }
|
|
24
|
+
rescue Exception => e
|
|
25
|
+
re_raise_exception(file, self, e)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@models.each { |model| Model::Validation.validate!(model) } if validate
|
|
29
|
+
|
|
30
|
+
return @models
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def model(name, &blk)
|
|
34
|
+
raise InvalidDSL_model.new("Name '#{name}' is neither a symbol nor string") unless name.is_a?(Symbol) || name.is_a?(String)
|
|
35
|
+
graph_ctx = Interpret::GraphContext.new(name.to_sym, self)
|
|
36
|
+
graph_ctx.instance_eval(&blk)
|
|
37
|
+
@models << graph_ctx.to_model()
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Re-raise an exception which occurred during evaluation of a script to more user
|
|
41
|
+
# friendly error messages.
|
|
42
|
+
def re_raise_exception(file, script, ex)
|
|
43
|
+
# Determine the line in the script file where the error was raised
|
|
44
|
+
line_number = find_source_location(ex.backtrace).line_number
|
|
45
|
+
|
|
46
|
+
# Include the full backtrace in the message if enabled
|
|
47
|
+
if Settings.full_stack_trace
|
|
48
|
+
title = "Here is the full backtrace of the exception!".c_sidenote + "\n"
|
|
49
|
+
title += "(You don't have to bother with this unless you are developing pg-verify)".c_sidenote
|
|
50
|
+
bt = ex.backtrace.map(&:c_sidenote).join("\n").indented(str: ">> ".c_sidenote)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
raise InterpretError.new(file, line_number, ex)
|
|
54
|
+
end
|
|
55
|
+
private :re_raise_exception
|
|
56
|
+
|
|
57
|
+
def find_source_location(trace = caller())
|
|
58
|
+
line_number = trace
|
|
59
|
+
.select { |l| l.include?(@script_file) }
|
|
60
|
+
.map { |l| l.split(":in")[0] }.reject(&:blank?)
|
|
61
|
+
.map { |l| l.split(":")[-1] }.reject(&:blank?)
|
|
62
|
+
.uniq.first.to_i
|
|
63
|
+
Model::SourceLocation.new(@script_file, line_number)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def get_binding()
|
|
67
|
+
binding()
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
module Interpret
|
|
3
|
+
|
|
4
|
+
class LTLBuilder
|
|
5
|
+
|
|
6
|
+
def globally()
|
|
7
|
+
return LTLBuilderGlobally.new()
|
|
8
|
+
end
|
|
9
|
+
def after(expression)
|
|
10
|
+
return LTLBuilderAfter.new(expression)
|
|
11
|
+
end
|
|
12
|
+
def before(expression)
|
|
13
|
+
LTLBuilderBefore.new(expression)
|
|
14
|
+
end
|
|
15
|
+
def between(); end
|
|
16
|
+
def after_until(); end
|
|
17
|
+
|
|
18
|
+
class LTLBuilderBase
|
|
19
|
+
|
|
20
|
+
def build(pattern, map)
|
|
21
|
+
map.each { |key, val|
|
|
22
|
+
pattern = pattern.gsub(key.to_s, "#{val}")
|
|
23
|
+
}
|
|
24
|
+
return pattern
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class LTLBuilderGlobally < LTLBuilderBase
|
|
30
|
+
def global(p)
|
|
31
|
+
build "G p", { p: p }
|
|
32
|
+
end
|
|
33
|
+
def never(p)
|
|
34
|
+
build "G !( p )", { p: p }
|
|
35
|
+
end
|
|
36
|
+
def exists(p)
|
|
37
|
+
build "F p", { p: p }
|
|
38
|
+
end
|
|
39
|
+
def reacts(p, s)
|
|
40
|
+
build "G ( p => F s )", { p: p, s: s }
|
|
41
|
+
end
|
|
42
|
+
def precedes(p, s)
|
|
43
|
+
build "!(p) W s", { p: p, s: s }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class LTLBuilderAfter < LTLBuilderBase
|
|
48
|
+
attr_accessor :q
|
|
49
|
+
def initialize(q); @q = q end
|
|
50
|
+
def global(p)
|
|
51
|
+
build "G ( q -> G p )", { p: p, q: q }
|
|
52
|
+
end
|
|
53
|
+
def never(p)
|
|
54
|
+
build "G ( q -> G !(p))", { p: p, q: q }
|
|
55
|
+
end
|
|
56
|
+
def exists(p)
|
|
57
|
+
build "( G !(q) ) || ( F ( q && F p ) )", { p: p, q: q }
|
|
58
|
+
end
|
|
59
|
+
def reacts(p, s)
|
|
60
|
+
build "G ( q => G (p => F s) )", { p: p, q: q, s: s}
|
|
61
|
+
end
|
|
62
|
+
def precedes(p, s)
|
|
63
|
+
build "G !(q) || F ( q && !(p) W s )", { p: p, q: q, s: s}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class LTLBuilderBefore < LTLBuilderBase
|
|
68
|
+
attr_accessor :q
|
|
69
|
+
def initialize(q); @q = q end
|
|
70
|
+
def global(p)
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
def never(p)
|
|
74
|
+
build "( F q ) => ( !(p) U q )", { p: p, q: q }
|
|
75
|
+
end
|
|
76
|
+
def exists(p)
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
def reacts(p, s)
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
def precedes(p, s)
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
module Interpret
|
|
3
|
+
|
|
4
|
+
class SpecContext
|
|
5
|
+
# The text of this spec as a string.
|
|
6
|
+
attr_accessor :text
|
|
7
|
+
|
|
8
|
+
# The LTL/CTL expression of this spec
|
|
9
|
+
attr_accessor :expression
|
|
10
|
+
|
|
11
|
+
# The parent set of this spec
|
|
12
|
+
attr_accessor :parent
|
|
13
|
+
|
|
14
|
+
attr_accessor :source_location
|
|
15
|
+
|
|
16
|
+
def initialize(text, expression, parent)
|
|
17
|
+
@text, @expression, @parent = text, expression, parent
|
|
18
|
+
@expression = Model::ParsedExpression.new(expression, Model::ParsedExpression::TYPE_TL)
|
|
19
|
+
@expression.source_location = parent.parent_graph.parent_script.find_source_location()
|
|
20
|
+
@source_location = parent.parent_graph.parent_script.find_source_location()
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_model(parent)
|
|
24
|
+
spec = Model::Spec.new(@text, @expression, parent)
|
|
25
|
+
spec.source_location = @source_location
|
|
26
|
+
return spec
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
module Interpret
|
|
3
|
+
|
|
4
|
+
class SpecSetContext
|
|
5
|
+
|
|
6
|
+
# The text of this spec set as a string.
|
|
7
|
+
attr_accessor :text
|
|
8
|
+
|
|
9
|
+
# The sub-spec sets contained in this spec set
|
|
10
|
+
attr_accessor :children
|
|
11
|
+
|
|
12
|
+
attr_accessor :parent
|
|
13
|
+
|
|
14
|
+
attr_accessor :assumption
|
|
15
|
+
|
|
16
|
+
attr_accessor :parent_graph
|
|
17
|
+
|
|
18
|
+
attr_accessor :source_location
|
|
19
|
+
|
|
20
|
+
def initialize(text, parent_graph, parent, children = [])
|
|
21
|
+
@text, @parent, @children = text, parent, children
|
|
22
|
+
@parent_graph = parent_graph
|
|
23
|
+
@source_location = parent_graph.parent_script.find_source_location()
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def specify(text, &blk)
|
|
27
|
+
subset = SpecSetContext.new(text, @parent_graph, self)
|
|
28
|
+
subset.instance_eval(&blk)
|
|
29
|
+
children << subset
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def assuming(hash, &blk)
|
|
33
|
+
assumption_text = hash.keys.first
|
|
34
|
+
assumption_expression = hash.values.first
|
|
35
|
+
subset = SpecSetContext.new("", @parent_graph, self)
|
|
36
|
+
subset.assumption = { text: assumption_text, expression: assumption_expression }
|
|
37
|
+
subset.instance_eval(&blk)
|
|
38
|
+
children << subset
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def no_errors()
|
|
42
|
+
err_graphs = @parent_graph.components.select(&:represents_fault)
|
|
43
|
+
expression = err_graphs.map { |g| "#{g.name} == No" }.join(" && ")
|
|
44
|
+
return { "there are no errors" => "G ( #{expression} )" }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def it(hash)
|
|
48
|
+
# TODO: Handle errors
|
|
49
|
+
text = hash.keys.first
|
|
50
|
+
expression = hash[text]
|
|
51
|
+
children << SpecContext.new(text, expression.to_s, self)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def ltl()
|
|
55
|
+
return LTLBuilder.new()
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to_model(parent)
|
|
59
|
+
model = Model::SpecSet.new(@text, @assumption, parent, [])
|
|
60
|
+
model.children = @children.map { |child| child.to_model(model) }
|
|
61
|
+
return model
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
module Interpret
|
|
3
|
+
|
|
4
|
+
class TransitionContext
|
|
5
|
+
|
|
6
|
+
TempExpression = Struct.new(:string, :source_location)
|
|
7
|
+
|
|
8
|
+
# Back-reference to the component which owns this transition
|
|
9
|
+
attr_accessor :parent_component
|
|
10
|
+
|
|
11
|
+
# Source and target states as symbols
|
|
12
|
+
attr_accessor :src_state, :tgt_state
|
|
13
|
+
|
|
14
|
+
# Strings for the guars, actions and preconditions
|
|
15
|
+
attr_accessor :guard, :action, :precon
|
|
16
|
+
|
|
17
|
+
# Source location for this transition
|
|
18
|
+
attr_accessor :source_location
|
|
19
|
+
|
|
20
|
+
def initialize(parent_component, src_state, tgt_state)
|
|
21
|
+
@parent_component = parent_component
|
|
22
|
+
@src_state, @tgt_state = src_state, tgt_state
|
|
23
|
+
@source_location = @parent_component.parent_graph.parent_script.find_source_location()
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def guard(str)
|
|
27
|
+
raise "Guard was already defined!" unless @guard.nil?
|
|
28
|
+
raise InvalidDSL_expression.new("guard", "Expression is neither a sting, nor symbol: #{str}") unless str.is_a?(String) | str.is_a?(Symbol)
|
|
29
|
+
@guard = Model::ParsedExpression.new(str, Model::ParsedExpression::TYPE_GUARD)
|
|
30
|
+
@guard.source_location = @parent_component.parent_graph.parent_script.find_source_location()
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def action(str)
|
|
34
|
+
raise "Action was already defined!" unless @action.nil?
|
|
35
|
+
raise InvalidDSL_expression.new("action", "Expression is neither a sting, nor symbol: #{str}") unless str.is_a?(String) | str.is_a?(Symbol)
|
|
36
|
+
@action = Model::ParsedExpression.new(str, Model::ParsedExpression::TYPE_ACTION)
|
|
37
|
+
@action.source_location = @parent_component.parent_graph.parent_script.find_source_location()
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def precon(str)
|
|
41
|
+
raise "Precondition was already defined!" unless @guard.nil?
|
|
42
|
+
raise InvalidDSL_expression.new("precon", "Expression is neither a sting, nor symbol: #{str}") unless str.is_a?(String) | str.is_a?(Symbol)
|
|
43
|
+
@precon = Model::ParsedExpression.new(str, Model::ParsedExpression::TYPE_GUARD)
|
|
44
|
+
@precon.source_location = @parent_component.parent_graph.parent_script.find_source_location()
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_model()
|
|
48
|
+
Model::Transition.new(@src_state, @tgt_state, action: @action, precon: @precon, guard: @guard)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
module Model
|
|
3
|
+
|
|
4
|
+
class AllocationSet
|
|
5
|
+
|
|
6
|
+
# An array of variables
|
|
7
|
+
attr_accessor :variables
|
|
8
|
+
|
|
9
|
+
# An array of allocations for those variables.
|
|
10
|
+
# Each allocation is a an array like [0, 1] where indices match the variable array
|
|
11
|
+
# to represent values for that variable
|
|
12
|
+
attr_accessor :allocations
|
|
13
|
+
|
|
14
|
+
def initialize(variables, allocations)
|
|
15
|
+
if !allocations.empty? && variables.length != allocations.first.length
|
|
16
|
+
raise "Variables and allocations must match in length!"
|
|
17
|
+
end
|
|
18
|
+
@variables, @allocations = variables, allocations
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def length()
|
|
22
|
+
allocations.length
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
module PgVerify
|
|
3
|
+
module Model
|
|
4
|
+
|
|
5
|
+
class Assignment
|
|
6
|
+
|
|
7
|
+
ASSIGN_OPERATOR = ':='
|
|
8
|
+
|
|
9
|
+
# The name of the variable which is assigned a value
|
|
10
|
+
attr_accessor :assigned_variable
|
|
11
|
+
|
|
12
|
+
# The Model::Expression which of this assignment
|
|
13
|
+
attr_accessor :expression
|
|
14
|
+
|
|
15
|
+
def self.from_string(string)
|
|
16
|
+
raise "Not an assignment: #{string}" unless string.include?(ASSIGN_OPERATOR)
|
|
17
|
+
split = string.split(ASSIGN_OPERATOR).map(&:strip)
|
|
18
|
+
assigned_variable = split[0].to_sym
|
|
19
|
+
expression = Expression.from_string(split[1])
|
|
20
|
+
return Assignment.new(assigned_variable, expression)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(assigned_variable, expression)
|
|
24
|
+
@assigned_variable, @expression = assigned_variable, expression
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_s()
|
|
28
|
+
return "#{assigned_variable} #{ASSIGN_OPERATOR} #{expression.to_s}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
module PgVerify
|
|
3
|
+
module Model
|
|
4
|
+
|
|
5
|
+
class Component
|
|
6
|
+
|
|
7
|
+
# The name of this component as a symbol
|
|
8
|
+
attr_accessor :name
|
|
9
|
+
# A list of symbols representing states
|
|
10
|
+
attr_accessor :states
|
|
11
|
+
# A list of transitions between states
|
|
12
|
+
attr_accessor :transitions
|
|
13
|
+
# Flag on whether this component is used to represent a fault.
|
|
14
|
+
attr_accessor :represents_fault
|
|
15
|
+
# The source location of this components definition
|
|
16
|
+
attr_accessor :source_location
|
|
17
|
+
|
|
18
|
+
# A Model::ParsedExpression used as the init expression.
|
|
19
|
+
# This expression must hold true initially. It can be used
|
|
20
|
+
# to restrict or set the initial state and variables.
|
|
21
|
+
# Can be left as nil in order to not restrict anything
|
|
22
|
+
attr_accessor :init_expression
|
|
23
|
+
|
|
24
|
+
def initialize(args = {})
|
|
25
|
+
@name = ( args[:name] || raise('Blubb') )
|
|
26
|
+
@states = ( args[:states] || [] )
|
|
27
|
+
@transitions = ( args[:transitions] || [] )
|
|
28
|
+
@represents_fault = args[:represents_fault].nil? ? false : args[:represents_fault]
|
|
29
|
+
@source_location = ( args[:source_location] )
|
|
30
|
+
@init_expression = args[:init_expression]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def represents_fault?()
|
|
34
|
+
return @represents_fault
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|