dentaku 3.4.0 → 3.5.0

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -3
  3. data/CHANGELOG.md +28 -0
  4. data/README.md +5 -4
  5. data/dentaku.gemspec +2 -0
  6. data/lib/dentaku/ast/access.rb +6 -0
  7. data/lib/dentaku/ast/arithmetic.rb +5 -1
  8. data/lib/dentaku/ast/array.rb +4 -0
  9. data/lib/dentaku/ast/bitwise.rb +8 -0
  10. data/lib/dentaku/ast/case/case_conditional.rb +4 -0
  11. data/lib/dentaku/ast/case/case_else.rb +6 -0
  12. data/lib/dentaku/ast/case/case_switch_variable.rb +6 -0
  13. data/lib/dentaku/ast/case/case_then.rb +6 -0
  14. data/lib/dentaku/ast/case/case_when.rb +10 -0
  15. data/lib/dentaku/ast/case.rb +6 -0
  16. data/lib/dentaku/ast/comparators.rb +25 -35
  17. data/lib/dentaku/ast/function.rb +6 -8
  18. data/lib/dentaku/ast/functions/all.rb +6 -19
  19. data/lib/dentaku/ast/functions/any.rb +6 -19
  20. data/lib/dentaku/ast/functions/duration.rb +2 -2
  21. data/lib/dentaku/ast/functions/enum.rb +37 -0
  22. data/lib/dentaku/ast/functions/filter.rb +23 -0
  23. data/lib/dentaku/ast/functions/if.rb +4 -0
  24. data/lib/dentaku/ast/functions/map.rb +5 -18
  25. data/lib/dentaku/ast/functions/mul.rb +2 -3
  26. data/lib/dentaku/ast/functions/pluck.rb +8 -7
  27. data/lib/dentaku/ast/functions/ruby_math.rb +6 -3
  28. data/lib/dentaku/ast/functions/sum.rb +2 -3
  29. data/lib/dentaku/ast/functions/xor.rb +44 -0
  30. data/lib/dentaku/ast/identifier.rb +11 -3
  31. data/lib/dentaku/ast/literal.rb +10 -0
  32. data/lib/dentaku/ast/negation.rb +4 -0
  33. data/lib/dentaku/ast/nil.rb +4 -0
  34. data/lib/dentaku/ast/node.rb +4 -0
  35. data/lib/dentaku/ast/operation.rb +9 -0
  36. data/lib/dentaku/ast/string.rb +7 -0
  37. data/lib/dentaku/ast.rb +2 -0
  38. data/lib/dentaku/bulk_expression_solver.rb +5 -3
  39. data/lib/dentaku/calculator.rb +1 -1
  40. data/lib/dentaku/parser.rb +5 -3
  41. data/lib/dentaku/print_visitor.rb +101 -0
  42. data/lib/dentaku/token_scanner.rb +1 -1
  43. data/lib/dentaku/version.rb +1 -1
  44. data/lib/dentaku/visitor/infix.rb +82 -0
  45. data/lib/dentaku.rb +4 -3
  46. data/spec/ast/all_spec.rb +25 -0
  47. data/spec/ast/any_spec.rb +23 -0
  48. data/spec/ast/comparator_spec.rb +6 -9
  49. data/spec/ast/filter_spec.rb +25 -0
  50. data/spec/ast/function_spec.rb +5 -0
  51. data/spec/ast/map_spec.rb +27 -0
  52. data/spec/ast/max_spec.rb +13 -0
  53. data/spec/ast/min_spec.rb +13 -0
  54. data/spec/ast/mul_spec.rb +3 -2
  55. data/spec/ast/pluck_spec.rb +32 -0
  56. data/spec/ast/sum_spec.rb +3 -2
  57. data/spec/ast/xor_spec.rb +35 -0
  58. data/spec/bulk_expression_solver_spec.rb +10 -0
  59. data/spec/calculator_spec.rb +66 -2
  60. data/spec/parser_spec.rb +18 -3
  61. data/spec/print_visitor_spec.rb +66 -0
  62. data/spec/tokenizer_spec.rb +6 -0
  63. data/spec/visitor/infix_spec.rb +31 -0
  64. data/spec/visitor_spec.rb +137 -0
  65. metadata +43 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1eb86933b899627333d93e993cd2d093a11f4ca54b2e0f6668c3580a8f133975
4
- data.tar.gz: 004bd53fa5c3c6815bafdbf98788de96757b688a6ba217b7f8e96be31a4f7d5d
3
+ metadata.gz: 9a0b093e29b178197c0f92c1f63ec56342716c1fa84a4d12a73a7d355d42bc76
4
+ data.tar.gz: 480ccf9248568006227518363a0ef6350843007389f2e94ac5610ae62028e87e
5
5
  SHA512:
6
- metadata.gz: 32f0e44638fc3738a3df5ba6a04abdb4ee66177049242260e26fea1ad6844a66a35e65ca5a27448f118838e6ccecd133f46650e9fbff5d878bbc14b1e7d952e8
7
- data.tar.gz: 8081bcf51e30daa33dbaba3f8796951151c1bfd2e3975f63e5291aac7e04e09f2fc2f00aa956cfdad99caf1b8a8fb4f04b2c0789d8909b0982372e75dc7f7fc0
6
+ metadata.gz: 904292b2d2fd834701fd18900d689b9125579d58421fc58aaad03cd75c0fb556eeaeb5635dcd034a3f29e9f70f5c8fe0370e605acefd599c3dd75eae64436fdd
7
+ data.tar.gz: 3b6ed8763b9241e55e85f1a1e5a09d2652ec91eee21c080ca825029e04867b8fe65b128ddfc557cab3ef4fae76abb30b936f5971326355ea763081f90ba66638
data/.travis.yml CHANGED
@@ -1,9 +1,10 @@
1
1
  language: ruby
2
2
  sudo: false
3
3
  rvm:
4
- - 2.5.7
5
- - 2.6.5
6
- - 2.7.0
4
+ - 2.5.9
5
+ - 2.6.7
6
+ - 2.7.3
7
+ - 3.0.1
7
8
  before_install:
8
9
  - gem update bundler
9
10
  - gem update --system
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Change Log
2
2
 
3
+ ## [v3.5.0]
4
+ - fix bug with function argument count
5
+ - add XOR operator
6
+ - make function args publicly accessible
7
+ - better argument handling for collection functions
8
+ - better dependency reporting for collection functions
9
+ - allow ruby math-backed functions to be serialized
10
+ - improve scientific notation handling
11
+ - improve comparator argument errors
12
+ - respect case sensitivity in nested case statments
13
+ - add visitor pattern
14
+
15
+ ## [v3.4.2]
16
+ - add FILTER function
17
+ - add concurrent-ruby dependency to make global calculator object thread safe
18
+ - add Ruby 3 support
19
+ - allow formulas to access intermediate context values
20
+ - fix incorrect Ruby Math function return type
21
+ - fix context mutation bug
22
+ - fix dependency resolution bug
23
+
24
+ ## [v3.4.1] 2020-12-12
25
+ - prevent extra evaluations in bulk expression solver
26
+
3
27
  ## [v3.4.0] 2020-12-07
4
28
  - allow access to intermediate values of flattened hashes
5
29
  - catch invalid array syntax in the parse phase
@@ -12,6 +36,7 @@
12
36
  - add enum functions `ANY`, `ALL`, `MAP` and `PLUCK`
13
37
  - allow self-referential formulas in bulk expression solver
14
38
  - misc internal fixes and enhancements
39
+
15
40
  ## [v3.3.4] 2019-11-21
16
41
  - bugfix release
17
42
 
@@ -199,6 +224,9 @@
199
224
  ## [v0.1.0] 2012-01-20
200
225
  - initial release
201
226
 
227
+ [v3.5.0]: https://github.com/rubysolo/dentaku/compare/v3.4.2...v3.5.0
228
+ [v3.4.2]: https://github.com/rubysolo/dentaku/compare/v3.4.1...v3.4.2
229
+ [v3.4.1]: https://github.com/rubysolo/dentaku/compare/v3.4.0...v3.4.1
202
230
  [v3.4.0]: https://github.com/rubysolo/dentaku/compare/v3.3.4...v3.4.0
203
231
  [v3.3.4]: https://github.com/rubysolo/dentaku/compare/v3.3.3...v3.3.4
204
232
  [v3.3.3]: https://github.com/rubysolo/dentaku/compare/v3.3.2...v3.3.3
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
 
@@ -144,14 +143,16 @@ 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
 
154
+ Collection: `MAP`, `FILTER`, `ALL`, `ANY`, `PLUCK`
155
+
155
156
  RESOLVING DEPENDENCIES
156
157
  ----------------------
157
158
 
@@ -321,7 +322,7 @@ LICENSE
321
322
 
322
323
  (The MIT License)
323
324
 
324
- Copyright © 2012-2019 Solomon White
325
+ Copyright © 2012-2022 Solomon White
325
326
 
326
327
  Permission is hereby granted, free of charge, to any person obtaining a copy of
327
328
  this software and associated documentation files (the ‘Software’), to deal in
data/dentaku.gemspec CHANGED
@@ -14,6 +14,8 @@ Gem::Specification.new do |s|
14
14
  Dentaku is a parser and evaluator for mathematical formulas
15
15
  DESC
16
16
 
17
+ s.add_dependency('concurrent-ruby')
18
+
17
19
  s.add_development_dependency('codecov')
18
20
  s.add_development_dependency('pry')
19
21
  s.add_development_dependency('pry-byebug')
@@ -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
@@ -57,7 +57,7 @@ module Dentaku
57
57
  end
58
58
 
59
59
  def valid_node?(node)
60
- node && (node.type == :numeric || node.dependencies.any?)
60
+ node && (node.type == :numeric || node.type == :integer || node.dependencies.any?)
61
61
  end
62
62
 
63
63
  def valid_left?
@@ -220,6 +220,10 @@ module Dentaku
220
220
  :**
221
221
  end
222
222
 
223
+ def display_operator
224
+ "^"
225
+ end
226
+
223
227
  def self.precedence
224
228
  30
225
229
  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
@@ -6,12 +6,20 @@ module Dentaku
6
6
  def value(context = {})
7
7
  left.value(context) | right.value(context)
8
8
  end
9
+
10
+ def operator
11
+ :|
12
+ end
9
13
  end
10
14
 
11
15
  class BitwiseAnd < Operation
12
16
  def value(context = {})
13
17
  left.value(context) & right.value(context)
14
18
  end
19
+
20
+ def operator
21
+ :&
22
+ end
15
23
  end
16
24
  end
17
25
  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,64 @@ module Dentaku
15
15
  raise NotImplementedError
16
16
  end
17
17
 
18
+ def value(context = {})
19
+ l = validate_value(left.value(context))
20
+ r = validate_value(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 validate_value(value)
30
+ unless value.respond_to?(operator)
31
+ raise Dentaku::ArgumentError.for(:invalid_operator, operation: self.class, operator: operator),
32
+ "#{ self.class } requires operands that respond to #{operator}"
33
+ end
34
+
35
+ value
26
36
  end
27
37
  end
28
38
 
29
39
  class LessThan < Comparator
30
- def value(context = {})
31
- super() { left.value(context) < right.value(context) }
32
- end
33
-
34
40
  def operator
35
- return :<
41
+ :<
36
42
  end
37
43
  end
38
44
 
39
45
  class LessThanOrEqual < Comparator
40
- def value(context = {})
41
- super() { left.value(context) <= right.value(context) }
42
- end
43
-
44
46
  def operator
45
- return :<=
47
+ :<=
46
48
  end
47
49
  end
48
50
 
49
51
  class GreaterThan < Comparator
50
- def value(context = {})
51
- super() { left.value(context) > right.value(context) }
52
- end
53
-
54
52
  def operator
55
- return :>
53
+ :>
56
54
  end
57
55
  end
58
56
 
59
57
  class GreaterThanOrEqual < Comparator
60
- def value(context = {})
61
- super() { left.value(context) >= right.value(context) }
62
- end
63
-
64
58
  def operator
65
- return :>=
59
+ :>=
66
60
  end
67
61
  end
68
62
 
69
63
  class NotEqual < Comparator
70
- def value(context = {})
71
- super() { left.value(context) != right.value(context) }
72
- end
73
-
74
64
  def operator
75
- return :!=
65
+ :!=
76
66
  end
77
67
  end
78
68
 
79
69
  class Equal < Comparator
80
- def value(context = {})
81
- super() { left.value(context) == right.value(context) }
70
+ def operator
71
+ :==
82
72
  end
83
73
 
84
- def operator
85
- return :==
74
+ def display_operator
75
+ "="
86
76
  end
87
77
  end
88
78
  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,30 +1,17 @@
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
- context.update(
27
- FlatHash.from_hash(item_identifier => item_value)
13
+ context.merge(
14
+ FlatHash.from_hash_with_intermediates(item_identifier => item_value)
28
15
  )
29
16
  )
30
17
  end
@@ -1,30 +1,17 @@
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
- context.update(
27
- FlatHash.from_hash(item_identifier => item_value)
13
+ context.merge(
14
+ FlatHash.from_hash_with_intermediates(item_identifier => item_value)
28
15
  )
29
16
  )
30
17
  end
@@ -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
@@ -0,0 +1,23 @@
1
+ require_relative './enum'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Filter < Enum
6
+ def value(context = {})
7
+ collection = Array(@args[0].value(context))
8
+ item_identifier = @args[1].identifier
9
+ expression = @args[2]
10
+
11
+ collection.select do |item_value|
12
+ expression.value(
13
+ context.merge(
14
+ FlatHash.from_hash_with_intermediates(item_identifier => item_value)
15
+ )
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ Dentaku::AST::Function.register_class(:filter, Dentaku::AST::Filter)
@@ -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
@@ -1,30 +1,17 @@
1
- require_relative '../function'
2
- require_relative '../../exceptions'
1
+ require_relative './enum'
3
2
 
4
3
  module Dentaku
5
4
  module AST
6
- class Map < 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 Map < 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
11
  collection.map do |item_value|
25
12
  expression.value(
26
- context.update(
27
- FlatHash.from_hash(item_identifier => item_value)
13
+ context.merge(
14
+ FlatHash.from_hash_with_intermediates(item_identifier => item_value)
28
15
  )
29
16
  )
30
17
  end
@@ -1,13 +1,12 @@
1
1
  require_relative '../function'
2
2
 
3
3
  Dentaku::AST::Function.register(:mul, :numeric, ->(*args) {
4
- flatten_args = args.flatten
5
- if flatten_args.empty?
4
+ if args.empty?
6
5
  raise Dentaku::ArgumentError.for(
7
6
  :too_few_arguments,
8
7
  function_name: 'MUL()', at_least: 1, given: 0
9
8
  ), 'MUL() requires at least one argument'
10
9
  end
11
10
 
12
- flatten_args.map { |arg| Dentaku::AST::Function.numeric(arg) }.reduce(1, :*)
11
+ args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.reduce(1, :*)
13
12
  })