ruby-rego 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.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/.reek.yml +80 -0
  3. data/.vscode/extensions.json +19 -0
  4. data/.vscode/launch.json +35 -0
  5. data/.vscode/settings.json +25 -0
  6. data/.vscode/tasks.json +117 -0
  7. data/.yardopts +12 -0
  8. data/ARCHITECTURE.md +39 -0
  9. data/CHANGELOG.md +25 -0
  10. data/CODE_OF_CONDUCT.md +10 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +183 -0
  13. data/RELEASING.md +37 -0
  14. data/Rakefile +38 -0
  15. data/SECURITY.md +26 -0
  16. data/Steepfile +10 -0
  17. data/TODO.md +35 -0
  18. data/benchmark/builtin_calls.rb +29 -0
  19. data/benchmark/complex_policy.rb +19 -0
  20. data/benchmark/comprehensions.rb +19 -0
  21. data/benchmark/simple_rules.rb +20 -0
  22. data/examples/README.md +27 -0
  23. data/examples/sample_config.yaml +2 -0
  24. data/examples/simple_policy.rego +7 -0
  25. data/examples/validation_policy.rego +11 -0
  26. data/exe/rego-validate +6 -0
  27. data/lib/ruby/rego/ast/base.rb +95 -0
  28. data/lib/ruby/rego/ast/binary_op.rb +64 -0
  29. data/lib/ruby/rego/ast/call.rb +27 -0
  30. data/lib/ruby/rego/ast/composite.rb +48 -0
  31. data/lib/ruby/rego/ast/comprehension.rb +63 -0
  32. data/lib/ruby/rego/ast/every.rb +37 -0
  33. data/lib/ruby/rego/ast/import.rb +32 -0
  34. data/lib/ruby/rego/ast/literal.rb +70 -0
  35. data/lib/ruby/rego/ast/module.rb +32 -0
  36. data/lib/ruby/rego/ast/package.rb +22 -0
  37. data/lib/ruby/rego/ast/query.rb +63 -0
  38. data/lib/ruby/rego/ast/reference.rb +58 -0
  39. data/lib/ruby/rego/ast/rule.rb +114 -0
  40. data/lib/ruby/rego/ast/unary_op.rb +42 -0
  41. data/lib/ruby/rego/ast/variable.rb +22 -0
  42. data/lib/ruby/rego/ast.rb +17 -0
  43. data/lib/ruby/rego/builtins/aggregates.rb +124 -0
  44. data/lib/ruby/rego/builtins/base.rb +95 -0
  45. data/lib/ruby/rego/builtins/collections/array_ops.rb +103 -0
  46. data/lib/ruby/rego/builtins/collections/object_ops.rb +120 -0
  47. data/lib/ruby/rego/builtins/collections/set_ops.rb +51 -0
  48. data/lib/ruby/rego/builtins/collections.rb +137 -0
  49. data/lib/ruby/rego/builtins/comparisons/casts.rb +139 -0
  50. data/lib/ruby/rego/builtins/comparisons.rb +84 -0
  51. data/lib/ruby/rego/builtins/numeric_helpers.rb +56 -0
  52. data/lib/ruby/rego/builtins/registry.rb +199 -0
  53. data/lib/ruby/rego/builtins/registry_helpers.rb +27 -0
  54. data/lib/ruby/rego/builtins/strings/case_ops.rb +22 -0
  55. data/lib/ruby/rego/builtins/strings/concat.rb +19 -0
  56. data/lib/ruby/rego/builtins/strings/formatting.rb +35 -0
  57. data/lib/ruby/rego/builtins/strings/helpers.rb +62 -0
  58. data/lib/ruby/rego/builtins/strings/number_helpers.rb +48 -0
  59. data/lib/ruby/rego/builtins/strings/search.rb +63 -0
  60. data/lib/ruby/rego/builtins/strings/split.rb +19 -0
  61. data/lib/ruby/rego/builtins/strings/substring.rb +22 -0
  62. data/lib/ruby/rego/builtins/strings/trim.rb +42 -0
  63. data/lib/ruby/rego/builtins/strings/trim_helpers.rb +62 -0
  64. data/lib/ruby/rego/builtins/strings.rb +58 -0
  65. data/lib/ruby/rego/builtins/types.rb +89 -0
  66. data/lib/ruby/rego/call_name.rb +55 -0
  67. data/lib/ruby/rego/cli.rb +1122 -0
  68. data/lib/ruby/rego/compiled_module.rb +114 -0
  69. data/lib/ruby/rego/compiler.rb +1097 -0
  70. data/lib/ruby/rego/environment/overrides.rb +33 -0
  71. data/lib/ruby/rego/environment/reference_resolution.rb +86 -0
  72. data/lib/ruby/rego/environment.rb +230 -0
  73. data/lib/ruby/rego/environment_pool.rb +71 -0
  74. data/lib/ruby/rego/error_handling.rb +58 -0
  75. data/lib/ruby/rego/error_payload.rb +34 -0
  76. data/lib/ruby/rego/errors.rb +196 -0
  77. data/lib/ruby/rego/evaluator/assignment_support.rb +126 -0
  78. data/lib/ruby/rego/evaluator/binding_helpers.rb +60 -0
  79. data/lib/ruby/rego/evaluator/comprehension_evaluator.rb +182 -0
  80. data/lib/ruby/rego/evaluator/expression_dispatch.rb +45 -0
  81. data/lib/ruby/rego/evaluator/expression_evaluator.rb +492 -0
  82. data/lib/ruby/rego/evaluator/object_literal_evaluator.rb +52 -0
  83. data/lib/ruby/rego/evaluator/operator_evaluator.rb +163 -0
  84. data/lib/ruby/rego/evaluator/query_node_builder.rb +38 -0
  85. data/lib/ruby/rego/evaluator/reference_key_resolver.rb +50 -0
  86. data/lib/ruby/rego/evaluator/reference_resolver.rb +352 -0
  87. data/lib/ruby/rego/evaluator/rule_evaluator/bindings.rb +70 -0
  88. data/lib/ruby/rego/evaluator/rule_evaluator.rb +550 -0
  89. data/lib/ruby/rego/evaluator/rule_value_provider.rb +56 -0
  90. data/lib/ruby/rego/evaluator/variable_collector.rb +221 -0
  91. data/lib/ruby/rego/evaluator.rb +174 -0
  92. data/lib/ruby/rego/lexer/number_reader.rb +68 -0
  93. data/lib/ruby/rego/lexer/stream.rb +137 -0
  94. data/lib/ruby/rego/lexer/string_reader.rb +90 -0
  95. data/lib/ruby/rego/lexer/template_string_reader.rb +62 -0
  96. data/lib/ruby/rego/lexer.rb +206 -0
  97. data/lib/ruby/rego/location.rb +73 -0
  98. data/lib/ruby/rego/memoization.rb +67 -0
  99. data/lib/ruby/rego/parser/collections.rb +173 -0
  100. data/lib/ruby/rego/parser/expressions.rb +216 -0
  101. data/lib/ruby/rego/parser/precedence.rb +42 -0
  102. data/lib/ruby/rego/parser/query.rb +139 -0
  103. data/lib/ruby/rego/parser/references.rb +115 -0
  104. data/lib/ruby/rego/parser/rules.rb +310 -0
  105. data/lib/ruby/rego/parser.rb +210 -0
  106. data/lib/ruby/rego/policy.rb +50 -0
  107. data/lib/ruby/rego/result.rb +91 -0
  108. data/lib/ruby/rego/token.rb +206 -0
  109. data/lib/ruby/rego/unifier.rb +451 -0
  110. data/lib/ruby/rego/value.rb +379 -0
  111. data/lib/ruby/rego/version.rb +7 -0
  112. data/lib/ruby/rego/with_modifiers/with_modifier.rb +37 -0
  113. data/lib/ruby/rego/with_modifiers/with_modifier_applier.rb +48 -0
  114. data/lib/ruby/rego/with_modifiers/with_modifier_builtin_override.rb +128 -0
  115. data/lib/ruby/rego/with_modifiers/with_modifier_context.rb +120 -0
  116. data/lib/ruby/rego/with_modifiers/with_modifier_path_key_resolver.rb +42 -0
  117. data/lib/ruby/rego/with_modifiers/with_modifier_path_override.rb +99 -0
  118. data/lib/ruby/rego/with_modifiers/with_modifier_root_scope.rb +58 -0
  119. data/lib/ruby/rego.rb +72 -0
  120. data/sig/objspace.rbs +4 -0
  121. data/sig/psych.rbs +7 -0
  122. data/sig/rego_validate.rbs +382 -0
  123. data/sig/ruby/rego.rbs +2150 -0
  124. metadata +172 -0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Rego
5
+ class Evaluator
6
+ # Builds query nodes from user input.
7
+ class QueryNodeBuilder
8
+ # @param query [Object]
9
+ def initialize(query)
10
+ @query = query
11
+ end
12
+
13
+ # @return [Object]
14
+ def build
15
+ return query if query.is_a?(AST::Base)
16
+ return reference_from_string if query.is_a?(String)
17
+
18
+ Value.from_ruby(query)
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :query
24
+
25
+ def reference_from_string
26
+ base, *path_segments = query.split(".")
27
+ raise EvaluationError.new("Invalid query path: #{query.inspect}", rule: nil, location: nil) if
28
+ base.to_s.empty? || path_segments.any?(&:empty?)
29
+
30
+ AST::Reference.new(
31
+ base: AST::Variable.new(name: base),
32
+ path: path_segments.map { |segment| AST::DotRefArg.new(value: segment) }
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Rego
5
+ class Evaluator
6
+ # Resolves reference path segments to Ruby keys.
7
+ class ReferenceKeyResolver
8
+ # @param environment [Environment]
9
+ # @param variable_resolver [#call]
10
+ def initialize(environment:, variable_resolver: nil)
11
+ @environment = environment
12
+ @variable_resolver = variable_resolver
13
+ end
14
+
15
+ # @param segment [Object]
16
+ # @return [Object]
17
+ def resolve(segment)
18
+ resolve_segment(segment)
19
+ rescue ArgumentError
20
+ UndefinedValue.new
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :environment, :variable_resolver
26
+
27
+ def resolve_segment(segment)
28
+ case segment
29
+ in AST::RefArg[value: value]
30
+ resolve_segment(value)
31
+ in AST::Literal[value:] then value
32
+ in AST::Variable => variable then resolve_variable_key(variable)
33
+ in Value => value then value.to_ruby
34
+ in value then Value.from_ruby(value).to_ruby
35
+ end
36
+ end
37
+
38
+ def resolve_variable_key(variable)
39
+ return environment.reference_key_for(variable) unless variable_resolver
40
+
41
+ resolved = variable_resolver.call(variable.name)
42
+ return UndefinedValue.new if resolved.is_a?(UndefinedValue)
43
+ return resolved.to_ruby if resolved.is_a?(Value)
44
+
45
+ Value.from_ruby(resolved).to_ruby
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,352 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Rego
5
+ class Evaluator
6
+ # Resolves AST references against input/data and rule outputs.
7
+ # rubocop:disable Metrics/ClassLength
8
+ class ReferenceResolver
9
+ UNCACHEABLE = Object.new.freeze
10
+
11
+ # Builds static reference keys for cacheable references.
12
+ class StaticKeyBuilder
13
+ ROOT_NAMES = %w[input data].freeze
14
+
15
+ # @param reference [AST::Reference]
16
+ def initialize(reference)
17
+ @reference = reference
18
+ end
19
+
20
+ # @return [Array<Object>, nil]
21
+ def call
22
+ base = reference.base
23
+ return nil unless base.is_a?(AST::Variable)
24
+ return nil unless ROOT_NAMES.include?(base.name)
25
+
26
+ keys = [] # @type var keys: Array[Object]
27
+ reference.path.each do |segment|
28
+ key = segment_key(segment)
29
+ return nil unless key
30
+
31
+ keys << key
32
+ end
33
+ keys
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :reference
39
+
40
+ def segment_key(segment)
41
+ value = segment.is_a?(AST::RefArg) ? segment.value : segment
42
+ return value.value if value.is_a?(AST::Literal)
43
+ return value.to_ruby if value.is_a?(Value)
44
+ return value if value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric)
45
+
46
+ nil
47
+ end
48
+ end
49
+
50
+ # @param environment [Environment]
51
+ # @param package_path [Array<String>]
52
+ # @param rule_value_provider [RuleValueProvider]
53
+ # @param memoization [Memoization::Store, nil]
54
+ def initialize(environment:, package_path:, rule_value_provider:, imports: [], memoization: nil)
55
+ @environment = environment
56
+ @package_path = package_path
57
+ @rule_value_provider = rule_value_provider
58
+ @memoization = memoization
59
+ @key_resolver = ReferenceKeyResolver.new(
60
+ environment: environment,
61
+ variable_resolver: method(:resolve_variable_key)
62
+ )
63
+ @import_map = build_import_map(imports)
64
+ end
65
+
66
+ # @param ref [Object]
67
+ # @return [Value]
68
+ def resolve(ref)
69
+ return environment.resolve_reference(ref) unless ref.is_a?(AST::Reference)
70
+
71
+ cached = cached_reference_value(ref)
72
+ return cached if cached
73
+
74
+ value = resolve_reference_value(ref)
75
+ cache_reference_value(ref, value) if cacheable_reference?(ref)
76
+ value
77
+ end
78
+
79
+ # Resolve an import alias used as a bare variable.
80
+ #
81
+ # @param name [String]
82
+ # @return [Value, nil]
83
+ def resolve_import_variable(name)
84
+ reference = import_map[name.to_s]
85
+ return nil unless reference
86
+ return nil if environment.local_bound?(name)
87
+ return nil unless environment.lookup(name).is_a?(UndefinedValue)
88
+
89
+ resolve(reference)
90
+ end
91
+
92
+ # Resolve a rule name used as a bare variable.
93
+ #
94
+ # @param name [String]
95
+ # @return [Value, nil]
96
+ def resolve_rule_variable(name)
97
+ return nil if environment.local_bound?(name)
98
+ return nil unless environment.lookup(name).is_a?(UndefinedValue)
99
+ return nil unless rule_value_provider.rule_defined?(name)
100
+
101
+ rule_value_provider.value_for(name)
102
+ end
103
+
104
+ # Resolve a function call reference to a rule name when possible.
105
+ #
106
+ # @param reference [AST::Reference]
107
+ # @return [String, nil]
108
+ def function_reference_name(reference)
109
+ return nil unless reference.is_a?(AST::Reference)
110
+
111
+ target = function_reference_target(reference)
112
+ return nil unless target
113
+
114
+ resolve_function_reference_name(target)
115
+ end
116
+
117
+ # :reek:FeatureEnvy
118
+ def function_reference_target(reference)
119
+ import_reference = import_reference_for(reference)
120
+ return reference unless import_reference
121
+
122
+ AST::Reference.new(
123
+ base: import_reference.base,
124
+ path: import_reference.path + reference.path,
125
+ location: reference.location
126
+ )
127
+ end
128
+
129
+ # :reek:FeatureEnvy
130
+ def resolve_function_reference_name(reference)
131
+ base = reference.base
132
+ return nil unless base.is_a?(AST::Variable) && base.name == "data"
133
+
134
+ rule_reference(reference.path)&.then do |(rule_name, remaining_path)|
135
+ remaining_path.empty? ? rule_name : nil
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ attr_reader :environment, :package_path, :rule_value_provider, :key_resolver, :import_map, :memoization
142
+
143
+ def resolve_reference_value(ref)
144
+ import_value = resolve_import_reference(ref)
145
+ return import_value if import_value
146
+
147
+ rule_value = resolve_rule_reference_without_data(ref)
148
+ return rule_value if rule_value
149
+
150
+ base_value = environment.resolve_reference(ref.base)
151
+ resolved = resolve_reference_path_fast(base_value, ref)
152
+ rule_value = resolve_rule_reference(ref)
153
+ rule_value || resolved
154
+ end
155
+
156
+ def resolve_rule_reference(ref)
157
+ base = ref.base
158
+ path = ref.path
159
+ return nil unless base.is_a?(AST::Variable) && base.name == "data"
160
+
161
+ rule_reference(path)&.then do |(rule_name, remaining_path)|
162
+ resolve_rule_value(rule_name, remaining_path)
163
+ end
164
+ end
165
+
166
+ def rule_reference(path)
167
+ keys = valid_reference_keys(path)
168
+ return nil unless keys
169
+
170
+ package_rule_reference(keys, path) || direct_rule_reference(keys, path)
171
+ end
172
+
173
+ def resolve_rule_value(rule_name, remaining_path)
174
+ value = rule_value_provider.value_for(rule_name)
175
+ return value if remaining_path.empty? || value.undefined?
176
+
177
+ resolve_reference_path(value, remaining_path)
178
+ end
179
+
180
+ def package_rule_reference(keys, path)
181
+ return nil unless package_match?(keys)
182
+
183
+ prefix_length = package_path.length
184
+ rule_name = keys[prefix_length].to_s
185
+ return nil unless rule_value_provider.rule_defined?(rule_name)
186
+
187
+ [rule_name, path[(prefix_length + 1)..] || []]
188
+ end
189
+
190
+ def direct_rule_reference(keys, path)
191
+ rule_name = keys.first&.to_s
192
+ return nil unless rule_name && rule_value_provider.rule_defined?(rule_name)
193
+
194
+ [rule_name, path[1..] || []]
195
+ end
196
+
197
+ def resolve_reference_path(current, path)
198
+ path.each do |segment|
199
+ current = resolve_path_segment(current, segment)
200
+ return current if current.is_a?(UndefinedValue)
201
+ end
202
+ current
203
+ end
204
+
205
+ def resolve_reference_path_fast(current, reference)
206
+ keys = static_reference_keys(reference)
207
+ return resolve_reference_path(current, reference.path) unless keys
208
+
209
+ resolve_reference_path_keys(current, keys)
210
+ end
211
+
212
+ def resolve_reference_path_keys(current, keys)
213
+ keys.each do |key|
214
+ current = current.fetch_reference(key)
215
+ return current if current.is_a?(UndefinedValue)
216
+ end
217
+ current
218
+ end
219
+
220
+ def resolve_path_segment(current, segment)
221
+ key = key_resolver.resolve(segment)
222
+ return UndefinedValue.new if key.is_a?(UndefinedValue)
223
+
224
+ current.fetch_reference(key)
225
+ end
226
+
227
+ def valid_reference_keys(path)
228
+ keys = reference_keys(path)
229
+ keys.any? { |key| key.is_a?(UndefinedValue) } ? nil : keys
230
+ end
231
+
232
+ def reference_keys(path)
233
+ path.map { |segment| key_resolver.resolve(segment) }
234
+ end
235
+
236
+ def package_match?(keys)
237
+ prefix_length = package_path.length
238
+ keys.length > prefix_length && keys[0, prefix_length] == package_path
239
+ end
240
+
241
+ def resolve_import_reference(ref)
242
+ import_reference = import_reference_for(ref)
243
+ return nil unless import_reference
244
+
245
+ combined = AST::Reference.new(
246
+ base: import_reference.base,
247
+ path: import_reference.path + ref.path,
248
+ location: ref.location
249
+ )
250
+ resolve(combined)
251
+ end
252
+
253
+ # rubocop:disable Metrics/AbcSize
254
+ def resolve_rule_reference_without_data(ref)
255
+ base = ref.base
256
+ return nil unless base.is_a?(AST::Variable)
257
+ return nil if environment.local_bound?(base.name)
258
+ return nil unless environment.lookup(base.name).is_a?(UndefinedValue)
259
+ return nil unless rule_value_provider.rule_defined?(base.name)
260
+
261
+ value = rule_value_provider.value_for(base.name)
262
+ return value if ref.path.empty? || value.undefined?
263
+
264
+ resolve_reference_path(value, ref.path)
265
+ end
266
+ # rubocop:enable Metrics/AbcSize
267
+
268
+ # :reek:FeatureEnvy
269
+ def import_reference_for(ref)
270
+ base = ref.base
271
+ return nil unless base.is_a?(AST::Variable)
272
+ return nil if environment.local_bound?(base.name)
273
+ return nil unless environment.lookup(base.name).is_a?(UndefinedValue)
274
+
275
+ import_map[base.name]
276
+ end
277
+
278
+ def resolve_variable_key(name)
279
+ resolved = environment.lookup(name)
280
+ return resolved unless resolved.is_a?(UndefinedValue)
281
+ return resolved if environment.local_bound?(name)
282
+
283
+ import_value = resolve_import_variable(name)
284
+ return import_value if import_value
285
+
286
+ rule_value = resolve_rule_variable(name)
287
+ rule_value || resolved
288
+ end
289
+
290
+ def build_import_map(imports)
291
+ # @type var import_map: Hash[String, AST::Reference]
292
+ import_map = {}
293
+ Array(imports).each_with_object(import_map) do |import, acc|
294
+ path = import_path_segments(import)
295
+ next if path.empty?
296
+
297
+ name = import.alias_name || path.last
298
+ acc[name.to_s] = build_reference_from_path(path)
299
+ end
300
+ end
301
+
302
+ def import_path_segments(import)
303
+ raw = import.path
304
+ return raw if raw.is_a?(Array)
305
+ return [] if raw.nil?
306
+
307
+ raw.to_s.split(".")
308
+ end
309
+
310
+ def build_reference_from_path(path)
311
+ base_name, *segments = path
312
+ AST::Reference.new(
313
+ base: AST::Variable.new(name: base_name.to_s),
314
+ path: segments.map { |segment| AST::DotRefArg.new(value: segment.to_s) }
315
+ )
316
+ end
317
+
318
+ def cached_reference_value(reference)
319
+ reference_cache&.fetch(reference, nil)
320
+ end
321
+
322
+ def cache_reference_value(reference, value)
323
+ cache = reference_cache
324
+ return unless cache
325
+
326
+ cache[reference] = value
327
+ end
328
+
329
+ def reference_cache
330
+ memoization&.context&.reference_values
331
+ end
332
+
333
+ def cacheable_reference?(reference)
334
+ !static_reference_keys(reference).nil?
335
+ end
336
+
337
+ def static_reference_keys(reference)
338
+ cache = memoization&.context&.reference_keys
339
+ return StaticKeyBuilder.new(reference).call unless cache
340
+
341
+ cached = cache.fetch(reference) do
342
+ StaticKeyBuilder.new(reference).call || UNCACHEABLE
343
+ end
344
+ return nil if cached == UNCACHEABLE
345
+
346
+ cached
347
+ end
348
+ end
349
+ # rubocop:enable Metrics/ClassLength
350
+ end
351
+ end
352
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Rego
5
+ class Evaluator
6
+ # Binding helpers for rule evaluation.
7
+ # :reek:DataClump
8
+ class RuleEvaluator
9
+ include BindingHelpers
10
+
11
+ private
12
+
13
+ # :reek:TooManyStatements
14
+ # :reek:NestedIterators
15
+ def eval_some_decl(literal, _env = environment)
16
+ Enumerator.new do |yielder|
17
+ collection = literal.collection
18
+ next yield_empty_bindings(yielder) unless collection
19
+
20
+ collection_value = expression_evaluator.evaluate(collection)
21
+ next if collection_value.undefined?
22
+
23
+ collection_bindings(literal.variables, collection_value).each { |bindings| yielder << bindings }
24
+ end
25
+ end
26
+
27
+ # :reek:UtilityFunction
28
+ def yield_empty_bindings(yielder)
29
+ empty_bindings = {} # @type var empty_bindings: Hash[String, Value]
30
+ yielder << empty_bindings
31
+ end
32
+
33
+ def each_some_solution(literal)
34
+ eval_some_decl(literal, environment)
35
+ end
36
+
37
+ def collection_bindings(variables, collection_value)
38
+ case collection_value
39
+ when ArrayValue
40
+ each_array_binding(variables, collection_value)
41
+ when SetValue
42
+ variables.length == 1 ? each_set_binding(variables, collection_value) : empty_bindings_enum
43
+ when ObjectValue
44
+ each_object_binding(variables, collection_value)
45
+ else
46
+ empty_bindings_enum
47
+ end
48
+ end
49
+
50
+ # :reek:UtilityFunction
51
+ # :reek:TooManyStatements
52
+ def merge_bindings(existing, additions)
53
+ merged = existing.dup
54
+ additions.each do |name, value|
55
+ current = merged[name]
56
+ return nil if current && current != value
57
+
58
+ merged[name] = value
59
+ end
60
+ merged
61
+ end
62
+
63
+ # :reek:UtilityFunction
64
+ def empty_bindings_enum
65
+ Enumerator.new { |yielder| yielder }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end