dentaku 3.0.0 → 3.1.0
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 +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
|