rast 0.18.0 → 0.19.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.
@@ -11,113 +11,38 @@ module LogicHelper
11
11
  TRUE = '*true'
12
12
  FALSE = '*false'
13
13
 
14
- # /**
15
- # * @param scenario list of scenario tokens.
16
- # * @param left_subscript left index.
17
- # * @param right_subscript right index.
18
- # * @param left left token, no subscript.
19
- # * @param right right token, no subscript.
20
- # */
21
- def perform_logical_and(scenario: [], left_subscript: -1, right_subscript: -1,
22
- left: nil, right: nil)
23
- if FALSE == left && left_subscript == -1 || FALSE == right && right_subscript == -1
24
- FALSE
25
- elsif TRUE == left && left_subscript == -1 && TRUE == right && right_subscript == -1
26
- TRUE
27
- elsif TRUE == left && left_subscript == -1
28
- if right_subscript < 0
29
- scenario.include?(right).to_s
30
- else
31
- (scenario[right_subscript] == right).to_s
32
- end
33
- elsif TRUE == right && right_subscript == -1
34
- if left_subscript < 0
35
- scenario.include?(left).to_s
36
- else
37
- (scenario[left_subscript] == left).to_s
38
- end
39
- else
40
- left_eval = pevaluate(
41
- scenario: scenario,
42
- subscript: left_subscript,
43
- object: left
44
- )
14
+ OPPOSITE = {
15
+ TRUE => FALSE,
16
+ FALSE => TRUE
17
+ }.freeze
45
18
 
46
- return 'false' unless left_eval
47
-
48
- right_eval = pevaluate(
49
- scenario: scenario,
50
- subscript: right_subscript,
51
- object: right
52
- )
53
-
54
- (left_eval && right_eval).to_s
55
- end
56
- end
19
+ LOGIC_PRIMARY_RESULT = {
20
+ and: FALSE,
21
+ or: TRUE
22
+ }.freeze
57
23
 
58
24
  # /**
59
- # * @param scenario list of scenario tokens.
60
- # * @param left_subscript left index.
61
- # * @param right_subscript right index.
62
- # * @param left left token.
63
- # * @param right right token.
25
+ # * @scenario list of scenario tokens.
26
+ # * @left left left token object.
27
+ # * @right right right token object.
28
+ # * @operation :and or :or.
64
29
  # */
65
- def perform_logical_or(scenario: [], left_subscript: -1, right_subscript: -1,
66
- left: nil, right: nil)
67
- if TRUE == left && left_subscript == -1 || TRUE == right && right_subscript == -1
68
- TRUE
69
- elsif FALSE == left && left_subscript == -1 && FALSE == right && right_subscript == -1
70
- FALSE
71
- elsif FALSE == left && left_subscript == -1
72
- if right_subscript < 0
73
- scenario.include?(right).to_s
74
- else
75
- (scenario[right_subscript] == right).to_s
76
- end
77
- elsif FALSE == right && right_subscript == -1
78
- if left_subscript < 0
79
- scenario.include?(left).to_s
80
- else
81
- (scenario[left_subscript] == left).to_s
82
- end
83
- else
84
- left_eval = pevaluate(
85
- scenario: scenario,
86
- subscript: left_subscript,
87
- object: left
88
- )
30
+ def perform_logical(scenario: [], left: {}, right: {}, operation: :nil)
31
+ evaluated = send(:both_internal?, left, right, operation)
32
+ return evaluated if evaluated
89
33
 
90
- return 'true' if left_eval
34
+ default = operation == :and ? TRUE : FALSE
35
+ return present?(scenario, right).to_s if internal_match?(default, left)
91
36
 
92
- right_eval = pevaluate(
93
- scenario: scenario,
94
- subscript: right_subscript,
95
- object: right
96
- )
97
-
98
- (left_eval || right_eval).to_s
99
- end
100
- end
37
+ return present?(scenario, left).to_s if internal_match?(default, right)
101
38
 
102
- # /**
103
- # * Helper method to evaluate left or right token.
104
- # *
105
- # * @param scenario list of scenario tokens.
106
- # * @param subscript scenario token subscript.
107
- # * @param object left or right token.
108
- # */
109
- def pevaluate(scenario: [], subscript: -1, object: nil)
110
- if subscript < 0
111
- scenario.include?(object)
112
- else
113
- scenario[subscript] == object
114
- end
39
+ send("evaluate_#{operation}", scenario, left, right).to_s
115
40
  end
116
41
 
117
42
  # /**
118
43
  # * Check if the token is opening bracket.
119
44
  # *
120
- # * @param token Input <code>String</code> token
45
+ # * @token Input <code>String</code> token
121
46
  # * @return <code>boolean</code> output
122
47
  # */
123
48
  def open_bracket?(token: '')
@@ -133,4 +58,60 @@ module LogicHelper
133
58
  def close_bracket?(token: '')
134
59
  token == ')'
135
60
  end
61
+
62
+ private
63
+
64
+ # @left hash containing token and subscript
65
+ # @right hash containing token and subscript
66
+ # @operation symbol either :and or :or
67
+ def both_internal?(left, right, operation)
68
+ default = LOGIC_PRIMARY_RESULT[operation]
69
+ if internal_match?(default, left) || internal_match?(default, right)
70
+ return default
71
+ end
72
+
73
+ opposite = OPPOSITE[default]
74
+ if internal_match?(opposite, left) && internal_match?(opposite, right)
75
+ return opposite
76
+ end
77
+
78
+ false
79
+ end
80
+
81
+ def evaluate_and(scenario, left, right)
82
+ left_eval = present?(scenario, left)
83
+
84
+ return false unless left_eval
85
+
86
+ right_eval = present?(scenario, right)
87
+ left_eval && right_eval
88
+ end
89
+
90
+ def evaluate_or(scenario, left, right)
91
+ left_eval = present?(scenario, left)
92
+
93
+ return true if left_eval
94
+
95
+ right_eval = present?(scenario, right)
96
+ left_eval || right_eval
97
+ end
98
+
99
+ # /**
100
+ # * Helper method to evaluate left or right token.
101
+ # *
102
+ # * @param scenario list of scenario tokens.
103
+ # * @param subscript scenario token subscript.
104
+ # * @param object left or right token.
105
+ # */
106
+ def present?(scenario, token)
107
+ if token[:subscript] < 0
108
+ scenario.include?(token[:value])
109
+ else
110
+ scenario[token[:subscript]] == token[:value]
111
+ end
112
+ end
113
+
114
+ def internal_match?(internal, token)
115
+ token[:value] == internal && token[:subscript] == -1
116
+ end
136
117
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rast/rules/operator'
4
+ require 'rast/rules/token_util'
4
5
  require 'rast/rules/logic_helper'
5
6
  require 'rast/converters/int_converter'
6
7
  require 'rast/converters/float_converter'
@@ -8,7 +9,7 @@ require 'rast/converters/default_converter'
8
9
  require 'rast/converters/bool_converter'
9
10
  require 'rast/converters/str_converter'
10
11
 
11
- # Evaluates the rules.
12
+ # Evaluates the rules. "Internal refers to the `*true` or `*false` results."
12
13
  class RuleEvaluator
13
14
  include LogicHelper
14
15
 
@@ -99,27 +100,30 @@ class RuleEvaluator
99
100
  # * @param rule_token_convert token to converter map.
100
101
  # * @param default_converter default converter to use.
101
102
  # */
102
- def next_value(rule_token_convert: {}, default_converter: nil)
103
- subscript = -1
104
- retval = []
105
- value = @stack_answer.pop
106
-
107
- return [-1, value] if value.is_a? Array
108
-
109
- if TRUE != value && FALSE != value
110
- subscript = extract_subscript(token: value.to_s)
111
- value_str = value.to_s.strip
112
- value = if subscript > -1
113
- value_token = value_str[/^.+(?=\[)/]
114
- rule_token_convert[value_token].convert(value_token)
115
- else
116
- rule_token_convert[value_str].convert(value_str)
117
- end
118
- end
103
+ def next_value(rule_token_convert: {})
104
+ token = @stack_answer.pop
105
+ default = {
106
+ subscript: -1,
107
+ value: token
108
+ }
109
+
110
+ return default if token.is_a?(Array) || [TRUE, FALSE].include?(token)
111
+
112
+ next_value_default(rule_token_convert, token)
113
+ end
114
+
115
+ # private
116
+ def next_value_default(rule_token_convert, token)
117
+ token_cleaned = token.to_s.strip
118
+ subscript = TokenUtil.extract_subscript(token: token_cleaned)
119
+ token_body = subscript > -1 ? token_cleaned[/^.+(?=\[)/] : token_cleaned
120
+
121
+ raise "Config Error: Outcome clause token: '#{token}' not found in variables" if rule_token_convert[token_body].nil?
119
122
 
120
- retval << subscript
121
- retval << value
122
- retval
123
+ {
124
+ value: rule_token_convert[token_body].convert(token_body),
125
+ subscript: subscript
126
+ }
123
127
  end
124
128
 
125
129
  # /** @param token token. */
@@ -127,39 +131,34 @@ class RuleEvaluator
127
131
  if open_bracket?(token: token)
128
132
  @stack_operations << token
129
133
  elsif close_bracket?(token: token)
130
- while @stack_operations.any? &&
131
- !open_bracket?(token: @stack_operations.last.strip)
132
- @stack_rpn << @stack_operations.pop
133
- end
134
- @stack_operations.pop
134
+ shunt_close
135
135
  elsif operator?(token: token)
136
- while !@stack_operations.empty? &&
137
- operator?(token: @stack_operations.last.strip) &&
138
- precedence(symbol_char: token[0]) <=
139
- precedence(symbol_char: @stack_operations.last.strip[0])
140
- @stack_rpn << @stack_operations.pop
141
- end
142
- @stack_operations << token
136
+ shunt_operator(token)
143
137
  else
144
138
  @stack_rpn << token
145
139
  end
146
140
  end
147
141
 
148
- private
149
-
150
- # /**
151
- # * Returns value of 'n' if rule token ends with '[n]'. where 'n' is the
152
- # * variable group index.
153
- # *
154
- # * @param string token to check for subscript.
155
- # */
156
- def extract_subscript(token: '')
157
- return -1 if token.is_a? Array
142
+ def shunt_operator(token)
143
+ while !@stack_operations.empty? &&
144
+ operator?(token: @stack_operations.last.strip) &&
145
+ precedence(symbol_char: token[0]) <=
146
+ precedence(symbol_char: @stack_operations.last.strip[0])
147
+ @stack_rpn << @stack_operations.pop
148
+ end
149
+ @stack_operations << token
150
+ end
158
151
 
159
- subscript = token[/\[(\d+)\]$/, 1]
160
- subscript.nil? ? -1 : subscript.to_i
152
+ def shunt_close
153
+ while @stack_operations.any? &&
154
+ !open_bracket?(token: @stack_operations.last.strip)
155
+ @stack_rpn << @stack_operations.pop
156
+ end
157
+ @stack_operations.pop
161
158
  end
162
159
 
160
+ private
161
+
163
162
  # /**
164
163
  # * @param scenario List of values to evaluate against the rule expression.
165
164
  # * @param rule_token_convert token to converter map.
@@ -170,29 +169,36 @@ class RuleEvaluator
170
169
 
171
170
  # /* get the clone of the RPN stack for further evaluating */
172
171
  stack_rpn_clone = Marshal.load(Marshal.dump(@stack_rpn))
172
+ evaluate_stack_rpn(stack_rpn_clone, scenario, rule_token_convert)
173
+
174
+ raise 'Some operator is missing' if @stack_answer.size > 1
173
175
 
174
- # /* evaluating the RPN expression */
175
- while stack_rpn_clone.any?
176
- token = stack_rpn_clone.pop
176
+ last = @stack_answer.pop
177
+ last[1..last.size]
178
+ end
179
+
180
+ # evaluating the RPN expression
181
+ def evaluate_stack_rpn(stack_rpn, scenario, rule_token_convert)
182
+ while stack_rpn.any?
183
+ token = stack_rpn.pop
177
184
  if operator?(token: token)
178
- if NOT.symbol == token
179
- evaluate_multi_not(scenario: scenario)
180
- else
181
- evaluate_multi(
182
- scenario: scenario,
183
- rule_token_convert: rule_token_convert,
184
- operator: RuleEvaluator.operator_from_symbol(symbol: token[0])
185
- )
186
- end
185
+ evaluate_operator(scenario, rule_token_convert, token)
187
186
  else
188
187
  @stack_answer << token
189
188
  end
190
189
  end
190
+ end
191
191
 
192
- raise 'Some operator is missing' if @stack_answer.size > 1
193
-
194
- last = @stack_answer.pop
195
- last[1..last.size]
192
+ def evaluate_operator(scenario, rule_token_convert, token)
193
+ if NOT.symbol == token
194
+ evaluate_multi_not(scenario: scenario)
195
+ else
196
+ evaluate_multi(
197
+ scenario: scenario,
198
+ rule_token_convert: rule_token_convert,
199
+ operator: RuleEvaluator.operator_from_symbol(symbol: token[0])
200
+ )
201
+ end
196
202
  end
197
203
 
198
204
  # /**
@@ -201,35 +207,21 @@ class RuleEvaluator
201
207
  # * @param operator OR/AND.
202
208
  # */
203
209
  def evaluate_multi(scenario: [], rule_token_convert: {}, operator: nil)
204
- default_converter = DEFAULT_CONVERT_HASH[scenario.first.class]
205
-
206
210
  # Convert 'nil' to nil.
207
- formatted_scenario = scenario.map { |token| token == 'nil' ? nil: token }
211
+ formatted_scenario = scenario.map { |token| token == 'nil' ? nil : token }
208
212
 
209
- left_arr = next_value(
210
- rule_token_convert: rule_token_convert,
211
- default_converter: default_converter
212
- )
213
-
214
- right_arr = next_value(
215
- rule_token_convert: rule_token_convert,
216
- default_converter: default_converter
217
- )
213
+ left = next_value(rule_token_convert: rule_token_convert)
214
+ right = next_value(rule_token_convert: rule_token_convert)
218
215
 
219
216
  answer = send(
220
- "perform_logical_#{operator.name}",
217
+ :perform_logical,
221
218
  scenario: formatted_scenario,
222
- left_subscript: left_arr[0],
223
- right_subscript: right_arr[0],
224
- left: left_arr[1],
225
- right: right_arr[1]
219
+ left: left,
220
+ right: right,
221
+ operation: operator.name.to_sym
226
222
  )
227
223
 
228
- @stack_answer << if answer[0] == '*'
229
- answer
230
- else
231
- "*#{answer}"
232
- end
224
+ @stack_answer << format_internal_result(answer)
233
225
  end
234
226
 
235
227
  # /**
@@ -243,28 +235,38 @@ class RuleEvaluator
243
235
  elsif LogicHelper::FALSE == latest
244
236
  LogicHelper::TRUE
245
237
  else
246
- subscript = extract_subscript(token: latest)
247
- converter = DEFAULT_CONVERT_HASH[scenario.first.class]
248
- if subscript < 0
249
- converted = converter.convert(latest)
250
- (!scenario.include?(converted)).to_s
251
- else
252
- converted = converter.convert(latest[RE_TOKEN_BODY])
253
- (scenario[subscript] != converted).to_s
254
- end
238
+ evaluate_non_internal(scenario, latest)
255
239
  end
256
240
 
257
- @stack_answer << if answer[0] == '*'
258
- answer
259
- else
260
- "*#{answer}"
261
- end
241
+ @stack_answer << format_internal_result(answer)
242
+ end
243
+
244
+ def evaluate_non_internal(scenario, latest)
245
+ subscript = TokenUtil.extract_subscript(token: latest)
246
+ converter = DEFAULT_CONVERT_HASH[scenario.first.class]
247
+ if subscript < 0
248
+ converted = converter.convert(latest)
249
+ (!scenario.include?(converted)).to_s
250
+ else
251
+ converted = converter.convert(latest[RE_TOKEN_BODY])
252
+ (scenario[subscript] != converted).to_s
253
+ end
254
+ end
255
+
256
+ # returns true if answer starts with *, *true if answer is true, same goes for
257
+ # false.
258
+ def format_internal_result(answer)
259
+ if answer[0] == '*'
260
+ answer
261
+ else
262
+ "*#{answer}"
263
+ end
262
264
  end
263
265
 
264
266
  # /** @param scenario to evaluate against the rule expression. */
265
267
  def evaluate_one_rpn(scenario: [])
266
268
  single = @stack_rpn.last
267
- subscript = extract_subscript(token: single)
269
+ subscript = TokenUtil.extract_subscript(token: single)
268
270
  default_converter = DEFAULT_CONVERT_HASH[scenario.first.class]
269
271
  if subscript > -1
270
272
  scenario[subscript] == default_converter.convert(single[RE_TOKEN_BODY])
@@ -11,8 +11,13 @@ class RuleValidator
11
11
  )
12
12
 
13
13
  spec = fixture[:spec]
14
- rule = spec.rule
14
+ validate_results(scenario, rule_result, spec)
15
+ end
16
+
17
+ private
15
18
 
19
+ def validate_results(scenario, rule_result, spec)
20
+ rule = spec.rule
16
21
  single_result = rule.size == 1
17
22
  if single_result
18
23
  next_result = rule_result.first
@@ -23,8 +28,6 @@ class RuleValidator
23
28
  end
24
29
  end
25
30
 
26
- private
27
-
28
31
  def validate_multi(scenario: [], spec: nil, rule_result: [])
29
32
  matched_outputs = []
30
33
  match_count = 0
@@ -36,14 +39,18 @@ class RuleValidator
36
39
  matched_outputs << spec.rule.outcomes[i]
37
40
  end
38
41
 
39
- Rast.assert("#{spec.description} #{scenario} must fall into a unique rule outcome/clause, matched: #{matched_outputs}") do
40
- match_count == 1 || match_count == 0 && !spec.default_outcome.nil?
41
- end
42
+ verify_results(spec, scenario, matched_outputs, match_count)
42
43
 
43
44
  matched_outputs.first || spec.default_outcome
44
45
  end
45
46
 
46
- #
47
+ def verify_results(spec, scenario, matched_outputs, match_count)
48
+ Rast.assert("#{spec.description} #{scenario} must fall into a unique rule" \
49
+ " outcome/clause, matched: #{matched_outputs}") do
50
+ match_count == 1 || match_count.zero? && !spec.default_outcome.nil?
51
+ end
52
+ end
53
+
47
54
  def binary_outcome(outcome: '', spec: nil, expected: false)
48
55
  if expected == 'true'
49
56
  outcome
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ module TokenUtil
5
+ # /**
6
+ # * Returns value of 'n' if rule token ends with '[n]'. where 'n' is the
7
+ # * variable group index.
8
+ # *
9
+ # * @param string token to check for subscript.
10
+ # */
11
+ def self.extract_subscript(token: '')
12
+ return -1 if token.is_a? Array
13
+
14
+ subscript = token[/\[(\d+)\]$/, 1]
15
+ subscript.nil? ? -1 : subscript.to_i
16
+ end
17
+ end