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,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruby
|
|
4
|
+
module Rego
|
|
5
|
+
# Parsing helpers for expressions.
|
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
|
7
|
+
class Parser
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def parse_expression(precedence = Precedence::LOWEST)
|
|
11
|
+
parse_infix_expression(parse_primary, precedence)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def parse_infix_expression(left, precedence)
|
|
15
|
+
return left unless infix_operator?(precedence)
|
|
16
|
+
|
|
17
|
+
operator_token = advance
|
|
18
|
+
parse_infix_expression(parse_infix(left, operator_token), precedence)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def infix_operator?(precedence)
|
|
22
|
+
Helpers.precedence_of(current_token.type) > precedence
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def parse_primary
|
|
26
|
+
handler = PRIMARY_PARSERS[current_token.type]
|
|
27
|
+
return send(handler) if handler
|
|
28
|
+
|
|
29
|
+
parse_error("Expected expression.")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# :reek:FeatureEnvy
|
|
33
|
+
def parse_infix(left, operator_token)
|
|
34
|
+
operator_type = operator_token.type
|
|
35
|
+
operator = BINARY_OPERATOR_MAP.fetch(operator_type) do
|
|
36
|
+
parse_error("Unsupported operator: #{operator_type}.")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
right = parse_expression(Helpers.precedence_of(operator_type))
|
|
40
|
+
AST::BinaryOp.new(operator: operator, left: left, right: right, location: operator_token.location)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# :reek:TooManyStatements
|
|
44
|
+
def parse_call_args
|
|
45
|
+
parse_parenthesized_expression_list(
|
|
46
|
+
open_message: "Expected '('.",
|
|
47
|
+
close_message: "Expected ')' after arguments."
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_string_literal
|
|
52
|
+
token = current_token
|
|
53
|
+
parse_error("Expected string literal.") unless [TokenType::STRING, TokenType::RAW_STRING].include?(token.type)
|
|
54
|
+
advance
|
|
55
|
+
value = token.value.to_s
|
|
56
|
+
AST::StringLiteral.new(value: value, location: token.location)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def parse_template_string
|
|
60
|
+
token = current_token
|
|
61
|
+
unless [TokenType::TEMPLATE_STRING, TokenType::RAW_TEMPLATE_STRING].include?(token.type)
|
|
62
|
+
parse_error("Expected template string literal.")
|
|
63
|
+
end
|
|
64
|
+
advance
|
|
65
|
+
AST::TemplateString.new(parts: parse_template_parts(token), location: token.location)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# :reek:FeatureEnvy
|
|
69
|
+
def parse_number_literal
|
|
70
|
+
token = consume(TokenType::NUMBER, "Expected number literal.")
|
|
71
|
+
value = token.value
|
|
72
|
+
if value.is_a?(String)
|
|
73
|
+
value = if value.match?(/[eE.]/)
|
|
74
|
+
Float(value)
|
|
75
|
+
else
|
|
76
|
+
Integer(value, 10)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
AST::NumberLiteral.new(value: value, location: token.location)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def parse_boolean_literal
|
|
83
|
+
token_type = current_token.type
|
|
84
|
+
location = current_token.location
|
|
85
|
+
advance
|
|
86
|
+
AST::BooleanLiteral.new(value: token_type == TokenType::TRUE, location: location)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def parse_null_literal
|
|
90
|
+
token = advance
|
|
91
|
+
AST::NullLiteral.new(location: token.location)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def parse_unary_expression
|
|
95
|
+
token = advance
|
|
96
|
+
operator = UNARY_OPERATOR_MAP.fetch(token.type)
|
|
97
|
+
operand = parse_expression(Precedence::UNARY)
|
|
98
|
+
AST::UnaryOp.new(operator: operator, operand: operand, location: token.location)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def parse_parenthesized_expression
|
|
102
|
+
advance
|
|
103
|
+
expression = parse_parenthesized_body
|
|
104
|
+
consume(TokenType::RPAREN, "Expected ')' after expression.")
|
|
105
|
+
expression
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def parse_parenthesized_body
|
|
109
|
+
consume_newlines
|
|
110
|
+
expression = parse_expression
|
|
111
|
+
consume_newlines
|
|
112
|
+
expression
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# :reek:TooManyStatements
|
|
116
|
+
def parse_parenthesized_expression_list(open_message:, close_message:)
|
|
117
|
+
consume(TokenType::LPAREN, open_message)
|
|
118
|
+
consume_newlines
|
|
119
|
+
args = [] # @type var args: Array[AST::expression]
|
|
120
|
+
args = parse_expression_list_until(TokenType::RPAREN) unless match?(TokenType::RPAREN)
|
|
121
|
+
consume_newlines
|
|
122
|
+
consume(TokenType::RPAREN, close_message)
|
|
123
|
+
args
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# :reek:NilCheck
|
|
127
|
+
# :reek:DuplicateMethodCall
|
|
128
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
129
|
+
def parse_template_parts(token)
|
|
130
|
+
text = token.value.to_s
|
|
131
|
+
location = token.location
|
|
132
|
+
parts = [] # @type var parts: Array[Object]
|
|
133
|
+
index = 0
|
|
134
|
+
|
|
135
|
+
while index < text.length
|
|
136
|
+
start = text.index("{", index)
|
|
137
|
+
if start.nil?
|
|
138
|
+
literal = text[index..]
|
|
139
|
+
append_template_literal(parts, literal, location)
|
|
140
|
+
break
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if start > index
|
|
144
|
+
literal = text[index...start]
|
|
145
|
+
append_template_literal(parts, literal, location)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
expr_start = start + 1
|
|
149
|
+
expr_end = find_template_expression_end(text, expr_start)
|
|
150
|
+
expr_source = text[expr_start...expr_end]
|
|
151
|
+
parts << self.class.parse_expression_from_string(expr_source)
|
|
152
|
+
index = expr_end + 1
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
parts = [AST::StringLiteral.new(value: "", location: location)] if parts.empty?
|
|
156
|
+
parts
|
|
157
|
+
end
|
|
158
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
159
|
+
|
|
160
|
+
def append_template_literal(parts, literal, location)
|
|
161
|
+
literal_value = normalize_template_literal(literal)
|
|
162
|
+
parts << AST::StringLiteral.new(value: literal_value, location: location)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# :reek:UtilityFunction
|
|
166
|
+
def normalize_template_literal(literal)
|
|
167
|
+
literal.tr(Lexer::TEMPLATE_ESCAPE, "{")
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# :reek:TooManyStatements
|
|
171
|
+
# :reek:FeatureEnvy
|
|
172
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
173
|
+
def find_template_expression_end(text, start_index)
|
|
174
|
+
depth = 0
|
|
175
|
+
index = start_index
|
|
176
|
+
in_string = nil
|
|
177
|
+
escaped = false
|
|
178
|
+
|
|
179
|
+
while index < text.length
|
|
180
|
+
char = text[index]
|
|
181
|
+
if in_string
|
|
182
|
+
if escaped
|
|
183
|
+
escaped = false
|
|
184
|
+
elsif in_string == "\"" && char == "\\"
|
|
185
|
+
escaped = true
|
|
186
|
+
elsif char == in_string
|
|
187
|
+
in_string = nil
|
|
188
|
+
end
|
|
189
|
+
index += 1
|
|
190
|
+
next
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
if char && ["\"", "`"].include?(char)
|
|
194
|
+
in_string = char
|
|
195
|
+
index += 1
|
|
196
|
+
next
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
if char == "{"
|
|
200
|
+
depth += 1
|
|
201
|
+
elsif char == "}"
|
|
202
|
+
return index if depth.zero?
|
|
203
|
+
|
|
204
|
+
depth -= 1
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
index += 1
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
parse_error("Unterminated template expression.")
|
|
211
|
+
end
|
|
212
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
213
|
+
end
|
|
214
|
+
# rubocop:enable Metrics/ClassLength
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../token"
|
|
4
|
+
|
|
5
|
+
module Ruby
|
|
6
|
+
module Rego
|
|
7
|
+
class Parser
|
|
8
|
+
# Operator precedence table for binary operators.
|
|
9
|
+
# :reek:TooManyConstants
|
|
10
|
+
module Precedence
|
|
11
|
+
LOWEST = 0
|
|
12
|
+
ASSIGNMENT = 1
|
|
13
|
+
OR = 2
|
|
14
|
+
AND = 3
|
|
15
|
+
EQUALS = 4
|
|
16
|
+
COMPARE = 5
|
|
17
|
+
SUM = 6
|
|
18
|
+
PRODUCT = 7
|
|
19
|
+
UNARY = 8
|
|
20
|
+
|
|
21
|
+
BINARY = {
|
|
22
|
+
TokenType::ASSIGN => ASSIGNMENT,
|
|
23
|
+
TokenType::UNIFY => ASSIGNMENT,
|
|
24
|
+
TokenType::OR => OR,
|
|
25
|
+
TokenType::AND => AND,
|
|
26
|
+
TokenType::IN => COMPARE,
|
|
27
|
+
TokenType::EQ => EQUALS,
|
|
28
|
+
TokenType::NEQ => EQUALS,
|
|
29
|
+
TokenType::LT => COMPARE,
|
|
30
|
+
TokenType::LTE => COMPARE,
|
|
31
|
+
TokenType::GT => COMPARE,
|
|
32
|
+
TokenType::GTE => COMPARE,
|
|
33
|
+
TokenType::PLUS => SUM,
|
|
34
|
+
TokenType::MINUS => SUM,
|
|
35
|
+
TokenType::STAR => PRODUCT,
|
|
36
|
+
TokenType::SLASH => PRODUCT,
|
|
37
|
+
TokenType::PERCENT => PRODUCT
|
|
38
|
+
}.freeze
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruby
|
|
4
|
+
module Rego
|
|
5
|
+
# Parsing helpers for queries.
|
|
6
|
+
class Parser
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# :reek:TooManyStatements
|
|
10
|
+
# :reek:DuplicateMethodCall
|
|
11
|
+
# :reek:BooleanParameter
|
|
12
|
+
def parse_query(*end_tokens, newline_delimiter: false)
|
|
13
|
+
terminators = end_tokens.flatten
|
|
14
|
+
literals = [] # @type var literals: Array[AST::query_literal]
|
|
15
|
+
|
|
16
|
+
loop do
|
|
17
|
+
consume_newlines if newline_delimiter
|
|
18
|
+
break if terminators.include?(current_token.type)
|
|
19
|
+
|
|
20
|
+
literals << parse_literal
|
|
21
|
+
break if terminators.include?(current_token.type)
|
|
22
|
+
|
|
23
|
+
break unless consume_query_separators(newline_delimiter)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
literals
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# rubocop:disable Metrics/MethodLength
|
|
30
|
+
# :reek:TooManyStatements
|
|
31
|
+
# :reek:ControlParameter
|
|
32
|
+
def consume_query_separators(newline_delimiter)
|
|
33
|
+
consumed = false
|
|
34
|
+
|
|
35
|
+
loop do
|
|
36
|
+
if match?(TokenType::SEMICOLON)
|
|
37
|
+
advance
|
|
38
|
+
consumed = true
|
|
39
|
+
next
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if newline_delimiter && newline_token?
|
|
43
|
+
advance
|
|
44
|
+
consumed = true
|
|
45
|
+
next
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
break
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
consumed
|
|
52
|
+
end
|
|
53
|
+
# rubocop:enable Metrics/MethodLength
|
|
54
|
+
|
|
55
|
+
def parse_literal
|
|
56
|
+
return parse_some_decl if match?(TokenType::SOME)
|
|
57
|
+
|
|
58
|
+
expression = parse_expression
|
|
59
|
+
AST::QueryLiteral.new(
|
|
60
|
+
expression: expression,
|
|
61
|
+
with_modifiers: parse_with_modifiers,
|
|
62
|
+
location: expression.location
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def parse_with_modifiers
|
|
67
|
+
modifiers = [] # @type var modifiers: Array[AST::WithModifier]
|
|
68
|
+
modifiers << parse_with_modifier while match?(TokenType::WITH)
|
|
69
|
+
modifiers
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def parse_some_decl
|
|
73
|
+
keyword = consume(TokenType::SOME, "Expected 'some' declaration.")
|
|
74
|
+
variables = parse_some_variables
|
|
75
|
+
collection = parse_some_collection
|
|
76
|
+
|
|
77
|
+
AST::SomeDecl.new(variables: variables, collection: collection, location: keyword.location)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# :reek:TooManyStatements
|
|
81
|
+
def parse_some_variables
|
|
82
|
+
variables = [] # @type var variables: Array[AST::Variable]
|
|
83
|
+
loop do
|
|
84
|
+
variables << parse_variable
|
|
85
|
+
break unless match?(TokenType::COMMA)
|
|
86
|
+
|
|
87
|
+
advance
|
|
88
|
+
end
|
|
89
|
+
variables
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def parse_some_collection
|
|
93
|
+
return nil unless match?(TokenType::IN)
|
|
94
|
+
|
|
95
|
+
advance
|
|
96
|
+
parse_expression
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def parse_every
|
|
100
|
+
keyword = consume(TokenType::EVERY, "Expected 'every' expression.")
|
|
101
|
+
key_var, value_var = parse_every_variables
|
|
102
|
+
domain = parse_every_domain
|
|
103
|
+
body = parse_every_body
|
|
104
|
+
AST::Every.new(key_var: key_var, value_var: value_var, domain: domain, body: body, location: keyword.location)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def parse_every_variables
|
|
108
|
+
value_var = parse_variable
|
|
109
|
+
return [nil, value_var] unless match?(TokenType::COMMA)
|
|
110
|
+
|
|
111
|
+
advance
|
|
112
|
+
[value_var, parse_variable]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def parse_every_domain
|
|
116
|
+
consume(TokenType::IN, "Expected 'in' after every variables.")
|
|
117
|
+
parse_expression
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# :reek:TooManyStatements
|
|
121
|
+
def parse_every_body
|
|
122
|
+
consume_newlines
|
|
123
|
+
consume(TokenType::LBRACE, "Expected '{' to start every body.")
|
|
124
|
+
consume_newlines
|
|
125
|
+
body = parse_query(TokenType::RBRACE, newline_delimiter: true)
|
|
126
|
+
consume(TokenType::RBRACE, "Expected '}' after every body.")
|
|
127
|
+
body
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def parse_with_modifier
|
|
131
|
+
keyword = consume(TokenType::WITH, "Expected 'with' modifier.")
|
|
132
|
+
target = parse_expression
|
|
133
|
+
consume(TokenType::AS, "Expected 'as' after with target.")
|
|
134
|
+
value = parse_expression
|
|
135
|
+
AST::WithModifier.new(target: target, value: value, location: keyword.location)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruby
|
|
4
|
+
module Rego
|
|
5
|
+
# Parsing helpers for references and identifiers.
|
|
6
|
+
class Parser
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def parse_reference(base)
|
|
10
|
+
reference_base, path = Helpers.normalize_reference_base(base)
|
|
11
|
+
parse_reference_path(path)
|
|
12
|
+
AST::Reference.new(base: reference_base, path: path, location: base.location)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# :reek:TooManyStatements
|
|
16
|
+
# :reek:DuplicateMethodCall
|
|
17
|
+
# rubocop:disable Metrics/MethodLength
|
|
18
|
+
def parse_path(identifier_context)
|
|
19
|
+
segments = [] # @type var segments: Array[String]
|
|
20
|
+
|
|
21
|
+
segments << parse_path_segment(identifier_context)
|
|
22
|
+
|
|
23
|
+
loop do
|
|
24
|
+
if match?(TokenType::DOT)
|
|
25
|
+
advance
|
|
26
|
+
segments << parse_path_segment(identifier_context)
|
|
27
|
+
next
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
segment = parse_bracket_path_segment
|
|
31
|
+
break unless segment
|
|
32
|
+
|
|
33
|
+
segments << segment
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
segments
|
|
37
|
+
end
|
|
38
|
+
# rubocop:enable Metrics/MethodLength
|
|
39
|
+
|
|
40
|
+
def parse_path_segment(identifier_context)
|
|
41
|
+
token_type = current_token.type
|
|
42
|
+
return parse_identifier(identifier_context) if identifier_context.allowed_types.include?(token_type)
|
|
43
|
+
|
|
44
|
+
parse_error("Expected #{identifier_context.name} identifier.")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parse_bracket_path_segment
|
|
48
|
+
return nil unless match?(TokenType::LBRACKET)
|
|
49
|
+
return nil unless bracket_string_segment?
|
|
50
|
+
|
|
51
|
+
advance
|
|
52
|
+
value = parse_string_literal
|
|
53
|
+
consume(TokenType::RBRACKET, "Expected ']' after path segment.")
|
|
54
|
+
value.value
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def bracket_string_segment?
|
|
58
|
+
return false unless match?(TokenType::LBRACKET)
|
|
59
|
+
|
|
60
|
+
token = peek
|
|
61
|
+
[TokenType::STRING, TokenType::RAW_STRING].include?(token.type)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# :reek:TooManyStatements
|
|
65
|
+
def parse_identifier(identifier_context)
|
|
66
|
+
context_name = identifier_context.name
|
|
67
|
+
allowed_types = identifier_context.allowed_types
|
|
68
|
+
token = current_token
|
|
69
|
+
token_type = token.type
|
|
70
|
+
|
|
71
|
+
parse_error("Expected #{context_name} identifier.") unless allowed_types.include?(token_type)
|
|
72
|
+
|
|
73
|
+
advance
|
|
74
|
+
return token.value.to_s if token_type == TokenType::IDENT
|
|
75
|
+
|
|
76
|
+
IDENTIFIER_TOKEN_NAMES.fetch(token_type) { token_type.to_s.downcase }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def parse_identifier_expression
|
|
80
|
+
base = parse_variable
|
|
81
|
+
base = parse_reference(base) if match?(TokenType::DOT, TokenType::LBRACKET)
|
|
82
|
+
return base unless match?(TokenType::LPAREN)
|
|
83
|
+
|
|
84
|
+
AST::Call.new(name: base, args: parse_call_args, location: base.location)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def parse_variable
|
|
88
|
+
token = current_token
|
|
89
|
+
name = Helpers.variable_name_for(token)
|
|
90
|
+
advance
|
|
91
|
+
AST::Variable.new(name: name, location: token.location)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def parse_reference_path(path)
|
|
95
|
+
while match?(TokenType::DOT, TokenType::LBRACKET)
|
|
96
|
+
match?(TokenType::DOT) ? parse_dot_reference(path) : parse_bracket_reference(path)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def parse_dot_reference(path)
|
|
101
|
+
advance
|
|
102
|
+
segment_token = current_token
|
|
103
|
+
segment = parse_identifier(IdentifierContext.new(name: "reference", allowed_types: IDENTIFIER_TOKEN_TYPES))
|
|
104
|
+
path << AST::DotRefArg.new(value: segment, location: segment_token.location)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def parse_bracket_reference(path)
|
|
108
|
+
bracket_token = consume(TokenType::LBRACKET)
|
|
109
|
+
value = parse_expression
|
|
110
|
+
consume(TokenType::RBRACKET, "Expected ']' after reference path.")
|
|
111
|
+
path << AST::BracketRefArg.new(value: value, location: bracket_token.location)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|