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,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "location"
4
+
5
+ module Ruby
6
+ module Rego
7
+ # Token type constants used by the lexer.
8
+ # rubocop:disable Metrics/ModuleLength
9
+ # :reek:TooManyConstants
10
+ module TokenType
11
+ PACKAGE = :PACKAGE
12
+ IMPORT = :IMPORT
13
+ AS = :AS
14
+ DEFAULT = :DEFAULT
15
+ IF = :IF
16
+ CONTAINS = :CONTAINS
17
+ SOME = :SOME
18
+ IN = :IN
19
+ EVERY = :EVERY
20
+ NOT = :NOT
21
+ AND = :AND
22
+ OR = :OR
23
+ WITH = :WITH
24
+ ELSE = :ELSE
25
+ TRUE = :TRUE
26
+ FALSE = :FALSE
27
+ NULL = :NULL
28
+ DATA = :DATA
29
+ INPUT = :INPUT
30
+
31
+ ASSIGN = :ASSIGN
32
+ EQ = :EQ
33
+ NEQ = :NEQ
34
+ LT = :LT
35
+ LTE = :LTE
36
+ GT = :GT
37
+ GTE = :GTE
38
+ PLUS = :PLUS
39
+ MINUS = :MINUS
40
+ STAR = :STAR
41
+ SLASH = :SLASH
42
+ PERCENT = :PERCENT
43
+ PIPE = :PIPE
44
+ AMPERSAND = :AMPERSAND
45
+ UNIFY = :UNIFY
46
+
47
+ LPAREN = :LPAREN
48
+ RPAREN = :RPAREN
49
+ LBRACKET = :LBRACKET
50
+ RBRACKET = :RBRACKET
51
+ LBRACE = :LBRACE
52
+ RBRACE = :RBRACE
53
+ DOT = :DOT
54
+ COMMA = :COMMA
55
+ SEMICOLON = :SEMICOLON
56
+ COLON = :COLON
57
+ UNDERSCORE = :UNDERSCORE
58
+
59
+ STRING = :STRING
60
+ TEMPLATE_STRING = :TEMPLATE_STRING
61
+ NUMBER = :NUMBER
62
+ RAW_STRING = :RAW_STRING
63
+ RAW_TEMPLATE_STRING = :RAW_TEMPLATE_STRING
64
+ IDENT = :IDENT
65
+
66
+ EOF = :EOF
67
+ NEWLINE = :NEWLINE
68
+ COMMENT = :COMMENT
69
+
70
+ # rubocop:disable Lint/DeprecatedConstants
71
+ KEYWORDS = [
72
+ PACKAGE,
73
+ IMPORT,
74
+ AS,
75
+ DEFAULT,
76
+ IF,
77
+ CONTAINS,
78
+ SOME,
79
+ IN,
80
+ EVERY,
81
+ NOT,
82
+ AND,
83
+ OR,
84
+ WITH,
85
+ ELSE,
86
+ TRUE,
87
+ FALSE,
88
+ NULL,
89
+ DATA,
90
+ INPUT
91
+ ].freeze
92
+ # rubocop:enable Lint/DeprecatedConstants
93
+
94
+ OPERATORS = [
95
+ ASSIGN,
96
+ EQ,
97
+ NEQ,
98
+ LT,
99
+ LTE,
100
+ GT,
101
+ GTE,
102
+ PLUS,
103
+ MINUS,
104
+ STAR,
105
+ SLASH,
106
+ PERCENT,
107
+ AND,
108
+ OR,
109
+ PIPE,
110
+ AMPERSAND,
111
+ UNIFY
112
+ ].freeze
113
+
114
+ DELIMITERS = [
115
+ LPAREN,
116
+ RPAREN,
117
+ LBRACKET,
118
+ RBRACKET,
119
+ LBRACE,
120
+ RBRACE,
121
+ DOT,
122
+ COMMA,
123
+ SEMICOLON,
124
+ COLON,
125
+ UNDERSCORE
126
+ ].freeze
127
+
128
+ LITERALS = [
129
+ STRING,
130
+ TEMPLATE_STRING,
131
+ NUMBER,
132
+ RAW_STRING,
133
+ RAW_TEMPLATE_STRING,
134
+ IDENT
135
+ ].freeze
136
+
137
+ SPECIALS = [
138
+ EOF,
139
+ NEWLINE,
140
+ COMMENT
141
+ ].freeze
142
+
143
+ # @param type [Symbol]
144
+ # @return [Boolean]
145
+ def self.keyword?(type)
146
+ KEYWORDS.include?(type)
147
+ end
148
+
149
+ # @param type [Symbol]
150
+ # @return [Boolean]
151
+ def self.operator?(type)
152
+ OPERATORS.include?(type)
153
+ end
154
+
155
+ # @param type [Symbol]
156
+ # @return [Boolean]
157
+ def self.literal?(type)
158
+ LITERALS.include?(type)
159
+ end
160
+ end
161
+ # rubocop:enable Metrics/ModuleLength
162
+
163
+ # Represents a single token emitted by the lexer.
164
+ class Token
165
+ # @param type [Symbol]
166
+ # @param value [Object, nil]
167
+ # @param location [Location, nil]
168
+ def initialize(type:, value: nil, location: nil)
169
+ @type = type
170
+ @value = value
171
+ @location = location
172
+ end
173
+
174
+ # @return [Symbol]
175
+ attr_reader :type
176
+
177
+ # @return [Object, nil]
178
+ attr_reader :value
179
+
180
+ # @return [Location, nil]
181
+ attr_reader :location
182
+
183
+ # @return [Boolean]
184
+ def keyword?
185
+ TokenType.keyword?(type)
186
+ end
187
+
188
+ # @return [Boolean]
189
+ def operator?
190
+ TokenType.operator?(type)
191
+ end
192
+
193
+ # @return [Boolean]
194
+ def literal?
195
+ TokenType.literal?(type)
196
+ end
197
+
198
+ # @return [String]
199
+ def to_s
200
+ parts = ["type=#{type}", "value=#{value.inspect}"]
201
+ parts << "location=#{location}" if location
202
+ "Token(#{parts.join(", ")})"
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,451 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ast"
4
+ require_relative "environment"
5
+ require_relative "value"
6
+
7
+ module Ruby
8
+ module Rego
9
+ # Handles pattern matching and unification for Rego terms.
10
+ # :reek:DataClump
11
+ # rubocop:disable Metrics/ClassLength
12
+ class Unifier
13
+ # Internal helpers that do not rely on instance state.
14
+ module Helpers
15
+ def self.scalar_pattern_value(pattern, env)
16
+ return Value.from_ruby(pattern.value) if pattern.is_a?(AST::Literal)
17
+ return env.resolve_reference(pattern) if pattern.is_a?(AST::Reference)
18
+ return pattern if pattern.is_a?(Value)
19
+
20
+ Value.from_ruby(pattern)
21
+ rescue ArgumentError, ObjectKeyConflictError
22
+ UndefinedValue.new
23
+ end
24
+
25
+ def self.normalize_value(value, env)
26
+ return value if value.is_a?(Value)
27
+ return env.resolve_reference(value) if value.is_a?(AST::Reference)
28
+ return Value.from_ruby(value.value) if value.is_a?(AST::Literal)
29
+
30
+ Value.from_ruby(value)
31
+ rescue ArgumentError, ObjectKeyConflictError
32
+ UndefinedValue.new
33
+ end
34
+
35
+ def self.value_for_pattern(pattern, env)
36
+ Helpers.scalar_pattern_value(pattern, env)
37
+ end
38
+
39
+ def self.normalize_array(value)
40
+ return normalize_elements(value.to_ruby) if value.is_a?(ArrayValue)
41
+ return normalize_elements(value) if value.is_a?(Array)
42
+
43
+ nil
44
+ end
45
+
46
+ def self.normalize_elements(elements)
47
+ values = [] # @type var values: Array[Value]
48
+ elements.each do |element|
49
+ values << Value.from_ruby(element)
50
+ rescue ArgumentError, ObjectKeyConflictError
51
+ return nil
52
+ end
53
+ values
54
+ end
55
+
56
+ def self.normalize_key(key)
57
+ key.is_a?(Symbol) ? key.to_s : key
58
+ end
59
+
60
+ def self.object_pairs(value)
61
+ return value.to_ruby if value.is_a?(ObjectValue)
62
+ return value if value.is_a?(Hash)
63
+
64
+ nil
65
+ end
66
+
67
+ # :reek:TooManyStatements
68
+ def self.normalize_object(value)
69
+ return nil unless (pairs = object_pairs(value))
70
+
71
+ values = {} # @type var values: Hash[Object, Value]
72
+ pairs.each do |key, val|
73
+ normalized_key = normalize_key(key)
74
+ return nil if values.key?(normalized_key)
75
+
76
+ values[normalized_key] = Value.from_ruby(val)
77
+ end
78
+ values
79
+ rescue ArgumentError, ObjectKeyConflictError
80
+ nil
81
+ end
82
+
83
+ def self.bound_value_for(name, bindings, env)
84
+ bound = bindings[name]
85
+ return bound if bound && !bound.is_a?(UndefinedValue)
86
+
87
+ env_value = env.lookup(name)
88
+ return nil if env_value.is_a?(UndefinedValue)
89
+
90
+ env_value
91
+ end
92
+
93
+ def self.merge_bindings(bindings, additions)
94
+ conflict = additions.any? do |name, value|
95
+ existing = bindings[name]
96
+ existing && existing != value
97
+ end
98
+ return nil if conflict
99
+
100
+ bindings.merge(additions)
101
+ end
102
+
103
+ # :reek:LongParameterList
104
+ # :reek:TooManyStatements
105
+ def self.unify_variable(variable, value, env, bindings)
106
+ name = variable.name
107
+ return [bindings] if name == "_"
108
+
109
+ bound_value = bound_value_for(name, bindings, env)
110
+ return bound_value == value ? [bindings] : [] if bound_value
111
+
112
+ [bindings.merge(name => value)]
113
+ end
114
+
115
+ # :reek:TooManyStatements
116
+ # :reek:LongParameterList
117
+ def self.candidate_keys_for(key_pattern, keys, env, bindings)
118
+ if key_pattern.is_a?(AST::Variable)
119
+ name = key_pattern.name
120
+ return keys if name == "_"
121
+
122
+ bound_value = bound_value_for(name, bindings, env)
123
+ return [normalize_key(bound_value.to_ruby)] if bound_value
124
+
125
+ return keys
126
+ end
127
+
128
+ key_value = value_for_pattern(key_pattern, env)
129
+ return [] if key_value.is_a?(UndefinedValue)
130
+
131
+ [normalize_key(key_value.to_ruby)]
132
+ end
133
+
134
+ # :reek:LongParameterList
135
+ # :reek:ControlParameter
136
+ def self.match_scalar(pattern, resolved_value, env, bindings)
137
+ pattern_value = scalar_pattern_value(pattern, env)
138
+ pattern_value == resolved_value ? [bindings] : []
139
+ end
140
+
141
+ # :reek:LongParameterList
142
+ # :reek:TooManyStatements
143
+ def self.bind_key_variable(key_pattern, candidate_key, bindings, env)
144
+ return bindings unless key_pattern.is_a?(AST::Variable)
145
+
146
+ name = key_pattern.name
147
+ return bindings if name == "_"
148
+
149
+ bound_value = bound_value_for(name, bindings, env)
150
+ return bindings if bound_value
151
+
152
+ merge_bindings(bindings, name => Value.from_ruby(candidate_key))
153
+ end
154
+ end
155
+
156
+ # Bundles inputs for reference key candidate evaluation.
157
+ ReferenceKeyContext = Struct.new(:current, :env, :bindings, :variable_resolver, keyword_init: true)
158
+
159
+ def initialize(variable_resolver: nil)
160
+ @variable_resolver = variable_resolver
161
+ end
162
+
163
+ # @param pattern [Object]
164
+ # @param value [Object]
165
+ # @param env [Environment]
166
+ # @return [Array<Hash{String => Value}>]
167
+ def unify(pattern, value, env)
168
+ bindings = {} # @type var bindings: Hash[String, Value]
169
+ unify_with_bindings(pattern, value, env, bindings)
170
+ end
171
+
172
+ # Resolve reference bindings for references with variable keys.
173
+ #
174
+ # @param reference [AST::Reference]
175
+ # @param env [Environment]
176
+ # @param bindings [Hash{String => Value}]
177
+ # @param variable_resolver [#call, nil]
178
+ # @return [Array<Array(Hash{String => Value}, Value)>]
179
+ # :reek:LongParameterList
180
+ def reference_bindings(reference, env, bindings = {}, base_value: nil, variable_resolver: nil)
181
+ base_value ||= resolve_reference_base(reference.base, env, bindings)
182
+ return [] if base_value.is_a?(UndefinedValue)
183
+
184
+ resolver = variable_resolver || @variable_resolver
185
+ traverse_reference(base_value, reference.path, env, bindings, variable_resolver: resolver)
186
+ end
187
+
188
+ # @param pattern_elems [Array<Object>]
189
+ # @param value_array [Object]
190
+ # @param env [Environment]
191
+ # @param bindings [Hash{String => Value}]
192
+ # @return [Array<Hash{String => Value}>]
193
+ # :reek:LongParameterList
194
+ def unify_array(pattern_elems, value_array, env, bindings = {})
195
+ elements = Helpers.normalize_array(value_array)
196
+ return [] unless elements && elements.length == pattern_elems.length
197
+
198
+ reduce_array_bindings(pattern_elems, elements, env, bindings)
199
+ end
200
+
201
+ # @param pattern_pairs [Array<Array(Object, Object)>]
202
+ # @param value_obj [Object]
203
+ # @param env [Environment]
204
+ # @param bindings [Hash{String => Value}]
205
+ # @return [Array<Hash{String => Value}>]
206
+ # :reek:LongParameterList
207
+ def unify_object(pattern_pairs, value_obj, env, bindings = {})
208
+ object_values = Helpers.normalize_object(value_obj)
209
+ return [] unless object_values
210
+
211
+ reduce_object_pairs(pattern_pairs, object_values, env, bindings)
212
+ end
213
+
214
+ private
215
+
216
+ # :reek:LongParameterList
217
+ def unify_with_bindings(pattern, value, env, bindings)
218
+ resolved_value = Helpers.normalize_value(value, env)
219
+ return [] if resolved_value.is_a?(UndefinedValue)
220
+
221
+ apply_unification(pattern, resolved_value, env, bindings)
222
+ rescue ArgumentError
223
+ []
224
+ end
225
+
226
+ # :reek:LongParameterList
227
+ # :reek:FeatureEnvy
228
+ def structured_unification(pattern, resolved_value, env, bindings)
229
+ return Helpers.unify_variable(pattern, resolved_value, env, bindings) if pattern.is_a?(AST::Variable)
230
+ return unify_reference(pattern, resolved_value, env, bindings) if pattern.is_a?(AST::Reference)
231
+ return unify_array(pattern.elements, resolved_value, env, bindings) if pattern.is_a?(AST::ArrayLiteral)
232
+ return unify_object(pattern.pairs, resolved_value, env, bindings) if pattern.is_a?(AST::ObjectLiteral)
233
+
234
+ nil
235
+ end
236
+
237
+ # :reek:LongParameterList
238
+ def apply_unification(pattern, resolved_value, env, bindings)
239
+ structured = structured_unification(pattern, resolved_value, env, bindings)
240
+ return structured if structured
241
+
242
+ Helpers.match_scalar(pattern, resolved_value, env, bindings)
243
+ end
244
+
245
+ # :reek:TooManyStatements
246
+ # :reek:LongParameterList
247
+ # :reek:FeatureEnvy
248
+ # rubocop:disable Metrics/MethodLength
249
+ def reduce_array_bindings(pattern_elems, elements, env, bindings)
250
+ binding_sets = [bindings]
251
+ index = 0
252
+ while index < pattern_elems.length
253
+ element = pattern_elems[index]
254
+ next_sets = [] # @type var next_sets: Array[Hash[String, Value]]
255
+ binding_sets.each do |current|
256
+ next_sets.concat(unify_with_bindings(element, elements[index], env, current))
257
+ end
258
+ binding_sets = next_sets
259
+ break if binding_sets.empty?
260
+
261
+ index += 1
262
+ end
263
+ binding_sets
264
+ end
265
+ # rubocop:enable Metrics/MethodLength
266
+
267
+ # :reek:TooManyStatements
268
+ # :reek:LongParameterList
269
+ # :reek:FeatureEnvy
270
+ # rubocop:disable Metrics/MethodLength
271
+ def reduce_object_pairs(pattern_pairs, object_values, env, bindings)
272
+ binding_sets = [bindings]
273
+ index = 0
274
+ while index < pattern_pairs.length
275
+ key_pattern, value_pattern = pattern_pairs[index]
276
+ next_sets = [] # @type var next_sets: Array[Hash[String, Value]]
277
+ binding_sets.each do |current|
278
+ next_sets.concat(unify_object_pair(key_pattern, value_pattern, object_values, env, current))
279
+ end
280
+ binding_sets = next_sets
281
+ break if binding_sets.empty?
282
+
283
+ index += 1
284
+ end
285
+ binding_sets
286
+ end
287
+ # rubocop:enable Metrics/MethodLength
288
+
289
+ # :reek:LongParameterList
290
+ # :reek:FeatureEnvy
291
+ def unify_object_pair(key_pattern, value_pattern, object_values, env, bindings)
292
+ candidate_keys = Helpers.candidate_keys_for(key_pattern, object_values.keys, env, bindings)
293
+ return [] if candidate_keys.empty?
294
+
295
+ candidate_keys.flat_map do |candidate_key|
296
+ unify_object_candidate(candidate_key, key_pattern, value_pattern, object_values, env, bindings)
297
+ end
298
+ end
299
+
300
+ # :reek:LongParameterList
301
+ # :reek:FeatureEnvy
302
+ # rubocop:disable Metrics/ParameterLists
303
+ def unify_object_candidate(candidate_key, key_pattern, value_pattern, object_values, env, bindings)
304
+ return [] unless object_values.key?(candidate_key)
305
+
306
+ updated_bindings = Helpers.bind_key_variable(key_pattern, candidate_key, bindings, env)
307
+ return [] unless updated_bindings
308
+
309
+ unify_with_bindings(value_pattern, object_values[candidate_key], env, updated_bindings)
310
+ end
311
+ # rubocop:enable Metrics/ParameterLists
312
+
313
+ # :reek:LongParameterList
314
+ def unify_reference(pattern, resolved_value, env, bindings)
315
+ # @type var results: Array[Hash[String, Value]]
316
+ results = []
317
+ reference_bindings(pattern, env, bindings).each_with_object(results) do |(candidate_bindings, value), acc|
318
+ next if value.is_a?(UndefinedValue)
319
+ next unless value == resolved_value
320
+
321
+ acc << candidate_bindings
322
+ end
323
+ end
324
+
325
+ def resolve_reference_base(base, env, bindings)
326
+ return env.input if base.is_a?(AST::Variable) && base.name == "input"
327
+ return env.data if base.is_a?(AST::Variable) && base.name == "data"
328
+
329
+ if base.is_a?(AST::Variable)
330
+ bound = Helpers.bound_value_for(base.name, bindings, env)
331
+ return bound if bound
332
+ end
333
+
334
+ Helpers.scalar_pattern_value(base, env)
335
+ end
336
+
337
+ # :reek:LongParameterList
338
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
339
+ def traverse_reference(current, path, env, bindings, variable_resolver: nil)
340
+ return [[bindings, current]] if path.empty?
341
+ return [] unless current.is_a?(ObjectValue) || current.is_a?(ArrayValue)
342
+
343
+ segment = path.first
344
+ key_node = segment.is_a?(AST::RefArg) ? segment.value : segment
345
+ candidates = reference_key_candidates(current, key_node, env, bindings, variable_resolver: variable_resolver)
346
+ candidates.flat_map do |candidate_key, candidate_bindings|
347
+ next [] if candidate_bindings.nil?
348
+
349
+ next_value = current.fetch_reference(candidate_key)
350
+ next [] if next_value.is_a?(UndefinedValue)
351
+
352
+ traverse_reference(next_value, path.drop(1), env, candidate_bindings, variable_resolver: variable_resolver)
353
+ end
354
+ end
355
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
356
+
357
+ # :reek:LongParameterList
358
+ def reference_key_candidates(current, key_node, env, bindings, variable_resolver: nil)
359
+ keys = reference_keys_for(current)
360
+ return [] if keys.empty?
361
+
362
+ context = ReferenceKeyContext.new(
363
+ current: current,
364
+ env: env,
365
+ bindings: bindings,
366
+ variable_resolver: variable_resolver
367
+ )
368
+ return variable_key_candidates(key_node, keys, context) if key_node.is_a?(AST::Variable)
369
+
370
+ value_key_candidates(key_node, context)
371
+ end
372
+
373
+ def variable_key_candidates(key_node, keys, context)
374
+ name = key_node.name
375
+ return wildcard_key_candidates(keys, context.bindings) if name == "_"
376
+
377
+ bound_candidate = bound_key_candidate(name, context)
378
+ return bound_candidate if bound_candidate
379
+
380
+ resolved_candidate = resolved_key_candidate(name, context)
381
+ return resolved_candidate if resolved_candidate
382
+
383
+ binding_key_candidates(name, keys, context.bindings)
384
+ end
385
+
386
+ def wildcard_key_candidates(keys, bindings)
387
+ keys.map { |key| [key, bindings] }
388
+ end
389
+
390
+ def bound_key_candidate(name, context)
391
+ bound = Helpers.bound_value_for(name, context.bindings, context.env)
392
+ return nil unless bound
393
+
394
+ [[normalize_reference_key(context.current, bound.to_ruby), context.bindings]]
395
+ end
396
+
397
+ def resolved_key_candidate(name, context)
398
+ resolved = resolve_variable_reference(name, context.variable_resolver)
399
+ return nil unless resolved
400
+
401
+ normalized = normalize_reference_key(context.current, resolved_reference_value(resolved))
402
+ [[normalized, context.bindings]]
403
+ end
404
+
405
+ def binding_key_candidates(name, keys, bindings)
406
+ keys.map do |key|
407
+ new_bindings = Helpers.merge_bindings(bindings, name => Value.from_ruby(key))
408
+ [key, new_bindings]
409
+ end
410
+ end
411
+
412
+ def value_key_candidates(key_node, context)
413
+ key_value = Helpers.value_for_pattern(key_node, context.env)
414
+ return [] if key_value.is_a?(UndefinedValue)
415
+
416
+ [[normalize_reference_key(context.current, key_value.to_ruby), context.bindings]]
417
+ end
418
+
419
+ def resolved_reference_value(resolved)
420
+ resolved.is_a?(Value) ? resolved.to_ruby : resolved
421
+ end
422
+
423
+ def resolve_variable_reference(name, resolver)
424
+ return nil unless resolver
425
+
426
+ resolved = resolver.call(name)
427
+ return nil if resolved.nil? || resolved.is_a?(UndefinedValue)
428
+
429
+ resolved
430
+ end
431
+
432
+ def reference_keys_for(current)
433
+ case current
434
+ when ObjectValue
435
+ current.value.keys
436
+ when ArrayValue
437
+ (0...current.value.length).to_a
438
+ else
439
+ []
440
+ end
441
+ end
442
+
443
+ def normalize_reference_key(current, key)
444
+ return Helpers.normalize_key(key) if current.is_a?(ObjectValue)
445
+
446
+ key
447
+ end
448
+ end
449
+ # rubocop:enable Metrics/ClassLength
450
+ end
451
+ end