dentaku 3.4.0 → 3.5.0

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