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.
- checksums.yaml +7 -0
- data/.github/workflows/rspec.yml +26 -0
- data/.github/workflows/rubocop.yml +14 -0
- data/.gitignore +14 -0
- data/.pryrc +2 -0
- data/.rubocop.yml +114 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +328 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +352 -0
- data/Rakefile +31 -0
- data/hayadentaku.gemspec +35 -0
- data/lib/dentaku/ast/access.rb +44 -0
- data/lib/dentaku/ast/arithmetic.rb +292 -0
- data/lib/dentaku/ast/array.rb +38 -0
- data/lib/dentaku/ast/bitwise.rb +42 -0
- data/lib/dentaku/ast/case/case_conditional.rb +38 -0
- data/lib/dentaku/ast/case/case_else.rb +35 -0
- data/lib/dentaku/ast/case/case_switch_variable.rb +35 -0
- data/lib/dentaku/ast/case/case_then.rb +35 -0
- data/lib/dentaku/ast/case/case_when.rb +39 -0
- data/lib/dentaku/ast/case.rb +93 -0
- data/lib/dentaku/ast/combinators.rb +50 -0
- data/lib/dentaku/ast/comparators.rb +88 -0
- data/lib/dentaku/ast/datetime.rb +8 -0
- data/lib/dentaku/ast/function.rb +56 -0
- data/lib/dentaku/ast/function_registry.rb +107 -0
- data/lib/dentaku/ast/functions/abs.rb +5 -0
- data/lib/dentaku/ast/functions/all.rb +19 -0
- data/lib/dentaku/ast/functions/and.rb +25 -0
- data/lib/dentaku/ast/functions/any.rb +19 -0
- data/lib/dentaku/ast/functions/avg.rb +13 -0
- data/lib/dentaku/ast/functions/count.rb +26 -0
- data/lib/dentaku/ast/functions/duration.rb +51 -0
- data/lib/dentaku/ast/functions/enum.rb +54 -0
- data/lib/dentaku/ast/functions/filter.rb +21 -0
- data/lib/dentaku/ast/functions/if.rb +47 -0
- data/lib/dentaku/ast/functions/intercept.rb +33 -0
- data/lib/dentaku/ast/functions/map.rb +19 -0
- data/lib/dentaku/ast/functions/max.rb +5 -0
- data/lib/dentaku/ast/functions/min.rb +5 -0
- data/lib/dentaku/ast/functions/mul.rb +12 -0
- data/lib/dentaku/ast/functions/not.rb +5 -0
- data/lib/dentaku/ast/functions/or.rb +25 -0
- data/lib/dentaku/ast/functions/pluck.rb +34 -0
- data/lib/dentaku/ast/functions/reduce.rb +60 -0
- data/lib/dentaku/ast/functions/round.rb +5 -0
- data/lib/dentaku/ast/functions/rounddown.rb +8 -0
- data/lib/dentaku/ast/functions/roundup.rb +8 -0
- data/lib/dentaku/ast/functions/ruby_math.rb +57 -0
- data/lib/dentaku/ast/functions/string_functions.rb +212 -0
- data/lib/dentaku/ast/functions/sum.rb +12 -0
- data/lib/dentaku/ast/functions/switch.rb +8 -0
- data/lib/dentaku/ast/functions/xor.rb +44 -0
- data/lib/dentaku/ast/grouping.rb +23 -0
- data/lib/dentaku/ast/identifier.rb +52 -0
- data/lib/dentaku/ast/literal.rb +30 -0
- data/lib/dentaku/ast/logical.rb +8 -0
- data/lib/dentaku/ast/negation.rb +54 -0
- data/lib/dentaku/ast/nil.rb +13 -0
- data/lib/dentaku/ast/node.rb +29 -0
- data/lib/dentaku/ast/numeric.rb +8 -0
- data/lib/dentaku/ast/operation.rb +44 -0
- data/lib/dentaku/ast/string.rb +15 -0
- data/lib/dentaku/ast.rb +42 -0
- data/lib/dentaku/bulk_expression_solver.rb +158 -0
- data/lib/dentaku/calculator.rb +192 -0
- data/lib/dentaku/date_arithmetic.rb +60 -0
- data/lib/dentaku/dependency_resolver.rb +29 -0
- data/lib/dentaku/exceptions.rb +116 -0
- data/lib/dentaku/flat_hash.rb +161 -0
- data/lib/dentaku/parser.rb +318 -0
- data/lib/dentaku/print_visitor.rb +112 -0
- data/lib/dentaku/string_casing.rb +7 -0
- data/lib/dentaku/token.rb +48 -0
- data/lib/dentaku/token_matcher.rb +138 -0
- data/lib/dentaku/token_matchers.rb +29 -0
- data/lib/dentaku/token_scanner.rb +240 -0
- data/lib/dentaku/tokenizer.rb +127 -0
- data/lib/dentaku/version.rb +3 -0
- data/lib/dentaku/visitor/infix.rb +86 -0
- data/lib/dentaku.rb +69 -0
- data/spec/ast/abs_spec.rb +26 -0
- data/spec/ast/addition_spec.rb +67 -0
- data/spec/ast/all_spec.rb +38 -0
- data/spec/ast/and_function_spec.rb +35 -0
- data/spec/ast/and_spec.rb +32 -0
- data/spec/ast/any_spec.rb +36 -0
- data/spec/ast/arithmetic_spec.rb +147 -0
- data/spec/ast/avg_spec.rb +42 -0
- data/spec/ast/case_spec.rb +84 -0
- data/spec/ast/comparator_spec.rb +87 -0
- data/spec/ast/count_spec.rb +40 -0
- data/spec/ast/division_spec.rb +64 -0
- data/spec/ast/filter_spec.rb +25 -0
- data/spec/ast/function_spec.rb +69 -0
- data/spec/ast/intercept_spec.rb +30 -0
- data/spec/ast/map_spec.rb +40 -0
- data/spec/ast/max_spec.rb +33 -0
- data/spec/ast/min_spec.rb +33 -0
- data/spec/ast/mul_spec.rb +43 -0
- data/spec/ast/negation_spec.rb +48 -0
- data/spec/ast/node_spec.rb +43 -0
- data/spec/ast/numeric_spec.rb +16 -0
- data/spec/ast/or_spec.rb +35 -0
- data/spec/ast/pluck_spec.rb +49 -0
- data/spec/ast/reduce_spec.rb +22 -0
- data/spec/ast/round_spec.rb +35 -0
- data/spec/ast/rounddown_spec.rb +35 -0
- data/spec/ast/roundup_spec.rb +35 -0
- data/spec/ast/string_functions_spec.rb +217 -0
- data/spec/ast/sum_spec.rb +43 -0
- data/spec/ast/switch_spec.rb +30 -0
- data/spec/ast/xor_spec.rb +35 -0
- data/spec/benchmark.rb +70 -0
- data/spec/bulk_expression_solver_spec.rb +241 -0
- data/spec/calculator_spec.rb +1003 -0
- data/spec/dentaku_spec.rb +52 -0
- data/spec/dependency_resolver_spec.rb +18 -0
- data/spec/exceptions_spec.rb +9 -0
- data/spec/external_function_spec.rb +177 -0
- data/spec/parser_spec.rb +183 -0
- data/spec/print_visitor_spec.rb +77 -0
- data/spec/spec_helper.rb +69 -0
- data/spec/token_matcher_spec.rb +134 -0
- data/spec/token_scanner_spec.rb +49 -0
- data/spec/token_spec.rb +16 -0
- data/spec/tokenizer_spec.rb +375 -0
- data/spec/visitor/infix_spec.rb +52 -0
- data/spec/visitor_spec.rb +139 -0
- 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
|