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.
- checksums.yaml +7 -0
- data/.reek.yml +80 -0
- data/.vscode/extensions.json +19 -0
- data/.vscode/launch.json +35 -0
- data/.vscode/settings.json +25 -0
- data/.vscode/tasks.json +117 -0
- data/.yardopts +12 -0
- data/ARCHITECTURE.md +39 -0
- data/CHANGELOG.md +25 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +183 -0
- data/RELEASING.md +37 -0
- data/Rakefile +38 -0
- data/SECURITY.md +26 -0
- data/Steepfile +10 -0
- data/TODO.md +35 -0
- data/benchmark/builtin_calls.rb +29 -0
- data/benchmark/complex_policy.rb +19 -0
- data/benchmark/comprehensions.rb +19 -0
- data/benchmark/simple_rules.rb +20 -0
- data/examples/README.md +27 -0
- data/examples/sample_config.yaml +2 -0
- data/examples/simple_policy.rego +7 -0
- data/examples/validation_policy.rego +11 -0
- data/exe/rego-validate +6 -0
- data/lib/ruby/rego/ast/base.rb +95 -0
- data/lib/ruby/rego/ast/binary_op.rb +64 -0
- data/lib/ruby/rego/ast/call.rb +27 -0
- data/lib/ruby/rego/ast/composite.rb +48 -0
- data/lib/ruby/rego/ast/comprehension.rb +63 -0
- data/lib/ruby/rego/ast/every.rb +37 -0
- data/lib/ruby/rego/ast/import.rb +32 -0
- data/lib/ruby/rego/ast/literal.rb +70 -0
- data/lib/ruby/rego/ast/module.rb +32 -0
- data/lib/ruby/rego/ast/package.rb +22 -0
- data/lib/ruby/rego/ast/query.rb +63 -0
- data/lib/ruby/rego/ast/reference.rb +58 -0
- data/lib/ruby/rego/ast/rule.rb +114 -0
- data/lib/ruby/rego/ast/unary_op.rb +42 -0
- data/lib/ruby/rego/ast/variable.rb +22 -0
- data/lib/ruby/rego/ast.rb +17 -0
- data/lib/ruby/rego/builtins/aggregates.rb +124 -0
- data/lib/ruby/rego/builtins/base.rb +95 -0
- data/lib/ruby/rego/builtins/collections/array_ops.rb +103 -0
- data/lib/ruby/rego/builtins/collections/object_ops.rb +120 -0
- data/lib/ruby/rego/builtins/collections/set_ops.rb +51 -0
- data/lib/ruby/rego/builtins/collections.rb +137 -0
- data/lib/ruby/rego/builtins/comparisons/casts.rb +139 -0
- data/lib/ruby/rego/builtins/comparisons.rb +84 -0
- data/lib/ruby/rego/builtins/numeric_helpers.rb +56 -0
- data/lib/ruby/rego/builtins/registry.rb +199 -0
- data/lib/ruby/rego/builtins/registry_helpers.rb +27 -0
- data/lib/ruby/rego/builtins/strings/case_ops.rb +22 -0
- data/lib/ruby/rego/builtins/strings/concat.rb +19 -0
- data/lib/ruby/rego/builtins/strings/formatting.rb +35 -0
- data/lib/ruby/rego/builtins/strings/helpers.rb +62 -0
- data/lib/ruby/rego/builtins/strings/number_helpers.rb +48 -0
- data/lib/ruby/rego/builtins/strings/search.rb +63 -0
- data/lib/ruby/rego/builtins/strings/split.rb +19 -0
- data/lib/ruby/rego/builtins/strings/substring.rb +22 -0
- data/lib/ruby/rego/builtins/strings/trim.rb +42 -0
- data/lib/ruby/rego/builtins/strings/trim_helpers.rb +62 -0
- data/lib/ruby/rego/builtins/strings.rb +58 -0
- data/lib/ruby/rego/builtins/types.rb +89 -0
- data/lib/ruby/rego/call_name.rb +55 -0
- data/lib/ruby/rego/cli.rb +1122 -0
- data/lib/ruby/rego/compiled_module.rb +114 -0
- data/lib/ruby/rego/compiler.rb +1097 -0
- data/lib/ruby/rego/environment/overrides.rb +33 -0
- data/lib/ruby/rego/environment/reference_resolution.rb +86 -0
- data/lib/ruby/rego/environment.rb +230 -0
- data/lib/ruby/rego/environment_pool.rb +71 -0
- data/lib/ruby/rego/error_handling.rb +58 -0
- data/lib/ruby/rego/error_payload.rb +34 -0
- data/lib/ruby/rego/errors.rb +196 -0
- data/lib/ruby/rego/evaluator/assignment_support.rb +126 -0
- data/lib/ruby/rego/evaluator/binding_helpers.rb +60 -0
- data/lib/ruby/rego/evaluator/comprehension_evaluator.rb +182 -0
- data/lib/ruby/rego/evaluator/expression_dispatch.rb +45 -0
- data/lib/ruby/rego/evaluator/expression_evaluator.rb +492 -0
- data/lib/ruby/rego/evaluator/object_literal_evaluator.rb +52 -0
- data/lib/ruby/rego/evaluator/operator_evaluator.rb +163 -0
- data/lib/ruby/rego/evaluator/query_node_builder.rb +38 -0
- data/lib/ruby/rego/evaluator/reference_key_resolver.rb +50 -0
- data/lib/ruby/rego/evaluator/reference_resolver.rb +352 -0
- data/lib/ruby/rego/evaluator/rule_evaluator/bindings.rb +70 -0
- data/lib/ruby/rego/evaluator/rule_evaluator.rb +550 -0
- data/lib/ruby/rego/evaluator/rule_value_provider.rb +56 -0
- data/lib/ruby/rego/evaluator/variable_collector.rb +221 -0
- data/lib/ruby/rego/evaluator.rb +174 -0
- data/lib/ruby/rego/lexer/number_reader.rb +68 -0
- data/lib/ruby/rego/lexer/stream.rb +137 -0
- data/lib/ruby/rego/lexer/string_reader.rb +90 -0
- data/lib/ruby/rego/lexer/template_string_reader.rb +62 -0
- data/lib/ruby/rego/lexer.rb +206 -0
- data/lib/ruby/rego/location.rb +73 -0
- data/lib/ruby/rego/memoization.rb +67 -0
- data/lib/ruby/rego/parser/collections.rb +173 -0
- data/lib/ruby/rego/parser/expressions.rb +216 -0
- data/lib/ruby/rego/parser/precedence.rb +42 -0
- data/lib/ruby/rego/parser/query.rb +139 -0
- data/lib/ruby/rego/parser/references.rb +115 -0
- data/lib/ruby/rego/parser/rules.rb +310 -0
- data/lib/ruby/rego/parser.rb +210 -0
- data/lib/ruby/rego/policy.rb +50 -0
- data/lib/ruby/rego/result.rb +91 -0
- data/lib/ruby/rego/token.rb +206 -0
- data/lib/ruby/rego/unifier.rb +451 -0
- data/lib/ruby/rego/value.rb +379 -0
- data/lib/ruby/rego/version.rb +7 -0
- data/lib/ruby/rego/with_modifiers/with_modifier.rb +37 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_applier.rb +48 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_builtin_override.rb +128 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_context.rb +120 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_path_key_resolver.rb +42 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_path_override.rb +99 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_root_scope.rb +58 -0
- data/lib/ruby/rego.rb +72 -0
- data/sig/objspace.rbs +4 -0
- data/sig/psych.rbs +7 -0
- data/sig/rego_validate.rbs +382 -0
- data/sig/ruby/rego.rbs +2150 -0
- 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
|