hayadentaku 3.5.7

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 (132) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rspec.yml +26 -0
  3. data/.github/workflows/rubocop.yml +14 -0
  4. data/.gitignore +14 -0
  5. data/.pryrc +2 -0
  6. data/.rubocop.yml +114 -0
  7. data/.travis.yml +10 -0
  8. data/CHANGELOG.md +328 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE +21 -0
  11. data/README.md +352 -0
  12. data/Rakefile +31 -0
  13. data/hayadentaku.gemspec +35 -0
  14. data/lib/dentaku/ast/access.rb +44 -0
  15. data/lib/dentaku/ast/arithmetic.rb +292 -0
  16. data/lib/dentaku/ast/array.rb +38 -0
  17. data/lib/dentaku/ast/bitwise.rb +42 -0
  18. data/lib/dentaku/ast/case/case_conditional.rb +38 -0
  19. data/lib/dentaku/ast/case/case_else.rb +35 -0
  20. data/lib/dentaku/ast/case/case_switch_variable.rb +35 -0
  21. data/lib/dentaku/ast/case/case_then.rb +35 -0
  22. data/lib/dentaku/ast/case/case_when.rb +39 -0
  23. data/lib/dentaku/ast/case.rb +93 -0
  24. data/lib/dentaku/ast/combinators.rb +50 -0
  25. data/lib/dentaku/ast/comparators.rb +88 -0
  26. data/lib/dentaku/ast/datetime.rb +8 -0
  27. data/lib/dentaku/ast/function.rb +56 -0
  28. data/lib/dentaku/ast/function_registry.rb +107 -0
  29. data/lib/dentaku/ast/functions/abs.rb +5 -0
  30. data/lib/dentaku/ast/functions/all.rb +19 -0
  31. data/lib/dentaku/ast/functions/and.rb +25 -0
  32. data/lib/dentaku/ast/functions/any.rb +19 -0
  33. data/lib/dentaku/ast/functions/avg.rb +13 -0
  34. data/lib/dentaku/ast/functions/count.rb +26 -0
  35. data/lib/dentaku/ast/functions/duration.rb +51 -0
  36. data/lib/dentaku/ast/functions/enum.rb +54 -0
  37. data/lib/dentaku/ast/functions/filter.rb +21 -0
  38. data/lib/dentaku/ast/functions/if.rb +47 -0
  39. data/lib/dentaku/ast/functions/intercept.rb +33 -0
  40. data/lib/dentaku/ast/functions/map.rb +19 -0
  41. data/lib/dentaku/ast/functions/max.rb +5 -0
  42. data/lib/dentaku/ast/functions/min.rb +5 -0
  43. data/lib/dentaku/ast/functions/mul.rb +12 -0
  44. data/lib/dentaku/ast/functions/not.rb +5 -0
  45. data/lib/dentaku/ast/functions/or.rb +25 -0
  46. data/lib/dentaku/ast/functions/pluck.rb +34 -0
  47. data/lib/dentaku/ast/functions/reduce.rb +60 -0
  48. data/lib/dentaku/ast/functions/round.rb +5 -0
  49. data/lib/dentaku/ast/functions/rounddown.rb +8 -0
  50. data/lib/dentaku/ast/functions/roundup.rb +8 -0
  51. data/lib/dentaku/ast/functions/ruby_math.rb +57 -0
  52. data/lib/dentaku/ast/functions/string_functions.rb +212 -0
  53. data/lib/dentaku/ast/functions/sum.rb +12 -0
  54. data/lib/dentaku/ast/functions/switch.rb +8 -0
  55. data/lib/dentaku/ast/functions/xor.rb +44 -0
  56. data/lib/dentaku/ast/grouping.rb +23 -0
  57. data/lib/dentaku/ast/identifier.rb +52 -0
  58. data/lib/dentaku/ast/literal.rb +30 -0
  59. data/lib/dentaku/ast/logical.rb +8 -0
  60. data/lib/dentaku/ast/negation.rb +54 -0
  61. data/lib/dentaku/ast/nil.rb +13 -0
  62. data/lib/dentaku/ast/node.rb +29 -0
  63. data/lib/dentaku/ast/numeric.rb +8 -0
  64. data/lib/dentaku/ast/operation.rb +44 -0
  65. data/lib/dentaku/ast/string.rb +15 -0
  66. data/lib/dentaku/ast.rb +42 -0
  67. data/lib/dentaku/bulk_expression_solver.rb +158 -0
  68. data/lib/dentaku/calculator.rb +192 -0
  69. data/lib/dentaku/date_arithmetic.rb +60 -0
  70. data/lib/dentaku/dependency_resolver.rb +29 -0
  71. data/lib/dentaku/exceptions.rb +116 -0
  72. data/lib/dentaku/flat_hash.rb +161 -0
  73. data/lib/dentaku/parser.rb +318 -0
  74. data/lib/dentaku/print_visitor.rb +112 -0
  75. data/lib/dentaku/string_casing.rb +7 -0
  76. data/lib/dentaku/token.rb +48 -0
  77. data/lib/dentaku/token_matcher.rb +138 -0
  78. data/lib/dentaku/token_matchers.rb +29 -0
  79. data/lib/dentaku/token_scanner.rb +240 -0
  80. data/lib/dentaku/tokenizer.rb +127 -0
  81. data/lib/dentaku/version.rb +3 -0
  82. data/lib/dentaku/visitor/infix.rb +86 -0
  83. data/lib/dentaku.rb +69 -0
  84. data/spec/ast/abs_spec.rb +26 -0
  85. data/spec/ast/addition_spec.rb +67 -0
  86. data/spec/ast/all_spec.rb +38 -0
  87. data/spec/ast/and_function_spec.rb +35 -0
  88. data/spec/ast/and_spec.rb +32 -0
  89. data/spec/ast/any_spec.rb +36 -0
  90. data/spec/ast/arithmetic_spec.rb +147 -0
  91. data/spec/ast/avg_spec.rb +42 -0
  92. data/spec/ast/case_spec.rb +84 -0
  93. data/spec/ast/comparator_spec.rb +87 -0
  94. data/spec/ast/count_spec.rb +40 -0
  95. data/spec/ast/division_spec.rb +64 -0
  96. data/spec/ast/filter_spec.rb +25 -0
  97. data/spec/ast/function_spec.rb +69 -0
  98. data/spec/ast/intercept_spec.rb +30 -0
  99. data/spec/ast/map_spec.rb +40 -0
  100. data/spec/ast/max_spec.rb +33 -0
  101. data/spec/ast/min_spec.rb +33 -0
  102. data/spec/ast/mul_spec.rb +43 -0
  103. data/spec/ast/negation_spec.rb +48 -0
  104. data/spec/ast/node_spec.rb +43 -0
  105. data/spec/ast/numeric_spec.rb +16 -0
  106. data/spec/ast/or_spec.rb +35 -0
  107. data/spec/ast/pluck_spec.rb +49 -0
  108. data/spec/ast/reduce_spec.rb +22 -0
  109. data/spec/ast/round_spec.rb +35 -0
  110. data/spec/ast/rounddown_spec.rb +35 -0
  111. data/spec/ast/roundup_spec.rb +35 -0
  112. data/spec/ast/string_functions_spec.rb +217 -0
  113. data/spec/ast/sum_spec.rb +43 -0
  114. data/spec/ast/switch_spec.rb +30 -0
  115. data/spec/ast/xor_spec.rb +35 -0
  116. data/spec/benchmark.rb +70 -0
  117. data/spec/bulk_expression_solver_spec.rb +241 -0
  118. data/spec/calculator_spec.rb +1003 -0
  119. data/spec/dentaku_spec.rb +52 -0
  120. data/spec/dependency_resolver_spec.rb +18 -0
  121. data/spec/exceptions_spec.rb +9 -0
  122. data/spec/external_function_spec.rb +177 -0
  123. data/spec/parser_spec.rb +183 -0
  124. data/spec/print_visitor_spec.rb +77 -0
  125. data/spec/spec_helper.rb +69 -0
  126. data/spec/token_matcher_spec.rb +134 -0
  127. data/spec/token_scanner_spec.rb +49 -0
  128. data/spec/token_spec.rb +16 -0
  129. data/spec/tokenizer_spec.rb +375 -0
  130. data/spec/visitor/infix_spec.rb +52 -0
  131. data/spec/visitor_spec.rb +139 -0
  132. metadata +353 -0
@@ -0,0 +1,292 @@
1
+ require_relative './operation'
2
+ require_relative '../date_arithmetic'
3
+ require 'bigdecimal'
4
+ require 'bigdecimal/util'
5
+
6
+ module Dentaku
7
+ module AST
8
+ class Arithmetic < Operation
9
+ DECIMAL = /\A-?\d*\.\d+\z/.freeze
10
+ INTEGER = /\A-?\d+\z/.freeze
11
+
12
+ def initialize(*)
13
+ super
14
+
15
+ unless valid_left?
16
+ raise NodeError.new(:incompatible, left.type, :left),
17
+ "#{self.class} requires operands that are numeric or compatible types, not #{left.type}"
18
+ end
19
+
20
+ unless valid_right?
21
+ raise NodeError.new(:incompatible, right.type, :right),
22
+ "#{self.class} requires operands that are numeric or compatible types, not #{right.type}"
23
+ end
24
+ end
25
+
26
+ def type
27
+ :numeric
28
+ end
29
+
30
+ def operator
31
+ raise NotImplementedError
32
+ end
33
+
34
+ def value(context = {})
35
+ calculate(left.value(context), right.value(context))
36
+ end
37
+
38
+ private
39
+
40
+ def calculate(left_value, right_value)
41
+ l = cast(left_value)
42
+ r = cast(right_value)
43
+
44
+ l.public_send(operator, r)
45
+ rescue ::TypeError => e
46
+ # Right cannot be converted to a suitable type for left. e.g. [] + 1
47
+ raise Dentaku::ArgumentError.for(:incompatible_type, value: r, for: l.class), e.message
48
+ end
49
+
50
+ def cast(val)
51
+ validate_value(val)
52
+ numeric(val)
53
+ end
54
+
55
+ # Coerce a value to its preferred numeric form (Integer or BigDecimal).
56
+ # Fast paths for the already-numeric types skip the otherwise-mandatory
57
+ # `val.to_s + regex` round-trip the previous implementation did on every
58
+ # operand of every arithmetic op.
59
+ def numeric(val)
60
+ case val
61
+ when Integer then val
62
+ when BigDecimal then val
63
+ when Float then decimal(val)
64
+ when String
65
+ if DECIMAL.match?(val)
66
+ decimal(val)
67
+ elsif INTEGER.match?(val)
68
+ val.to_i
69
+ else
70
+ val
71
+ end
72
+ else
73
+ # Fall back to the string-based check for exotic Numeric subclasses
74
+ # (e.g. Rational, Complex) whose to_s happens to look numeric.
75
+ s = val.to_s
76
+ case s
77
+ when DECIMAL then decimal(val)
78
+ when INTEGER then val.to_i
79
+ else val
80
+ end
81
+ end
82
+ end
83
+
84
+ def decimal(val)
85
+ case val
86
+ when BigDecimal then val
87
+ when String then BigDecimal(val, Float::DIG + 1)
88
+ else BigDecimal(val.to_s, Float::DIG + 1)
89
+ end
90
+ rescue # return as is, in case value can't be coerced to big decimal
91
+ val
92
+ end
93
+
94
+ def datetime?(val)
95
+ # val is a Date, Time, or DateTime
96
+ return true if val.respond_to?(:strftime)
97
+ return false unless val.is_a?(::String)
98
+
99
+ val =~ Dentaku::TokenScanner::DATE_TIME_REGEXP
100
+ end
101
+
102
+ def valid_node?(node)
103
+ return false unless node
104
+
105
+ # Allow nodes with dependencies (identifiers that will be resolved later)
106
+ return true if node.dependencies.any?
107
+
108
+ # Allow compatible types
109
+ return true if [:numeric, :integer, :array].include?(node.type)
110
+
111
+ # Allow nodes without a type (operations, groupings)
112
+ return true if node.type.nil?
113
+
114
+ # Reject incompatible types
115
+ false
116
+ end
117
+
118
+ def valid_left?
119
+ valid_node?(left) || left.type == :datetime
120
+ end
121
+
122
+ def valid_right?
123
+ valid_node?(right) || right.type == :duration || right.type == :datetime
124
+ end
125
+
126
+ def validate_value(val)
127
+ if val.is_a?(::String)
128
+ validate_format(val)
129
+ else
130
+ validate_operation(val)
131
+ end
132
+ end
133
+
134
+ def validate_operation(val)
135
+ unless val.respond_to?(operator)
136
+ raise Dentaku::ArgumentError.for(:invalid_operator, operation: self.class, operator: operator),
137
+ "#{ self.class } requires operands that respond to #{operator}"
138
+ end
139
+ end
140
+
141
+ def validate_format(string)
142
+ unless string =~ /\A-?\d*(\.\d+)?\z/ && !string.empty?
143
+ raise Dentaku::ArgumentError.for(:invalid_value, value: string, for: BigDecimal),
144
+ "String input '#{string}' is not coercible to numeric"
145
+ end
146
+ end
147
+ end
148
+
149
+ class Addition < Arithmetic
150
+ def operator
151
+ :+
152
+ end
153
+
154
+ def self.precedence
155
+ 10
156
+ end
157
+
158
+ def value(context = {})
159
+ left_value = left.value(context)
160
+ right_value = right.value(context)
161
+
162
+ if left.type == :datetime || datetime?(left_value)
163
+ Dentaku::DateArithmetic.new(left_value).add(right_value)
164
+ else
165
+ calculate(left_value, right_value)
166
+ end
167
+ end
168
+ end
169
+
170
+ class Subtraction < Arithmetic
171
+ def operator
172
+ :-
173
+ end
174
+
175
+ def self.precedence
176
+ 10
177
+ end
178
+
179
+ def value(context = {})
180
+ left_value = left.value(context)
181
+ right_value = right.value(context)
182
+
183
+ if left.type == :datetime || datetime?(left_value)
184
+ Dentaku::DateArithmetic.new(left_value).sub(right_value)
185
+ else
186
+ calculate(left_value, right_value)
187
+ end
188
+ end
189
+ end
190
+
191
+ class Multiplication < Arithmetic
192
+ def operator
193
+ :*
194
+ end
195
+
196
+ def self.precedence
197
+ 20
198
+ end
199
+ end
200
+
201
+ class Division < Arithmetic
202
+ def operator
203
+ :/
204
+ end
205
+
206
+ def value(context = {})
207
+ r = decimal(cast(right.value(context)))
208
+ raise Dentaku::ZeroDivisionError if r.zero?
209
+
210
+ cast(cast(left.value(context)) / r)
211
+ end
212
+
213
+ def self.precedence
214
+ 20
215
+ end
216
+ end
217
+
218
+ class Modulo < Arithmetic
219
+ def self.arity
220
+ 2
221
+ end
222
+
223
+ def self.precedence
224
+ 20
225
+ end
226
+
227
+ def self.resolve_class(next_token)
228
+ next_token.nil? || next_token.operator? || next_token.close? ? Percentage : self
229
+ end
230
+
231
+ def operator
232
+ :%
233
+ end
234
+
235
+ def value(context = {})
236
+ r = decimal(cast(right.value(context)))
237
+ raise Dentaku::ZeroDivisionError if r.zero?
238
+
239
+ cast(cast(left.value(context)) % r)
240
+ end
241
+ end
242
+
243
+ class Percentage < Arithmetic
244
+ def self.arity
245
+ 1
246
+ end
247
+
248
+ def initialize(child)
249
+ @left = child
250
+
251
+ unless valid_left?
252
+ raise NodeError.new(:numeric, left.type, :left),
253
+ "#{self.class} requires a numeric operand"
254
+ end
255
+ end
256
+
257
+ def dependencies(context = {})
258
+ @left.dependencies(context)
259
+ end
260
+
261
+ def value(context = {})
262
+ cast(left.value(context)) * 0.01
263
+ end
264
+
265
+ def operator
266
+ :%
267
+ end
268
+
269
+ def operator_spacing
270
+ ""
271
+ end
272
+
273
+ def self.precedence
274
+ 30
275
+ end
276
+ end
277
+
278
+ class Exponentiation < Arithmetic
279
+ def operator
280
+ :**
281
+ end
282
+
283
+ def display_operator
284
+ "^"
285
+ end
286
+
287
+ def self.precedence
288
+ 30
289
+ end
290
+ end
291
+ end
292
+ end
@@ -0,0 +1,38 @@
1
+ require_relative "./node"
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Array < Node
6
+ def self.arity
7
+ end
8
+
9
+ def self.min_param_count
10
+ 0
11
+ end
12
+
13
+ def self.max_param_count
14
+ Float::INFINITY
15
+ end
16
+
17
+ def initialize(*elements)
18
+ @elements = *elements
19
+ end
20
+
21
+ def value(context = {})
22
+ @elements.map { |el| el.value(context) }
23
+ end
24
+
25
+ def dependencies(context = {})
26
+ @elements.flat_map { |el| el.dependencies(context) }
27
+ end
28
+
29
+ def type
30
+ nil
31
+ end
32
+
33
+ def accept(visitor)
34
+ visitor.visit_array(self)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ require_relative './operation'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Bitwise < Operation
6
+ def value(context = {})
7
+ left_value = left.value(context)
8
+ right_value = right.value(context)
9
+
10
+ left_value.public_send(operator, right_value)
11
+ rescue NoMethodError => e
12
+ raise Dentaku::ArgumentError.for(:invalid_operator, value: left_value, for: left_value.class)
13
+ rescue TypeError => e
14
+ raise Dentaku::ArgumentError.for(:invalid_operator, value: right_value, for: right_value.class)
15
+ end
16
+ end
17
+
18
+ class BitwiseOr < Bitwise
19
+ def operator
20
+ :|
21
+ end
22
+ end
23
+
24
+ class BitwiseAnd < Bitwise
25
+ def operator
26
+ :&
27
+ end
28
+ end
29
+
30
+ class BitwiseShiftLeft < Bitwise
31
+ def operator
32
+ :<<
33
+ end
34
+ end
35
+
36
+ class BitwiseShiftRight < Bitwise
37
+ def operator
38
+ :>>
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,38 @@
1
+ require 'dentaku/exceptions'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class CaseConditional < Node
6
+ attr_reader :when,
7
+ :then
8
+
9
+ def self.min_param_count
10
+ 2
11
+ end
12
+
13
+ def self.max_param_count
14
+ 2
15
+ end
16
+
17
+ def initialize(when_statement, then_statement)
18
+ @when = when_statement
19
+ unless @when.is_a?(AST::CaseWhen)
20
+ raise ParseError.for(:node_invalid), 'Expected first argument to be a CaseWhen'
21
+ end
22
+
23
+ @then = then_statement
24
+ unless @then.is_a?(AST::CaseThen)
25
+ raise ParseError.for(:node_invalid), 'Expected second argument to be a CaseThen'
26
+ end
27
+ end
28
+
29
+ def dependencies(context = {})
30
+ @when.dependencies(context) + @then.dependencies(context)
31
+ end
32
+
33
+ def accept(visitor)
34
+ visitor.visit_case_conditional(self)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ module Dentaku
2
+ module AST
3
+ class CaseElse < Node
4
+ attr_reader :node
5
+
6
+ def initialize(node)
7
+ @node = node
8
+ end
9
+
10
+ def value(context = {})
11
+ @node.value(context)
12
+ end
13
+
14
+ def dependencies(context = {})
15
+ @node.dependencies(context)
16
+ end
17
+
18
+ def self.arity
19
+ 1
20
+ end
21
+
22
+ def self.min_param_count
23
+ 1
24
+ end
25
+
26
+ def self.max_param_count
27
+ 1
28
+ end
29
+
30
+ def accept(visitor)
31
+ visitor.visit_else(self)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ module Dentaku
2
+ module AST
3
+ class CaseSwitchVariable < Node
4
+ attr_reader :node
5
+
6
+ def initialize(node)
7
+ @node = node
8
+ end
9
+
10
+ def value(context = {})
11
+ @node.value(context)
12
+ end
13
+
14
+ def dependencies(context = {})
15
+ @node.dependencies(context)
16
+ end
17
+
18
+ def self.arity
19
+ 1
20
+ end
21
+
22
+ def self.min_param_count
23
+ 1
24
+ end
25
+
26
+ def self.max_param_count
27
+ 1
28
+ end
29
+
30
+ def accept(visitor)
31
+ visitor.visit_switch(self)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ module Dentaku
2
+ module AST
3
+ class CaseThen < Node
4
+ attr_reader :node
5
+
6
+ def initialize(node)
7
+ @node = node
8
+ end
9
+
10
+ def value(context = {})
11
+ @node.value(context)
12
+ end
13
+
14
+ def dependencies(context = {})
15
+ @node.dependencies(context)
16
+ end
17
+
18
+ def self.arity
19
+ 1
20
+ end
21
+
22
+ def self.min_param_count
23
+ 1
24
+ end
25
+
26
+ def self.max_param_count
27
+ 1
28
+ end
29
+
30
+ def accept(visitor)
31
+ visitor.visit_then(self)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ module Dentaku
2
+ module AST
3
+ class CaseWhen < Operation
4
+ attr_reader :node
5
+
6
+ def initialize(node)
7
+ @node = node
8
+ end
9
+
10
+ def value(context = {})
11
+ @node.value(context)
12
+ end
13
+
14
+ def dependencies(context = {})
15
+ @node.dependencies(context)
16
+ end
17
+
18
+ def self.arity
19
+ 1
20
+ end
21
+
22
+ def self.min_param_count
23
+ 1
24
+ end
25
+
26
+ def self.max_param_count
27
+ 1
28
+ end
29
+
30
+ def accept(visitor)
31
+ visitor.visit_when(self)
32
+ end
33
+
34
+ def to_s
35
+ 'WHEN'
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,93 @@
1
+ require_relative './case/case_conditional'
2
+ require_relative './case/case_when'
3
+ require_relative './case/case_then'
4
+ require_relative './case/case_switch_variable'
5
+ require_relative './case/case_else'
6
+ require 'dentaku/exceptions'
7
+
8
+ module Dentaku
9
+ module AST
10
+ # Examples of using in a formula:
11
+ #
12
+ # CASE x WHEN 1 THEN 2 WHEN 3 THEN 4 ELSE END
13
+ #
14
+ # CASE fruit
15
+ # WHEN 'apple'
16
+ # THEN 1 * quantity
17
+ # WHEN 'banana'
18
+ # THEN 2 * quantity
19
+ # ELSE
20
+ # 3 * quantity
21
+ # END
22
+ class Case < Node
23
+ attr_reader :switch, :conditions, :else
24
+
25
+ def self.min_param_count
26
+ 2
27
+ end
28
+
29
+ def self.max_param_count
30
+ Float::INFINITY
31
+ end
32
+
33
+ def initialize(*nodes)
34
+ @switch = nodes.shift
35
+
36
+ unless @switch.is_a?(AST::CaseSwitchVariable)
37
+ raise ParseError.for(:node_invalid), 'Case missing switch variable'
38
+ end
39
+
40
+ @conditions = nodes
41
+
42
+ @else = nil
43
+ @else = @conditions.pop if @conditions.last.is_a?(AST::CaseElse)
44
+
45
+ @conditions.each do |condition|
46
+ unless condition.is_a?(AST::CaseConditional)
47
+ raise ParseError.for(:node_invalid), "#{condition} is not a CaseConditional"
48
+ end
49
+ end
50
+ end
51
+
52
+ def value(context = {})
53
+ switch_value = @switch.value(context)
54
+ @conditions.each do |condition|
55
+ if condition.when.value(context) == switch_value
56
+ return condition.then.value(context)
57
+ end
58
+ end
59
+
60
+ if @else
61
+ return @else.value(context)
62
+ else
63
+ raise ArgumentError.for(:invalid_value), "No block matched the switch value '#{switch_value}'"
64
+ end
65
+ end
66
+
67
+ def dependencies(context = {})
68
+ # TODO: should short-circuit
69
+ switch_dependencies(context) +
70
+ condition_dependencies(context) +
71
+ else_dependencies(context)
72
+ end
73
+
74
+ def accept(visitor)
75
+ visitor.visit_case(self)
76
+ end
77
+
78
+ private
79
+
80
+ def switch_dependencies(context = {})
81
+ @switch.dependencies(context)
82
+ end
83
+
84
+ def condition_dependencies(context = {})
85
+ @conditions.flat_map { |condition| condition.dependencies(context) }
86
+ end
87
+
88
+ def else_dependencies(context = {})
89
+ @else ? @else.dependencies(context) : []
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,50 @@
1
+ require_relative './operation'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Combinator < Operation
6
+ def initialize(*)
7
+ super
8
+
9
+ unless valid_node?(left)
10
+ raise NodeError.new(:logical, left.type, :left),
11
+ "#{self.class} requires logical operands"
12
+ end
13
+ unless valid_node?(right)
14
+ raise NodeError.new(:logical, right.type, :right),
15
+ "#{self.class} requires logical operands"
16
+ end
17
+ end
18
+
19
+ def type
20
+ :logical
21
+ end
22
+
23
+ private
24
+
25
+ def valid_node?(node)
26
+ node && (node.dependencies.any? || node.type == :logical)
27
+ end
28
+ end
29
+
30
+ class And < Combinator
31
+ def operator
32
+ :and
33
+ end
34
+
35
+ def value(context = {})
36
+ left.value(context) && right.value(context)
37
+ end
38
+ end
39
+
40
+ class Or < Combinator
41
+ def operator
42
+ :or
43
+ end
44
+
45
+ def value(context = {})
46
+ left.value(context) || right.value(context)
47
+ end
48
+ end
49
+ end
50
+ end