dentaku 3.0.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +119 -0
- data/.travis.yml +8 -9
- data/CHANGELOG.md +9 -0
- data/Gemfile +0 -5
- data/LICENSE +21 -0
- data/README.md +44 -3
- data/Rakefile +4 -1
- data/dentaku.gemspec +8 -4
- data/lib/dentaku.rb +15 -2
- data/lib/dentaku/ast.rb +1 -0
- data/lib/dentaku/ast/access.rb +2 -2
- data/lib/dentaku/ast/arithmetic.rb +7 -7
- data/lib/dentaku/ast/bitwise.rb +2 -2
- data/lib/dentaku/ast/case.rb +5 -5
- data/lib/dentaku/ast/case/case_conditional.rb +1 -1
- data/lib/dentaku/ast/case/case_else.rb +2 -2
- data/lib/dentaku/ast/case/case_switch_variable.rb +2 -2
- data/lib/dentaku/ast/case/case_then.rb +2 -2
- data/lib/dentaku/ast/case/case_when.rb +2 -2
- data/lib/dentaku/ast/combinators.rb +10 -2
- data/lib/dentaku/ast/comparators.rb +34 -6
- data/lib/dentaku/ast/function.rb +1 -1
- data/lib/dentaku/ast/function_registry.rb +1 -1
- data/lib/dentaku/ast/functions/if.rb +6 -2
- data/lib/dentaku/ast/functions/max.rb +1 -1
- data/lib/dentaku/ast/functions/min.rb +1 -1
- data/lib/dentaku/ast/functions/ruby_math.rb +1 -1
- data/lib/dentaku/ast/functions/string_functions.rb +8 -8
- data/lib/dentaku/ast/functions/sum.rb +12 -0
- data/lib/dentaku/ast/grouping.rb +2 -2
- data/lib/dentaku/ast/identifier.rb +8 -5
- data/lib/dentaku/ast/negation.rb +2 -2
- data/lib/dentaku/ast/node.rb +1 -1
- data/lib/dentaku/ast/operation.rb +1 -1
- data/lib/dentaku/bulk_expression_solver.rb +39 -20
- data/lib/dentaku/calculator.rb +38 -28
- data/lib/dentaku/dependency_resolver.rb +1 -1
- data/lib/dentaku/flat_hash.rb +31 -0
- data/lib/dentaku/parser.rb +7 -6
- data/lib/dentaku/string_casing.rb +7 -0
- data/lib/dentaku/token.rb +1 -1
- data/lib/dentaku/token_matcher.rb +4 -4
- data/lib/dentaku/token_scanner.rb +18 -7
- data/lib/dentaku/tokenizer.rb +26 -2
- data/lib/dentaku/version.rb +1 -1
- data/spec/ast/arithmetic_spec.rb +2 -2
- data/spec/ast/comparator_spec.rb +57 -0
- data/spec/ast/function_spec.rb +1 -1
- data/spec/ast/max_spec.rb +5 -0
- data/spec/ast/min_spec.rb +5 -0
- data/spec/ast/sum_spec.rb +38 -0
- data/spec/benchmark.rb +2 -2
- data/spec/bulk_expression_solver_spec.rb +89 -1
- data/spec/calculator_spec.rb +40 -7
- data/spec/dentaku_spec.rb +11 -0
- data/spec/external_function_spec.rb +7 -7
- data/spec/parser_spec.rb +11 -11
- data/spec/spec_helper.rb +21 -3
- data/spec/token_matcher_spec.rb +0 -1
- data/spec/token_spec.rb +6 -0
- data/spec/tokenizer_spec.rb +37 -0
- metadata +70 -5
data/lib/dentaku/tokenizer.rb
CHANGED
@@ -4,16 +4,21 @@ require 'dentaku/token_scanner'
|
|
4
4
|
|
5
5
|
module Dentaku
|
6
6
|
class Tokenizer
|
7
|
+
attr_reader :case_sensitive, :aliases
|
8
|
+
|
7
9
|
LPAREN = TokenMatcher.new(:grouping, :open)
|
8
10
|
RPAREN = TokenMatcher.new(:grouping, :close)
|
9
11
|
|
10
|
-
def tokenize(string)
|
12
|
+
def tokenize(string, options = {})
|
11
13
|
@nesting = 0
|
12
14
|
@tokens = []
|
15
|
+
@aliases = options.fetch(:aliases, Dentaku.aliases)
|
13
16
|
input = strip_comments(string.to_s.dup)
|
17
|
+
input = replace_aliases(input)
|
18
|
+
@case_sensitive = options.fetch(:case_sensitive, false)
|
14
19
|
|
15
20
|
until input.empty?
|
16
|
-
scanned = TokenScanner.scanners.any? do |scanner|
|
21
|
+
scanned = TokenScanner.scanners(case_sensitive: case_sensitive).any? do |scanner|
|
17
22
|
scanned, input = scan(input, scanner)
|
18
23
|
scanned
|
19
24
|
end
|
@@ -58,6 +63,25 @@ module Dentaku
|
|
58
63
|
input.gsub(/\/\*[^*]*\*+(?:[^*\/][^*]*\*+)*\//, '')
|
59
64
|
end
|
60
65
|
|
66
|
+
def replace_aliases(string)
|
67
|
+
return string unless @aliases.any?
|
68
|
+
|
69
|
+
string.gsub!(alias_regex) do |match|
|
70
|
+
match_regex = /^#{Regexp.escape(match)}$/i
|
71
|
+
|
72
|
+
@aliases.detect do |(_key, aliases)|
|
73
|
+
!aliases.grep(match_regex).empty?
|
74
|
+
end.first
|
75
|
+
end
|
76
|
+
|
77
|
+
string
|
78
|
+
end
|
79
|
+
|
80
|
+
def alias_regex
|
81
|
+
values = @aliases.values.flatten.join('|')
|
82
|
+
/(?<=\p{Punct}|[[:space:]]|\A)(#{values})(?=\()/i
|
83
|
+
end
|
84
|
+
|
61
85
|
private
|
62
86
|
|
63
87
|
def fail!(reason, **meta)
|
data/lib/dentaku/version.rb
CHANGED
data/spec/ast/arithmetic_spec.rb
CHANGED
@@ -8,10 +8,10 @@ describe Dentaku::AST::Arithmetic do
|
|
8
8
|
let(:two) { Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, 2) }
|
9
9
|
let(:x) { Dentaku::AST::Identifier.new Dentaku::Token.new(:identifier, 'x') }
|
10
10
|
let(:y) { Dentaku::AST::Identifier.new Dentaku::Token.new(:identifier, 'y') }
|
11
|
-
let(:ctx) {{'x' => 1, 'y' => 2}}
|
11
|
+
let(:ctx) { {'x' => 1, 'y' => 2} }
|
12
12
|
|
13
13
|
it 'performs an arithmetic operation with numeric operands' do
|
14
|
-
expect(add(one, two)).to eq
|
14
|
+
expect(add(one, two)).to eq 3
|
15
15
|
expect(sub(one, two)).to eq -1
|
16
16
|
expect(mul(one, two)).to eq 2
|
17
17
|
expect(div(one, two)).to eq 0.5
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku/ast/comparators'
|
3
|
+
|
4
|
+
require 'dentaku/token'
|
5
|
+
|
6
|
+
describe Dentaku::AST::Comparator do
|
7
|
+
let(:one) { Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, 1) }
|
8
|
+
let(:two) { Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, 2) }
|
9
|
+
let(:x) { Dentaku::AST::Identifier.new Dentaku::Token.new(:identifier, 'x') }
|
10
|
+
let(:y) { Dentaku::AST::Identifier.new Dentaku::Token.new(:identifier, 'y') }
|
11
|
+
let(:ctx) { { 'x' => 'hello', 'y' => 'world' } }
|
12
|
+
|
13
|
+
it 'performs comparison with numeric operands' do
|
14
|
+
expect(less_than(one, two).value(ctx)).to be_truthy
|
15
|
+
expect(less_than(two, one).value(ctx)).to be_falsey
|
16
|
+
expect(greater_than(two, one).value(ctx)).to be_truthy
|
17
|
+
expect(not_equal(x, y).value(ctx)).to be_truthy
|
18
|
+
expect(equal(x, y).value(ctx)).to be_falsey
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns correct operator symbols' do
|
22
|
+
expect(less_than(one, two).operator).to eq(:<)
|
23
|
+
expect(less_than_or_equal(one, two).operator).to eq(:<=)
|
24
|
+
expect(greater_than(one, two).operator).to eq(:>)
|
25
|
+
expect(greater_than_or_equal(one, two).operator).to eq(:>=)
|
26
|
+
expect(not_equal(x, y).operator).to eq(:!=)
|
27
|
+
expect(equal(x, y).operator).to eq(:==)
|
28
|
+
expect { Dentaku::AST::Comparator.new(one, two).operator }
|
29
|
+
.to raise_error(NotImplementedError)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def less_than(left, right)
|
35
|
+
Dentaku::AST::LessThan.new(left, right)
|
36
|
+
end
|
37
|
+
|
38
|
+
def less_than_or_equal(left, right)
|
39
|
+
Dentaku::AST::LessThanOrEqual.new(left, right)
|
40
|
+
end
|
41
|
+
|
42
|
+
def greater_than(left, right)
|
43
|
+
Dentaku::AST::GreaterThan.new(left, right)
|
44
|
+
end
|
45
|
+
|
46
|
+
def greater_than_or_equal(left, right)
|
47
|
+
Dentaku::AST::GreaterThanOrEqual.new(left, right)
|
48
|
+
end
|
49
|
+
|
50
|
+
def not_equal(left, right)
|
51
|
+
Dentaku::AST::NotEqual.new(left, right)
|
52
|
+
end
|
53
|
+
|
54
|
+
def equal(left, right)
|
55
|
+
Dentaku::AST::Equal.new(left, right)
|
56
|
+
end
|
57
|
+
end
|
data/spec/ast/function_spec.rb
CHANGED
@@ -29,7 +29,7 @@ describe Dentaku::AST::Function do
|
|
29
29
|
one = described_class.register("one", :numeric, ->(x) { x * 2 })
|
30
30
|
expect(one.arity).to eq 1
|
31
31
|
|
32
|
-
two = described_class.register("two", :numeric, ->(x,y) { x + y })
|
32
|
+
two = described_class.register("two", :numeric, ->(x, y) { x + y })
|
33
33
|
expect(two.arity).to eq 2
|
34
34
|
|
35
35
|
many = described_class.register("many", :numeric, ->(*args) { args.max })
|
data/spec/ast/max_spec.rb
CHANGED
@@ -12,4 +12,9 @@ describe 'Dentaku::AST::Function::Max' do
|
|
12
12
|
result = Dentaku('MAX(1, x, 1.8)', x: '2.3')
|
13
13
|
expect(result).to eq 2.3
|
14
14
|
end
|
15
|
+
|
16
|
+
it 'returns the largest value even if an Array is passed' do
|
17
|
+
result = Dentaku('MAX(1, x, 1.8)', x: [1.5, 2.3, 1.7])
|
18
|
+
expect(result).to eq 2.3
|
19
|
+
end
|
15
20
|
end
|
data/spec/ast/min_spec.rb
CHANGED
@@ -12,4 +12,9 @@ describe 'Dentaku::AST::Function::Min' do
|
|
12
12
|
result = Dentaku('MIN(1, x, 1.8)', x: '0.3')
|
13
13
|
expect(result).to eq 0.3
|
14
14
|
end
|
15
|
+
|
16
|
+
it 'returns the smallest value even if an Array is passed' do
|
17
|
+
result = Dentaku('MIN(1, x, 1.8)', x: [1.5, 0.3, 1.7])
|
18
|
+
expect(result).to eq 0.3
|
19
|
+
end
|
15
20
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku/ast/functions/sum'
|
3
|
+
require 'dentaku'
|
4
|
+
|
5
|
+
describe 'Dentaku::AST::Function::Sum' do
|
6
|
+
it 'returns the sum of an array of Numeric values' do
|
7
|
+
result = Dentaku('SUM(1, x, 1.8)', x: 2.3)
|
8
|
+
expect(result).to eq 5.1
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns the sum of a single entry array of a Numeric value' do
|
12
|
+
result = Dentaku('SUM(x)', x: 2.3)
|
13
|
+
expect(result).to eq 2.3
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns the sum even if a String is passed' do
|
17
|
+
result = Dentaku('SUM(1, x, 1.8)', x: '2.3')
|
18
|
+
expect(result).to eq 5.1
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns the sum even if an array is passed' do
|
22
|
+
result = Dentaku('SUM(1, x, 2.3)', x: [4, 5])
|
23
|
+
expect(result).to eq 12.3
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns the sum of nested sums' do
|
27
|
+
result = Dentaku('SUM(1, x, SUM(4, 5))', x: '2.3')
|
28
|
+
expect(result).to eq 12.3
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'checking errors' do
|
32
|
+
let(:calculator) { Dentaku::Calculator.new }
|
33
|
+
|
34
|
+
it 'raises an error if no arguments are passed' do
|
35
|
+
expect { calculator.evaluate!('SUM()') }.to raise_error(ArgumentError)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spec/benchmark.rb
CHANGED
@@ -9,12 +9,12 @@ puts "Ruby version #{RUBY_VERSION}"
|
|
9
9
|
|
10
10
|
with_duplicate_variables = [
|
11
11
|
"R1+R2+R3+R4+R5+R6",
|
12
|
-
{"R1"=>100000, "R2"=>0, "R3"=>200000, "R4"=>0, "R5"=>500000, "R6"=>0, "r1"=>100000, "r2"=>0, "r3"=>200000, "r4"=>0, "r5"=>500000, "r6"=>0}
|
12
|
+
{"R1" => 100000, "R2" => 0, "R3" => 200000, "R4" => 0, "R5" => 500000, "R6" => 0, "r1" => 100000, "r2" => 0, "r3" => 200000, "r4" => 0, "r5" => 500000, "r6" => 0}
|
13
13
|
]
|
14
14
|
|
15
15
|
without_duplicate_variables = [
|
16
16
|
"R1+R2+R3+R4+R5+R6",
|
17
|
-
{"R1"=>100000, "R2"=>0, "R3"=>200000, "R4"=>0, "R5"=>500000, "R6"=>0}
|
17
|
+
{"R1" => 100000, "R2" => 0, "R3" => 200000, "R4" => 0, "R5" => 500000, "R6" => 0}
|
18
18
|
]
|
19
19
|
|
20
20
|
def test(args, custom_function: true)
|
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'dentaku'
|
2
3
|
require 'dentaku/bulk_expression_solver'
|
4
|
+
require 'dentaku/calculator'
|
5
|
+
require 'dentaku/exceptions'
|
3
6
|
|
4
7
|
RSpec.describe Dentaku::BulkExpressionSolver do
|
5
8
|
let(:calculator) { Dentaku::Calculator.new }
|
@@ -33,7 +36,46 @@ RSpec.describe Dentaku::BulkExpressionSolver do
|
|
33
36
|
it "does not require keys to be parseable" do
|
34
37
|
expressions = { "the value of x, incremented" => "x + 1" }
|
35
38
|
solver = described_class.new(expressions, calculator.store("x" => 3))
|
36
|
-
expect(solver.solve!).to eq(
|
39
|
+
expect(solver.solve!).to eq("the value of x, incremented" => 4)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "evaluates expressions in hashes and arrays, and expands the results" do
|
43
|
+
calculator.store(
|
44
|
+
fruit_quantities: {
|
45
|
+
apple: 5,
|
46
|
+
pear: 9
|
47
|
+
},
|
48
|
+
fruit_prices: {
|
49
|
+
apple: 1.66,
|
50
|
+
pear: 2.50
|
51
|
+
}
|
52
|
+
)
|
53
|
+
expressions = {
|
54
|
+
weekly_budget: {
|
55
|
+
fruit: "weekly_budget.apples + weekly_budget.pears",
|
56
|
+
apples: "fruit_quantities.apple * discounted_fruit_prices.apple",
|
57
|
+
pears: "fruit_quantities.pear * discounted_fruit_prices.pear",
|
58
|
+
},
|
59
|
+
discounted_fruit_prices: {
|
60
|
+
apple: "round(fruit_prices.apple * discounts[0], 2)",
|
61
|
+
pear: "round(fruit_prices.pear * discounts[1], 2)"
|
62
|
+
},
|
63
|
+
discounts: ["0.4 * 2", "0.3 * 2"],
|
64
|
+
}
|
65
|
+
solver = described_class.new(expressions, calculator)
|
66
|
+
|
67
|
+
expect(solver.solve!).to eq(
|
68
|
+
weekly_budget: {
|
69
|
+
fruit: 20.15,
|
70
|
+
apples: 6.65,
|
71
|
+
pears: 13.50
|
72
|
+
},
|
73
|
+
discounted_fruit_prices: {
|
74
|
+
apple: 1.33,
|
75
|
+
pear: 1.50
|
76
|
+
},
|
77
|
+
discounts: [0.8, 0.6]
|
78
|
+
)
|
37
79
|
end
|
38
80
|
end
|
39
81
|
|
@@ -73,5 +115,51 @@ RSpec.describe Dentaku::BulkExpressionSolver do
|
|
73
115
|
end
|
74
116
|
expect(exception.recipient_variable).to eq('more_apples')
|
75
117
|
end
|
118
|
+
|
119
|
+
it 'safely handles argument errors' do
|
120
|
+
expressions = {i: "a / 5 + d", a: "m * 12", d: "a + b"}
|
121
|
+
result = described_class.new(expressions, calculator.store(m: 3)).solve
|
122
|
+
expect(result).to eq(
|
123
|
+
i: :undefined,
|
124
|
+
d: :undefined,
|
125
|
+
a: 36,
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'supports nested hashes of expressions using dot notation' do
|
130
|
+
expressions = {
|
131
|
+
a: "25",
|
132
|
+
b: {
|
133
|
+
c: "a / 5",
|
134
|
+
d: [3, 4, 5]
|
135
|
+
},
|
136
|
+
e: ["b.c + b.d[1]"],
|
137
|
+
f: "e[0] + 1"
|
138
|
+
}
|
139
|
+
results = described_class.new(expressions, calculator).solve
|
140
|
+
expect(results[:f]).to eq 10
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'uses stored values for expressions when they are known' do
|
144
|
+
calculator.store(Force: 50, Mass: 25)
|
145
|
+
expressions = {
|
146
|
+
Force: "Mass * Acceleration",
|
147
|
+
Mass: "Force / Acceleration",
|
148
|
+
Acceleration: "Force / Mass",
|
149
|
+
}
|
150
|
+
solver = described_class.new(expressions, calculator)
|
151
|
+
results = solver.solve
|
152
|
+
expect(results).to eq(Force: 50, Mass: 25, Acceleration: 2)
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'solves all array expressions for which context exists, returning :undefined for the rest' do
|
156
|
+
calculator.store(first: 1, equation: 3)
|
157
|
+
system = {'key' => ['first * equation', 'second * equation'] }
|
158
|
+
solver = described_class.new(system, calculator)
|
159
|
+
expect(solver.dependencies).to eq('key' => ['second'])
|
160
|
+
results = solver.solve
|
161
|
+
expect(results).to eq('key' => [3, :undefined])
|
162
|
+
expect { solver.solve! }.to raise_error(Dentaku::UnboundVariableError)
|
163
|
+
end
|
76
164
|
end
|
77
165
|
end
|
data/spec/calculator_spec.rb
CHANGED
@@ -2,8 +2,10 @@ require 'spec_helper'
|
|
2
2
|
require 'dentaku'
|
3
3
|
|
4
4
|
describe Dentaku::Calculator do
|
5
|
-
let(:calculator)
|
6
|
-
let(:with_memory)
|
5
|
+
let(:calculator) { described_class.new }
|
6
|
+
let(:with_memory) { described_class.new.store(apples: 3) }
|
7
|
+
let(:with_aliases) { described_class.new(aliases: { round: ['rrround'] }) }
|
8
|
+
let(:without_nested_data) { described_class.new(nested_data_support: false) }
|
7
9
|
|
8
10
|
it 'evaluates an expression' do
|
9
11
|
expect(calculator.evaluate('7+3')).to eq(10)
|
@@ -32,6 +34,7 @@ describe Dentaku::Calculator do
|
|
32
34
|
expect(calculator.evaluate('0.253/0.253')).to eq(1)
|
33
35
|
expect(calculator.evaluate('0.253/d', d: 0.253)).to eq(1)
|
34
36
|
expect(calculator.evaluate('10 + x', x: 'abc')).to be_nil
|
37
|
+
expect(calculator.evaluate('x * y', x: '.123', y: '100')).to eq(12.3)
|
35
38
|
expect(calculator.evaluate('a/b', a: '10', b: '2')).to eq(5)
|
36
39
|
expect(calculator.evaluate('t + 1*24*60*60', t: Time.local(2017, 1, 1))).to eq(Time.local(2017, 1, 2))
|
37
40
|
expect(calculator.evaluate("2 | 3 * 9")).to eq (27)
|
@@ -65,17 +68,21 @@ describe Dentaku::Calculator do
|
|
65
68
|
end
|
66
69
|
|
67
70
|
it 'stores nested hashes' do
|
68
|
-
calculator.store(
|
71
|
+
calculator.store(a: {basket: {of: 'apples'}}, b: 2)
|
69
72
|
expect(calculator.evaluate!('a.basket.of')).to eq 'apples'
|
70
73
|
expect(calculator.evaluate!('b')).to eq 2
|
71
74
|
end
|
72
75
|
|
73
76
|
it 'stores arrays' do
|
74
|
-
calculator.store(
|
77
|
+
calculator.store(a: [1, 2, 3])
|
75
78
|
expect(calculator.evaluate!('a[0]')).to eq 1
|
76
79
|
expect(calculator.evaluate!('a[x]', x: 1)).to eq 2
|
77
80
|
expect(calculator.evaluate!('a[x+1]', x: 1)).to eq 3
|
78
81
|
end
|
82
|
+
|
83
|
+
it 'evalutates arrays' do
|
84
|
+
expect(calculator.evaluate([1, 2, 3])).to eq([1, 2, 3])
|
85
|
+
end
|
79
86
|
end
|
80
87
|
|
81
88
|
describe 'dependencies' do
|
@@ -92,6 +99,10 @@ describe Dentaku::Calculator do
|
|
92
99
|
it "doesn't consider variables in memory as dependencies" do
|
93
100
|
expect(with_memory.dependencies("apples + oranges")).to eq(['oranges'])
|
94
101
|
end
|
102
|
+
|
103
|
+
it "finds no dependencies in array literals" do
|
104
|
+
expect(calculator.dependencies([1, 2, 3])).to eq([])
|
105
|
+
end
|
95
106
|
end
|
96
107
|
|
97
108
|
describe 'solve!' do
|
@@ -286,7 +297,7 @@ describe Dentaku::Calculator do
|
|
286
297
|
expect(calculator.evaluate('round(8.8)')).to eq(9)
|
287
298
|
expect(calculator.evaluate('round(8.75, 1)')).to eq(BigDecimal.new('8.8'))
|
288
299
|
|
289
|
-
expect(calculator.evaluate('ROUND(apples * 0.93)',
|
300
|
+
expect(calculator.evaluate('ROUND(apples * 0.93)', apples: 10)).to eq(9)
|
290
301
|
end
|
291
302
|
|
292
303
|
it 'include NOT' do
|
@@ -483,11 +494,11 @@ describe Dentaku::Calculator do
|
|
483
494
|
end
|
484
495
|
|
485
496
|
it 'disables the AST cache' do
|
486
|
-
expect(calculator.disable_cache{ |c| c.cache_ast? }).to be false
|
497
|
+
expect(calculator.disable_cache { |c| c.cache_ast? }).to be false
|
487
498
|
end
|
488
499
|
|
489
500
|
it 'calculates normally' do
|
490
|
-
expect(calculator.disable_cache{ |c| c.evaluate("2 + 2") }).to eq(4)
|
501
|
+
expect(calculator.disable_cache { |c| c.evaluate("2 + 2") }).to eq(4)
|
491
502
|
end
|
492
503
|
end
|
493
504
|
|
@@ -539,4 +550,26 @@ describe Dentaku::Calculator do
|
|
539
550
|
expect(calculator.evaluate("max(1, two())")).to eq 2
|
540
551
|
end
|
541
552
|
end
|
553
|
+
|
554
|
+
describe 'aliases' do
|
555
|
+
it 'accepts aliases as instance option' do
|
556
|
+
expect(with_aliases.evaluate('rrround(5.1)')).to eq 5
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
describe 'nested_data' do
|
561
|
+
it 'default to nested data enabled' do
|
562
|
+
expect(calculator.nested_data_support).to be_truthy
|
563
|
+
end
|
564
|
+
|
565
|
+
it 'allow opt out of nested data support' do
|
566
|
+
expect(without_nested_data.nested_data_support).to be_falsy
|
567
|
+
end
|
568
|
+
|
569
|
+
it 'should allow optout of nested hash' do
|
570
|
+
expect do
|
571
|
+
without_nested_data.solve!('a.b.c')
|
572
|
+
end.to raise_error(Dentaku::UnboundVariableError)
|
573
|
+
end
|
574
|
+
end
|
542
575
|
end
|
data/spec/dentaku_spec.rb
CHANGED
@@ -25,4 +25,15 @@ describe Dentaku do
|
|
25
25
|
Dentaku('true AND')
|
26
26
|
}.to raise_error(Dentaku::ParseError)
|
27
27
|
end
|
28
|
+
|
29
|
+
it 'evaluates with class-level shortcut functions' do
|
30
|
+
expect(Dentaku.evaluate('2+2')).to eq(4)
|
31
|
+
expect(Dentaku.evaluate!('2+2')).to eq(4)
|
32
|
+
expect { Dentaku.evaluate!('a+1') }.to raise_error(Dentaku::UnboundVariableError)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'evaluates with class-level aliases' do
|
36
|
+
Dentaku.aliases = { roundup: ['roundupup'] }
|
37
|
+
expect(Dentaku.evaluate('roundupup(6.1)')).to eq(7)
|
38
|
+
end
|
28
39
|
end
|
@@ -11,7 +11,7 @@ describe Dentaku::Calculator do
|
|
11
11
|
c.add_function(:now, :string, -> { Time.now.to_s })
|
12
12
|
|
13
13
|
fns = [
|
14
|
-
[:pow, :numeric, ->(mantissa, exponent) { mantissa
|
14
|
+
[:pow, :numeric, ->(mantissa, exponent) { mantissa**exponent }],
|
15
15
|
[:biggest, :numeric, ->(*args) { args.max }],
|
16
16
|
[:smallest, :numeric, ->(*args) { args.min }],
|
17
17
|
]
|
@@ -49,7 +49,7 @@ describe Dentaku::Calculator do
|
|
49
49
|
}
|
50
50
|
)
|
51
51
|
|
52
|
-
expect(calculator.evaluate("INCLUDES(list, 2)", list: [1,2,3])).to eq(true)
|
52
|
+
expect(calculator.evaluate("INCLUDES(list, 2)", list: [1, 2, 3])).to eq(true)
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
@@ -61,15 +61,15 @@ describe Dentaku::Calculator do
|
|
61
61
|
|
62
62
|
it 'does not store functions across all calculators' do
|
63
63
|
calculator1 = Dentaku::Calculator.new
|
64
|
-
calculator1.add_function(:my_function, :numeric, ->(x) { 2*x + 1 })
|
64
|
+
calculator1.add_function(:my_function, :numeric, ->(x) { 2 * x + 1 })
|
65
65
|
|
66
66
|
calculator2 = Dentaku::Calculator.new
|
67
|
-
calculator2.add_function(:my_function, :numeric, ->(x) { 4*x + 3 })
|
67
|
+
calculator2.add_function(:my_function, :numeric, ->(x) { 4 * x + 3 })
|
68
68
|
|
69
|
-
expect(calculator1.evaluate("1 + my_function(2)")). to eq (1 + 2*2 + 1)
|
70
|
-
expect(calculator2.evaluate("1 + my_function(2)")). to eq (1 + 4*2 + 3)
|
69
|
+
expect(calculator1.evaluate("1 + my_function(2)")). to eq (1 + 2 * 2 + 1)
|
70
|
+
expect(calculator2.evaluate("1 + my_function(2)")). to eq (1 + 4 * 2 + 3)
|
71
71
|
|
72
|
-
expect{Dentaku::Calculator.new.evaluate("1 + my_function(2)")}.to raise_error(Dentaku::ParseError)
|
72
|
+
expect { Dentaku::Calculator.new.evaluate("1 + my_function(2)") }.to raise_error(Dentaku::ParseError)
|
73
73
|
end
|
74
74
|
|
75
75
|
it 'self.add_function adds to default/global function registry' do
|