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,18 @@
1
+ module PgVerify
2
+ module Model
3
+ module Validation
4
+
5
+ module EmptyStateSetValidation
6
+
7
+ def self.validate(model)
8
+ errors = model.components.map { |cmp|
9
+ next if cmp.states.length > 0
10
+ EmptyStateSetError.new(cmp)
11
+ }.compact
12
+ return errors
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,119 @@
1
+ module PgVerify
2
+ module Model
3
+ module Validation
4
+
5
+ class ValidationError < PgVerify::Core::Error
6
+ def initialize(model, errors)
7
+ @model, @errors = model, errors
8
+ end
9
+
10
+ def formatted()
11
+ title = "There are #{@errors.length} errors for model #{@model.name}"
12
+
13
+ summary = @errors.map{ |e| e.formatted.first }
14
+ summary = summary.each_with_index.map { |e, i| "#{i + 1}: #{e}" }.join("\n")
15
+
16
+ body = @errors.map(&:to_formatted).join("\n")
17
+ return title, "#{summary}\n\n#{body}"
18
+ end
19
+
20
+ end
21
+
22
+ class UnknownTokenError < PgVerify::Core::Error
23
+ def initialize(token, expression, varset)
24
+ @token, @expression, @varset = token, expression, varset
25
+ end
26
+ def formatted()
27
+ title = "Unknown token"
28
+
29
+ body = []
30
+ body << @expression.source_location.to_s.c_sidenote unless @expression.source_location.blank?
31
+ body << @expression.source_location.render_code_block() unless @expression.source_location.blank?
32
+ body << ""
33
+ body << "The expression '#{@expression.to_s.c_expression}' uses token #{@token.to_s.c_string}."
34
+ body << "This is neither a known variable, nor literal."
35
+ body << "Known variables: #{@varset.names.map(&:to_s).map(&:c_var).join(", ")}"
36
+ body << "Known literals: #{@varset.values.reject{ |v| v.is_a?(Numeric) }.map(&:to_s).map(&:c_literal).join(", ")}"
37
+
38
+ return title, body.join("\n")
39
+ end
40
+ end
41
+
42
+ class UnknownVariableError < PgVerify::Core::Error
43
+ def initialize(assigned_variable, assignment_expression, var_set)
44
+ @assigned_variable, @assignment_expression, @var_set = assigned_variable, assignment_expression, var_set
45
+ end
46
+ def formatted()
47
+ title = "Unknown variable '#{@assigned_variable}'"
48
+
49
+ body = []
50
+ body << @assignment_expression.source_location.to_s.c_sidenote
51
+ body << @assignment_expression.source_location.render_code_block()
52
+ body << ""
53
+ body << "The expression '#{@assignment_expression.to_s.c_expression}' uses variable #{@assigned_variable.to_s.c_var}."
54
+ body << "However there is no such variable in the context of this model."
55
+ body << "Known variables: #{@var_set.names.map(&:to_s).map(&:c_var).join(', ')}."
56
+
57
+ hint = "Declare variables before using them in expressions"
58
+
59
+ return title, body.join("\n"), hint
60
+ end
61
+ end
62
+
63
+ class ForeignVariableAssignmentError < PgVerify::Core::Error
64
+ def initialize(variable, expression, varset, component)
65
+ @variable, @expression, @varset = variable, expression, varset
66
+ @component = component
67
+ end
68
+ def formatted()
69
+ title = "Foreign variable assignment"
70
+
71
+ body = []
72
+ body << @expression.source_location.to_s.c_sidenote
73
+ body << @expression.source_location.render_code_block()
74
+ body << ""
75
+ body << "Component #{@component.name.to_s.c_cmp} assigns a value to variable #{@variable.to_s.c_var},"
76
+ body << "using this expression '#{@expression.to_s.c_expression}'."
77
+ body << "However #{@variable.to_s.c_var} is owned by component #{@varset[@variable].owner_name.to_s.c_cmp}"
78
+
79
+ hint = "A variable can only be written to by the component which declared it"
80
+
81
+ return title, body.join("\n"), hint
82
+ end
83
+ end
84
+
85
+ class AssignmentToStateVariableError < PgVerify::Core::Error
86
+ def initialize(variable, expression, varset)
87
+ @variable, @expression, @varset = variable, expression, varset
88
+ end
89
+ def formatted()
90
+ title = "Assignment to state variable"
91
+
92
+ body = []
93
+ body << @expression.source_location.to_s.c_sidenote
94
+ body << @expression.source_location.render_code_block()
95
+ body << ""
96
+ body << "The expression '#{@expression.to_s.c_expression}' assigns a value to variable #{@variable.to_s.c_var}."
97
+ body << "However this variable is a state variable and can not be written to."
98
+
99
+ hint = "State variables are read only"
100
+
101
+ return title, body.join("\n"), hint
102
+ end
103
+ end
104
+
105
+ class EmptyStateSetError < PgVerify::Core::Error
106
+ def initialize(component)
107
+ @component = component
108
+ end
109
+ def formatted()
110
+ title = "Empty state set"
111
+ body = "The graph component '#{@component.name.to_s.c_cmp}' does not define any states."
112
+ hint = "Each graph component needs at least one state"
113
+ return title, body, hint
114
+ end
115
+ end
116
+
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,30 @@
1
+ module PgVerify
2
+ module Model
3
+ module Validation
4
+
5
+ module ForeignAssignmentValidation
6
+
7
+ def self.validate(model)
8
+ errors = []
9
+ varset = model.all_variables()
10
+
11
+ model.components.each { |component|
12
+ actions = component.transitions.map(&:action).compact
13
+ actions.each { |a|
14
+ var_strings = a.assigned_variables()
15
+ # Null values (variable not known is not an error for this validation)
16
+ vars = var_strings.map { |vs| varset[vs] }.compact
17
+ vars.map { |var|
18
+ next if var.owner_name == component.name
19
+ errors << ForeignVariableAssignmentError.new(var.name, a, varset, component)
20
+ }
21
+ }
22
+ }
23
+ return errors
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ module PgVerify
2
+ module Model
3
+ module Validation
4
+
5
+ module UnknownTokenValidation
6
+
7
+ def self.validate(model)
8
+ errors = []
9
+ varset = model.all_variables()
10
+ transitions = model.components.map(&:transitions).flatten
11
+ transitions.each { |tx|
12
+ errors += validate_expression(varset, tx.precon)
13
+ errors += validate_expression(varset, tx.guard)
14
+ errors += validate_expression(varset, tx.action)
15
+
16
+ }
17
+ return errors
18
+ end
19
+
20
+ def self.validate_expression(varset, expression)
21
+ return [] if expression.nil?
22
+ tokens = expression.word_tokens()
23
+ tokens = tokens.reject { |token|
24
+ varset.varname?(token) || varset.constname?(token)
25
+ }
26
+ return tokens.map { |token|
27
+ UnknownTokenError.new(token, expression, varset)
28
+ }
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ module PgVerify
2
+ module Model
3
+ module Validation
4
+
5
+ def self.validate(model)
6
+ errors = []
7
+ errors += EmptyStateSetValidation.validate(model)
8
+ errors += UnknownTokenValidation.validate(model)
9
+ errors += ForeignAssignmentValidation.validate(model)
10
+ errors += AssignmentToStateVariableValidation.validate(model)
11
+ return errors
12
+ end
13
+
14
+ def self.validate!(model)
15
+ errors = validate(model)
16
+ return if errors.empty?
17
+ raise ValidationError.new(model, errors)
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,47 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ class Variable
5
+
6
+ # Name of this variable as a symbol
7
+ attr_accessor :name
8
+
9
+ # The name of the component which owns this variable as a symbol
10
+ attr_accessor :owner_name
11
+
12
+ # Range of this variable. This can either be a range like (0..2)
13
+ # an array of numbers like [0, 1, 2] or an array of symbols representing
14
+ # states of components like [:Idle, :Breaking]
15
+ attr_accessor :range
16
+
17
+ # An expression to be used to initialize this variable
18
+ # While this can be something simple like "my_var = 10", it can
19
+ # also be something more involved like "my_var > 10 && my_var < 15".
20
+ attr_accessor :init_expression
21
+
22
+ # The location in source, where this variable was defined
23
+ attr_accessor :source_location
24
+
25
+ def initialize(name, range, owner_name, source_location, init: nil)
26
+ init = Model::ParsedExpression.new(init, Model::ParsedExpression::TYPE_PL) if init.is_a?(String)
27
+ @name, @range, @owner_name, @init_expression = name, range, owner_name, init
28
+ @source_location = source_location
29
+ end
30
+
31
+ # Returns all possible values of this variable as an array
32
+ def values()
33
+ return @range if @range.is_a?(Array)
34
+ return @range.to_a
35
+ end
36
+
37
+ # A state variable represents the states of a component.
38
+ # e.g: Switch with range [:on, :off] for component switch.
39
+ def state_variable?()
40
+ return @name == @owner_name
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,84 @@
1
+
2
+ module PgVerify
3
+ module Model
4
+
5
+ class VariableSet
6
+
7
+ attr_accessor :map
8
+
9
+ def initialize(*variables)
10
+ @map = {}
11
+ self << variables
12
+ end
13
+
14
+ def <<(var)
15
+ return @map[var.name] = var if var.is_a?(Variable)
16
+ return var.map { |v| self << v } if var.is_a?(Array)
17
+ raise "Not a variable '#{var}'"
18
+ end
19
+
20
+ def get(*names)
21
+ return names.map { |n| self[n] }
22
+ end
23
+
24
+ def +(varset)
25
+ varset = VariableSet.new(*varset) if varset.is_a?(Array)
26
+ raise "Not a variable set '#{varset}'" unless varset.is_a?(VariableSet)
27
+ return VariableSet.new(*(self.to_a() + varset.to_a()))
28
+ end
29
+
30
+ def to_a()
31
+ return @map.values()
32
+ end
33
+
34
+ def names()
35
+ return @map.keys()
36
+ end
37
+
38
+ def [](name)
39
+ name = name.to_sym
40
+ raise "No such variable #{name}!" unless @map.key?(name)
41
+ return @map[name]
42
+ end
43
+
44
+ def include?(name_or_var)
45
+ return varname?(name_or_var) || constname?(name_or_var)
46
+ end
47
+
48
+ def empty?()
49
+ return @map.empty?
50
+ end
51
+
52
+ def map(&blk)
53
+ return @map.values.map(&blk)
54
+ end
55
+
56
+ def varname?(name)
57
+ return @map.key?(name.to_sym)
58
+ end
59
+
60
+ def constname?(name)
61
+ values().map(&:to_s).include?(name.to_s)
62
+ end
63
+
64
+ def to_s()
65
+ return "{#{@map.map { |name, var| "#{name}:#{var.range}(#{var.owner_name})" }.join(", ")}}"
66
+ end
67
+
68
+ def select_by_owner(owner)
69
+ owner = owner.name if owner.is_a?(Model::Component)
70
+ owner = owner.to_sym if owner.is_a?(String)
71
+ found = self.to_a().select { |var| var.owner_name == owner }
72
+ return VariableSet.new(*found)
73
+ end
74
+
75
+ # Returns all possible values of variables in this set as an array
76
+ def values()
77
+ return self.to_a().map(&:values).flatten
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+ end
84
+
@@ -0,0 +1,23 @@
1
+ # Require all module files
2
+ Dir[File.join(__dir__, "**", '*.rb')].sort.each { |file| require file }
3
+
4
+ module PgVerify
5
+ module NuSMV
6
+
7
+ class RawNuSMVError < PgVerify::Core::Error
8
+ def initialize(cmd, out, err, status, file)
9
+ @cmd, @out, @err, @status, @file = cmd, out, err, status, file
10
+ end
11
+
12
+ def formatted()
13
+ title = "Execution of NuSMV exited with #{@status}"
14
+
15
+ body = "The exact command was\n#{@cmd.c_string}\n\n"
16
+ body += "#{@out}\n#{@err}"
17
+
18
+ return title, body
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,124 @@
1
+
2
+ module PgVerify
3
+ module NuSMV
4
+
5
+ class Runner
6
+
7
+ def run_specs(program_graph)
8
+ nusmv_s = Transform::NuSmvTransformation.new.transform_graph(program_graph)
9
+ output = eval_nusmv(nusmv_s)
10
+ specs = program_graph.specification.flatten()
11
+ return parse_spec_results(program_graph, specs, output)
12
+ end
13
+
14
+ def parse_spec_results(program_graph, specs, nusmv_output)
15
+ block = Struct.new(:success, :lines)
16
+
17
+ # Split the output into blocks which describe the
18
+ # specifications
19
+ blocks, current_block = [], nil
20
+ nusmv_output.split("\n").each { |line|
21
+ result = line[/-- specification .* is (true|false)/, 1]
22
+ if !result.nil?
23
+ blocks << current_block unless current_block.nil?
24
+ current_block = block.new(result == "true", [])
25
+ next
26
+ end
27
+ current_block.lines << line unless current_block.nil?
28
+ }
29
+ blocks << current_block unless current_block.nil?
30
+
31
+ return blocks.each_with_index.map { |block, index|
32
+ trace = block.success ? nil : block.lines.select { |l| l.start_with?(/\s+/) }
33
+ trace = parse_trace(program_graph, trace.join("\n")) unless trace.nil?
34
+
35
+ Model::SpecResult.new(specs[index], block.success, trace)
36
+ }
37
+ end
38
+
39
+ # Calls NuSMV to calculate a possible state trace.
40
+ # Returns an array of states, where each state is a hash from
41
+ # variable to the value of that variable in that state
42
+ def run_simulation(program_graph, steps, random: false)
43
+ commands = []
44
+ commands << "read_model"
45
+ commands << "flatten_hierarchy"
46
+ commands << "encode_variables"
47
+ commands << "build_model"
48
+ commands << "pick_state #{random ? '-r' : ''}"
49
+ commands << "simulate -k #{steps.to_s.to_i} -v #{random ? '-r' : ''}"
50
+ commands << "quit"
51
+ nusmv_s = Transform::NuSmvTransformation.new.transform_graph(program_graph)
52
+ output = eval_nusmv(nusmv_s, commands: commands)
53
+ return parse_trace(program_graph, output)
54
+ end
55
+
56
+ def parse_trace(program_graph, nusmv_output)
57
+ var_states, current_var_state = [], nil
58
+
59
+ loop_index = -1
60
+
61
+ nusmv_output.split("\n").each { |line|
62
+ # Wait for heading of new state
63
+ if line.match(/\s*-> State: .+ <-/)
64
+ # Complete and store the old state if any
65
+ unless current_var_state.nil?
66
+ missing_keys = var_states.empty? ? [] : var_states[-1].keys - current_var_state.keys
67
+ missing_keys.each { |key|
68
+ current_var_state[key] = var_states[-1][key]
69
+ }
70
+ var_states << current_var_state
71
+ end
72
+ # Create a new state
73
+ current_var_state = {}
74
+ next
75
+ end
76
+ # Skip lines before the first state
77
+ next if current_var_state.nil?
78
+
79
+ if line.include?("Loop starts here")
80
+ loop_index == var_states.length - 1
81
+ next
82
+ end
83
+
84
+ key_val = line.split("=").map(&:strip)
85
+ key = key_val[0].gsub("v.V_", "").to_sym
86
+ val = key_val[1].gsub("L_", "")
87
+ current_var_state[key] = val
88
+ }
89
+ return Model::Trace.new(program_graph, var_states)
90
+ end
91
+
92
+ def eval_nusmv(nusmv_string, commands: [])
93
+ tmp_file = PgVerify.tmp_file("pg.smv")
94
+ File.write(tmp_file, nusmv_string)
95
+ return eval_file(tmp_file, commands: commands)
96
+ end
97
+
98
+ def eval_file(file, commands: [])
99
+ nusmv_path = find_nusmv_path()
100
+ raise "NuSMV not integrated! (TODO: Make a better error message)" if nusmv_path.blank?
101
+ if commands.blank?
102
+ nusmv_cmd = "#{nusmv_path} #{file}"
103
+ else
104
+ tmp_cmd_file = PgVerify.tmp_file("nusmv_commands")
105
+ File.write(tmp_cmd_file, commands.join("\n"))
106
+ nusmv_cmd = "#{nusmv_path} -source '#{tmp_cmd_file}' #{file}"
107
+ end
108
+ output, err, status = Open3.capture3({}, nusmv_cmd)
109
+ raise RawNuSMVError.new(nusmv_cmd, output, err, status, file) unless status.success?
110
+ return output
111
+ end
112
+
113
+ def find_nusmv_path
114
+ # Return by settings path if that exists
115
+ return Settings.nusmv.path if !Settings.nusmv.path.blank? && File.file?(Settings.nusmv.path)
116
+
117
+ # Fall back to looking in the addon directory
118
+ candidates = Dir[File.join(PgVerify.addon_dir, "*", "bin", "NuSMV*")]
119
+ return candidates.sort.first unless candidates.empty?
120
+ end
121
+
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,23 @@
1
+ # Require all module files
2
+ Dir[File.join(__dir__, "**", '*.rb')].sort.each { |file| require file }
3
+
4
+
5
+ module PgVerify
6
+ module Puml
7
+
8
+ def self.find_path
9
+ # Return by settings path if that exists
10
+ return Settings.puml.path if !Settings.puml.path.blank? && File.file?(Settings.puml.path)
11
+
12
+ # Fall back to looking in the addon directory
13
+ candidates = Dir[File.join(PgVerify.addon_dir, "plantuml-*.jar")]
14
+ return candidates.sort.first unless candidates.empty?
15
+ end
16
+
17
+ def self.convert_file(in_path, out_path)
18
+ # TODO: The PlantUML jar switches focus to the desktop is if it would
19
+ # attempt to open a window each time it is invoked.
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ require_relative 'loading_animation.rb'
2
+
3
+ module PgVerify
4
+ module Shell
5
+
6
+ class LineAnimation < LoadingAnimation
7
+
8
+ def initialize(loading_message)
9
+ super(loading_message)
10
+ @last_line = nil
11
+ end
12
+
13
+ def on_anim(passed_time)
14
+ color = (passed_time * 2).to_i % 2 == 0 ? :loading : :white
15
+ circle = (passed_time * 2).to_i % 2 == 0 ? "▶" : "-"
16
+ circle = circle.color(color)
17
+ " #{circle} #{string = format_line(@last_line)} "
18
+ end
19
+
20
+ def printl(string)
21
+ return unless started?()
22
+ string = string.to_s
23
+ string = (string ||= "").gsub("\n", '')
24
+ string = string.c_sidenote unless Colorizer.color?(string)
25
+ @last_line = string
26
+ # print_anim(string)
27
+ end
28
+
29
+ def format_line(string)
30
+ return loading_message() if string.blank?
31
+ "#{loading_message()} [ #{string} ]"
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,80 @@
1
+ module PgVerify
2
+ module Shell
3
+
4
+ class LoadingAnimation
5
+
6
+ attr_reader :loading_message, :last_output_length, :start_time
7
+
8
+ def initialize(loading_message)
9
+ @loading_message = loading_message
10
+ @last_output_length = 0
11
+ @animation_thread = nil
12
+ @start_time = nil
13
+ end
14
+
15
+ def start
16
+ return unless @animation_thread.nil?
17
+ @start_time = Time.now
18
+ on_start()
19
+ @animation_thread = Thread.new {
20
+ begin
21
+ while true do
22
+ passed_time = Time.now - @start_time
23
+ string = on_anim(passed_time)
24
+ print_anim(string) unless string.nil?
25
+ sleep(1.0 / 10)
26
+ end
27
+ rescue => e
28
+ puts e
29
+ puts e.backtrace
30
+ raise e
31
+ end
32
+ }
33
+ self
34
+ end
35
+
36
+ def stop(status, finish_message)
37
+ return if @animation_thread.nil?
38
+ @animation_thread.exit
39
+ prompt = Shell.gen_prompt(status)
40
+ string = on_stop(prompt, finish_message)
41
+ @animation_thread = nil
42
+ print_anim(string)
43
+ puts
44
+ end
45
+
46
+ def print_anim(string)
47
+ return if string.blank?
48
+ string = string.to_s
49
+ # Fill the string with spaces to overwrite the last printed string
50
+ print_string = string + ( " " * [0, @last_output_length - string.length].max )
51
+ # Set the length of the last print to the string length without spaces,
52
+ # since they do not need to be overwritten.
53
+ @last_output_length = string.length
54
+ # Print the string with spaces.
55
+ print("\r#{print_string}")
56
+ end
57
+
58
+ def on_anim(passed_time); end
59
+ def on_start(); end
60
+ def printl(string); end
61
+
62
+ def on_stop(prompt, finish_message)
63
+ message = "#{prompt} #{@loading_message}"
64
+ message += " | #{finish_message}".c_sidenote unless finish_message.nil? || finish_message.empty?
65
+ if Settings.print_loading_times
66
+ duration = TimeUtil.duration_string((Time.now - @start_time).to_i)
67
+ duration_string = duration.nil? ? "" : "Took #{duration}".c_sidenote
68
+ return Shell.expand_to_console(message, duration_string)
69
+ else
70
+ return message
71
+ end
72
+ end
73
+
74
+ def started?()
75
+ !@animation_thread.nil?
76
+ end
77
+
78
+ end
79
+ end
80
+ end