dentaku 3.4.1 → 3.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -3
- data/CHANGELOG.md +11 -1
- data/README.md +2 -0
- data/dentaku.gemspec +2 -0
- data/lib/dentaku.rb +4 -3
- data/lib/dentaku/ast/functions/all.rb +2 -2
- data/lib/dentaku/ast/functions/any.rb +2 -2
- data/lib/dentaku/ast/functions/filter.rb +36 -0
- data/lib/dentaku/ast/functions/map.rb +2 -2
- data/lib/dentaku/ast/functions/mul.rb +2 -3
- data/lib/dentaku/ast/functions/ruby_math.rb +3 -1
- data/lib/dentaku/ast/functions/sum.rb +2 -3
- data/lib/dentaku/ast/identifier.rb +3 -3
- data/lib/dentaku/calculator.rb +1 -1
- data/lib/dentaku/version.rb +1 -1
- data/spec/ast/filter_spec.rb +18 -0
- data/spec/ast/map_spec.rb +15 -0
- data/spec/ast/max_spec.rb +13 -0
- data/spec/ast/min_spec.rb +13 -0
- data/spec/ast/mul_spec.rb +3 -2
- data/spec/ast/sum_spec.rb +3 -2
- data/spec/calculator_spec.rb +8 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3dadbc209e58b2d2206dfaf48b83be3224db37f71d60cf590aadb63be9f4bebd
|
4
|
+
data.tar.gz: 86530c9c3333139994775a6bde16202005d89161a0b98670db58bcdda47f8e55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f132401168218a3c021124ddabb56bbf485550bdff60a03a291ad811d8628e25caab2ba36e4bed4f8148bde048e56bed1fa31480774f2c19617a79282da81280
|
7
|
+
data.tar.gz: 3b6a8fc5e40988a9442be9dbdf890dc813e13b95fc2d0cb2f60252b2492d07fdf4ba47cdfedd0a85c002aaaa23a83bffb141b8f8f31fbf518678439d55c1e795
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## [v3.4.2]
|
4
|
+
- add FILTER function
|
5
|
+
- add concurrent-ruby dependency to make global calculator object thread safe
|
6
|
+
- add Ruby 3 support
|
7
|
+
- allow formulas to access intermediate context values
|
8
|
+
- fix incorrect Ruby Math function return type
|
9
|
+
- fix context mutation bug
|
10
|
+
- fix dependency resolution bug
|
11
|
+
|
3
12
|
## [v3.4.1] 2020-12-12
|
4
13
|
- prevent extra evaluations in bulk expression solver
|
5
14
|
|
@@ -203,7 +212,8 @@
|
|
203
212
|
## [v0.1.0] 2012-01-20
|
204
213
|
- initial release
|
205
214
|
|
206
|
-
[
|
215
|
+
[v3.4.2]: https://github.com/rubysolo/dentaku/compare/v3.4.1...v3.4.2
|
216
|
+
[v3.4.1]: https://github.com/rubysolo/dentaku/compare/v3.4.0...v3.4.1
|
207
217
|
[v3.4.0]: https://github.com/rubysolo/dentaku/compare/v3.3.4...v3.4.0
|
208
218
|
[v3.3.4]: https://github.com/rubysolo/dentaku/compare/v3.3.3...v3.3.4
|
209
219
|
[v3.3.3]: https://github.com/rubysolo/dentaku/compare/v3.3.2...v3.3.3
|
data/README.md
CHANGED
@@ -152,6 +152,8 @@ Selections: `CASE` (syntax see [spec](https://github.com/rubysolo/dentaku/blob/m
|
|
152
152
|
|
153
153
|
String: `LEFT`, `RIGHT`, `MID`, `LEN`, `FIND`, `SUBSTITUTE`, `CONCAT`, `CONTAINS`
|
154
154
|
|
155
|
+
Collection: `MAP`, `FILTER`, `ALL`, `ANY`, `PLUCK`
|
156
|
+
|
155
157
|
RESOLVING DEPENDENCIES
|
156
158
|
----------------------
|
157
159
|
|
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')
|
data/lib/dentaku.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "bigdecimal"
|
2
|
+
require "concurrent"
|
2
3
|
require "dentaku/calculator"
|
3
4
|
require "dentaku/version"
|
4
5
|
|
@@ -9,11 +10,11 @@ module Dentaku
|
|
9
10
|
@aliases = {}
|
10
11
|
|
11
12
|
def self.evaluate(expression, data = {}, &block)
|
12
|
-
calculator.evaluate(expression, data, &block)
|
13
|
+
calculator.value.evaluate(expression, data, &block)
|
13
14
|
end
|
14
15
|
|
15
16
|
def self.evaluate!(expression, data = {}, &block)
|
16
|
-
calculator.evaluate!(expression, data, &block)
|
17
|
+
calculator.value.evaluate!(expression, data, &block)
|
17
18
|
end
|
18
19
|
|
19
20
|
def self.enable_caching!
|
@@ -55,7 +56,7 @@ module Dentaku
|
|
55
56
|
end
|
56
57
|
|
57
58
|
def self.calculator
|
58
|
-
@calculator ||= Dentaku::Calculator.new
|
59
|
+
@calculator ||= Concurrent::ThreadLocalVar.new { Dentaku::Calculator.new }
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
@@ -23,8 +23,8 @@ module Dentaku
|
|
23
23
|
|
24
24
|
Array(collection).all? do |item_value|
|
25
25
|
expression.value(
|
26
|
-
context.
|
27
|
-
FlatHash.
|
26
|
+
context.merge(
|
27
|
+
FlatHash.from_hash_with_intermediates(item_identifier => item_value)
|
28
28
|
)
|
29
29
|
)
|
30
30
|
end
|
@@ -23,8 +23,8 @@ module Dentaku
|
|
23
23
|
|
24
24
|
Array(collection).any? do |item_value|
|
25
25
|
expression.value(
|
26
|
-
context.
|
27
|
-
FlatHash.
|
26
|
+
context.merge(
|
27
|
+
FlatHash.from_hash_with_intermediates(item_identifier => item_value)
|
28
28
|
)
|
29
29
|
)
|
30
30
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative '../function'
|
2
|
+
require_relative '../../exceptions'
|
3
|
+
|
4
|
+
module Dentaku
|
5
|
+
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
|
+
|
19
|
+
def value(context = {})
|
20
|
+
collection = @args[0].value(context)
|
21
|
+
item_identifier = @args[1].identifier
|
22
|
+
expression = @args[2]
|
23
|
+
|
24
|
+
Array(collection).select do |item_value|
|
25
|
+
expression.value(
|
26
|
+
context.merge(
|
27
|
+
FlatHash.from_hash_with_intermediates(item_identifier => item_value)
|
28
|
+
)
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Dentaku::AST::Function.register_class(:filter, Dentaku::AST::Filter)
|
@@ -1,13 +1,12 @@
|
|
1
1
|
require_relative '../function'
|
2
2
|
|
3
3
|
Dentaku::AST::Function.register(:mul, :numeric, ->(*args) {
|
4
|
-
|
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
|
-
|
11
|
+
args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.reduce(1, :*)
|
13
12
|
})
|
@@ -1,13 +1,12 @@
|
|
1
1
|
require_relative '../function'
|
2
2
|
|
3
3
|
Dentaku::AST::Function.register(:sum, :numeric, ->(*args) {
|
4
|
-
|
5
|
-
if flatten_args.empty?
|
4
|
+
if args.empty?
|
6
5
|
raise Dentaku::ArgumentError.for(
|
7
6
|
:too_few_arguments,
|
8
7
|
function_name: 'SUM()', at_least: 1, given: 0
|
9
8
|
), 'SUM() requires at least one argument'
|
10
9
|
end
|
11
10
|
|
12
|
-
|
11
|
+
args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.reduce(0, :+)
|
13
12
|
})
|
@@ -31,13 +31,13 @@ module Dentaku
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def dependencies(context = {})
|
34
|
-
context.key?(identifier) ? dependencies_of(context[identifier]) : [identifier]
|
34
|
+
context.key?(identifier) ? dependencies_of(context[identifier], context) : [identifier]
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
38
38
|
|
39
|
-
def dependencies_of(node)
|
40
|
-
node.respond_to?(:dependencies) ? node.dependencies : []
|
39
|
+
def dependencies_of(node, context)
|
40
|
+
node.respond_to?(:dependencies) ? node.dependencies(context) : []
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
data/lib/dentaku/calculator.rb
CHANGED
@@ -59,7 +59,7 @@ module Dentaku
|
|
59
59
|
store(data) do
|
60
60
|
node = expression
|
61
61
|
node = ast(node) unless node.is_a?(AST::Node)
|
62
|
-
unbound = node.dependencies
|
62
|
+
unbound = node.dependencies(memory)
|
63
63
|
unless unbound.empty?
|
64
64
|
raise UnboundVariableError.new(unbound),
|
65
65
|
"no value provided for variables: #{unbound.uniq.join(', ')}"
|
data/lib/dentaku/version.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku/ast/functions/filter'
|
3
|
+
require 'dentaku'
|
4
|
+
|
5
|
+
describe Dentaku::AST::Filter do
|
6
|
+
it 'excludes unmatched values' do
|
7
|
+
result = Dentaku('SUM(FILTER(vals, val, val > 1))', vals: [1, 2, 3])
|
8
|
+
expect(result).to eq(5)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'works with a single value if needed for some reason' do
|
12
|
+
result = Dentaku('FILTER(vals, val, val > 1)', vals: 1)
|
13
|
+
expect(result).to eq([])
|
14
|
+
|
15
|
+
result = Dentaku('FILTER(vals, val, val > 1)', vals: 2)
|
16
|
+
expect(result).to eq([2])
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku/ast/functions/map'
|
3
|
+
require 'dentaku'
|
4
|
+
|
5
|
+
describe Dentaku::AST::Map do
|
6
|
+
it 'operates on each value in an array' do
|
7
|
+
result = Dentaku('SUM(MAP(vals, val, val + 1))', vals: [1, 2, 3])
|
8
|
+
expect(result).to eq(9)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'works with an empty array' do
|
12
|
+
result = Dentaku('MAP(vals, val, val + 1)', vals: [])
|
13
|
+
expect(result).to eq([])
|
14
|
+
end
|
15
|
+
end
|
data/spec/ast/max_spec.rb
CHANGED
@@ -17,4 +17,17 @@ describe 'Dentaku::AST::Function::Max' do
|
|
17
17
|
result = Dentaku('MAX(1, x, 1.8)', x: [1.5, 2.3, 1.7])
|
18
18
|
expect(result).to eq(2.3)
|
19
19
|
end
|
20
|
+
|
21
|
+
it 'returns the largest value if only an Array is passed' do
|
22
|
+
result = Dentaku('MAX(x)', x: [1.5, 2.3, 1.7])
|
23
|
+
expect(result).to eq(2.3)
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'checking errors' do
|
27
|
+
let(:calculator) { Dentaku::Calculator.new }
|
28
|
+
|
29
|
+
it 'does not raise an error if an empty array is passed' do
|
30
|
+
expect(calculator.evaluate!('MAX(x)', x: [])).to eq(nil)
|
31
|
+
end
|
32
|
+
end
|
20
33
|
end
|
data/spec/ast/min_spec.rb
CHANGED
@@ -17,4 +17,17 @@ describe 'Dentaku::AST::Function::Min' do
|
|
17
17
|
result = Dentaku('MIN(1, x, 1.8)', x: [1.5, 0.3, 1.7])
|
18
18
|
expect(result).to eq(0.3)
|
19
19
|
end
|
20
|
+
|
21
|
+
it 'returns the smallest value if only an Array is passed' do
|
22
|
+
result = Dentaku('MIN(x)', x: [1.5, 2.3, 1.7])
|
23
|
+
expect(result).to eq(1.5)
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'checking errors' do
|
27
|
+
let(:calculator) { Dentaku::Calculator.new }
|
28
|
+
|
29
|
+
it 'does not raise an error if an empty array is passed' do
|
30
|
+
expect(calculator.evaluate!('MIN(x)', x: [])).to eq(nil)
|
31
|
+
end
|
32
|
+
end
|
20
33
|
end
|
data/spec/ast/mul_spec.rb
CHANGED
@@ -35,8 +35,9 @@ describe 'Dentaku::AST::Function::Mul' do
|
|
35
35
|
expect { calculator.evaluate!('MUL()') }.to raise_error(Dentaku::ArgumentError)
|
36
36
|
end
|
37
37
|
|
38
|
-
it '
|
39
|
-
|
38
|
+
it 'does not raise an error if an empty array is passed' do
|
39
|
+
result = calculator.evaluate!('MUL(x)', x: [])
|
40
|
+
expect(result).to eq(1)
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
data/spec/ast/sum_spec.rb
CHANGED
@@ -35,8 +35,9 @@ describe 'Dentaku::AST::Function::Sum' do
|
|
35
35
|
expect { calculator.evaluate!('SUM()') }.to raise_error(Dentaku::ArgumentError)
|
36
36
|
end
|
37
37
|
|
38
|
-
it '
|
39
|
-
|
38
|
+
it 'does not raise an error if an empty array is passed' do
|
39
|
+
result = calculator.evaluate!('SUM(x)', x: [])
|
40
|
+
expect(result).to eq(0)
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
data/spec/calculator_spec.rb
CHANGED
@@ -502,6 +502,10 @@ describe Dentaku::Calculator do
|
|
502
502
|
{"name" => "Bob", "age" => 44},
|
503
503
|
{"name" => "Jane", "age" => 27}
|
504
504
|
])).to eq(["Bob", "Jane"])
|
505
|
+
expect(calculator.evaluate!('map(users, u, IF(u.age < 30, u, null))', users: [
|
506
|
+
{"name" => "Bob", "age" => 44},
|
507
|
+
{"name" => "Jane", "age" => 27}
|
508
|
+
])).to eq([nil, { "name" => "Jane", "age" => 27 }])
|
505
509
|
end
|
506
510
|
end
|
507
511
|
|
@@ -696,9 +700,13 @@ describe Dentaku::Calculator do
|
|
696
700
|
it method do
|
697
701
|
if Math.method(method).arity == 2
|
698
702
|
expect(calculator.evaluate("#{method}(x,y)", x: 1, y: '2')).to eq(Math.send(method, 1, 2))
|
703
|
+
expect(calculator.evaluate("#{method}(x,y) + 1", x: 1, y: '2')).to be_within(0.00001).of(Math.send(method, 1, 2) + 1)
|
699
704
|
expect { calculator.evaluate!("#{method}(x)", x: 1) }.to raise_error(Dentaku::ParseError)
|
700
705
|
else
|
701
706
|
expect(calculator.evaluate("#{method}(1)")).to eq(Math.send(method, 1))
|
707
|
+
unless [:atanh, :frexp, :lgamma].include?(method)
|
708
|
+
expect(calculator.evaluate("#{method}(1) + 1")).to be_within(0.00001).of(Math.send(method, 1) + 1)
|
709
|
+
end
|
702
710
|
end
|
703
711
|
end
|
704
712
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dentaku
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.4.
|
4
|
+
version: 3.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Solomon White
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: concurrent-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: codecov
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -162,6 +176,7 @@ files:
|
|
162
176
|
- lib/dentaku/ast/functions/avg.rb
|
163
177
|
- lib/dentaku/ast/functions/count.rb
|
164
178
|
- lib/dentaku/ast/functions/duration.rb
|
179
|
+
- lib/dentaku/ast/functions/filter.rb
|
165
180
|
- lib/dentaku/ast/functions/if.rb
|
166
181
|
- lib/dentaku/ast/functions/map.rb
|
167
182
|
- lib/dentaku/ast/functions/max.rb
|
@@ -210,7 +225,9 @@ files:
|
|
210
225
|
- spec/ast/comparator_spec.rb
|
211
226
|
- spec/ast/count_spec.rb
|
212
227
|
- spec/ast/division_spec.rb
|
228
|
+
- spec/ast/filter_spec.rb
|
213
229
|
- spec/ast/function_spec.rb
|
230
|
+
- spec/ast/map_spec.rb
|
214
231
|
- spec/ast/max_spec.rb
|
215
232
|
- spec/ast/min_spec.rb
|
216
233
|
- spec/ast/mul_spec.rb
|
@@ -269,7 +286,9 @@ test_files:
|
|
269
286
|
- spec/ast/comparator_spec.rb
|
270
287
|
- spec/ast/count_spec.rb
|
271
288
|
- spec/ast/division_spec.rb
|
289
|
+
- spec/ast/filter_spec.rb
|
272
290
|
- spec/ast/function_spec.rb
|
291
|
+
- spec/ast/map_spec.rb
|
273
292
|
- spec/ast/max_spec.rb
|
274
293
|
- spec/ast/min_spec.rb
|
275
294
|
- spec/ast/mul_spec.rb
|