dentaku 3.3.1 → 3.4.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -8
  3. data/.travis.yml +3 -4
  4. data/CHANGELOG.md +37 -1
  5. data/README.md +2 -2
  6. data/dentaku.gemspec +0 -2
  7. data/lib/dentaku.rb +14 -6
  8. data/lib/dentaku/ast.rb +5 -0
  9. data/lib/dentaku/ast/access.rb +15 -1
  10. data/lib/dentaku/ast/arithmetic.rb +28 -5
  11. data/lib/dentaku/ast/array.rb +15 -1
  12. data/lib/dentaku/ast/case.rb +8 -0
  13. data/lib/dentaku/ast/case/case_conditional.rb +8 -0
  14. data/lib/dentaku/ast/case/case_else.rb +12 -4
  15. data/lib/dentaku/ast/case/case_switch_variable.rb +8 -0
  16. data/lib/dentaku/ast/case/case_then.rb +12 -4
  17. data/lib/dentaku/ast/case/case_when.rb +12 -4
  18. data/lib/dentaku/ast/function.rb +10 -1
  19. data/lib/dentaku/ast/function_registry.rb +21 -0
  20. data/lib/dentaku/ast/functions/all.rb +36 -0
  21. data/lib/dentaku/ast/functions/any.rb +36 -0
  22. data/lib/dentaku/ast/functions/avg.rb +2 -2
  23. data/lib/dentaku/ast/functions/count.rb +8 -0
  24. data/lib/dentaku/ast/functions/duration.rb +51 -0
  25. data/lib/dentaku/ast/functions/if.rb +15 -2
  26. data/lib/dentaku/ast/functions/map.rb +36 -0
  27. data/lib/dentaku/ast/functions/mul.rb +3 -2
  28. data/lib/dentaku/ast/functions/pluck.rb +29 -0
  29. data/lib/dentaku/ast/functions/round.rb +1 -1
  30. data/lib/dentaku/ast/functions/rounddown.rb +1 -1
  31. data/lib/dentaku/ast/functions/roundup.rb +1 -1
  32. data/lib/dentaku/ast/functions/ruby_math.rb +47 -3
  33. data/lib/dentaku/ast/functions/string_functions.rb +68 -4
  34. data/lib/dentaku/ast/functions/sum.rb +3 -2
  35. data/lib/dentaku/ast/grouping.rb +3 -1
  36. data/lib/dentaku/ast/identifier.rb +5 -1
  37. data/lib/dentaku/ast/negation.rb +3 -1
  38. data/lib/dentaku/ast/node.rb +4 -0
  39. data/lib/dentaku/ast/operation.rb +8 -0
  40. data/lib/dentaku/bulk_expression_solver.rb +36 -25
  41. data/lib/dentaku/calculator.rb +19 -6
  42. data/lib/dentaku/date_arithmetic.rb +45 -0
  43. data/lib/dentaku/exceptions.rb +4 -4
  44. data/lib/dentaku/flat_hash.rb +7 -0
  45. data/lib/dentaku/parser.rb +14 -3
  46. data/lib/dentaku/tokenizer.rb +1 -1
  47. data/lib/dentaku/version.rb +1 -1
  48. data/spec/ast/addition_spec.rb +6 -0
  49. data/spec/ast/arithmetic_spec.rb +41 -13
  50. data/spec/ast/avg_spec.rb +4 -0
  51. data/spec/ast/division_spec.rb +6 -0
  52. data/spec/ast/function_spec.rb +1 -1
  53. data/spec/ast/mul_spec.rb +4 -0
  54. data/spec/ast/negation_spec.rb +48 -0
  55. data/spec/ast/node_spec.rb +4 -1
  56. data/spec/ast/round_spec.rb +10 -0
  57. data/spec/ast/rounddown_spec.rb +10 -0
  58. data/spec/ast/roundup_spec.rb +10 -0
  59. data/spec/ast/string_functions_spec.rb +35 -0
  60. data/spec/ast/sum_spec.rb +4 -0
  61. data/spec/bulk_expression_solver_spec.rb +27 -0
  62. data/spec/calculator_spec.rb +144 -3
  63. data/spec/dentaku_spec.rb +18 -5
  64. data/spec/external_function_spec.rb +29 -5
  65. data/spec/parser_spec.rb +13 -0
  66. data/spec/tokenizer_spec.rb +24 -5
  67. metadata +11 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b760fc43a6d5745ea9c95827fb0ce03e8fef8453c2264ee1073e5754c2b8d402
4
- data.tar.gz: 04e03adf0729e170babe62c8f91cc88ca8aa46932dacb694ccfc564696eed765
3
+ metadata.gz: 0275b981a44fdb17f4eb1ec144c57baf3f5ab46f0bf65c6f98dda51b3bc80658
4
+ data.tar.gz: b5120974bd987a35e5388a5932e0a01d9a0d05692df9d0281d65ad729484754c
5
5
  SHA512:
6
- metadata.gz: 006ec24c2c61758a1b7f5921e181ed49e2d54bf479091f9600ed31019fedd0a9411d744932e7dbf973b2e8fea098ff7299e58fd97851e5b2382a81e05043f8e4
7
- data.tar.gz: 7f9d3735fc20e918d808158e8c618e11a0021762f102d9d5b32eec7d24f6a28845c01912fbc63c4cb835438f7f8d6d494fe811a5b01e373a5a6296a087c4edda
6
+ metadata.gz: 76f427ebaed43836b95201f5939c90d3391b9887a7bfbbc3fc51973a7fdf7c25453be641fba639480e095284af26391437b5711731625e211ccf55cd616967c2
7
+ data.tar.gz: 6934989420f0c0d86914bcd10c2f562c138e883b3bb4b0d33013553f2403d45064b472932d069d79cb69c82c24a672668b50cc46fbca7740e2bf71982913635c
@@ -8,11 +8,6 @@ AllCops:
8
8
  Style/AndOr:
9
9
  Enabled: true
10
10
 
11
- # Do not use braces for hash literals when they are the last argument of a
12
- # method call.
13
- Style/BracesAroundHashParameters:
14
- Enabled: true
15
-
16
11
  # Align `when` with `case`.
17
12
  Layout/CaseIndentation:
18
13
  Enabled: true
@@ -93,11 +88,11 @@ Style/StringLiterals:
93
88
  EnforcedStyle: double_quotes
94
89
 
95
90
  # Detect hard tabs, no hard tabs.
96
- Layout/Tab:
91
+ Layout/IndentationStyle:
97
92
  Enabled: true
98
93
 
99
94
  # Blank lines should not have any spaces.
100
- Layout/TrailingBlankLines:
95
+ Layout/TrailingEmptyLines:
101
96
  Enabled: true
102
97
 
103
98
  # No trailing whitespace.
@@ -105,7 +100,7 @@ Layout/TrailingWhitespace:
105
100
  Enabled: true
106
101
 
107
102
  # Use quotes for string literals when they are enough.
108
- Style/UnneededPercentQ:
103
+ Style/RedundantPercentQ:
109
104
  Enabled: true
110
105
 
111
106
  # Align `end` with the matching keyword or starting expression except for
@@ -1,10 +1,9 @@
1
1
  language: ruby
2
2
  sudo: false
3
3
  rvm:
4
- - 2.3.8
5
- - 2.4.4
6
- - 2.5.3
7
- - 2.6.0
4
+ - 2.5.7
5
+ - 2.6.5
6
+ - 2.7.0
8
7
  before_install:
9
8
  - gem update bundler
10
9
  - gem update --system
@@ -1,5 +1,37 @@
1
1
  # Change Log
2
2
 
3
+ ## [v3.4.1] 2020-12-12
4
+ - prevent extra evaluations in bulk expression solver
5
+
6
+ ## [v3.4.0] 2020-12-07
7
+ - allow access to intermediate values of flattened hashes
8
+ - catch invalid array syntax in the parse phase
9
+ - drop support for Ruby < 2.5, add support for Ruby 2.7
10
+ - add support for subtracting date literals
11
+ - improve error handling
12
+ - improve math function implementation
13
+ - add caching for calculated variable values
14
+ - allow custom unbound variable handling block at Dentaku module level
15
+ - add enum functions `ANY`, `ALL`, `MAP` and `PLUCK`
16
+ - allow self-referential formulas in bulk expression solver
17
+ - misc internal fixes and enhancements
18
+
19
+ ## [v3.3.4] 2019-11-21
20
+ - bugfix release
21
+
22
+ ## [v3.3.3] 2019-11-20
23
+ - date / duration addition and subtraction
24
+ - validate arity for custom functions with variable arity
25
+ - make AST serializable with Marshal.dump
26
+ - performance optimization for arithmetic node validation
27
+ - support lazy evaluation for expensive values
28
+ - short-circuit IF function
29
+ - better error when empty string is used in arithmetic operation
30
+
31
+ ## [v3.3.2] 2019-06-10
32
+ - add ability to pre-load AST cache
33
+ - fix negation node bug
34
+
3
35
  ## [v3.3.1] 2019-03-26
4
36
  - better errors for parse failures and exceptions in internal functions
5
37
  - fix Ruby 2.6.0 deprecation warnings
@@ -171,7 +203,11 @@
171
203
  ## [v0.1.0] 2012-01-20
172
204
  - initial release
173
205
 
174
- [HEAD]: https://github.com/rubysolo/dentaku/compare/v3.3.1...HEAD
206
+ [HEAD]: https://github.com/rubysolo/dentaku/compare/v3.4.0...v3.4.1
207
+ [v3.4.0]: https://github.com/rubysolo/dentaku/compare/v3.3.4...v3.4.0
208
+ [v3.3.4]: https://github.com/rubysolo/dentaku/compare/v3.3.3...v3.3.4
209
+ [v3.3.3]: https://github.com/rubysolo/dentaku/compare/v3.3.2...v3.3.3
210
+ [v3.3.2]: https://github.com/rubysolo/dentaku/compare/v3.3.1...v3.3.2
175
211
  [v3.3.1]: https://github.com/rubysolo/dentaku/compare/v3.3.0...v3.3.1
176
212
  [v3.3.0]: https://github.com/rubysolo/dentaku/compare/v3.2.1...v3.3.0
177
213
  [v3.2.1]: https://github.com/rubysolo/dentaku/compare/v3.2.0...v3.2.1
data/README.md CHANGED
@@ -284,7 +284,7 @@ using Calculator#add_functions.
284
284
  FUNCTION ALIASES
285
285
  ----------------
286
286
 
287
- Every function can be aliased by synonyms. For example, it can be useful if
287
+ Every function can be aliased by synonyms. For example, it can be useful if
288
288
  your application is multilingual.
289
289
 
290
290
  ```ruby
@@ -321,7 +321,7 @@ LICENSE
321
321
 
322
322
  (The MIT License)
323
323
 
324
- Copyright © 2012-2018 Solomon White
324
+ Copyright © 2012-2019 Solomon White
325
325
 
326
326
  Permission is hereby granted, free of charge, to any person obtaining a copy of
327
327
  this software and associated documentation files (the ‘Software’), to deal in
@@ -14,8 +14,6 @@ Gem::Specification.new do |s|
14
14
  Dentaku is a parser and evaluator for mathematical formulas
15
15
  DESC
16
16
 
17
- s.rubyforge_project = "dentaku"
18
-
19
17
  s.add_development_dependency('codecov')
20
18
  s.add_development_dependency('pry')
21
19
  s.add_development_dependency('pry-byebug')
@@ -5,19 +5,21 @@ require "dentaku/version"
5
5
  module Dentaku
6
6
  @enable_ast_caching = false
7
7
  @enable_dependency_order_caching = false
8
+ @enable_identifier_caching = false
8
9
  @aliases = {}
9
10
 
10
- def self.evaluate(expression, data = {})
11
- calculator.evaluate(expression, data)
11
+ def self.evaluate(expression, data = {}, &block)
12
+ calculator.evaluate(expression, data, &block)
12
13
  end
13
14
 
14
- def self.evaluate!(expression, data = {})
15
- calculator.evaluate!(expression, data)
15
+ def self.evaluate!(expression, data = {}, &block)
16
+ calculator.evaluate!(expression, data, &block)
16
17
  end
17
18
 
18
19
  def self.enable_caching!
19
20
  enable_ast_cache!
20
21
  enable_dependency_order_cache!
22
+ enable_identifier_cache!
21
23
  end
22
24
 
23
25
  def self.enable_ast_cache!
@@ -36,6 +38,14 @@ module Dentaku
36
38
  @enable_dependency_order_caching
37
39
  end
38
40
 
41
+ def self.enable_identifier_cache!
42
+ @enable_identifier_caching = true
43
+ end
44
+
45
+ def self.cache_identifier?
46
+ @enable_identifier_caching
47
+ end
48
+
39
49
  def self.aliases
40
50
  @aliases
41
51
  end
@@ -44,8 +54,6 @@ module Dentaku
44
54
  @aliases = hash
45
55
  end
46
56
 
47
- private
48
-
49
57
  def self.calculator
50
58
  @calculator ||= Dentaku::Calculator.new
51
59
  end
@@ -15,14 +15,19 @@ require_relative './ast/array'
15
15
  require_relative './ast/grouping'
16
16
  require_relative './ast/case'
17
17
  require_relative './ast/function_registry'
18
+ require_relative './ast/functions/all'
18
19
  require_relative './ast/functions/and'
20
+ require_relative './ast/functions/any'
19
21
  require_relative './ast/functions/avg'
20
22
  require_relative './ast/functions/count'
23
+ require_relative './ast/functions/duration'
21
24
  require_relative './ast/functions/if'
25
+ require_relative './ast/functions/map'
22
26
  require_relative './ast/functions/max'
23
27
  require_relative './ast/functions/min'
24
28
  require_relative './ast/functions/not'
25
29
  require_relative './ast/functions/or'
30
+ require_relative './ast/functions/pluck'
26
31
  require_relative './ast/functions/round'
27
32
  require_relative './ast/functions/rounddown'
28
33
  require_relative './ast/functions/roundup'
@@ -1,10 +1,20 @@
1
+ require_relative "./node"
2
+
1
3
  module Dentaku
2
4
  module AST
3
- class Access
5
+ class Access < Node
4
6
  def self.arity
5
7
  2
6
8
  end
7
9
 
10
+ def self.min_param_count
11
+ arity
12
+ end
13
+
14
+ def self.max_param_count
15
+ arity
16
+ end
17
+
8
18
  def self.peek(*)
9
19
  end
10
20
 
@@ -22,6 +32,10 @@ module Dentaku
22
32
  def dependencies(context = {})
23
33
  @structure.dependencies(context) + @index.dependencies(context)
24
34
  end
35
+
36
+ def type
37
+ nil
38
+ end
25
39
  end
26
40
  end
27
41
  end
@@ -1,4 +1,5 @@
1
1
  require_relative './operation'
2
+ require_relative '../date_arithmetic'
2
3
  require 'bigdecimal'
3
4
  require 'bigdecimal/util'
4
5
 
@@ -12,6 +13,7 @@ module Dentaku
12
13
  raise NodeError.new(:numeric, left.type, :left),
13
14
  "#{self.class} requires numeric operands"
14
15
  end
16
+
15
17
  unless valid_right?
16
18
  raise NodeError.new(:numeric, right.type, :right),
17
19
  "#{self.class} requires numeric operands"
@@ -29,7 +31,12 @@ module Dentaku
29
31
  def value(context = {})
30
32
  l = cast(left.value(context))
31
33
  r = cast(right.value(context))
32
- l.public_send(operator, r)
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
33
40
  end
34
41
 
35
42
  private
@@ -50,15 +57,15 @@ module Dentaku
50
57
  end
51
58
 
52
59
  def valid_node?(node)
53
- node && (node.dependencies.any? || node.type == :numeric)
60
+ node && (node.type == :numeric || node.dependencies.any?)
54
61
  end
55
62
 
56
63
  def valid_left?
57
- valid_node?(left)
64
+ valid_node?(left) || left.type == :datetime
58
65
  end
59
66
 
60
67
  def valid_right?
61
- valid_node?(right)
68
+ valid_node?(right) || right.type == :duration || right.type == :datetime
62
69
  end
63
70
 
64
71
  def validate_value(val)
@@ -77,7 +84,7 @@ module Dentaku
77
84
  end
78
85
 
79
86
  def validate_format(string)
80
- unless string =~ /\A-?\d*(\.\d+)?\z/
87
+ unless string =~ /\A-?\d*(\.\d+)?\z/ && !string.empty?
81
88
  raise Dentaku::ArgumentError.for(:invalid_value, value: string, for: BigDecimal),
82
89
  "String input '#{string}' is not coercible to numeric"
83
90
  end
@@ -92,6 +99,14 @@ module Dentaku
92
99
  def self.precedence
93
100
  10
94
101
  end
102
+
103
+ def value(context = {})
104
+ if left.type == :datetime
105
+ Dentaku::DateArithmetic.new(left.value(context)).add(right.value(context))
106
+ else
107
+ super
108
+ end
109
+ end
95
110
  end
96
111
 
97
112
  class Subtraction < Arithmetic
@@ -102,6 +117,14 @@ module Dentaku
102
117
  def self.precedence
103
118
  10
104
119
  end
120
+
121
+ def value(context = {})
122
+ if left.type == :datetime
123
+ Dentaku::DateArithmetic.new(left.value(context)).sub(right.value(context))
124
+ else
125
+ super
126
+ end
127
+ end
105
128
  end
106
129
 
107
130
  class Multiplication < Arithmetic
@@ -1,9 +1,19 @@
1
+ require_relative "./node"
2
+
1
3
  module Dentaku
2
4
  module AST
3
- class Array
5
+ class Array < Node
4
6
  def self.arity
5
7
  end
6
8
 
9
+ def self.min_param_count
10
+ 0
11
+ end
12
+
13
+ def self.max_param_count
14
+ Float::INFINITY
15
+ end
16
+
7
17
  def self.peek(*)
8
18
  end
9
19
 
@@ -18,6 +28,10 @@ module Dentaku
18
28
  def dependencies(context = {})
19
29
  @elements.flat_map { |el| el.dependencies(context) }
20
30
  end
31
+
32
+ def type
33
+ nil
34
+ end
21
35
  end
22
36
  end
23
37
  end
@@ -8,6 +8,14 @@ require 'dentaku/exceptions'
8
8
  module Dentaku
9
9
  module AST
10
10
  class Case < Node
11
+ def self.min_param_count
12
+ 2
13
+ end
14
+
15
+ def self.max_param_count
16
+ Float::INFINITY
17
+ end
18
+
11
19
  def initialize(*nodes)
12
20
  @switch = nodes.shift
13
21
 
@@ -6,6 +6,14 @@ module Dentaku
6
6
  attr_reader :when,
7
7
  :then
8
8
 
9
+ def self.min_param_count
10
+ 2
11
+ end
12
+
13
+ def self.max_param_count
14
+ 2
15
+ end
16
+
9
17
  def initialize(when_statement, then_statement)
10
18
  @when = when_statement
11
19
  unless @when.is_a?(AST::CaseWhen)
@@ -1,10 +1,6 @@
1
1
  module Dentaku
2
2
  module AST
3
3
  class CaseElse < Node
4
- def self.arity
5
- 1
6
- end
7
-
8
4
  def initialize(node)
9
5
  @node = node
10
6
  end
@@ -16,6 +12,18 @@ module Dentaku
16
12
  def dependencies(context = {})
17
13
  @node.dependencies(context)
18
14
  end
15
+
16
+ def self.arity
17
+ 1
18
+ end
19
+
20
+ def self.min_param_count
21
+ 1
22
+ end
23
+
24
+ def self.max_param_count
25
+ 1
26
+ end
19
27
  end
20
28
  end
21
29
  end
@@ -16,6 +16,14 @@ module Dentaku
16
16
  def self.arity
17
17
  1
18
18
  end
19
+
20
+ def self.min_param_count
21
+ 1
22
+ end
23
+
24
+ def self.max_param_count
25
+ 1
26
+ end
19
27
  end
20
28
  end
21
29
  end
@@ -1,10 +1,6 @@
1
1
  module Dentaku
2
2
  module AST
3
3
  class CaseThen < Node
4
- def self.arity
5
- 1
6
- end
7
-
8
4
  def initialize(node)
9
5
  @node = node
10
6
  end
@@ -16,6 +12,18 @@ module Dentaku
16
12
  def dependencies(context = {})
17
13
  @node.dependencies(context)
18
14
  end
15
+
16
+ def self.arity
17
+ 1
18
+ end
19
+
20
+ def self.min_param_count
21
+ 1
22
+ end
23
+
24
+ def self.max_param_count
25
+ 1
26
+ end
19
27
  end
20
28
  end
21
29
  end