rast 0.18.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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