rox-rollout 0.1.3
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/.circleci/config.yml +45 -0
- data/.gitignore +8 -0
- data/Gemfile +6 -0
- data/LICENSE +50 -0
- data/README_DEVELOP.md +19 -0
- data/Rakefile +16 -0
- data/_archive/.document +5 -0
- data/_archive/.rspec +1 -0
- data/_archive/Gemfile +15 -0
- data/_archive/Gemfile.lock +87 -0
- data/_archive/README.md +32 -0
- data/_archive/README.rdoc +19 -0
- data/_archive/Rakefile +50 -0
- data/_archive/lib/expr_function_definition.rb +52 -0
- data/_archive/lib/function_definition.rb +48 -0
- data/_archive/lib/function_token.rb +12 -0
- data/_archive/lib/object_extends.rb +12 -0
- data/_archive/lib/ruby_interpreter.rb +292 -0
- data/_archive/lib/stack.rb +48 -0
- data/_archive/lib/string_extends.rb +14 -0
- data/_archive/spec/ruby_interpreter_spec.rb +203 -0
- data/_archive/spec/spec_helper.rb +30 -0
- data/_archive/spec/stack_spec.rb +77 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/e2e/container.rb +38 -0
- data/e2e/custom_props.rb +55 -0
- data/e2e/rox_e2e_test.rb +159 -0
- data/e2e/test_vars.rb +24 -0
- data/lib/rox.rb +5 -0
- data/lib/rox/core/client/buid.rb +82 -0
- data/lib/rox/core/client/device_properties.rb +45 -0
- data/lib/rox/core/client/internal_flags.rb +20 -0
- data/lib/rox/core/client/sdk_settings.rb +5 -0
- data/lib/rox/core/configuration/configuration.rb +5 -0
- data/lib/rox/core/configuration/configuration_fetched_args.rb +23 -0
- data/lib/rox/core/configuration/configuration_fetched_invoker.rb +37 -0
- data/lib/rox/core/configuration/configuration_parser.rb +85 -0
- data/lib/rox/core/configuration/fetcher_error.rb +13 -0
- data/lib/rox/core/configuration/fetcher_status.rb +10 -0
- data/lib/rox/core/configuration/models/experiment_model.rb +5 -0
- data/lib/rox/core/configuration/models/target_group_model.rb +5 -0
- data/lib/rox/core/consts/build.rb +8 -0
- data/lib/rox/core/consts/environment.rb +42 -0
- data/lib/rox/core/consts/property_type.rb +29 -0
- data/lib/rox/core/context/merged_context.rb +16 -0
- data/lib/rox/core/core.rb +131 -0
- data/lib/rox/core/entities/flag.rb +26 -0
- data/lib/rox/core/entities/flag_setter.rb +39 -0
- data/lib/rox/core/entities/variant.rb +56 -0
- data/lib/rox/core/impression/impression_args.rb +5 -0
- data/lib/rox/core/impression/impression_invoker.rb +41 -0
- data/lib/rox/core/impression/models/experiment.rb +14 -0
- data/lib/rox/core/impression/models/reporting_value.rb +5 -0
- data/lib/rox/core/logging/logging.rb +17 -0
- data/lib/rox/core/logging/no_op_logger.rb +11 -0
- data/lib/rox/core/network/configuration_fetch_result.rb +5 -0
- data/lib/rox/core/network/configuration_fetcher.rb +38 -0
- data/lib/rox/core/network/configuration_fetcher_base.rb +25 -0
- data/lib/rox/core/network/configuration_fetcher_roxy.rb +29 -0
- data/lib/rox/core/network/configuration_source.rb +9 -0
- data/lib/rox/core/network/request.rb +46 -0
- data/lib/rox/core/network/request_configuration_builder.rb +48 -0
- data/lib/rox/core/network/request_data.rb +5 -0
- data/lib/rox/core/network/response.rb +16 -0
- data/lib/rox/core/properties/custom_property.rb +18 -0
- data/lib/rox/core/properties/custom_property_type.rb +18 -0
- data/lib/rox/core/properties/device_property.rb +11 -0
- data/lib/rox/core/register/registerer.rb +35 -0
- data/lib/rox/core/reporting/error_reporter.rb +152 -0
- data/lib/rox/core/repositories/custom_property_repository.rb +61 -0
- data/lib/rox/core/repositories/experiment_repository.rb +21 -0
- data/lib/rox/core/repositories/flag_repository.rb +49 -0
- data/lib/rox/core/repositories/roxx/experiments_extensions.rb +82 -0
- data/lib/rox/core/repositories/roxx/properties_extensions.rb +26 -0
- data/lib/rox/core/repositories/target_group_repository.rb +17 -0
- data/lib/rox/core/roxx/core_stack.rb +22 -0
- data/lib/rox/core/roxx/evaluation_result.rb +28 -0
- data/lib/rox/core/roxx/node.rb +11 -0
- data/lib/rox/core/roxx/parser.rb +143 -0
- data/lib/rox/core/roxx/regular_expression_extensions.rb +33 -0
- data/lib/rox/core/roxx/string_tokenizer.rb +68 -0
- data/lib/rox/core/roxx/symbols.rb +14 -0
- data/lib/rox/core/roxx/token_type.rb +30 -0
- data/lib/rox/core/roxx/tokenized_expression.rb +119 -0
- data/lib/rox/core/roxx/value_compare_extensions.rb +137 -0
- data/lib/rox/core/security/signature_verifier.rb +18 -0
- data/lib/rox/core/utils/periodic_task.rb +12 -0
- data/lib/rox/core/utils/type_utils.rb +9 -0
- data/lib/rox/server/client/sdk_settings.rb +5 -0
- data/lib/rox/server/client/server_properties.rb +20 -0
- data/lib/rox/server/flags/rox_flag.rb +19 -0
- data/lib/rox/server/flags/rox_variant.rb +8 -0
- data/lib/rox/server/logging/server_logger.rb +35 -0
- data/lib/rox/server/rox_options.rb +27 -0
- data/lib/rox/server/rox_server.rb +83 -0
- data/lib/rox/version.rb +3 -0
- data/rox.gemspec +29 -0
- metadata +184 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'rox/core/roxx/token_type'
|
|
2
|
+
|
|
3
|
+
module Rox
|
|
4
|
+
module Core
|
|
5
|
+
class PropertiesExtensions
|
|
6
|
+
def initialize(parser, properties_repository)
|
|
7
|
+
@parser = parser
|
|
8
|
+
@properties_repository = properties_repository
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def extend
|
|
12
|
+
@parser.add_operator('property') do |parser, stack, context|
|
|
13
|
+
prop_name = stack.pop.to_s
|
|
14
|
+
property = @properties_repository.custom_property(prop_name)
|
|
15
|
+
|
|
16
|
+
if property.nil?
|
|
17
|
+
stack.push(TokenType::UNDEFINED)
|
|
18
|
+
else
|
|
19
|
+
value = property.value(context)
|
|
20
|
+
stack.push(value.nil? ? TokenType::UNDEFINED : value)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Rox
|
|
2
|
+
module Core
|
|
3
|
+
class TargetGroupRepository
|
|
4
|
+
def initialize
|
|
5
|
+
@target_groups = []
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def target_groups=(target_groups)
|
|
9
|
+
@target_groups = target_groups
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def target_group(id)
|
|
13
|
+
@target_groups.detect { |g| g.id == id }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'rox/core/entities/flag'
|
|
2
|
+
require 'rox/core/utils/type_utils'
|
|
3
|
+
|
|
4
|
+
module Rox
|
|
5
|
+
module Core
|
|
6
|
+
class EvaluationResult
|
|
7
|
+
attr_reader :value
|
|
8
|
+
|
|
9
|
+
def initialize(value)
|
|
10
|
+
@value = value
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def bool_value
|
|
14
|
+
return false if value.nil?
|
|
15
|
+
return value if Utils.boolean?(value)
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def string_value
|
|
20
|
+
return value if value.is_a? String
|
|
21
|
+
return nil if value.nil?
|
|
22
|
+
return Flag::FLAG_TRUE_VALUE if value
|
|
23
|
+
return Flag::FLAG_FALSE_VALUE unless value
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
require 'digest'
|
|
2
|
+
require 'rox/core/roxx/core_stack'
|
|
3
|
+
require 'rox/core/roxx/evaluation_result'
|
|
4
|
+
require 'rox/core/roxx/value_compare_extensions'
|
|
5
|
+
require 'rox/core/roxx/regular_expression_extensions'
|
|
6
|
+
require 'rox/core/roxx/tokenized_expression'
|
|
7
|
+
require 'rox/core/utils/type_utils'
|
|
8
|
+
require 'rox/core/logging/logging'
|
|
9
|
+
|
|
10
|
+
module Rox
|
|
11
|
+
module Core
|
|
12
|
+
class Parser
|
|
13
|
+
def initialize
|
|
14
|
+
@operators_map = {}
|
|
15
|
+
set_basic_operators
|
|
16
|
+
ValueCompareExtensions.new(self).extend
|
|
17
|
+
RegularExpressionExtensions.new(self).extend
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_operator(oper, &block)
|
|
21
|
+
@operators_map[oper] = block
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def evaluate_expression(expression, context = nil)
|
|
25
|
+
stack = CoreStack.new
|
|
26
|
+
tokens = TokenizedExpression.new(expression, @operators_map.keys).tokens
|
|
27
|
+
reverse_tokens = tokens.reverse
|
|
28
|
+
begin
|
|
29
|
+
reverse_tokens.each do |token|
|
|
30
|
+
node = token
|
|
31
|
+
if node.type == NodeTypes::RAND
|
|
32
|
+
stack.push(node.value)
|
|
33
|
+
elsif node.type == NodeTypes::RATOR
|
|
34
|
+
handler = @operators_map[node.value]
|
|
35
|
+
handler.call(self, stack, context) unless handler.nil?
|
|
36
|
+
else
|
|
37
|
+
return EvaluationResult.new(nil)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
result = stack.pop
|
|
42
|
+
result = nil if result == TokenType::UNDEFINED
|
|
43
|
+
|
|
44
|
+
EvaluationResult.new(result)
|
|
45
|
+
rescue StandardError => ex
|
|
46
|
+
Logging.logger.warn("Roxx Exception: Failed evaluate expression: #{ex}")
|
|
47
|
+
EvaluationResult.new(nil)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def set_basic_operators
|
|
52
|
+
add_operator('isUndefined') do |parser, stack, context|
|
|
53
|
+
op1 = stack.pop
|
|
54
|
+
if op1.is_a?(TokenType)
|
|
55
|
+
stack.push(op1 == TokenType::UNDEFINED)
|
|
56
|
+
else
|
|
57
|
+
stack.push(false)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
add_operator('now') do |parser, stack, context|
|
|
62
|
+
stack.push((Time.now.to_f * 1000).to_i)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
add_operator('and') do |parser, stack, context|
|
|
66
|
+
op1 = stack.pop
|
|
67
|
+
op2 = stack.pop
|
|
68
|
+
op1 = false if op1 == TokenType::UNDEFINED
|
|
69
|
+
op2 = false if op2 == TokenType::UNDEFINED
|
|
70
|
+
raise ArgumentError, 'should be boolean' unless Utils.boolean?(op1) && Utils.boolean?(op2)
|
|
71
|
+
stack.push(op1 && op2)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
add_operator('or') do |parser, stack, context|
|
|
75
|
+
op1 = stack.pop
|
|
76
|
+
op2 = stack.pop
|
|
77
|
+
op1 = false if op1 == TokenType::UNDEFINED
|
|
78
|
+
op2 = false if op2 == TokenType::UNDEFINED
|
|
79
|
+
raise ArgumentError, 'should be boolean' unless Utils.boolean?(op1) && Utils.boolean?(op2)
|
|
80
|
+
stack.push(op1 || op2)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
add_operator('ne') do |parser, stack, context|
|
|
84
|
+
op1 = stack.pop
|
|
85
|
+
op2 = stack.pop
|
|
86
|
+
op1 = false if op1 == TokenType::UNDEFINED
|
|
87
|
+
op2 = false if op2 == TokenType::UNDEFINED
|
|
88
|
+
stack.push(op1 != op2)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
add_operator('eq') do |parser, stack, context|
|
|
92
|
+
op1 = stack.pop
|
|
93
|
+
op2 = stack.pop
|
|
94
|
+
op1 = false if op1 == TokenType::UNDEFINED
|
|
95
|
+
op2 = false if op2 == TokenType::UNDEFINED
|
|
96
|
+
stack.push(op1 == op2)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
add_operator('not') do |parser, stack, context|
|
|
100
|
+
op1 = stack.pop
|
|
101
|
+
op1 = false if op1 == TokenType::UNDEFINED
|
|
102
|
+
raise ArgumentError, 'should be boolean' unless Utils.boolean?(op1)
|
|
103
|
+
stack.push(!op1)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
add_operator('ifThen') do |parser, stack, context|
|
|
107
|
+
condition_expression = stack.pop
|
|
108
|
+
true_expression = stack.pop
|
|
109
|
+
false_expression = stack.pop
|
|
110
|
+
raise ArgumentError, 'should be boolean' unless Utils.boolean?(condition_expression)
|
|
111
|
+
if condition_expression
|
|
112
|
+
stack.push(true_expression)
|
|
113
|
+
else
|
|
114
|
+
stack.push(false_expression)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
add_operator('inArray') do |parser, stack, context|
|
|
119
|
+
op1 = stack.pop
|
|
120
|
+
op2 = stack.pop
|
|
121
|
+
if op2.is_a?(Array)
|
|
122
|
+
stack.push(op2.include?(op1))
|
|
123
|
+
else
|
|
124
|
+
stack.push(false)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
add_operator('md5') do |parser, stack, context|
|
|
129
|
+
op1 = stack.pop
|
|
130
|
+
raise ArgumentError, 'should be string' unless op1.is_a?(String)
|
|
131
|
+
stack.push(Digest::MD5.hexdigest(op1))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
add_operator('concat') do |parser, stack, context|
|
|
135
|
+
op1 = stack.pop
|
|
136
|
+
op2 = stack.pop
|
|
137
|
+
raise ArgumentError, 'should be string' unless op1.is_a?(String) && op2.is_a?(String)
|
|
138
|
+
stack.push("#{op1}#{op2}")
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Rox
|
|
2
|
+
module Core
|
|
3
|
+
class RegularExpressionExtensions
|
|
4
|
+
def initialize(parser)
|
|
5
|
+
@parser = parser
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def extend
|
|
9
|
+
@parser.add_operator('match') do |parser, stack, context|
|
|
10
|
+
text = stack.pop
|
|
11
|
+
pattern = stack.pop
|
|
12
|
+
flags = stack.pop
|
|
13
|
+
raise ArgumentError, 'should be string' unless text.is_a?(String) && pattern.is_a?(String) && flags.is_a?(String)
|
|
14
|
+
|
|
15
|
+
options = 0
|
|
16
|
+
flags.each_char do |flag|
|
|
17
|
+
case flag
|
|
18
|
+
when 'i'
|
|
19
|
+
options |= Regexp::IGNORECASE
|
|
20
|
+
when 'x'
|
|
21
|
+
options |= Regexp::EXTENDED
|
|
22
|
+
when 'm'
|
|
23
|
+
options |= Regexp::MULTILINE
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
matched = !Regexp.new(pattern, options).match(text).nil?
|
|
28
|
+
stack.push(matched)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Rox
|
|
2
|
+
module Core
|
|
3
|
+
class StringTokenizer
|
|
4
|
+
def initialize(text, delim, return_delims)
|
|
5
|
+
@current_position = 0
|
|
6
|
+
@new_position = -1
|
|
7
|
+
@text = text
|
|
8
|
+
@max_position = text.length
|
|
9
|
+
@delimiters = delim
|
|
10
|
+
@ret_delims = return_delims
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def skip_delimiters(start_pos)
|
|
14
|
+
raise ArgumentError, 'delimiters is nil' if @delimiters.nil?
|
|
15
|
+
|
|
16
|
+
position = start_pos
|
|
17
|
+
while !@ret_delims && position < @max_position
|
|
18
|
+
c = @text[position]
|
|
19
|
+
break unless delimiter?(c)
|
|
20
|
+
position += 1
|
|
21
|
+
end
|
|
22
|
+
position
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def scan_token(start_pos)
|
|
26
|
+
position = start_pos
|
|
27
|
+
while position < @max_position
|
|
28
|
+
c = @text[position]
|
|
29
|
+
break if delimiter?(c)
|
|
30
|
+
position += 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if @ret_delims && start_pos == position
|
|
34
|
+
c = @text[position]
|
|
35
|
+
position += 1 if delimiter?(c)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
position
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def delimiter?(char)
|
|
42
|
+
@delimiters.include?(char)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def more_tokens?
|
|
46
|
+
@new_position = skip_delimiters(@current_position)
|
|
47
|
+
@new_position < @max_position
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def next_token(delim = nil)
|
|
51
|
+
delims_changed = false
|
|
52
|
+
unless delim.nil?
|
|
53
|
+
@delimiters = delim
|
|
54
|
+
delims_changed = true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@current_position = @new_position >= 0 && !delims_changed ? @new_position : skip_delimiters(@current_position)
|
|
58
|
+
@new_position = -1
|
|
59
|
+
|
|
60
|
+
raise ArgumentError, 'invalid operation' if @current_position >= @max_position
|
|
61
|
+
|
|
62
|
+
start = @current_position
|
|
63
|
+
@current_position = scan_token(@current_position)
|
|
64
|
+
@text[start...@current_position]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Rox
|
|
2
|
+
module Core
|
|
3
|
+
module Symbols
|
|
4
|
+
ROXX_UNDEFINED = 'undefined'.freeze
|
|
5
|
+
ROXX_TRUE = 'true'.freeze
|
|
6
|
+
ROXX_FALSE = 'false'.freeze
|
|
7
|
+
ROXX_EMPTY_STRING = '""'.freeze
|
|
8
|
+
ROXX_STRING_TYPE = 'StringType'.freeze
|
|
9
|
+
ROXX_BOOL_TYPE = 'BooleanType'.freeze
|
|
10
|
+
ROXX_NUMBER_TYPE = 'NumberType'.freeze
|
|
11
|
+
ROXX_UNDEFINED_TYPE = 'UndefinedType'.freeze
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'rox/core/roxx/symbols'
|
|
2
|
+
|
|
3
|
+
module Rox
|
|
4
|
+
module Core
|
|
5
|
+
class TokenType
|
|
6
|
+
attr_accessor :text, :pattern
|
|
7
|
+
|
|
8
|
+
def self.from_token(token)
|
|
9
|
+
unless token.nil?
|
|
10
|
+
tested_token = token.downcase
|
|
11
|
+
[TokenType::STRING, TokenType::NUMBER, TokenType::BOOLEAN, TokenType::UNDEFINED].each do |token_type|
|
|
12
|
+
return token_type unless token_type.pattern.match(tested_token).nil?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
TokenType::NOT_A_TYPE
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(text, pattern)
|
|
19
|
+
@text = text
|
|
20
|
+
@pattern = Regexp.new(pattern)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
NOT_A_TYPE = TokenType.new('NOT_A_TYPE', '')
|
|
24
|
+
STRING = TokenType.new(Symbols::ROXX_STRING_TYPE, '"((\\\\.)|[^\\\\"])*"')
|
|
25
|
+
NUMBER = TokenType.new(Symbols::ROXX_NUMBER_TYPE, '[\\-]{0,1}\\d+[\\.]\\d+|[\\-]{0,1}\\d+')
|
|
26
|
+
BOOLEAN = TokenType.new(Symbols::ROXX_BOOL_TYPE, "#{Symbols::ROXX_TRUE}|#{Symbols::ROXX_FALSE}")
|
|
27
|
+
UNDEFINED = TokenType.new(Symbols::ROXX_UNDEFINED_TYPE, Symbols::ROXX_UNDEFINED)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require 'rox/core/roxx/string_tokenizer'
|
|
2
|
+
require 'rox/core/roxx/node'
|
|
3
|
+
require 'rox/core/roxx/symbols'
|
|
4
|
+
require 'rox/core/roxx/token_type'
|
|
5
|
+
|
|
6
|
+
module Rox
|
|
7
|
+
module Core
|
|
8
|
+
class TokenizedExpression
|
|
9
|
+
DICT_START_DELIMITER = '{'.freeze
|
|
10
|
+
DICT_END_DELIMITER = '}'.freeze
|
|
11
|
+
ARRAY_START_DELIMITER = '['.freeze
|
|
12
|
+
ARRAY_END_DELIMITER = ']'.freeze
|
|
13
|
+
TOKEN_DELIMITERS = "{}[]():, \t\r\n\"".freeze
|
|
14
|
+
PRE_POST_STRING_CHAR = ''.freeze
|
|
15
|
+
STRING_DELIMITER = '"'.freeze
|
|
16
|
+
ESCAPED_QUOTE = '\\"'.freeze
|
|
17
|
+
ESCAPED_QUOTE_PLACEHOLDER = '\\RO_Q'.freeze
|
|
18
|
+
|
|
19
|
+
def initialize(expression, operators)
|
|
20
|
+
@expression = expression
|
|
21
|
+
@operators = operators
|
|
22
|
+
@result_list = nil
|
|
23
|
+
@array_accumulator = nil
|
|
24
|
+
@dict_accumulator = nil
|
|
25
|
+
@dict_key = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def tokens
|
|
29
|
+
tokenize(@expression)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def push_node(node)
|
|
33
|
+
if !@dict_accumulator.nil? && @dict_key.nil?
|
|
34
|
+
@dict_key = node.value.to_s
|
|
35
|
+
elsif !@dict_accumulator.nil? && !@dict_key.nil?
|
|
36
|
+
@dict_accumulator[@dict_key] = node.value
|
|
37
|
+
@dict_key = nil
|
|
38
|
+
elsif !@array_accumulator.nil?
|
|
39
|
+
@array_accumulator << node.value
|
|
40
|
+
else
|
|
41
|
+
@result_list << node
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def tokenize(expression)
|
|
46
|
+
@result_list = []
|
|
47
|
+
@array_accumulator = nil
|
|
48
|
+
@dict_accumulator = nil
|
|
49
|
+
@dict_key = nil
|
|
50
|
+
|
|
51
|
+
delimiters_to_use = TokenizedExpression::TOKEN_DELIMITERS
|
|
52
|
+
normalized_expression = expression.gsub(TokenizedExpression::ESCAPED_QUOTE, TokenizedExpression::ESCAPED_QUOTE_PLACEHOLDER)
|
|
53
|
+
tokenizer = StringTokenizer.new(normalized_expression, delimiters_to_use, true)
|
|
54
|
+
|
|
55
|
+
token = nil
|
|
56
|
+
while tokenizer.more_tokens?
|
|
57
|
+
prev_token = token
|
|
58
|
+
token = tokenizer.next_token(delimiters_to_use)
|
|
59
|
+
in_string = delimiters_to_use == TokenizedExpression::STRING_DELIMITER
|
|
60
|
+
|
|
61
|
+
if !in_string && token == TokenizedExpression::DICT_START_DELIMITER
|
|
62
|
+
@dict_accumulator = {}
|
|
63
|
+
elsif !in_string && token == TokenizedExpression::DICT_END_DELIMITER
|
|
64
|
+
dict_result = @dict_accumulator
|
|
65
|
+
@dict_accumulator = nil
|
|
66
|
+
push_node(node_from_dict(dict_result))
|
|
67
|
+
elsif !in_string && token == TokenizedExpression::ARRAY_START_DELIMITER
|
|
68
|
+
@array_accumulator = []
|
|
69
|
+
elsif !in_string && token == TokenizedExpression::ARRAY_END_DELIMITER
|
|
70
|
+
array_result = @array_accumulator
|
|
71
|
+
@array_accumulator = nil
|
|
72
|
+
push_node(node_from_array(array_result))
|
|
73
|
+
elsif token == TokenizedExpression::STRING_DELIMITER
|
|
74
|
+
push_node(node_from_token(Symbols::ROXX_EMPTY_STRING)) if prev_token == TokenizedExpression::STRING_DELIMITER
|
|
75
|
+
delimiters_to_use = in_string ? TokenizedExpression::TOKEN_DELIMITERS : TokenizedExpression::STRING_DELIMITER
|
|
76
|
+
else
|
|
77
|
+
if delimiters_to_use == TokenizedExpression::STRING_DELIMITER
|
|
78
|
+
push_node(Node.new(NodeTypes::RAND, token.gsub(TokenizedExpression::ESCAPED_QUOTE_PLACEHOLDER, TokenizedExpression::ESCAPED_QUOTE)))
|
|
79
|
+
elsif !TokenizedExpression::TOKEN_DELIMITERS.include?(token) && token != TokenizedExpression::PRE_POST_STRING_CHAR
|
|
80
|
+
push_node(node_from_token(token))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
@result_list
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def node_from_array(items)
|
|
89
|
+
Node.new(NodeTypes::RAND, items)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def node_from_dict(items)
|
|
93
|
+
Node.new(NodeTypes::RAND, items)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def node_from_token(token)
|
|
97
|
+
return Node.new(NodeTypes::RATOR, token) if @operators.include?(token)
|
|
98
|
+
return Node.new(NodeTypes::RAND, true) if token == Symbols::ROXX_TRUE
|
|
99
|
+
return Node.new(NodeTypes::RAND, false) if token == Symbols::ROXX_FALSE
|
|
100
|
+
return Node.new(NodeTypes::RAND, TokenType::UNDEFINED) if token == Symbols::ROXX_UNDEFINED
|
|
101
|
+
|
|
102
|
+
token_type = TokenType.from_token(token)
|
|
103
|
+
return Node.new(NodeTypes::RAND, token[1...-1]) if token_type == TokenType::STRING
|
|
104
|
+
if token_type == TokenType::NUMBER
|
|
105
|
+
begin
|
|
106
|
+
return Node.new(NodeTypes::RAND, Integer(token))
|
|
107
|
+
rescue ArgumentError
|
|
108
|
+
begin
|
|
109
|
+
return Node.new(NodeTypes::RAND, Float(token))
|
|
110
|
+
rescue ArgumentError => ex
|
|
111
|
+
raise ArgumentError, "Excepted Number, got '#{token}' (#{token_type}): #{ex}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
Node.new(NodeTypes::UNKNOWN, nil)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|