rox-rollout 4.1.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +51 -23
  3. data/.editorconfig +12 -0
  4. data/.github/workflows/ruby.yml +20 -0
  5. data/.gitignore +2 -0
  6. data/.rubocop.yml +17 -0
  7. data/Gemfile +2 -2
  8. data/README.md +32 -0
  9. data/Rakefile +12 -9
  10. data/bin/console +3 -4
  11. data/e2e-server/run_server.sh +12 -0
  12. data/e2e-server/server.rb +158 -0
  13. data/e2e/container.rb +11 -14
  14. data/e2e/custom_props.rb +9 -9
  15. data/e2e/rox_e2e_test.rb +18 -20
  16. data/e2e/test_vars.rb +3 -6
  17. data/example/local.rb +40 -0
  18. data/lib/rox/core/analytics.rb +18 -0
  19. data/lib/rox/core/client/buid.rb +9 -44
  20. data/lib/rox/core/client/device_properties.rb +8 -2
  21. data/lib/rox/core/client/dynamic_api.rb +53 -15
  22. data/lib/rox/core/client/internal_flags.rb +14 -2
  23. data/lib/rox/core/client/sdk_settings.rb +1 -1
  24. data/lib/rox/core/configuration/configuration.rb +1 -1
  25. data/lib/rox/core/configuration/configuration_fetched_args.rb +2 -2
  26. data/lib/rox/core/configuration/configuration_fetched_invoker.rb +8 -3
  27. data/lib/rox/core/configuration/configuration_parser.rb +39 -38
  28. data/lib/rox/core/configuration/fetcher_error.rb +1 -1
  29. data/lib/rox/core/configuration/fetcher_status.rb +1 -1
  30. data/lib/rox/core/configuration/models/experiment_model.rb +1 -1
  31. data/lib/rox/core/configuration/models/target_group_model.rb +1 -1
  32. data/lib/rox/core/consts/build.rb +3 -3
  33. data/lib/rox/core/consts/environment.rb +34 -10
  34. data/lib/rox/core/consts/property_type.rb +18 -17
  35. data/lib/rox/core/context/merged_context.rb +2 -1
  36. data/lib/rox/core/core.rb +101 -33
  37. data/lib/rox/core/entities/default_flag_values.rb +10 -0
  38. data/lib/rox/core/entities/flag.rb +22 -7
  39. data/lib/rox/core/entities/flag_setter.rb +6 -6
  40. data/lib/rox/core/entities/rox_double.rb +11 -0
  41. data/lib/rox/core/entities/rox_int.rb +11 -0
  42. data/lib/rox/core/entities/{variant.rb → rox_string.rb} +20 -13
  43. data/lib/rox/core/error_handling/exception_trigger.rb +10 -0
  44. data/lib/rox/core/error_handling/userspace_handler_exception.rb +12 -0
  45. data/lib/rox/core/error_handling/userspace_unhandled_error_invoker.rb +41 -0
  46. data/lib/rox/core/impression/impression_args.rb +2 -2
  47. data/lib/rox/core/impression/impression_invoker.rb +41 -7
  48. data/lib/rox/core/impression/models/experiment.rb +1 -1
  49. data/lib/rox/core/impression/models/reporting_value.rb +10 -2
  50. data/lib/rox/core/logging/logging.rb +3 -3
  51. data/lib/rox/core/logging/no_op_logger.rb +1 -1
  52. data/lib/rox/core/network/configuration_fetch_result.rb +1 -1
  53. data/lib/rox/core/network/configuration_fetcher.rb +9 -8
  54. data/lib/rox/core/network/configuration_fetcher_base.rb +1 -1
  55. data/lib/rox/core/network/configuration_fetcher_roxy.rb +4 -4
  56. data/lib/rox/core/network/configuration_fetcher_self_managed.rb +30 -0
  57. data/lib/rox/core/network/configuration_source.rb +1 -1
  58. data/lib/rox/core/network/request.rb +3 -3
  59. data/lib/rox/core/network/request_configuration_builder.rb +12 -9
  60. data/lib/rox/core/network/request_data.rb +1 -1
  61. data/lib/rox/core/network/response.rb +20 -4
  62. data/lib/rox/core/network/state_sender.rb +146 -0
  63. data/lib/rox/core/notifications/notification_listener.rb +4 -4
  64. data/lib/rox/core/properties/custom_property.rb +1 -1
  65. data/lib/rox/core/properties/custom_property_type.rb +1 -1
  66. data/lib/rox/core/properties/device_property.rb +2 -2
  67. data/lib/rox/core/properties/property_factory.rb +132 -0
  68. data/lib/rox/core/register/registerer.rb +13 -12
  69. data/lib/rox/core/reporting/error_reporter.rb +14 -13
  70. data/lib/rox/core/repositories/custom_property_repository.rb +1 -1
  71. data/lib/rox/core/repositories/experiment_repository.rb +2 -4
  72. data/lib/rox/core/repositories/flag_repository.rb +8 -8
  73. data/lib/rox/core/repositories/roxx/experiments_extensions.rb +8 -8
  74. data/lib/rox/core/repositories/roxx/properties_extensions.rb +32 -7
  75. data/lib/rox/core/repositories/target_group_repository.rb +2 -4
  76. data/lib/rox/core/roxx/evaluation_result.rb +2 -0
  77. data/lib/rox/core/roxx/node.rb +1 -1
  78. data/lib/rox/core/roxx/parser.rb +46 -20
  79. data/lib/rox/core/roxx/regular_expression_extensions.rb +6 -3
  80. data/lib/rox/core/roxx/string_tokenizer.rb +3 -1
  81. data/lib/rox/core/roxx/symbols.rb +1 -1
  82. data/lib/rox/core/roxx/token_type.rb +1 -1
  83. data/lib/rox/core/roxx/tokenized_expression.rb +16 -12
  84. data/lib/rox/core/roxx/value_compare_extensions.rb +49 -29
  85. data/lib/rox/core/security/signature_verifier.rb +3 -3
  86. data/lib/rox/core/security/signature_verifier_mock.rb +12 -0
  87. data/lib/rox/core/utils/debouncer.rb +18 -0
  88. data/lib/rox/core/utils/type_utils.rb +1 -1
  89. data/lib/rox/server/client/sdk_settings.rb +1 -1
  90. data/lib/rox/server/client/server_properties.rb +1 -1
  91. data/lib/rox/server/flags/normalize_flag_type.rb +25 -0
  92. data/lib/rox/server/flags/rox_double.rb +8 -0
  93. data/lib/rox/server/flags/rox_flag.rb +1 -1
  94. data/lib/rox/server/flags/rox_int.rb +8 -0
  95. data/lib/rox/server/flags/rox_string.rb +8 -0
  96. data/lib/rox/server/flags/server_entities_provider.rb +14 -4
  97. data/lib/rox/server/logging/server_logger.rb +2 -2
  98. data/lib/rox/server/rox_options.rb +39 -8
  99. data/lib/rox/server/rox_server.rb +81 -60
  100. data/lib/rox/version.rb +1 -1
  101. data/rox.gemspec +13 -9
  102. metadata +95 -36
  103. data/README_DEVELOP.md +0 -19
  104. data/_archive/.document +0 -5
  105. data/_archive/.rspec +0 -1
  106. data/_archive/Gemfile +0 -15
  107. data/_archive/Gemfile.lock +0 -87
  108. data/_archive/README.md +0 -32
  109. data/_archive/README.rdoc +0 -19
  110. data/_archive/Rakefile +0 -50
  111. data/_archive/lib/expr_function_definition.rb +0 -52
  112. data/_archive/lib/function_definition.rb +0 -48
  113. data/_archive/lib/function_token.rb +0 -12
  114. data/_archive/lib/object_extends.rb +0 -12
  115. data/_archive/lib/ruby_interpreter.rb +0 -292
  116. data/_archive/lib/stack.rb +0 -48
  117. data/_archive/lib/string_extends.rb +0 -14
  118. data/_archive/spec/ruby_interpreter_spec.rb +0 -203
  119. data/_archive/spec/spec_helper.rb +0 -30
  120. data/_archive/spec/stack_spec.rb +0 -77
  121. data/lib/rox/server/flags/rox_variant.rb +0 -8
@@ -7,25 +7,26 @@ module Rox
7
7
  @mutex = Mutex.new
8
8
  end
9
9
 
10
- def register_instance(container, ns)
11
- raise ArgumentError, 'A namespace cannot be null' if ns.nil?
10
+ def register_instance(container, namespace)
11
+ raise ArgumentError, 'A namespace cannot be null' if namespace.nil?
12
12
 
13
13
  @mutex.synchronize do
14
- raise ArgumentError, "A container with the given namespace (#{ns}) has already been registered" if @namespaces.include?(ns)
14
+ if @namespaces.include?(namespace)
15
+ raise ArgumentError,
16
+ "A container with the given namespace (#{namespace}) has already been registered"
17
+ end
15
18
  end
16
19
 
17
- @namespaces << ns
20
+ @namespaces << namespace
18
21
 
19
- container.instance_variables().each do |attribute_name|
22
+ container.instance_variables.each do |attribute_name|
20
23
  begin
21
24
  value = container.instance_variable_get(attribute_name)
22
- if value != nil && value.is_a?(Variant)
23
- var_name = attribute_name.to_s();
24
- # removing the attribute starting @ (if [always] exists)
25
- if (var_name[0] == '@')
26
- var_name[0] = '';
27
- end
28
- @flag_repository.add_flag(value, ns == '' ? var_name : "#{ns}.#{var_name}")
25
+ if !value.nil? && value.is_a?(RoxString)
26
+ var_name = attribute_name.to_s
27
+ # removing the attribute starting @ (if [always] exists)
28
+ var_name[0] = '' if var_name[0] == '@'
29
+ @flag_repository.add_flag(value, namespace == '' ? var_name : "#{namespace}.#{var_name}")
29
30
  end
30
31
  rescue StandardError
31
32
  next
@@ -13,6 +13,7 @@ module Rox
13
13
 
14
14
  def report(message, ex)
15
15
  return if @device_properties.rollout_environment == 'LOCAL'
16
+ return if @device_properties.rox_options.self_managed?
16
17
 
17
18
  Logging.logger.error("Error report: #{message}", ex)
18
19
 
@@ -33,8 +34,8 @@ module Rox
33
34
  begin
34
35
  @request.send_post(ErrorReporter::BUGSNAG_NOTIFY_URL, payload)
35
36
  Logging.logger.debug('Bugsnag error report was sent')
36
- rescue StandardError => ex
37
- Logging.logger.error('Failed to send bugsnag error ', ex)
37
+ rescue StandardError => e
38
+ Logging.logger.error('Failed to send bugsnag error ', e)
38
39
  end
39
40
  end
40
41
 
@@ -49,13 +50,13 @@ module Rox
49
50
 
50
51
  def add_metadata(message, ev)
51
52
  inner_data = {
52
- 'message' => message,
53
- 'deviceId' => @device_properties.distinct_id,
54
- 'buid' => @buid.to_s
53
+ 'message' => message,
54
+ 'deviceId' => @device_properties.distinct_id,
55
+ 'buid' => @buid.to_s
55
56
  }
56
57
 
57
58
  metadata = {
58
- 'data' => inner_data
59
+ 'data' => inner_data
59
60
  }
60
61
 
61
62
  ev['metaData'] = metadata
@@ -88,15 +89,15 @@ module Rox
88
89
 
89
90
  def add_notifier(payload)
90
91
  notifier = {
91
- 'name' => 'Rollout Ruby SDK',
92
- 'version' => @device_properties.lib_version
92
+ 'name' => 'Rollout Ruby SDK',
93
+ 'version' => @device_properties.lib_version
93
94
  }
94
95
  payload['notifier'] = notifier
95
96
  end
96
97
 
97
98
  def add_user(id, rollout_key, ev)
98
99
  user = {
99
- id => rollout_key
100
+ id => rollout_key
100
101
  }
101
102
  ev['user'] = user
102
103
  end
@@ -121,13 +122,13 @@ module Rox
121
122
 
122
123
  def add_app(ev)
123
124
  app = {
124
- 'releaseStage' => @device_properties.rollout_environment,
125
- 'version' => @device_properties.lib_version
125
+ 'releaseStage' => @device_properties.rollout_environment,
126
+ 'version' => @device_properties.lib_version
126
127
  }
127
128
  ev['app'] = app
128
129
  end
129
130
 
130
- STACK_TRACE_LINE_REGEX = /^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$/
131
+ STACK_TRACE_LINE_REGEX = /^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$/.freeze
131
132
 
132
133
  def parse_stack_trace(stack_trace)
133
134
  stack = []
@@ -149,4 +150,4 @@ module Rox
149
150
  end
150
151
  end
151
152
  end
152
- end
153
+ end
@@ -58,4 +58,4 @@ module Rox
58
58
  end
59
59
  end
60
60
  end
61
- end
61
+ end
@@ -5,9 +5,7 @@ module Rox
5
5
  @experiments = []
6
6
  end
7
7
 
8
- def experiments=(experiments)
9
- @experiments = experiments
10
- end
8
+ attr_writer :experiments
11
9
 
12
10
  def experiment_by_flag(flag_name)
13
11
  @experiments.detect { |e| e.flags.include?(flag_name) }
@@ -18,4 +16,4 @@ module Rox
18
16
  end
19
17
  end
20
18
  end
21
- end
19
+ end
@@ -2,29 +2,29 @@ module Rox
2
2
  module Core
3
3
  class FlagRepository
4
4
  def initialize
5
- @variants = {}
5
+ @strings = {}
6
6
  @flag_added_handlers = []
7
7
  @mutex = Mutex.new
8
8
  @handlers_mutex = Mutex.new
9
9
  end
10
10
 
11
- def add_flag(variant, name)
12
- variant.name = name if variant.name.nil? || variant.name.empty?
11
+ def add_flag(string, name)
12
+ string.name = name if string.name.nil? || string.name.empty?
13
13
  @mutex.synchronize do
14
- @variants[name] = variant
14
+ @strings[name] = string
15
15
  end
16
- raise_flag_added_event(variant)
16
+ raise_flag_added_event(string)
17
17
  end
18
18
 
19
19
  def flag(name)
20
20
  @mutex.synchronize do
21
- return @variants[name]
21
+ return @strings[name]
22
22
  end
23
23
  end
24
24
 
25
25
  def all_flags
26
26
  @mutex.synchronize do
27
- return @variants.values
27
+ return @strings.values
28
28
  end
29
29
  end
30
30
 
@@ -46,4 +46,4 @@ module Rox
46
46
  end
47
47
  end
48
48
  end
49
- end
49
+ end
@@ -11,13 +11,13 @@ module Rox
11
11
  end
12
12
 
13
13
  def extend
14
- @parser.add_operator('mergeSeed') do |parser, stack, context|
14
+ @parser.add_operator('mergeSeed') do |_parser, stack, _context|
15
15
  seed1 = stack.pop
16
16
  seed2 = stack.pop
17
17
  stack.push("#{seed1}.#{seed2}")
18
18
  end
19
19
 
20
- @parser.add_operator('isInPercentage') do |parser, stack, context|
20
+ @parser.add_operator('isInPercentage') do |_parser, stack, _context|
21
21
  percentage = stack.pop
22
22
  seed = stack.pop
23
23
 
@@ -27,7 +27,7 @@ module Rox
27
27
  stack.push(bucket <= percentage)
28
28
  end
29
29
 
30
- @parser.add_operator('isInPercentageRange') do |parser, stack, context|
30
+ @parser.add_operator('isInPercentageRange') do |_parser, stack, _context|
31
31
  percentage_low = stack.pop
32
32
  percentage_high = stack.pop
33
33
  seed = stack.pop
@@ -42,17 +42,17 @@ module Rox
42
42
  @parser.add_operator('flagValue') do |parser, stack, context|
43
43
  feature_flag_identifier = stack.pop
44
44
  result = Flag::FLAG_FALSE_VALUE
45
- variant = @flags_repository.flag(feature_flag_identifier)
45
+ string = @flags_repository.flag(feature_flag_identifier)
46
46
 
47
- if !variant.nil?
48
- result = variant.value(context)
49
- else
47
+ if string.nil?
50
48
  flags_experiment = @experiment_repository.experiment_by_flag(feature_flag_identifier)
51
49
 
52
50
  if !flags_experiment.nil? && !flags_experiment.condition.nil? && !flags_experiment.condition.empty?
53
51
  experiment_eval_result = parser.evaluate_expression(flags_experiment.condition, context).string_value
54
52
  result = experiment_eval_result if !experiment_eval_result.nil? && !experiment_eval_result.empty?
55
53
  end
54
+ else
55
+ result = string.value(context)
56
56
  end
57
57
 
58
58
  stack.push(result)
@@ -79,4 +79,4 @@ module Rox
79
79
  end
80
80
  end
81
81
  end
82
- end
82
+ end
@@ -1,26 +1,51 @@
1
1
  require 'rox/core/roxx/token_type'
2
+ require 'rox/core/error_handling/exception_trigger'
3
+ require 'rox/core/error_handling/userspace_handler_exception'
2
4
 
3
5
  module Rox
4
6
  module Core
5
7
  class PropertiesExtensions
6
- def initialize(parser, properties_repository)
8
+ def initialize(parser, properties_repository, dynamic_property_rule_handler = nil)
7
9
  @parser = parser
8
10
  @properties_repository = properties_repository
11
+ @dynamic_property_rule_handler = dynamic_property_rule_handler
9
12
  end
10
13
 
11
14
  def extend
12
- @parser.add_operator('property') do |parser, stack, context|
15
+ @parser.add_operator('property') do |_parser, stack, context|
13
16
  prop_name = stack.pop.to_s
14
17
  property = @properties_repository.custom_property(prop_name)
18
+ value = get_value(prop_name, property, context)
15
19
 
16
- if property.nil?
17
- stack.push(TokenType::UNDEFINED)
20
+ stack.push(value.nil? ? TokenType::UNDEFINED : value)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def get_value(prop_name, property, context)
27
+ if property.nil?
28
+ if @dynamic_property_rule_handler.nil?
29
+ TokenType::UNDEFINED
18
30
  else
19
- value = property.value(context)
20
- stack.push(value.nil? ? TokenType::UNDEFINED : value)
31
+ get_value_from_dynamic_property_rule_handler(prop_name, context)
21
32
  end
33
+ else
34
+ get_value_from_property(property, context)
22
35
  end
23
36
  end
37
+
38
+ def get_value_from_dynamic_property_rule_handler(prop_name, context)
39
+ @dynamic_property_rule_handler.call(prop_name, context)
40
+ rescue StandardError => e
41
+ raise Rox::Core::UserspaceHandlerException, handler, ExceptionTrigger::DYNAMIC_PROPERTIES_RULE, e
42
+ end
43
+
44
+ def get_value_from_property(property, context)
45
+ property.value(context)
46
+ rescue StandardError => e
47
+ raise Rox::Core::UserspaceHandlerException, handler, ExceptionTrigger::CUSTOM_PROPERTY_GENERATOR, e
48
+ end
24
49
  end
25
50
  end
26
- end
51
+ end
@@ -5,13 +5,11 @@ module Rox
5
5
  @target_groups = []
6
6
  end
7
7
 
8
- def target_groups=(target_groups)
9
- @target_groups = target_groups
10
- end
8
+ attr_writer :target_groups
11
9
 
12
10
  def target_group(id)
13
11
  @target_groups.detect { |g| g.id == id }
14
12
  end
15
13
  end
16
14
  end
17
- end
15
+ end
@@ -13,6 +13,7 @@ module Rox
13
13
  def bool_value
14
14
  return false if value.nil?
15
15
  return value if Utils.boolean?(value)
16
+
16
17
  nil
17
18
  end
18
19
 
@@ -21,6 +22,7 @@ module Rox
21
22
  return nil if value.nil?
22
23
  return Flag::FLAG_TRUE_VALUE if value
23
24
  return Flag::FLAG_FALSE_VALUE unless value
25
+
24
26
  nil
25
27
  end
26
28
  end
@@ -8,4 +8,4 @@ module Rox
8
8
  UNKNOWN = 'Unknown'.freeze
9
9
  end
10
10
  end
11
- end
11
+ end
@@ -1,4 +1,5 @@
1
1
  require 'digest'
2
+ require 'base64'
2
3
  require 'rox/core/roxx/core_stack'
3
4
  require 'rox/core/roxx/evaluation_result'
4
5
  require 'rox/core/roxx/value_compare_extensions'
@@ -6,19 +7,24 @@ require 'rox/core/roxx/regular_expression_extensions'
6
7
  require 'rox/core/roxx/tokenized_expression'
7
8
  require 'rox/core/utils/type_utils'
8
9
  require 'rox/core/logging/logging'
10
+ require 'rox/core/error_handling/userspace_handler_exception'
9
11
 
10
12
  module Rox
11
13
  module Core
12
14
  class Parser
13
- def initialize
15
+ attr_accessor :global_context
16
+
17
+ def initialize(user_unhandled_error_invoker)
14
18
  @operators_map = {}
19
+ @global_context = nil
15
20
  set_basic_operators
21
+ @user_unhandled_error_invoker = user_unhandled_error_invoker
16
22
  ValueCompareExtensions.new(self).extend
17
23
  RegularExpressionExtensions.new(self).extend
18
24
  end
19
25
 
20
- def add_operator(oper, &block)
21
- @operators_map[oper] = block
26
+ def add_operator(operator, &block)
27
+ @operators_map[operator] = block
22
28
  end
23
29
 
24
30
  def evaluate_expression(expression, context = nil)
@@ -28,11 +34,12 @@ module Rox
28
34
  begin
29
35
  reverse_tokens.each do |token|
30
36
  node = token
31
- if node.type == NodeTypes::RAND
37
+ case node.type
38
+ when NodeTypes::RAND
32
39
  stack.push(node.value)
33
- elsif node.type == NodeTypes::RATOR
40
+ when NodeTypes::RATOR
34
41
  handler = @operators_map[node.value]
35
- handler.call(self, stack, context) unless handler.nil?
42
+ handler&.call(self, stack, context)
36
43
  else
37
44
  return EvaluationResult.new(nil)
38
45
  end
@@ -42,14 +49,18 @@ module Rox
42
49
  result = nil if result == TokenType::UNDEFINED
43
50
 
44
51
  EvaluationResult.new(result)
45
- rescue StandardError => ex
46
- Logging.logger.warn("Roxx Exception: Failed evaluate expression: #{ex}")
52
+ rescue Rox::Core::UserspaceHandlerException => e
53
+ @user_unhandled_error_invoker.invoke(e.exception_source, e.exception_trigger, e.exception)
54
+ Logging.logger.warn("Roxx Exception: Failed evaluate expression, user unhandled expression: #{e}")
55
+ EvaluationResult.new(nil)
56
+ rescue StandardError => e
57
+ Logging.logger.warn("Roxx Exception: Failed evaluate expression: #{e}")
47
58
  EvaluationResult.new(nil)
48
59
  end
49
60
  end
50
61
 
51
62
  def set_basic_operators
52
- add_operator('isUndefined') do |parser, stack, context|
63
+ add_operator('isUndefined') do |_parser, stack, _context|
53
64
  op1 = stack.pop
54
65
  if op1.is_a?(TokenType)
55
66
  stack.push(op1 == TokenType::UNDEFINED)
@@ -58,29 +69,31 @@ module Rox
58
69
  end
59
70
  end
60
71
 
61
- add_operator('now') do |parser, stack, context|
72
+ add_operator('now') do |_parser, stack, _context|
62
73
  stack.push((Time.now.to_f * 1000).to_i)
63
74
  end
64
75
 
65
- add_operator('and') do |parser, stack, context|
76
+ add_operator('and') do |_parser, stack, _context|
66
77
  op1 = stack.pop
67
78
  op2 = stack.pop
68
79
  op1 = false if op1 == TokenType::UNDEFINED
69
80
  op2 = false if op2 == TokenType::UNDEFINED
70
81
  raise ArgumentError, 'should be boolean' unless Utils.boolean?(op1) && Utils.boolean?(op2)
82
+
71
83
  stack.push(op1 && op2)
72
84
  end
73
85
 
74
- add_operator('or') do |parser, stack, context|
86
+ add_operator('or') do |_parser, stack, _context|
75
87
  op1 = stack.pop
76
88
  op2 = stack.pop
77
89
  op1 = false if op1 == TokenType::UNDEFINED
78
90
  op2 = false if op2 == TokenType::UNDEFINED
79
91
  raise ArgumentError, 'should be boolean' unless Utils.boolean?(op1) && Utils.boolean?(op2)
92
+
80
93
  stack.push(op1 || op2)
81
94
  end
82
95
 
83
- add_operator('ne') do |parser, stack, context|
96
+ add_operator('ne') do |_parser, stack, _context|
84
97
  op1 = stack.pop
85
98
  op2 = stack.pop
86
99
  op1 = false if op1 == TokenType::UNDEFINED
@@ -88,7 +101,7 @@ module Rox
88
101
  stack.push(op1 != op2)
89
102
  end
90
103
 
91
- add_operator('eq') do |parser, stack, context|
104
+ add_operator('eq') do |_parser, stack, _context|
92
105
  op1 = stack.pop
93
106
  op2 = stack.pop
94
107
  op1 = false if op1 == TokenType::UNDEFINED
@@ -96,18 +109,20 @@ module Rox
96
109
  stack.push(op1 == op2)
97
110
  end
98
111
 
99
- add_operator('not') do |parser, stack, context|
112
+ add_operator('not') do |_parser, stack, _context|
100
113
  op1 = stack.pop
101
114
  op1 = false if op1 == TokenType::UNDEFINED
102
115
  raise ArgumentError, 'should be boolean' unless Utils.boolean?(op1)
116
+
103
117
  stack.push(!op1)
104
118
  end
105
119
 
106
- add_operator('ifThen') do |parser, stack, context|
120
+ add_operator('ifThen') do |_parser, stack, _context|
107
121
  condition_expression = stack.pop
108
122
  true_expression = stack.pop
109
123
  false_expression = stack.pop
110
124
  raise ArgumentError, 'should be boolean' unless Utils.boolean?(condition_expression)
125
+
111
126
  if condition_expression
112
127
  stack.push(true_expression)
113
128
  else
@@ -115,7 +130,7 @@ module Rox
115
130
  end
116
131
  end
117
132
 
118
- add_operator('inArray') do |parser, stack, context|
133
+ add_operator('inArray') do |_parser, stack, _context|
119
134
  op1 = stack.pop
120
135
  op2 = stack.pop
121
136
  if op2.is_a?(Array)
@@ -125,7 +140,7 @@ module Rox
125
140
  end
126
141
  end
127
142
 
128
- add_operator('md5') do |parser, stack, context|
143
+ add_operator('md5') do |_parser, stack, _context|
129
144
  op1 = stack.pop
130
145
  if op1.is_a?(String)
131
146
  stack.push(Digest::MD5.hexdigest(op1))
@@ -134,7 +149,7 @@ module Rox
134
149
  end
135
150
  end
136
151
 
137
- add_operator('concat') do |parser, stack, context|
152
+ add_operator('concat') do |_parser, stack, _context|
138
153
  op1 = stack.pop
139
154
  op2 = stack.pop
140
155
  if op1.is_a?(String) && op2.is_a?(String)
@@ -143,7 +158,18 @@ module Rox
143
158
  stack.push(TokenType::UNDEFINED)
144
159
  end
145
160
  end
161
+
162
+ add_operator('b64d') do |_parser, stack, _context|
163
+ op1 = stack.pop
164
+ if op1.is_a?(String)
165
+ decoded = Base64.decode64(op1)
166
+ decoded = decoded.force_encoding('UTF-8')
167
+ stack.push(decoded)
168
+ else
169
+ stack.push(TokenType::UNDEFINED)
170
+ end
171
+ end
146
172
  end
147
173
  end
148
174
  end
149
- end
175
+ end