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,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruby
|
|
4
|
+
module Rego
|
|
5
|
+
# Template string lexer helpers.
|
|
6
|
+
class Lexer
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# rubocop:disable Metrics/MethodLength
|
|
10
|
+
def read_template_string
|
|
11
|
+
start = capture_position
|
|
12
|
+
advance
|
|
13
|
+
return read_raw_template_string(start) if current_char == "`"
|
|
14
|
+
|
|
15
|
+
read_standard_template_string(start)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def read_standard_template_string(start)
|
|
19
|
+
advance
|
|
20
|
+
buffer = +""
|
|
21
|
+
|
|
22
|
+
until eof?
|
|
23
|
+
char_position = capture_position
|
|
24
|
+
char = advance
|
|
25
|
+
return build_token(TokenType::TEMPLATE_STRING, buffer, start) if char == "\""
|
|
26
|
+
|
|
27
|
+
break if char == "\n"
|
|
28
|
+
|
|
29
|
+
if char == "\\" && current_char == "{"
|
|
30
|
+
advance
|
|
31
|
+
buffer << TEMPLATE_ESCAPE
|
|
32
|
+
next
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
buffer << (char == "\\" ? read_escape_sequence(char_position) : char)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
raise_unterminated_string(start)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def read_raw_template_string(start)
|
|
42
|
+
advance
|
|
43
|
+
buffer = +""
|
|
44
|
+
|
|
45
|
+
until eof?
|
|
46
|
+
char = advance
|
|
47
|
+
return build_token(TokenType::RAW_TEMPLATE_STRING, buffer, start) if char == "`"
|
|
48
|
+
|
|
49
|
+
if char == "\\" && current_char == "{"
|
|
50
|
+
advance
|
|
51
|
+
buffer << TEMPLATE_ESCAPE
|
|
52
|
+
else
|
|
53
|
+
buffer << char
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
raise_unterminated_raw_string(start)
|
|
58
|
+
end
|
|
59
|
+
# rubocop:enable Metrics/MethodLength
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require_relative "location"
|
|
5
|
+
require_relative "token"
|
|
6
|
+
require_relative "lexer/number_reader"
|
|
7
|
+
require_relative "lexer/stream"
|
|
8
|
+
require_relative "lexer/string_reader"
|
|
9
|
+
require_relative "lexer/template_string_reader"
|
|
10
|
+
|
|
11
|
+
module Ruby
|
|
12
|
+
module Rego
|
|
13
|
+
# Converts Rego source code into a stream of tokens.
|
|
14
|
+
# rubocop:disable Metrics/ClassLength
|
|
15
|
+
class Lexer
|
|
16
|
+
KEYWORDS = {
|
|
17
|
+
"package" => TokenType::PACKAGE,
|
|
18
|
+
"import" => TokenType::IMPORT,
|
|
19
|
+
"as" => TokenType::AS,
|
|
20
|
+
"default" => TokenType::DEFAULT,
|
|
21
|
+
"if" => TokenType::IF,
|
|
22
|
+
"contains" => TokenType::CONTAINS,
|
|
23
|
+
"some" => TokenType::SOME,
|
|
24
|
+
"in" => TokenType::IN,
|
|
25
|
+
"every" => TokenType::EVERY,
|
|
26
|
+
"not" => TokenType::NOT,
|
|
27
|
+
"and" => TokenType::AND,
|
|
28
|
+
"or" => TokenType::OR,
|
|
29
|
+
"with" => TokenType::WITH,
|
|
30
|
+
"else" => TokenType::ELSE,
|
|
31
|
+
"true" => TokenType::TRUE,
|
|
32
|
+
"false" => TokenType::FALSE,
|
|
33
|
+
"null" => TokenType::NULL,
|
|
34
|
+
"data" => TokenType::DATA,
|
|
35
|
+
"input" => TokenType::INPUT
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
SINGLE_CHAR_TOKENS = {
|
|
39
|
+
"(" => TokenType::LPAREN,
|
|
40
|
+
")" => TokenType::RPAREN,
|
|
41
|
+
"[" => TokenType::LBRACKET,
|
|
42
|
+
"]" => TokenType::RBRACKET,
|
|
43
|
+
"{" => TokenType::LBRACE,
|
|
44
|
+
"}" => TokenType::RBRACE,
|
|
45
|
+
"." => TokenType::DOT,
|
|
46
|
+
"," => TokenType::COMMA,
|
|
47
|
+
";" => TokenType::SEMICOLON,
|
|
48
|
+
"+" => TokenType::PLUS,
|
|
49
|
+
"-" => TokenType::MINUS,
|
|
50
|
+
"*" => TokenType::STAR,
|
|
51
|
+
"/" => TokenType::SLASH,
|
|
52
|
+
"%" => TokenType::PERCENT,
|
|
53
|
+
"|" => TokenType::PIPE,
|
|
54
|
+
"&" => TokenType::AMPERSAND
|
|
55
|
+
}.freeze
|
|
56
|
+
|
|
57
|
+
COMPOUND_TOKENS = {
|
|
58
|
+
":" => [TokenType::COLON, TokenType::ASSIGN],
|
|
59
|
+
"=" => [TokenType::UNIFY, TokenType::EQ],
|
|
60
|
+
"!" => [nil, TokenType::NEQ],
|
|
61
|
+
"<" => [TokenType::LT, TokenType::LTE],
|
|
62
|
+
">" => [TokenType::GT, TokenType::GTE]
|
|
63
|
+
}.freeze
|
|
64
|
+
|
|
65
|
+
NEWLINE_CHARS = ["\n", "\r"].freeze
|
|
66
|
+
WHITESPACE_CHARS = [" ", "\t"].freeze
|
|
67
|
+
EXPONENT_CHARS = %w[e E].freeze
|
|
68
|
+
SIGN_CHARS = %w[+ -].freeze
|
|
69
|
+
|
|
70
|
+
IDENTIFIER_START = /[A-Za-z_]/
|
|
71
|
+
IDENTIFIER_PART = /[A-Za-z0-9_]/
|
|
72
|
+
DIGIT = /\d/
|
|
73
|
+
HEX_DIGIT = /[0-9A-Fa-f]/
|
|
74
|
+
|
|
75
|
+
# Create a lexer for the provided source.
|
|
76
|
+
#
|
|
77
|
+
# @param source [String] Rego source code
|
|
78
|
+
def initialize(source)
|
|
79
|
+
@source = source.to_s
|
|
80
|
+
@position = 0
|
|
81
|
+
@line = 1
|
|
82
|
+
@column = 1
|
|
83
|
+
@offset = 0
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Tokenize the source into a list of tokens, including EOF.
|
|
87
|
+
#
|
|
88
|
+
# @return [Array<Token>] token stream
|
|
89
|
+
def tokenize
|
|
90
|
+
# @type var tokens: Array[Token]
|
|
91
|
+
tokens = []
|
|
92
|
+
|
|
93
|
+
loop do
|
|
94
|
+
skip_whitespace
|
|
95
|
+
break if eof?
|
|
96
|
+
|
|
97
|
+
tokens << next_token
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
tokens << eof_token
|
|
101
|
+
tokens
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
attr_reader :source, :position, :line, :column, :offset
|
|
107
|
+
|
|
108
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
109
|
+
def next_token
|
|
110
|
+
char = current_char
|
|
111
|
+
return eof_token if char.nil?
|
|
112
|
+
|
|
113
|
+
return read_newline if newline?(char)
|
|
114
|
+
return read_number if digit?(char)
|
|
115
|
+
return read_identifier if identifier_start?(char)
|
|
116
|
+
return read_template_string if template_string_start?(char)
|
|
117
|
+
return read_string if char == "\""
|
|
118
|
+
return read_raw_string if char == "`"
|
|
119
|
+
|
|
120
|
+
raise_error("Invalid number literal", capture_position, length: 1) if char == "." && digit?(peek(1))
|
|
121
|
+
|
|
122
|
+
token = simple_token_for(char)
|
|
123
|
+
return token if token
|
|
124
|
+
|
|
125
|
+
token = read_compound_token(char)
|
|
126
|
+
return token if token
|
|
127
|
+
|
|
128
|
+
raise_error("Unexpected character: #{char.inspect}", capture_position, length: 1)
|
|
129
|
+
end
|
|
130
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
131
|
+
|
|
132
|
+
def eof_token
|
|
133
|
+
start = capture_position
|
|
134
|
+
build_token(TokenType::EOF, nil, start)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def simple_token_for(char)
|
|
138
|
+
type = SINGLE_CHAR_TOKENS[char]
|
|
139
|
+
return nil unless type
|
|
140
|
+
|
|
141
|
+
start = capture_position
|
|
142
|
+
advance
|
|
143
|
+
build_token(type, nil, start)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def read_compound_token(char)
|
|
147
|
+
config = COMPOUND_TOKENS[char]
|
|
148
|
+
return nil unless config
|
|
149
|
+
|
|
150
|
+
single_type, double_type = config
|
|
151
|
+
start = capture_position
|
|
152
|
+
advance
|
|
153
|
+
return build_token(double_type, nil, start) if match?("=")
|
|
154
|
+
return build_token(single_type, nil, start) if single_type
|
|
155
|
+
|
|
156
|
+
raise_error("Unexpected character: #{char.inspect}", start, length: 1)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def skip_whitespace
|
|
160
|
+
loop do
|
|
161
|
+
char = current_char
|
|
162
|
+
break if char.nil?
|
|
163
|
+
|
|
164
|
+
if char == "#"
|
|
165
|
+
skip_comment
|
|
166
|
+
next
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
break unless whitespace?(char)
|
|
170
|
+
|
|
171
|
+
advance
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def skip_comment
|
|
176
|
+
advance
|
|
177
|
+
advance until eof? || newline?(current_char)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def template_string_start?(char)
|
|
181
|
+
char == "$" && ["\"", "`"].include?(peek(1).to_s)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def read_newline
|
|
185
|
+
start = capture_position
|
|
186
|
+
advance
|
|
187
|
+
build_token(TokenType::NEWLINE, "\n", start)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def read_identifier
|
|
191
|
+
start = capture_position
|
|
192
|
+
buffer = +""
|
|
193
|
+
|
|
194
|
+
buffer << advance while identifier_part?(current_char)
|
|
195
|
+
|
|
196
|
+
return build_token(TokenType::UNDERSCORE, nil, start) if buffer == "_"
|
|
197
|
+
|
|
198
|
+
keyword = KEYWORDS[buffer]
|
|
199
|
+
return build_token(keyword, nil, start) if keyword
|
|
200
|
+
|
|
201
|
+
build_token(TokenType::IDENT, buffer, start)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
# rubocop:enable Metrics/ClassLength
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruby
|
|
4
|
+
module Rego
|
|
5
|
+
# Represents a source location in a Rego policy.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# location = Ruby::Rego::Location.new(line: 3, column: 12, offset: 42, length: 5)
|
|
9
|
+
# location.to_s # => "line 3, column 12, offset 42, length 5"
|
|
10
|
+
#
|
|
11
|
+
class Location
|
|
12
|
+
# Coerce a hash or location into a Location instance.
|
|
13
|
+
#
|
|
14
|
+
# @param position [Location, Hash] position data
|
|
15
|
+
# @return [Location]
|
|
16
|
+
def self.from(position)
|
|
17
|
+
return position if position.is_a?(Location)
|
|
18
|
+
|
|
19
|
+
new(
|
|
20
|
+
line: position.fetch(:line),
|
|
21
|
+
column: position.fetch(:column),
|
|
22
|
+
offset: position[:offset],
|
|
23
|
+
length: position[:length]
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Create a new source location.
|
|
28
|
+
#
|
|
29
|
+
# @param line [Integer] 1-based line number
|
|
30
|
+
# @param column [Integer] 1-based column number
|
|
31
|
+
# @param offset [Integer, nil] 0-based character offset
|
|
32
|
+
# @param length [Integer, nil] length of the token or span
|
|
33
|
+
def initialize(line:, column:, offset: nil, length: nil)
|
|
34
|
+
@line = line
|
|
35
|
+
@column = column
|
|
36
|
+
@offset = offset
|
|
37
|
+
@length = length
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Line number.
|
|
41
|
+
#
|
|
42
|
+
# @return [Integer]
|
|
43
|
+
attr_reader :line
|
|
44
|
+
|
|
45
|
+
# Column number.
|
|
46
|
+
#
|
|
47
|
+
# @return [Integer]
|
|
48
|
+
attr_reader :column
|
|
49
|
+
|
|
50
|
+
# Character offset.
|
|
51
|
+
#
|
|
52
|
+
# @return [Integer, nil]
|
|
53
|
+
attr_reader :offset
|
|
54
|
+
|
|
55
|
+
# Length of the token or span.
|
|
56
|
+
#
|
|
57
|
+
# @return [Integer, nil]
|
|
58
|
+
attr_reader :length
|
|
59
|
+
|
|
60
|
+
# Convert the location to a readable string.
|
|
61
|
+
#
|
|
62
|
+
# @return [String]
|
|
63
|
+
def to_s
|
|
64
|
+
{
|
|
65
|
+
line: line,
|
|
66
|
+
column: column,
|
|
67
|
+
offset: offset,
|
|
68
|
+
length: length
|
|
69
|
+
}.compact.map { |key, value| "#{key} #{value}" }.join(", ")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruby
|
|
4
|
+
module Rego
|
|
5
|
+
# Shared memoization store for evaluation caches.
|
|
6
|
+
module Memoization
|
|
7
|
+
# Holds per-scope caches used during evaluation.
|
|
8
|
+
class Context
|
|
9
|
+
def initialize
|
|
10
|
+
@rule_values = {} # @type var @rule_values: Hash[String, Value]
|
|
11
|
+
@reference_values = {} # @type var @reference_values: Hash[AST::Reference, Value]
|
|
12
|
+
@reference_keys = {} # @type var @reference_keys: Hash[AST::Reference, Array[Object] | Object]
|
|
13
|
+
@function_values = {} # @type var @function_values: Hash[Array[Object], Value]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @return [Hash]
|
|
17
|
+
attr_reader :rule_values
|
|
18
|
+
|
|
19
|
+
# @return [Hash]
|
|
20
|
+
attr_reader :reference_values
|
|
21
|
+
|
|
22
|
+
# @return [Hash]
|
|
23
|
+
attr_reader :reference_keys
|
|
24
|
+
|
|
25
|
+
# @return [Hash]
|
|
26
|
+
attr_reader :function_values
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Stack-based memoization store for nested scopes.
|
|
30
|
+
class Store
|
|
31
|
+
def initialize
|
|
32
|
+
@contexts = [Context.new]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Reset memoized data for a new evaluation.
|
|
36
|
+
#
|
|
37
|
+
# @return [void]
|
|
38
|
+
def reset!
|
|
39
|
+
@contexts = [Context.new]
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Reset memoized data without mutation semantics.
|
|
44
|
+
#
|
|
45
|
+
# @return [void]
|
|
46
|
+
def reset
|
|
47
|
+
reset!
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Run with a fresh memoization context.
|
|
51
|
+
#
|
|
52
|
+
# @return [Object]
|
|
53
|
+
def with_context
|
|
54
|
+
@contexts << Context.new
|
|
55
|
+
yield
|
|
56
|
+
ensure
|
|
57
|
+
@contexts.pop
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [Context]
|
|
61
|
+
def context
|
|
62
|
+
@contexts.last
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruby
|
|
4
|
+
module Rego
|
|
5
|
+
# Parsing helpers for collection literals and comprehensions.
|
|
6
|
+
# :reek:TooManyMethods
|
|
7
|
+
# :reek:RepeatedConditional
|
|
8
|
+
# :reek:DataClump
|
|
9
|
+
# rubocop:disable Metrics/ClassLength
|
|
10
|
+
class Parser
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# :reek:TooManyStatements
|
|
14
|
+
# :reek:DuplicateMethodCall
|
|
15
|
+
def parse_array
|
|
16
|
+
start = consume(TokenType::LBRACKET)
|
|
17
|
+
location = start.location
|
|
18
|
+
consume_newlines
|
|
19
|
+
return AST::ArrayLiteral.new(elements: [], location: location) if match?(TokenType::RBRACKET)
|
|
20
|
+
|
|
21
|
+
term = parse_expression(Precedence::OR)
|
|
22
|
+
return parse_array_comprehension(start, term) if pipe_token?
|
|
23
|
+
|
|
24
|
+
elements = parse_expression_list_until_with_first(TokenType::RBRACKET, term)
|
|
25
|
+
consume_newlines
|
|
26
|
+
consume(TokenType::RBRACKET, "Expected ']' after array literal.")
|
|
27
|
+
AST::ArrayLiteral.new(elements: elements, location: location)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def parse_object(start_token, first_key, first_value)
|
|
31
|
+
pairs = build_object_pairs(first_key, first_value)
|
|
32
|
+
consume_newlines
|
|
33
|
+
consume(TokenType::RBRACE, "Expected '}' after object literal.")
|
|
34
|
+
AST::ObjectLiteral.new(pairs: pairs, location: start_token.location)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_object_pairs(first_key, first_value)
|
|
38
|
+
pairs = [] # @type var pairs: Array[[AST::expression, AST::expression]]
|
|
39
|
+
pairs << [first_key, first_value]
|
|
40
|
+
append_object_pairs(pairs)
|
|
41
|
+
pairs
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def parse_set(start_token, first_element = nil)
|
|
45
|
+
return empty_set_literal(start_token) if empty_set?(first_element)
|
|
46
|
+
|
|
47
|
+
elements = parse_expression_list_until_with_first(TokenType::RBRACE, first_element)
|
|
48
|
+
consume_newlines
|
|
49
|
+
consume(TokenType::RBRACE, "Expected '}' after set literal.")
|
|
50
|
+
AST::SetLiteral.new(elements: elements, location: start_token.location)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# :reek:TooManyStatements
|
|
54
|
+
def parse_braced_literal
|
|
55
|
+
start = consume(TokenType::LBRACE)
|
|
56
|
+
consume_newlines
|
|
57
|
+
return empty_object_literal(start) if rbrace_token?
|
|
58
|
+
|
|
59
|
+
first = parse_expression(Precedence::OR)
|
|
60
|
+
return parse_set_comprehension(start, first) if pipe_token?
|
|
61
|
+
return parse_object_literal_or_comprehension(start, first) if match?(TokenType::COLON)
|
|
62
|
+
|
|
63
|
+
parse_set(start, first)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def parse_object_literal_or_comprehension(start, key)
|
|
67
|
+
advance
|
|
68
|
+
consume_newlines
|
|
69
|
+
value = parse_expression(Precedence::OR)
|
|
70
|
+
return parse_object_comprehension(start, key, value) if pipe_token?
|
|
71
|
+
|
|
72
|
+
parse_object(start, key, value)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def parse_expression_list_until(end_token)
|
|
76
|
+
elements = [] # @type var elements: Array[AST::expression]
|
|
77
|
+
consume_newlines
|
|
78
|
+
elements << parse_expression
|
|
79
|
+
append_expression_list(elements, end_token)
|
|
80
|
+
elements
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def parse_expression_list_until_with_first(end_token, first_element)
|
|
84
|
+
elements = [] # @type var elements: Array[AST::expression]
|
|
85
|
+
elements << first_element
|
|
86
|
+
consume_newlines
|
|
87
|
+
append_expression_list(elements, end_token)
|
|
88
|
+
elements
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def parse_object_pair(key)
|
|
92
|
+
consume(TokenType::COLON, "Expected ':' after object key.")
|
|
93
|
+
consume_newlines
|
|
94
|
+
value = parse_expression
|
|
95
|
+
[key, value]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def append_expression_list(elements, end_token)
|
|
99
|
+
while match?(TokenType::COMMA)
|
|
100
|
+
advance
|
|
101
|
+
consume_newlines
|
|
102
|
+
break if match?(end_token)
|
|
103
|
+
|
|
104
|
+
elements << parse_expression
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def append_object_pairs(pairs)
|
|
109
|
+
while match?(TokenType::COMMA)
|
|
110
|
+
advance
|
|
111
|
+
consume_newlines
|
|
112
|
+
break if rbrace_token?
|
|
113
|
+
|
|
114
|
+
pairs << parse_object_pair(parse_expression)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def parse_array_comprehension(start_token, term)
|
|
119
|
+
parse_comprehension(
|
|
120
|
+
start_token,
|
|
121
|
+
term,
|
|
122
|
+
TokenType::RBRACKET,
|
|
123
|
+
["Expected '|' after array term.", "Expected ']' after array comprehension."],
|
|
124
|
+
AST::ArrayComprehension
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def parse_object_comprehension(start_token, key, value)
|
|
129
|
+
parse_comprehension(
|
|
130
|
+
start_token,
|
|
131
|
+
[key, value],
|
|
132
|
+
TokenType::RBRACE,
|
|
133
|
+
["Expected '|' after object term.", "Expected '}' after object comprehension."],
|
|
134
|
+
AST::ObjectComprehension
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def parse_set_comprehension(start_token, term)
|
|
139
|
+
parse_comprehension(
|
|
140
|
+
start_token,
|
|
141
|
+
term,
|
|
142
|
+
TokenType::RBRACE,
|
|
143
|
+
["Expected '|' after set term.", "Expected '}' after set comprehension."],
|
|
144
|
+
AST::SetComprehension
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# :reek:LongParameterList
|
|
149
|
+
def parse_comprehension(start_token, term, end_token, messages, node_class)
|
|
150
|
+
pipe_message, end_message = messages
|
|
151
|
+
consume(TokenType::PIPE, pipe_message)
|
|
152
|
+
body = parse_query(end_token, newline_delimiter: true)
|
|
153
|
+
consume(end_token, end_message)
|
|
154
|
+
node_class.new(term: term, body: body, location: start_token.location)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def empty_set?(first_element)
|
|
158
|
+
rbrace_token? && !first_element
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def empty_object_literal(start_token)
|
|
162
|
+
advance
|
|
163
|
+
AST::ObjectLiteral.new(pairs: [], location: start_token.location)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def empty_set_literal(start_token)
|
|
167
|
+
advance
|
|
168
|
+
AST::SetLiteral.new(elements: [], location: start_token.location)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
# rubocop:enable Metrics/ClassLength
|
|
172
|
+
end
|
|
173
|
+
end
|