dentaku 3.4.2 → 3.5.1

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +4 -5
  4. data/lib/dentaku/ast/access.rb +6 -0
  5. data/lib/dentaku/ast/arithmetic.rb +23 -18
  6. data/lib/dentaku/ast/array.rb +4 -0
  7. data/lib/dentaku/ast/bitwise.rb +30 -5
  8. data/lib/dentaku/ast/case/case_conditional.rb +4 -0
  9. data/lib/dentaku/ast/case/case_else.rb +6 -0
  10. data/lib/dentaku/ast/case/case_switch_variable.rb +6 -0
  11. data/lib/dentaku/ast/case/case_then.rb +6 -0
  12. data/lib/dentaku/ast/case/case_when.rb +10 -0
  13. data/lib/dentaku/ast/case.rb +6 -0
  14. data/lib/dentaku/ast/comparators.rb +35 -35
  15. data/lib/dentaku/ast/function.rb +6 -8
  16. data/lib/dentaku/ast/functions/all.rb +4 -17
  17. data/lib/dentaku/ast/functions/any.rb +4 -17
  18. data/lib/dentaku/ast/functions/duration.rb +2 -2
  19. data/lib/dentaku/ast/functions/enum.rb +37 -0
  20. data/lib/dentaku/ast/functions/filter.rb +4 -17
  21. data/lib/dentaku/ast/functions/if.rb +4 -0
  22. data/lib/dentaku/ast/functions/map.rb +3 -16
  23. data/lib/dentaku/ast/functions/pluck.rb +8 -7
  24. data/lib/dentaku/ast/functions/ruby_math.rb +3 -2
  25. data/lib/dentaku/ast/functions/xor.rb +44 -0
  26. data/lib/dentaku/ast/identifier.rb +8 -0
  27. data/lib/dentaku/ast/literal.rb +10 -0
  28. data/lib/dentaku/ast/negation.rb +4 -0
  29. data/lib/dentaku/ast/nil.rb +4 -0
  30. data/lib/dentaku/ast/node.rb +4 -0
  31. data/lib/dentaku/ast/operation.rb +9 -0
  32. data/lib/dentaku/ast/string.rb +7 -0
  33. data/lib/dentaku/ast.rb +2 -0
  34. data/lib/dentaku/bulk_expression_solver.rb +1 -5
  35. data/lib/dentaku/exceptions.rb +2 -2
  36. data/lib/dentaku/parser.rb +10 -3
  37. data/lib/dentaku/print_visitor.rb +101 -0
  38. data/lib/dentaku/token_scanner.rb +3 -3
  39. data/lib/dentaku/version.rb +1 -1
  40. data/lib/dentaku/visitor/infix.rb +82 -0
  41. data/spec/ast/all_spec.rb +25 -0
  42. data/spec/ast/any_spec.rb +23 -0
  43. data/spec/ast/arithmetic_spec.rb +7 -0
  44. data/spec/ast/comparator_spec.rb +14 -9
  45. data/spec/ast/filter_spec.rb +7 -0
  46. data/spec/ast/function_spec.rb +5 -0
  47. data/spec/ast/map_spec.rb +12 -0
  48. data/spec/ast/or_spec.rb +1 -1
  49. data/spec/ast/pluck_spec.rb +32 -0
  50. data/spec/ast/xor_spec.rb +35 -0
  51. data/spec/bulk_expression_solver_spec.rb +9 -0
  52. data/spec/calculator_spec.rb +71 -2
  53. data/spec/parser_spec.rb +18 -3
  54. data/spec/print_visitor_spec.rb +66 -0
  55. data/spec/tokenizer_spec.rb +18 -0
  56. data/spec/visitor/infix_spec.rb +31 -0
  57. data/spec/visitor_spec.rb +138 -0
  58. metadata +24 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3dadbc209e58b2d2206dfaf48b83be3224db37f71d60cf590aadb63be9f4bebd
4
- data.tar.gz: 86530c9c3333139994775a6bde16202005d89161a0b98670db58bcdda47f8e55
3
+ metadata.gz: d5592654ee45adeb24167b584374fb2d69bc67d1db4d5f4ac99050130f187f8f
4
+ data.tar.gz: 31d3952b08887ae934661f4c0ecc5c298b2f36f0f8365cc1503495eb044eb558
5
5
  SHA512:
6
- metadata.gz: f132401168218a3c021124ddabb56bbf485550bdff60a03a291ad811d8628e25caab2ba36e4bed4f8148bde048e56bed1fa31480774f2c19617a79282da81280
7
- data.tar.gz: 3b6a8fc5e40988a9442be9dbdf890dc813e13b95fc2d0cb2f60252b2492d07fdf4ba47cdfedd0a85c002aaaa23a83bffb141b8f8f31fbf518678439d55c1e795
6
+ metadata.gz: d8e0d003f897e06173c91b200e62d9fed12ec3bacfe0f2ecc3f3705a1cbf914d1705948b79962f5ac6a5397138ff89c2123aa5c3753963f0046a88280b29825b
7
+ data.tar.gz: 5f48d3b8fef4e56ed308e717da88937bef508e47dfca4c3e16610aff7523f11405e53e2b55d00c53b5eca8efbe7be64df0d0d12e7ddafbc61099cde08bf92a53
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Change Log
2
2
 
3
+ ## [v3.5.1]
4
+ - add bitwise shift left and shift right operators
5
+ - improve numeric conversions
6
+ - improve parse exceptions
7
+ - improve bitwise exceptions
8
+ - include variable name in bulk expression exceptions
9
+
10
+ ## [v3.5.0]
11
+ - fix bug with function argument count
12
+ - add XOR operator
13
+ - make function args publicly accessible
14
+ - better argument handling for collection functions
15
+ - better dependency reporting for collection functions
16
+ - allow ruby math-backed functions to be serialized
17
+ - improve scientific notation handling
18
+ - improve comparator argument errors
19
+ - respect case sensitivity in nested case statments
20
+ - add visitor pattern
21
+
3
22
  ## [v3.4.2]
4
23
  - add FILTER function
5
24
  - add concurrent-ruby dependency to make global calculator object thread safe
@@ -212,6 +231,9 @@
212
231
  ## [v0.1.0] 2012-01-20
213
232
  - initial release
214
233
 
234
+ [Unreleased]: https://github.com/rubysolo/dentaku/compare/v3.5.1...HEAD
235
+ [v3.5.1]: https://github.com/rubysolo/dentaku/compare/v3.5.0...v3.5.1
236
+ [v3.5.0]: https://github.com/rubysolo/dentaku/compare/v3.4.2...v3.5.0
215
237
  [v3.4.2]: https://github.com/rubysolo/dentaku/compare/v3.4.1...v3.4.2
216
238
  [v3.4.1]: https://github.com/rubysolo/dentaku/compare/v3.4.0...v3.4.1
217
239
  [v3.4.0]: https://github.com/rubysolo/dentaku/compare/v3.3.4...v3.4.0
data/README.md CHANGED
@@ -5,7 +5,6 @@ Dentaku
5
5
  [![Gem Version](https://badge.fury.io/rb/dentaku.png)](http://badge.fury.io/rb/dentaku)
6
6
  [![Build Status](https://travis-ci.org/rubysolo/dentaku.png?branch=master)](https://travis-ci.org/rubysolo/dentaku)
7
7
  [![Code Climate](https://codeclimate.com/github/rubysolo/dentaku.png)](https://codeclimate.com/github/rubysolo/dentaku)
8
- [![Hakiri](https://hakiri.io/github/rubysolo/dentaku/master.svg)](https://hakiri.io/github/rubysolo/dentaku)
9
8
  [![Coverage](https://codecov.io/gh/rubysolo/dentaku/branch/master/graph/badge.svg)](https://codecov.io/gh/rubysolo/dentaku)
10
9
 
11
10
 
@@ -138,17 +137,17 @@ application, AST caching will consume more memory with each new formula.
138
137
  BUILT-IN OPERATORS AND FUNCTIONS
139
138
  ---------------------------------
140
139
 
141
- Math: `+`, `-`, `*`, `/`, `%`, `^`, `|`, `&`
140
+ Math: `+`, `-`, `*`, `/`, `%`, `^`, `|`, `&`, `<<`, `>>`
142
141
 
143
142
  Also, all functions from Ruby's Math module, including `SIN`, `COS`, `TAN`, etc.
144
143
 
145
144
  Comparison: `<`, `>`, `<=`, `>=`, `<>`, `!=`, `=`,
146
145
 
147
- Logic: `IF`, `AND`, `OR`, `NOT`, `SWITCH`
146
+ Logic: `IF`, `AND`, `OR`, `XOR`, `NOT`, `SWITCH`
148
147
 
149
148
  Numeric: `MIN`, `MAX`, `SUM`, `AVG`, `COUNT`, `ROUND`, `ROUNDDOWN`, `ROUNDUP`
150
149
 
151
- Selections: `CASE` (syntax see [spec](https://github.com/rubysolo/dentaku/blob/master/spec/calculator_spec.rb#L292))
150
+ Selections: `CASE` (syntax see [spec](https://github.com/rubysolo/dentaku/blob/master/spec/calculator_spec.rb#L593))
152
151
 
153
152
  String: `LEFT`, `RIGHT`, `MID`, `LEN`, `FIND`, `SUBSTITUTE`, `CONCAT`, `CONTAINS`
154
153
 
@@ -323,7 +322,7 @@ LICENSE
323
322
 
324
323
  (The MIT License)
325
324
 
326
- Copyright © 2012-2019 Solomon White
325
+ Copyright © 2012-2022 Solomon White
327
326
 
328
327
  Permission is hereby granted, free of charge, to any person obtaining a copy of
329
328
  this software and associated documentation files (the ‘Software’), to deal in
@@ -3,6 +3,8 @@ require_relative "./node"
3
3
  module Dentaku
4
4
  module AST
5
5
  class Access < Node
6
+ attr_reader :structure, :index
7
+
6
8
  def self.arity
7
9
  2
8
10
  end
@@ -36,6 +38,10 @@ module Dentaku
36
38
  def type
37
39
  nil
38
40
  end
41
+
42
+ def accept(visitor)
43
+ visitor.visit_access(self)
44
+ end
39
45
  end
40
46
  end
41
47
  end
@@ -31,33 +31,34 @@ module Dentaku
31
31
  def value(context = {})
32
32
  l = cast(left.value(context))
33
33
  r = cast(right.value(context))
34
- begin
35
- l.public_send(operator, r)
36
- rescue ::TypeError => e
37
- # Right cannot be converted to a suitable type for left. e.g. [] + 1
38
- raise Dentaku::ArgumentError.for(:incompatible_type, value: r, for: l.class), e.message
39
- end
34
+
35
+ l.public_send(operator, r)
36
+ rescue ::TypeError => e
37
+ # Right cannot be converted to a suitable type for left. e.g. [] + 1
38
+ raise Dentaku::ArgumentError.for(:incompatible_type, value: r, for: l.class), e.message
40
39
  end
41
40
 
42
41
  private
43
42
 
44
- def cast(val, prefer_integer = true)
43
+ def cast(val)
45
44
  validate_value(val)
46
- numeric(val, prefer_integer)
45
+ numeric(val)
47
46
  end
48
47
 
49
- def numeric(val, prefer_integer)
50
- v = BigDecimal(val, Float::DIG + 1)
51
- v = v.to_i if prefer_integer && v.frac.zero?
52
- v
53
- rescue ::TypeError
54
- # If we got a TypeError BigDecimal or to_i failed;
55
- # let value through so ruby things like Time - integer work
56
- val
48
+ def numeric(val)
49
+ case val.to_s
50
+ when /\A\d*\.\d+\z/ then decimal(val)
51
+ when /\A-?\d+\z/ then val.to_i
52
+ else val
53
+ end
54
+ end
55
+
56
+ def decimal(val)
57
+ BigDecimal(val.to_s, Float::DIG + 1)
57
58
  end
58
59
 
59
60
  def valid_node?(node)
60
- node && (node.type == :numeric || node.dependencies.any?)
61
+ node && (node.type == :numeric || node.type == :integer || node.dependencies.any?)
61
62
  end
62
63
 
63
64
  def valid_left?
@@ -143,7 +144,7 @@ module Dentaku
143
144
  end
144
145
 
145
146
  def value(context = {})
146
- r = cast(right.value(context), false)
147
+ r = decimal(cast(right.value(context)))
147
148
  raise Dentaku::ZeroDivisionError if r.zero?
148
149
 
149
150
  cast(cast(left.value(context)) / r)
@@ -220,6 +221,10 @@ module Dentaku
220
221
  :**
221
222
  end
222
223
 
224
+ def display_operator
225
+ "^"
226
+ end
227
+
223
228
  def self.precedence
224
229
  30
225
230
  end
@@ -32,6 +32,10 @@ module Dentaku
32
32
  def type
33
33
  nil
34
34
  end
35
+
36
+ def accept(visitor)
37
+ visitor.visit_array(self)
38
+ end
35
39
  end
36
40
  end
37
41
  end
@@ -2,15 +2,40 @@ require_relative './operation'
2
2
 
3
3
  module Dentaku
4
4
  module AST
5
- class BitwiseOr < Operation
5
+ class Bitwise < Operation
6
6
  def value(context = {})
7
- left.value(context) | right.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)
8
15
  end
9
16
  end
10
17
 
11
- class BitwiseAnd < Operation
12
- def value(context = {})
13
- left.value(context) & right.value(context)
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
+ :>>
14
39
  end
15
40
  end
16
41
  end
@@ -29,6 +29,10 @@ module Dentaku
29
29
  def dependencies(context = {})
30
30
  @when.dependencies(context) + @then.dependencies(context)
31
31
  end
32
+
33
+ def accept(visitor)
34
+ visitor.visit_case_conditional(self)
35
+ end
32
36
  end
33
37
  end
34
38
  end
@@ -1,6 +1,8 @@
1
1
  module Dentaku
2
2
  module AST
3
3
  class CaseElse < Node
4
+ attr_reader :node
5
+
4
6
  def initialize(node)
5
7
  @node = node
6
8
  end
@@ -24,6 +26,10 @@ module Dentaku
24
26
  def self.max_param_count
25
27
  1
26
28
  end
29
+
30
+ def accept(visitor)
31
+ visitor.visit_else(self)
32
+ end
27
33
  end
28
34
  end
29
35
  end
@@ -1,6 +1,8 @@
1
1
  module Dentaku
2
2
  module AST
3
3
  class CaseSwitchVariable < Node
4
+ attr_reader :node
5
+
4
6
  def initialize(node)
5
7
  @node = node
6
8
  end
@@ -24,6 +26,10 @@ module Dentaku
24
26
  def self.max_param_count
25
27
  1
26
28
  end
29
+
30
+ def accept(visitor)
31
+ visitor.visit_switch(self)
32
+ end
27
33
  end
28
34
  end
29
35
  end
@@ -1,6 +1,8 @@
1
1
  module Dentaku
2
2
  module AST
3
3
  class CaseThen < Node
4
+ attr_reader :node
5
+
4
6
  def initialize(node)
5
7
  @node = node
6
8
  end
@@ -24,6 +26,10 @@ module Dentaku
24
26
  def self.max_param_count
25
27
  1
26
28
  end
29
+
30
+ def accept(visitor)
31
+ visitor.visit_then(self)
32
+ end
27
33
  end
28
34
  end
29
35
  end
@@ -1,6 +1,8 @@
1
1
  module Dentaku
2
2
  module AST
3
3
  class CaseWhen < Operation
4
+ attr_reader :node
5
+
4
6
  def initialize(node)
5
7
  @node = node
6
8
  end
@@ -24,6 +26,14 @@ module Dentaku
24
26
  def self.max_param_count
25
27
  1
26
28
  end
29
+
30
+ def accept(visitor)
31
+ visitor.visit_when(self)
32
+ end
33
+
34
+ def to_s
35
+ 'WHEN'
36
+ end
27
37
  end
28
38
  end
29
39
  end
@@ -8,6 +8,8 @@ require 'dentaku/exceptions'
8
8
  module Dentaku
9
9
  module AST
10
10
  class Case < Node
11
+ attr_reader :switch, :conditions, :else
12
+
11
13
  def self.min_param_count
12
14
  2
13
15
  end
@@ -57,6 +59,10 @@ module Dentaku
57
59
  else_dependencies(context)
58
60
  end
59
61
 
62
+ def accept(visitor)
63
+ visitor.visit_case(self)
64
+ end
65
+
60
66
  private
61
67
 
62
68
  def switch_dependencies(context = {})
@@ -15,74 +15,74 @@ module Dentaku
15
15
  raise NotImplementedError
16
16
  end
17
17
 
18
+ def value(context = {})
19
+ l = validate_value(cast(left.value(context)))
20
+ r = validate_value(cast(right.value(context)))
21
+
22
+ l.public_send(operator, r)
23
+ rescue ::ArgumentError => e
24
+ raise Dentaku::ArgumentError.for(:incompatible_type, value: r, for: l.class), e.message
25
+ end
26
+
18
27
  private
19
28
 
20
- def value
21
- yield
22
- rescue ::ArgumentError => argument_error
23
- raise Dentaku::ArgumentError, argument_error.message
24
- rescue NoMethodError => no_method_error
25
- raise Dentaku::Error, no_method_error.message
29
+ def cast(val)
30
+ return val unless val.is_a?(::String)
31
+ return val if val.empty?
32
+ return val unless val.match?(/\A-?\d*(\.\d+)?\z/)
33
+
34
+ v = BigDecimal(val, Float::DIG + 1)
35
+ v = v.to_i if v.frac.zero?
36
+ v
26
37
  end
27
- end
28
38
 
29
- class LessThan < Comparator
30
- def value(context = {})
31
- super() { left.value(context) < right.value(context) }
39
+ def validate_value(value)
40
+ unless value.respond_to?(operator)
41
+ raise Dentaku::ArgumentError.for(:invalid_operator, operation: self.class, operator: operator),
42
+ "#{ self.class } requires operands that respond to #{operator}"
43
+ end
44
+
45
+ value
32
46
  end
47
+ end
33
48
 
49
+ class LessThan < Comparator
34
50
  def operator
35
- return :<
51
+ :<
36
52
  end
37
53
  end
38
54
 
39
55
  class LessThanOrEqual < Comparator
40
- def value(context = {})
41
- super() { left.value(context) <= right.value(context) }
42
- end
43
-
44
56
  def operator
45
- return :<=
57
+ :<=
46
58
  end
47
59
  end
48
60
 
49
61
  class GreaterThan < Comparator
50
- def value(context = {})
51
- super() { left.value(context) > right.value(context) }
52
- end
53
-
54
62
  def operator
55
- return :>
63
+ :>
56
64
  end
57
65
  end
58
66
 
59
67
  class GreaterThanOrEqual < Comparator
60
- def value(context = {})
61
- super() { left.value(context) >= right.value(context) }
62
- end
63
-
64
68
  def operator
65
- return :>=
69
+ :>=
66
70
  end
67
71
  end
68
72
 
69
73
  class NotEqual < Comparator
70
- def value(context = {})
71
- super() { left.value(context) != right.value(context) }
72
- end
73
-
74
74
  def operator
75
- return :!=
75
+ :!=
76
76
  end
77
77
  end
78
78
 
79
79
  class Equal < Comparator
80
- def value(context = {})
81
- super() { left.value(context) == right.value(context) }
80
+ def operator
81
+ :==
82
82
  end
83
83
 
84
- def operator
85
- return :==
84
+ def display_operator
85
+ "="
86
86
  end
87
87
  end
88
88
  end
@@ -4,6 +4,8 @@ require_relative 'function_registry'
4
4
  module Dentaku
5
5
  module AST
6
6
  class Function < Node
7
+ attr_reader :args
8
+
7
9
  # @return [Integer] with the number of significant decimal digits to use.
8
10
  DIG = Float::DIG + 1
9
11
 
@@ -11,19 +13,15 @@ module Dentaku
11
13
  @args = args
12
14
  end
13
15
 
16
+ def accept(visitor)
17
+ visitor.visit_function(self)
18
+ end
19
+
14
20
  def dependencies(context = {})
15
- deferred = deferred_args
16
21
  @args.each_with_index
17
- .reject { |_, i| deferred.include? i }
18
22
  .flat_map { |a, _| a.dependencies(context) }
19
23
  end
20
24
 
21
- # override if your function implementation needs to defer evaluation of
22
- # any arguments
23
- def deferred_args
24
- []
25
- end
26
-
27
25
  def self.get(name)
28
26
  registry.get(name)
29
27
  end
@@ -1,27 +1,14 @@
1
- require_relative '../function'
2
- require_relative '../../exceptions'
1
+ require_relative './enum'
3
2
 
4
3
  module Dentaku
5
4
  module AST
6
- class All < Function
7
- def self.min_param_count
8
- 3
9
- end
10
-
11
- def self.max_param_count
12
- 3
13
- end
14
-
15
- def deferred_args
16
- [1, 2]
17
- end
18
-
5
+ class All < Enum
19
6
  def value(context = {})
20
- collection = @args[0].value(context)
7
+ collection = Array(@args[0].value(context))
21
8
  item_identifier = @args[1].identifier
22
9
  expression = @args[2]
23
10
 
24
- Array(collection).all? do |item_value|
11
+ collection.all? do |item_value|
25
12
  expression.value(
26
13
  context.merge(
27
14
  FlatHash.from_hash_with_intermediates(item_identifier => item_value)
@@ -1,27 +1,14 @@
1
- require_relative '../function'
2
- require_relative '../../exceptions'
1
+ require_relative './enum'
3
2
 
4
3
  module Dentaku
5
4
  module AST
6
- class Any < Function
7
- def self.min_param_count
8
- 3
9
- end
10
-
11
- def self.max_param_count
12
- 3
13
- end
14
-
15
- def deferred_args
16
- [1, 2]
17
- end
18
-
5
+ class Any < Enum
19
6
  def value(context = {})
20
- collection = @args[0].value(context)
7
+ collection = Array(@args[0].value(context))
21
8
  item_identifier = @args[1].identifier
22
9
  expression = @args[2]
23
10
 
24
- Array(collection).any? do |item_value|
11
+ collection.any? do |item_value|
25
12
  expression.value(
26
13
  context.merge(
27
14
  FlatHash.from_hash_with_intermediates(item_identifier => item_value)
@@ -4,11 +4,11 @@ module Dentaku
4
4
  module AST
5
5
  class Duration < Function
6
6
  def self.min_param_count
7
- 1
7
+ 2
8
8
  end
9
9
 
10
10
  def self.max_param_count
11
- 1
11
+ 2
12
12
  end
13
13
 
14
14
  class Value
@@ -0,0 +1,37 @@
1
+ require_relative '../function'
2
+ require_relative '../../exceptions'
3
+
4
+ module Dentaku
5
+ module AST
6
+ class Enum < Function
7
+ def self.min_param_count
8
+ 3
9
+ end
10
+
11
+ def self.max_param_count
12
+ 3
13
+ end
14
+
15
+ def dependencies(context = {})
16
+ validate_identifier(@args[1])
17
+
18
+ collection = @args[0]
19
+ item_identifier = @args[1].identifier
20
+ expression = @args[2]
21
+
22
+ collection_deps = collection.dependencies(context)
23
+ expression_deps = (expression&.dependencies(context) || []).reject do |i|
24
+ i == item_identifier || i.start_with?("#{item_identifier}.")
25
+ end
26
+
27
+ collection_deps + expression_deps
28
+ end
29
+
30
+ def validate_identifier(arg, message = "#{name}() requires second argument to be an identifier")
31
+ unless arg.is_a?(Identifier)
32
+ raise ArgumentError.for(:incompatible_type, value: arg, for: Identifier), message
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,27 +1,14 @@
1
- require_relative '../function'
2
- require_relative '../../exceptions'
1
+ require_relative './enum'
3
2
 
4
3
  module Dentaku
5
4
  module AST
6
- class Filter < Function
7
- def self.min_param_count
8
- 3
9
- end
10
-
11
- def self.max_param_count
12
- 3
13
- end
14
-
15
- def deferred_args
16
- [1, 2]
17
- end
18
-
5
+ class Filter < Enum
19
6
  def value(context = {})
20
- collection = @args[0].value(context)
7
+ collection = Array(@args[0].value(context))
21
8
  item_identifier = @args[1].identifier
22
9
  expression = @args[2]
23
10
 
24
- Array(collection).select do |item_value|
11
+ collection.select do |item_value|
25
12
  expression.value(
26
13
  context.merge(
27
14
  FlatHash.from_hash_with_intermediates(item_identifier => item_value)
@@ -19,6 +19,10 @@ module Dentaku
19
19
  @right = right
20
20
  end
21
21
 
22
+ def args
23
+ [predicate, left, right]
24
+ end
25
+
22
26
  def value(context = {})
23
27
  predicate.value(context) ? left.value(context) : right.value(context)
24
28
  end