dentaku 3.4.2 → 3.5.1

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