dentaku 2.0.11 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +0 -1
- data/CHANGELOG.md +19 -0
- data/README.md +3 -2
- data/dentaku.gemspec +1 -0
- data/lib/dentaku/ast.rb +4 -0
- data/lib/dentaku/ast/access.rb +27 -0
- data/lib/dentaku/ast/arithmetic.rb +49 -7
- data/lib/dentaku/ast/case.rb +17 -3
- data/lib/dentaku/ast/combinators.rb +8 -2
- data/lib/dentaku/ast/function.rb +16 -0
- data/lib/dentaku/ast/function_registry.rb +15 -2
- data/lib/dentaku/ast/functions/and.rb +25 -0
- data/lib/dentaku/ast/functions/max.rb +1 -1
- data/lib/dentaku/ast/functions/min.rb +1 -1
- data/lib/dentaku/ast/functions/or.rb +25 -0
- data/lib/dentaku/ast/functions/round.rb +2 -2
- data/lib/dentaku/ast/functions/rounddown.rb +3 -2
- data/lib/dentaku/ast/functions/roundup.rb +3 -2
- data/lib/dentaku/ast/functions/ruby_math.rb +3 -3
- data/lib/dentaku/ast/functions/switch.rb +8 -0
- data/lib/dentaku/ast/identifier.rb +3 -2
- data/lib/dentaku/ast/negation.rb +5 -1
- data/lib/dentaku/ast/node.rb +3 -0
- data/lib/dentaku/bulk_expression_solver.rb +1 -2
- data/lib/dentaku/calculator.rb +7 -6
- data/lib/dentaku/exceptions.rb +75 -1
- data/lib/dentaku/parser.rb +73 -12
- data/lib/dentaku/token.rb +4 -0
- data/lib/dentaku/token_scanner.rb +20 -3
- data/lib/dentaku/tokenizer.rb +31 -4
- data/lib/dentaku/version.rb +1 -1
- data/spec/ast/addition_spec.rb +6 -6
- data/spec/ast/and_function_spec.rb +35 -0
- data/spec/ast/and_spec.rb +1 -1
- data/spec/ast/arithmetic_spec.rb +56 -0
- data/spec/ast/division_spec.rb +1 -1
- data/spec/ast/function_spec.rb +43 -6
- data/spec/ast/max_spec.rb +15 -0
- data/spec/ast/min_spec.rb +15 -0
- data/spec/ast/or_spec.rb +35 -0
- data/spec/ast/round_spec.rb +25 -0
- data/spec/ast/rounddown_spec.rb +25 -0
- data/spec/ast/roundup_spec.rb +25 -0
- data/spec/ast/switch_spec.rb +30 -0
- data/spec/calculator_spec.rb +26 -4
- data/spec/exceptions_spec.rb +1 -1
- data/spec/parser_spec.rb +22 -3
- data/spec/spec_helper.rb +12 -2
- data/spec/token_scanner_spec.rb +0 -4
- data/spec/tokenizer_spec.rb +40 -2
- metadata +39 -3
data/lib/dentaku/version.rb
CHANGED
data/spec/ast/addition_spec.rb
CHANGED
@@ -4,8 +4,8 @@ require 'dentaku/ast/arithmetic'
|
|
4
4
|
require 'dentaku/token'
|
5
5
|
|
6
6
|
describe Dentaku::AST::Addition do
|
7
|
-
let(:five) { Dentaku::AST::
|
8
|
-
let(:six) { Dentaku::AST::
|
7
|
+
let(:five) { Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, 5) }
|
8
|
+
let(:six) { Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, 6) }
|
9
9
|
|
10
10
|
let(:t) { Dentaku::AST::Numeric.new Dentaku::Token.new(:logical, true) }
|
11
11
|
|
@@ -17,7 +17,7 @@ describe Dentaku::AST::Addition do
|
|
17
17
|
it 'requires numeric operands' do
|
18
18
|
expect {
|
19
19
|
described_class.new(five, t)
|
20
|
-
}.to raise_error(Dentaku::
|
20
|
+
}.to raise_error(Dentaku::NodeError, /requires numeric operands/)
|
21
21
|
|
22
22
|
expression = Dentaku::AST::Multiplication.new(five, five)
|
23
23
|
group = Dentaku::AST::Grouping.new(expression)
|
@@ -29,7 +29,7 @@ describe Dentaku::AST::Addition do
|
|
29
29
|
|
30
30
|
it 'allows operands that respond to addition' do
|
31
31
|
# Sample struct that has a custom definition for addition
|
32
|
-
|
32
|
+
|
33
33
|
Operand = Struct.new(:value) do
|
34
34
|
def +(other)
|
35
35
|
case other
|
@@ -41,8 +41,8 @@ describe Dentaku::AST::Addition do
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
operand_five = Dentaku::AST::
|
45
|
-
operand_six = Dentaku::AST::
|
44
|
+
operand_five = Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, Operand.new(5))
|
45
|
+
operand_six = Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, Operand.new(6))
|
46
46
|
|
47
47
|
expect {
|
48
48
|
described_class.new(operand_five, operand_six)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku'
|
3
|
+
require 'dentaku/ast/functions/and'
|
4
|
+
|
5
|
+
describe 'Dentaku::AST::And' do
|
6
|
+
let(:calculator) { Dentaku::Calculator.new }
|
7
|
+
|
8
|
+
it 'returns false if any of the arguments is false' do
|
9
|
+
result = Dentaku('AND(1 = 1, 0 = 1)')
|
10
|
+
expect(result).to eq false
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'supports nested expressions' do
|
14
|
+
result = Dentaku('AND(y = 1, x = 1)', x: 1, y: 2)
|
15
|
+
expect(result).to eq false
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns true if all of the arguments are true' do
|
19
|
+
result = Dentaku('AND(1 = 1, "2" = "2", true = true, true)')
|
20
|
+
expect(result).to eq true
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns true if all nested AND functions return true' do
|
24
|
+
result = Dentaku('AND(AND(1 = 1), AND(true != false, AND(true)))')
|
25
|
+
expect(result).to eq true
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'raises an error if no arguments are passed' do
|
29
|
+
expect { calculator.evaluate!('AND()') }.to raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'raises an error if a non logical argument is passed' do
|
33
|
+
expect { calculator.evaluate!('AND("r")') }.to raise_error(ArgumentError)
|
34
|
+
end
|
35
|
+
end
|
data/spec/ast/and_spec.rb
CHANGED
@@ -17,7 +17,7 @@ describe Dentaku::AST::And do
|
|
17
17
|
it 'requires logical operands' do
|
18
18
|
expect {
|
19
19
|
described_class.new(t, five)
|
20
|
-
}.to raise_error(Dentaku::
|
20
|
+
}.to raise_error(Dentaku::NodeError, /requires logical operands/)
|
21
21
|
|
22
22
|
expression = Dentaku::AST::LessThanOrEqual.new(five, five)
|
23
23
|
expect {
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku/ast/arithmetic'
|
3
|
+
|
4
|
+
require 'dentaku/token'
|
5
|
+
|
6
|
+
describe Dentaku::AST::Arithmetic 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' => 1, 'y' => 2}}
|
12
|
+
|
13
|
+
it 'performs an arithmetic operation with numeric operands' do
|
14
|
+
expect(add(one, two)).to eq 3
|
15
|
+
expect(sub(one, two)).to eq -1
|
16
|
+
expect(mul(one, two)).to eq 2
|
17
|
+
expect(div(one, two)).to eq 0.5
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'performs an arithmetic operation with one numeric operand and one string operand' do
|
21
|
+
expect(add(one, x)).to eq 2
|
22
|
+
expect(sub(one, x)).to eq 0
|
23
|
+
expect(mul(one, x)).to eq 1
|
24
|
+
expect(div(one, x)).to eq 1
|
25
|
+
|
26
|
+
expect(add(y, two)).to eq 4
|
27
|
+
expect(sub(y, two)).to eq 0
|
28
|
+
expect(mul(y, two)).to eq 4
|
29
|
+
expect(div(y, two)).to eq 1
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'performs an arithmetic operation with string operands' do
|
33
|
+
expect(add(x, y)).to eq 3
|
34
|
+
expect(sub(x, y)).to eq -1
|
35
|
+
expect(mul(x, y)).to eq 2
|
36
|
+
expect(div(x, y)).to eq 0.5
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def add(left, right)
|
42
|
+
Dentaku::AST::Addition.new(left, right).value(ctx)
|
43
|
+
end
|
44
|
+
|
45
|
+
def sub(left, right)
|
46
|
+
Dentaku::AST::Subtraction.new(left, right).value(ctx)
|
47
|
+
end
|
48
|
+
|
49
|
+
def mul(left, right)
|
50
|
+
Dentaku::AST::Multiplication.new(left, right).value(ctx)
|
51
|
+
end
|
52
|
+
|
53
|
+
def div(left, right)
|
54
|
+
Dentaku::AST::Division.new(left, right).value(ctx)
|
55
|
+
end
|
56
|
+
end
|
data/spec/ast/division_spec.rb
CHANGED
@@ -17,7 +17,7 @@ describe Dentaku::AST::Division do
|
|
17
17
|
it 'requires numeric operands' do
|
18
18
|
expect {
|
19
19
|
described_class.new(five, t)
|
20
|
-
}.to raise_error(Dentaku::
|
20
|
+
}.to raise_error(Dentaku::NodeError, /requires numeric operands/)
|
21
21
|
|
22
22
|
expression = Dentaku::AST::Multiplication.new(five, five)
|
23
23
|
group = Dentaku::AST::Grouping.new(expression)
|
data/spec/ast/function_spec.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
require 'bigdecimal'
|
1
2
|
require 'spec_helper'
|
2
3
|
require 'dentaku/ast/function'
|
4
|
+
require 'dentaku/exceptions'
|
3
5
|
|
4
6
|
class Clazz; end
|
5
7
|
|
@@ -8,12 +10,6 @@ describe Dentaku::AST::Function do
|
|
8
10
|
expect(described_class).to respond_to(:get)
|
9
11
|
end
|
10
12
|
|
11
|
-
it 'raises an exception when trying to access an undefined function' do
|
12
|
-
expect {
|
13
|
-
described_class.get("flarble")
|
14
|
-
}.to raise_error(Dentaku::ParseError, /undefined function/i)
|
15
|
-
end
|
16
|
-
|
17
13
|
it 'registers a custom function' do
|
18
14
|
described_class.register("flarble", :string, -> { "flarble" })
|
19
15
|
expect { described_class.get("flarble") }.not_to raise_error
|
@@ -24,4 +20,45 @@ describe Dentaku::AST::Function do
|
|
24
20
|
it 'does not throw an error when registering a function with a name that matches a currently defined constant' do
|
25
21
|
expect { described_class.register("clazz", :string, -> { "clazzified" }) }.not_to raise_error
|
26
22
|
end
|
23
|
+
|
24
|
+
describe "#arity" do
|
25
|
+
it "gives the correct arity for custom functions" do
|
26
|
+
zero = described_class.register("zero", :numeric, ->() { 0 })
|
27
|
+
expect(zero.arity).to eq 0
|
28
|
+
|
29
|
+
one = described_class.register("one", :numeric, ->(x) { x * 2 })
|
30
|
+
expect(one.arity).to eq 1
|
31
|
+
|
32
|
+
two = described_class.register("two", :numeric, ->(x,y) { x + y })
|
33
|
+
expect(two.arity).to eq 2
|
34
|
+
|
35
|
+
many = described_class.register("many", :numeric, ->(*args) { args.max })
|
36
|
+
expect(many.arity).to be_nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'casts a String to an Integer if possible' do
|
41
|
+
expect(described_class.numeric('3')).to eq 3
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'casts a String to a BigDecimal if possible and if Integer would loose information' do
|
45
|
+
expect(described_class.numeric('3.2')).to eq 3.2
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'casts a String to a BigDecimal with a negative number' do
|
49
|
+
expect(described_class.numeric('-3.2')).to eq -3.2
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'casts a String to a BigDecimal without a leading zero' do
|
53
|
+
expect(described_class.numeric('-.2')).to eq -0.2
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'raises an error if the value could not be cast to a Numeric' do
|
57
|
+
expect { described_class.numeric('flarble') }.to raise_error TypeError
|
58
|
+
expect { described_class.numeric('-') }.to raise_error TypeError
|
59
|
+
expect { described_class.numeric('') }.to raise_error TypeError
|
60
|
+
expect { described_class.numeric(nil) }.to raise_error TypeError
|
61
|
+
expect { described_class.numeric('7.') }.to raise_error TypeError
|
62
|
+
expect { described_class.numeric(true) }.to raise_error TypeError
|
63
|
+
end
|
27
64
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku/ast/functions/max'
|
3
|
+
require 'dentaku'
|
4
|
+
|
5
|
+
describe 'Dentaku::AST::Function::Max' do
|
6
|
+
it 'returns the largest numeric value in an array of Numeric values' do
|
7
|
+
result = Dentaku('MAX(1, x, 1.8)', x: 2.3)
|
8
|
+
expect(result).to eq 2.3
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns the largest value even if a String is passed' do
|
12
|
+
result = Dentaku('MAX(1, x, 1.8)', x: '2.3')
|
13
|
+
expect(result).to eq 2.3
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku/ast/functions/min'
|
3
|
+
require 'dentaku'
|
4
|
+
|
5
|
+
describe 'Dentaku::AST::Function::Min' do
|
6
|
+
it 'returns the smallest numeric value in an array of Numeric values' do
|
7
|
+
result = Dentaku('MIN(1, x, 1.8)', x: 2.3)
|
8
|
+
expect(result).to eq 1
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns the smallest value even if a String is passed' do
|
12
|
+
result = Dentaku('MIN(1, x, 1.8)', x: '0.3')
|
13
|
+
expect(result).to eq 0.3
|
14
|
+
end
|
15
|
+
end
|
data/spec/ast/or_spec.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku'
|
3
|
+
require 'dentaku/ast/functions/or'
|
4
|
+
|
5
|
+
describe 'Dentaku::AST::Or' do
|
6
|
+
let(:calculator) { Dentaku::Calculator.new }
|
7
|
+
|
8
|
+
it 'returns false if all of the arguments are false' do
|
9
|
+
result = Dentaku('OR(1 = "1", 0 = 1)')
|
10
|
+
expect(result).to eq false
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'supports nested expressions' do
|
14
|
+
result = Dentaku('OR(y = 1, x = 1)', x: 1, y: 2)
|
15
|
+
expect(result).to eq true
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns true if any of the arguments is true' do
|
19
|
+
result = Dentaku('OR(1 = "1", "2" = "2", true = false, false)')
|
20
|
+
expect(result).to eq true
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns true if any nested OR function returns true' do
|
24
|
+
result = Dentaku('OR(OR(1 = 0), OR(true = false, OR(true)))')
|
25
|
+
expect(result).to eq true
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'raises an error if no arguments are passed' do
|
29
|
+
expect { calculator.evaluate!('OR()') }.to raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'raises an error if a non logical argument is passed' do
|
33
|
+
expect { calculator.evaluate!('OR("r")') }.to raise_error(ArgumentError)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku/ast/functions/round'
|
3
|
+
require 'dentaku'
|
4
|
+
|
5
|
+
describe 'Dentaku::AST::Function::Round' do
|
6
|
+
it 'returns the rounded down value' do
|
7
|
+
result = Dentaku('ROUND(1.8)')
|
8
|
+
expect(result).to eq 2
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns the rounded down value to the given precision' do
|
12
|
+
result = Dentaku('ROUND(x, y)', x: 1.8453, y: 3)
|
13
|
+
expect(result).to eq 1.845
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns the rounded down value to the given precision, also with strings' do
|
17
|
+
result = Dentaku('ROUND(x, y)', x: '1.8453', y: '3')
|
18
|
+
expect(result).to eq 1.845
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns the rounded down value to the given precision, also with nil' do
|
22
|
+
result = Dentaku('ROUND(x, y)', x: '1.8453', y: nil)
|
23
|
+
expect(result).to eq 2
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku/ast/functions/rounddown'
|
3
|
+
require 'dentaku'
|
4
|
+
|
5
|
+
describe 'Dentaku::AST::Function::Round' do
|
6
|
+
it 'returns the rounded value' do
|
7
|
+
result = Dentaku('ROUNDDOWN(1.8)')
|
8
|
+
expect(result).to eq 1
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns the rounded value to the given precision' do
|
12
|
+
result = Dentaku('ROUNDDOWN(x, y)', x: 1.8453, y: 3)
|
13
|
+
expect(result).to eq 1.845
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns the rounded value to the given precision, also with strings' do
|
17
|
+
result = Dentaku('ROUNDDOWN(x, y)', x: '1.8453', y: '3')
|
18
|
+
expect(result).to eq 1.845
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns the rounded value to the given precision, also with nil' do
|
22
|
+
result = Dentaku('ROUNDDOWN(x, y)', x: '1.8453', y: nil)
|
23
|
+
expect(result).to eq 1
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku/ast/functions/roundup'
|
3
|
+
require 'dentaku'
|
4
|
+
|
5
|
+
describe 'Dentaku::AST::Function::Round' do
|
6
|
+
it 'returns the rounded value' do
|
7
|
+
result = Dentaku('ROUNDUP(1.8)')
|
8
|
+
expect(result).to eq 2
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns the rounded value to the given precision' do
|
12
|
+
result = Dentaku('ROUNDUP(x, y)', x: 1.8453, y: 3)
|
13
|
+
expect(result).to eq 1.846
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns the rounded value to the given precision, also with strings' do
|
17
|
+
result = Dentaku('ROUNDUP(x, y)', x: '1.8453', y: '3')
|
18
|
+
expect(result).to eq 1.846
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns the rounded value to the given precision, also with nil' do
|
22
|
+
result = Dentaku('ROUNDUP(x, y)', x: '1.8453', y: nil)
|
23
|
+
expect(result).to eq 2
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dentaku/ast/functions/switch'
|
3
|
+
require 'dentaku'
|
4
|
+
|
5
|
+
describe 'Dentaku::AST::Function::Switch' do
|
6
|
+
it 'returns the match if present in argumtents' do
|
7
|
+
result = Dentaku('SWITCH(1, 1, "one", 2, "two")')
|
8
|
+
expect(result).to eq 'one'
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns nil if no match was found' do
|
12
|
+
result = Dentaku('SWITCH(3, 1, "one", 2, "two")')
|
13
|
+
expect(result).to eq nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns the default value if present and no match was found' do
|
17
|
+
result = Dentaku('SWITCH(3, 1, "one", 2, "two", "no match")')
|
18
|
+
expect(result).to eq 'no match'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns the first match if multiple matches exist' do
|
22
|
+
result = Dentaku('SWITCH(1, 1, "one", 2, "two", 1, "three")')
|
23
|
+
expect(result).to eq 'one'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'does not return a match where a value matches the search value' do
|
27
|
+
result = Dentaku('SWITCH(1, "one", 1, 2, "two", 3)')
|
28
|
+
expect(result).to eq 3
|
29
|
+
end
|
30
|
+
end
|
data/spec/calculator_spec.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'dentaku
|
2
|
+
require 'dentaku'
|
3
3
|
|
4
4
|
describe Dentaku::Calculator do
|
5
5
|
let(:calculator) { described_class.new }
|
@@ -32,6 +32,7 @@ describe Dentaku::Calculator do
|
|
32
32
|
expect(calculator.evaluate('0.253/0.253')).to eq(1)
|
33
33
|
expect(calculator.evaluate('0.253/d', d: 0.253)).to eq(1)
|
34
34
|
expect(calculator.evaluate('10 + x', x: 'abc')).to be_nil
|
35
|
+
expect(calculator.evaluate('a/b', a: '10', b: '2')).to eq(5)
|
35
36
|
expect(calculator.evaluate('t + 1*24*60*60', t: Time.local(2017, 1, 1))).to eq(Time.local(2017, 1, 2))
|
36
37
|
expect(calculator.evaluate("2 | 3 * 9")).to eq (27)
|
37
38
|
expect(calculator.evaluate("2 & 3 * 9")).to eq (2)
|
@@ -68,6 +69,13 @@ describe Dentaku::Calculator do
|
|
68
69
|
expect(calculator.evaluate!('a.basket.of')).to eq 'apples'
|
69
70
|
expect(calculator.evaluate!('b')).to eq 2
|
70
71
|
end
|
72
|
+
|
73
|
+
it 'stores arrays' do
|
74
|
+
calculator.store({a: [1, 2, 3]})
|
75
|
+
expect(calculator.evaluate!('a[0]')).to eq 1
|
76
|
+
expect(calculator.evaluate!('a[x]', x: 1)).to eq 2
|
77
|
+
expect(calculator.evaluate!('a[x+1]', x: 1)).to eq 3
|
78
|
+
end
|
71
79
|
end
|
72
80
|
|
73
81
|
describe 'dependencies' do
|
@@ -192,14 +200,20 @@ describe Dentaku::Calculator do
|
|
192
200
|
expect { calculator.evaluate!(unbound) }.to raise_error do |error|
|
193
201
|
expect(error.unbound_variables).to eq ['foo']
|
194
202
|
end
|
203
|
+
expect { calculator.evaluate!('a + b') }.to raise_error do |error|
|
204
|
+
expect(error.unbound_variables).to eq ['a', 'b']
|
205
|
+
end
|
195
206
|
expect(calculator.evaluate(unbound)).to be_nil
|
196
207
|
expect(calculator.evaluate(unbound) { :bar }).to eq :bar
|
197
208
|
expect(calculator.evaluate(unbound) { |e| e }).to eq unbound
|
198
209
|
end
|
199
210
|
|
200
211
|
it 'fails to evaluate incomplete statements' do
|
201
|
-
|
202
|
-
|
212
|
+
['true AND', 'a a ^&'].each do |statement|
|
213
|
+
expect {
|
214
|
+
calculator.evaluate!(statement)
|
215
|
+
}.to raise_error(Dentaku::ParseError)
|
216
|
+
end
|
203
217
|
end
|
204
218
|
|
205
219
|
it 'evaluates unbound statements given a binding in memory' do
|
@@ -455,7 +469,7 @@ describe Dentaku::Calculator do
|
|
455
469
|
Math.methods(false).each do |method|
|
456
470
|
it method do
|
457
471
|
if Math.method(method).arity == 2
|
458
|
-
expect(calculator.evaluate("#{method}(1,2)
|
472
|
+
expect(calculator.evaluate("#{method}(x,y)", x: 1, y: '2')).to eq Math.send(method, 1, 2)
|
459
473
|
else
|
460
474
|
expect(calculator.evaluate("#{method}(1)")).to eq Math.send(method, 1)
|
461
475
|
end
|
@@ -517,4 +531,12 @@ describe Dentaku::Calculator do
|
|
517
531
|
).to eq 'abcdef'
|
518
532
|
end
|
519
533
|
end
|
534
|
+
|
535
|
+
describe 'zero-arity functions' do
|
536
|
+
it 'can be used in formulas' do
|
537
|
+
calculator.add_function(:two, :numeric, -> { 2 })
|
538
|
+
expect(calculator.evaluate("max(two(), 1)")).to eq 2
|
539
|
+
expect(calculator.evaluate("max(1, two())")).to eq 2
|
540
|
+
end
|
541
|
+
end
|
520
542
|
end
|