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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -8
- data/.travis.yml +3 -4
- data/CHANGELOG.md +37 -1
- data/README.md +2 -2
- data/dentaku.gemspec +0 -2
- data/lib/dentaku.rb +14 -6
- data/lib/dentaku/ast.rb +5 -0
- data/lib/dentaku/ast/access.rb +15 -1
- data/lib/dentaku/ast/arithmetic.rb +28 -5
- data/lib/dentaku/ast/array.rb +15 -1
- data/lib/dentaku/ast/case.rb +8 -0
- data/lib/dentaku/ast/case/case_conditional.rb +8 -0
- data/lib/dentaku/ast/case/case_else.rb +12 -4
- data/lib/dentaku/ast/case/case_switch_variable.rb +8 -0
- data/lib/dentaku/ast/case/case_then.rb +12 -4
- data/lib/dentaku/ast/case/case_when.rb +12 -4
- data/lib/dentaku/ast/function.rb +10 -1
- data/lib/dentaku/ast/function_registry.rb +21 -0
- data/lib/dentaku/ast/functions/all.rb +36 -0
- data/lib/dentaku/ast/functions/any.rb +36 -0
- data/lib/dentaku/ast/functions/avg.rb +2 -2
- data/lib/dentaku/ast/functions/count.rb +8 -0
- data/lib/dentaku/ast/functions/duration.rb +51 -0
- data/lib/dentaku/ast/functions/if.rb +15 -2
- data/lib/dentaku/ast/functions/map.rb +36 -0
- data/lib/dentaku/ast/functions/mul.rb +3 -2
- data/lib/dentaku/ast/functions/pluck.rb +29 -0
- data/lib/dentaku/ast/functions/round.rb +1 -1
- data/lib/dentaku/ast/functions/rounddown.rb +1 -1
- data/lib/dentaku/ast/functions/roundup.rb +1 -1
- data/lib/dentaku/ast/functions/ruby_math.rb +47 -3
- data/lib/dentaku/ast/functions/string_functions.rb +68 -4
- data/lib/dentaku/ast/functions/sum.rb +3 -2
- data/lib/dentaku/ast/grouping.rb +3 -1
- data/lib/dentaku/ast/identifier.rb +5 -1
- data/lib/dentaku/ast/negation.rb +3 -1
- data/lib/dentaku/ast/node.rb +4 -0
- data/lib/dentaku/ast/operation.rb +8 -0
- data/lib/dentaku/bulk_expression_solver.rb +36 -25
- data/lib/dentaku/calculator.rb +19 -6
- data/lib/dentaku/date_arithmetic.rb +45 -0
- data/lib/dentaku/exceptions.rb +4 -4
- data/lib/dentaku/flat_hash.rb +7 -0
- data/lib/dentaku/parser.rb +14 -3
- data/lib/dentaku/tokenizer.rb +1 -1
- data/lib/dentaku/version.rb +1 -1
- data/spec/ast/addition_spec.rb +6 -0
- data/spec/ast/arithmetic_spec.rb +41 -13
- data/spec/ast/avg_spec.rb +4 -0
- data/spec/ast/division_spec.rb +6 -0
- data/spec/ast/function_spec.rb +1 -1
- data/spec/ast/mul_spec.rb +4 -0
- data/spec/ast/negation_spec.rb +48 -0
- data/spec/ast/node_spec.rb +4 -1
- data/spec/ast/round_spec.rb +10 -0
- data/spec/ast/rounddown_spec.rb +10 -0
- data/spec/ast/roundup_spec.rb +10 -0
- data/spec/ast/string_functions_spec.rb +35 -0
- data/spec/ast/sum_spec.rb +4 -0
- data/spec/bulk_expression_solver_spec.rb +27 -0
- data/spec/calculator_spec.rb +144 -3
- data/spec/dentaku_spec.rb +18 -5
- data/spec/external_function_spec.rb +29 -5
- data/spec/parser_spec.rb +13 -0
- data/spec/tokenizer_spec.rb +24 -5
- metadata +11 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0275b981a44fdb17f4eb1ec144c57baf3f5ab46f0bf65c6f98dda51b3bc80658
|
|
4
|
+
data.tar.gz: b5120974bd987a35e5388a5932e0a01d9a0d05692df9d0281d65ad729484754c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76f427ebaed43836b95201f5939c90d3391b9887a7bfbbc3fc51973a7fdf7c25453be641fba639480e095284af26391437b5711731625e211ccf55cd616967c2
|
|
7
|
+
data.tar.gz: 6934989420f0c0d86914bcd10c2f562c138e883b3bb4b0d33013553f2403d45064b472932d069d79cb69c82c24a672668b50cc46fbca7740e2bf71982913635c
|
data/.rubocop.yml
CHANGED
|
@@ -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/
|
|
91
|
+
Layout/IndentationStyle:
|
|
97
92
|
Enabled: true
|
|
98
93
|
|
|
99
94
|
# Blank lines should not have any spaces.
|
|
100
|
-
Layout/
|
|
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/
|
|
103
|
+
Style/RedundantPercentQ:
|
|
109
104
|
Enabled: true
|
|
110
105
|
|
|
111
106
|
# Align `end` with the matching keyword or starting expression except for
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -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.
|
|
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-
|
|
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
|
data/dentaku.gemspec
CHANGED
|
@@ -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')
|
data/lib/dentaku.rb
CHANGED
|
@@ -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
|
data/lib/dentaku/ast.rb
CHANGED
|
@@ -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'
|
data/lib/dentaku/ast/access.rb
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
data/lib/dentaku/ast/array.rb
CHANGED
|
@@ -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
|
data/lib/dentaku/ast/case.rb
CHANGED
|
@@ -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
|
|
@@ -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
|