dentaku 1.0.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93d875177f2fb6231220227687ce2fa8cfb01a83
4
- data.tar.gz: 2bfd4229e677bacb4838f101180574def2d1941b
3
+ metadata.gz: 8e51005fddbd5c542da8892bb3db03358e7d4cdf
4
+ data.tar.gz: 7c13a0a048a14e5bb40151224a95f7a2635ae9fa
5
5
  SHA512:
6
- metadata.gz: 95daa0fa7ff7bc708492f0747ef33601c555b32a441f5b50b13c8201edbc3f76d0d606cdd8057ee408e271a9869f1ff1a34ca8219d19e2bd00fc5119c52ea311
7
- data.tar.gz: 25e9184f6f077d7af863e434dbdbd87e505cf44d4fd3baf9c1a69400de77912f31b39ff48ad94f412ac3e759acd3a5ead9943837484b8555cc83d9e333a3a6e4
6
+ metadata.gz: 46026444a4b39bdc8df5f8913422619ac54b52d671e030b5599aa2db9642beabfdfd0c4e79ac9f40dc211de34e3ec791420aed2e162f86dd373c460229e6350a
7
+ data.tar.gz: 14409916459c8b1481e6f9e1005bd7c2bf49f9c84e7b29a3a97cc2161d92bf0615bde9b3b08005e177e9fcd188d76298324041fd54b36f966961e52ee4ecdf4f
@@ -1,3 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "1.9.3"
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - 2.1.1
7
+ - jruby-19mode
8
+ - rbx-2
data/README.md CHANGED
@@ -10,7 +10,8 @@ DESCRIPTION
10
10
 
11
11
  Dentaku is a parser and evaluator for a mathematical and logical formula
12
12
  language that allows run-time binding of values to variables referenced in the
13
- formulas.
13
+ formulas. It is intended to safely evaluate untrusted expressions without
14
+ opening security holes.
14
15
 
15
16
  EXAMPLE
16
17
  -------
@@ -129,7 +130,9 @@ and the exponent, so the token list could be defined as: `[:numeric,
129
130
  :numeric]`. Other functions might be variadic -- consider `max`, a function
130
131
  that takes any number of numeric inputs and returns the largest one. Its token
131
132
  list could be defined as: `[:non_close_plus]` (one or more tokens that are not
132
- closing parentheses.
133
+ closing parentheses). See the
134
+ [rules definitions](https://github.com/rubysolo/dentaku/blob/master/lib/dentaku/token_matcher.rb#L61)
135
+ for the names of token patterns you can use.
133
136
 
134
137
  Functions can be added individually using Calculator#add_function, or en masse using
135
138
  Calculator#add_functions.
@@ -169,7 +172,17 @@ THANKS
169
172
  ------
170
173
 
171
174
  Big thanks to [ElkStone Basements](http://www.elkstonebasements.com/) for
172
- allowing me to extract and open source this code.
175
+ allowing me to extract and open source this code. Thanks also to all the
176
+ contributors:
177
+
178
+ * [CraigCottingham](https://github.com/CraigCottingham)
179
+ * [arnaudl](https://github.com/arnaudl)
180
+ * [thbar](https://github.com/thbar) / [BoxCar](https://www.boxcar.io)
181
+ * [antonversal](https://github.com/antonversal)
182
+ * [mvbrocato](https://github.com/mvbrocato)
183
+ * [brixen](https://github.com/brixen)
184
+ * [0xCCD](https://github.com/0xCCD)
185
+
173
186
 
174
187
  LICENSE
175
188
  -------
@@ -1,3 +1,4 @@
1
+ require "bigdecimal"
1
2
  require "dentaku/calculator"
2
3
  require "dentaku/version"
3
4
 
@@ -6,6 +7,14 @@ module Dentaku
6
7
  calculator.evaluate(expression, data)
7
8
  end
8
9
 
10
+ class UnboundVariableError < StandardError
11
+ attr_reader :unbound_variables
12
+
13
+ def initialize(unbound_variables)
14
+ @unbound_variables = unbound_variables
15
+ end
16
+ end
17
+
9
18
  private
10
19
 
11
20
  def self.calculator
@@ -15,7 +15,7 @@ module Dentaku
15
15
  def divide
16
16
  quotient, remainder = left.divmod(right)
17
17
  return [:numeric, quotient] if remainder == 0
18
- [:numeric, left.to_f / right.to_f]
18
+ [:numeric, BigDecimal.new(left.to_s) / BigDecimal.new(right.to_s)]
19
19
  end
20
20
 
21
21
  def mod; [:numeric, left % right]; end
@@ -1,7 +1,7 @@
1
1
  require 'dentaku/evaluator'
2
+ require 'dentaku/expression'
2
3
  require 'dentaku/rules'
3
4
  require 'dentaku/token'
4
- require 'dentaku/tokenizer'
5
5
 
6
6
  module Dentaku
7
7
  class Calculator
@@ -22,19 +22,20 @@ module Dentaku
22
22
  end
23
23
 
24
24
  def evaluate(expression, data={})
25
- @tokenizer ||= Tokenizer.new
26
- @tokens = @tokenizer.tokenize(expression)
25
+ evaluate!(expression, data)
26
+ rescue UnboundVariableError
27
+ yield expression if block_given?
28
+ end
27
29
 
30
+ def evaluate!(expression, data={})
28
31
  store(data) do
32
+ expr = Expression.new(expression, @memory)
33
+ raise UnboundVariableError.new(expr.identifiers) if expr.unbound?
29
34
  @evaluator ||= Evaluator.new
30
- @result = @evaluator.evaluate(replace_identifiers_with_values)
35
+ @result = @evaluator.evaluate(expr.tokens)
31
36
  end
32
37
  end
33
38
 
34
- def memory(key=nil)
35
- key ? @memory[key.to_sym] : @memory
36
- end
37
-
38
39
  def store(key_or_hash, value=nil)
39
40
  restore = @memory.dup
40
41
 
@@ -63,28 +64,5 @@ module Dentaku
63
64
  def empty?
64
65
  @memory.empty?
65
66
  end
66
-
67
- private
68
-
69
- def replace_identifiers_with_values
70
- @tokens.map do |token|
71
- if token.is?(:identifier)
72
- value = memory(token.value)
73
- type = type_for_value(value)
74
-
75
- Token.new(type, value)
76
- else
77
- token
78
- end
79
- end
80
- end
81
-
82
- def type_for_value(value)
83
- case value
84
- when String then :string
85
- when TrueClass, FalseClass then :logical
86
- else :numeric
87
- end
88
- end
89
67
  end
90
68
  end
@@ -10,7 +10,7 @@ module Dentaku
10
10
  def evaluate_token_stream(tokens)
11
11
  while tokens.length > 1
12
12
  matched, tokens = match_rule_pattern(tokens)
13
- raise "no rule matched #{ tokens.map(&:category).inspect }" unless matched
13
+ raise "no rule matched {{#{ inspect_tokens(tokens) }}}" unless matched
14
14
  end
15
15
 
16
16
  tokens << Token.new(:numeric, 0) if tokens.empty?
@@ -18,6 +18,10 @@ module Dentaku
18
18
  tokens.first
19
19
  end
20
20
 
21
+ def inspect_tokens(tokens)
22
+ tokens.map { |t| t.to_s }.join(' ')
23
+ end
24
+
21
25
  def match_rule_pattern(tokens)
22
26
  matched = false
23
27
  Rules.each do |pattern, evaluator|
@@ -41,8 +45,8 @@ module Dentaku
41
45
  matched = true
42
46
 
43
47
  pattern.each do |matcher|
44
- match = matcher.match(token_stream, position + matches.length)
45
- matched &&= match.matched?
48
+ _matched, match = matcher.match(token_stream, position + matches.length)
49
+ matched &&= _matched
46
50
  matches += match
47
51
  end
48
52
 
@@ -54,19 +58,23 @@ module Dentaku
54
58
  end
55
59
 
56
60
  def evaluate_step(token_stream, start, length, evaluator)
57
- expr = token_stream.slice!(start, length)
61
+ substream = token_stream.slice!(start, length)
58
62
 
59
63
  if self.respond_to?(evaluator)
60
- token_stream.insert start, *self.send(evaluator, *expr)
64
+ token_stream.insert start, *self.send(evaluator, *substream)
61
65
  else
62
- func = Rules.func(evaluator)
63
- raise "unknown evaluator '#{evaluator.to_s}'" if func.nil?
66
+ result = user_defined_function(evaluator, substream)
67
+ token_stream.insert start, result
68
+ end
69
+ end
64
70
 
65
- arguments = extract_arguments_from_function_call(expr).map { |t| t.value }
66
- return_value = func.body.call(*arguments)
71
+ def user_defined_function(evaluator, tokens)
72
+ function = Rules.func(evaluator)
73
+ raise "unknown function '#{ evaluator }'" unless function
67
74
 
68
- token_stream.insert start, Token.new(func.type, return_value)
69
- end
75
+ arguments = extract_arguments_from_function_call(tokens).map { |t| t.value }
76
+ return_value = function.body.call(*arguments)
77
+ Token.new(function.type, return_value)
70
78
  end
71
79
 
72
80
  def extract_arguments_from_function_call(tokens)
@@ -0,0 +1,55 @@
1
+ require 'dentaku/tokenizer'
2
+
3
+ module Dentaku
4
+ class Expression
5
+ attr_reader :tokens
6
+
7
+ def initialize(string, variables={})
8
+ @raw = string
9
+ @tokenizer ||= Tokenizer.new
10
+ @tokens = @tokenizer.tokenize(@raw)
11
+ replace_identifiers_with_values(variables)
12
+ end
13
+
14
+ def identifiers
15
+ @tokens.select { |t| t.category == :identifier }.map { |t| t.value }
16
+ end
17
+
18
+ def unbound?
19
+ identifiers.any?
20
+ end
21
+
22
+ private
23
+
24
+ def replace_identifiers_with_values(variables)
25
+ @tokens.map! do |token|
26
+ if token.is?(:identifier)
27
+ replace_identifier_with_value(token, variables)
28
+ else
29
+ token
30
+ end
31
+ end
32
+ end
33
+
34
+ def replace_identifier_with_value(token, variables)
35
+ key = token.value.to_sym
36
+
37
+ if variables.key? key
38
+ value = variables[key]
39
+ type = type_for_value(value)
40
+
41
+ Token.new(type, value)
42
+ else
43
+ token
44
+ end
45
+ end
46
+
47
+ def type_for_value(value)
48
+ case value
49
+ when String then :string
50
+ when TrueClass, FalseClass then :logical
51
+ else :numeric
52
+ end
53
+ end
54
+ end
55
+ end
@@ -8,6 +8,10 @@ module Dentaku
8
8
  @raw_value = raw_value
9
9
  end
10
10
 
11
+ def to_s
12
+ raw_value || value
13
+ end
14
+
11
15
  def length
12
16
  raw_value.to_s.length
13
17
  end
@@ -7,8 +7,12 @@ module Dentaku
7
7
  @values = [values].compact.flatten
8
8
  @invert = false
9
9
 
10
+ @categories_hash = Hash[@categories.map { |cat| [cat, 1] }]
11
+ @values_hash = Hash[@values.map { |value| [value, 1] }]
12
+
10
13
  @min = 1
11
14
  @max = 1
15
+ @range = (@min..@max)
12
16
  end
13
17
 
14
18
  def invert
@@ -23,39 +27,40 @@ module Dentaku
23
27
 
24
28
  def match(token_stream, offset=0)
25
29
  matched_tokens = []
30
+ matched = false
26
31
 
27
32
  while self == token_stream[matched_tokens.length + offset] && matched_tokens.length < @max
28
33
  matched_tokens << token_stream[matched_tokens.length + offset]
29
34
  end
30
35
 
31
- if (@min..@max).include? matched_tokens.length
32
- def matched_tokens.matched?() true end
33
- else
34
- def matched_tokens.matched?() false end
36
+ if @range.cover?(matched_tokens.length)
37
+ matched = true
35
38
  end
36
39
 
37
- matched_tokens
40
+ [matched, matched_tokens]
38
41
  end
39
42
 
40
43
  def star
41
44
  @min = 0
42
45
  @max = Float::INFINITY
46
+ @range = (@min..@max)
43
47
  self
44
48
  end
45
49
 
46
50
  def plus
47
51
  @max = Float::INFINITY
52
+ @range = (@min..@max)
48
53
  self
49
54
  end
50
55
 
51
56
  private
52
57
 
53
58
  def category_match(category)
54
- @categories.empty? || @categories.include?(category)
59
+ @categories_hash.empty? || @categories_hash.key?(category)
55
60
  end
56
61
 
57
62
  def value_match(value)
58
- @values.empty? || @values.include?(value)
63
+ @values.empty? || @values_hash.key?(value)
59
64
  end
60
65
 
61
66
  def self.numeric; new(:numeric); end
@@ -92,5 +97,4 @@ module Dentaku
92
97
  end
93
98
 
94
99
  end
95
- end
96
-
100
+ end
@@ -40,7 +40,7 @@ module Dentaku
40
40
  end
41
41
 
42
42
  def numeric
43
- new(:numeric, '(\d+(\.\d+)?|\.\d+)\b', lambda { |raw| raw =~ /\./ ? raw.to_f : raw.to_i })
43
+ new(:numeric, '(\d+(\.\d+)?|\.\d+)\b', lambda { |raw| raw =~ /\./ ? BigDecimal.new(raw) : raw.to_i })
44
44
  end
45
45
 
46
46
  def double_quoted_string
@@ -63,8 +63,8 @@ module Dentaku
63
63
 
64
64
  def comparator
65
65
  names = { le: '<=', ge: '>=', ne: '!=', lt: '<', gt: '>', eq: '=' }.invert
66
- alternate = { ne: '<>' }.invert
67
- new(:comparator, '<=|>=|!=|<>|<|>|=', lambda { |raw| names[raw] || alternate[raw] })
66
+ alternate = { ne: '<>', eq: '==' }.invert
67
+ new(:comparator, '<=|>=|!=|<>|<|>|==|=', lambda { |raw| names[raw] || alternate[raw] })
68
68
  end
69
69
 
70
70
  def combinator
@@ -1,3 +1,3 @@
1
1
  module Dentaku
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -5,41 +5,41 @@ describe Dentaku::BinaryOperation do
5
5
  let(:logical) { described_class.new(true, false) }
6
6
 
7
7
  it 'raises a number to a power' do
8
- operation.pow.should eq [:numeric, 8]
8
+ expect(operation.pow).to eq [:numeric, 8]
9
9
  end
10
10
 
11
11
  it 'adds two numbers' do
12
- operation.add.should eq [:numeric, 5]
12
+ expect(operation.add).to eq [:numeric, 5]
13
13
  end
14
14
 
15
15
  it 'subtracts two numbers' do
16
- operation.subtract.should eq [:numeric, -1]
16
+ expect(operation.subtract).to eq [:numeric, -1]
17
17
  end
18
18
 
19
19
  it 'multiplies two numbers' do
20
- operation.multiply.should eq [:numeric, 6]
20
+ expect(operation.multiply).to eq [:numeric, 6]
21
21
  end
22
22
 
23
23
  it 'divides two numbers' do
24
- operation.divide.should eq [:numeric, (2.0/3.0)]
24
+ expect(operation.divide).to eq [:numeric, (BigDecimal.new('2.0')/BigDecimal.new('3.0'))]
25
25
  end
26
26
 
27
27
  it 'compares two numbers' do
28
- operation.le.should eq [:logical, true]
29
- operation.lt.should eq [:logical, true]
30
- operation.ne.should eq [:logical, true]
28
+ expect(operation.le).to eq [:logical, true]
29
+ expect(operation.lt).to eq [:logical, true]
30
+ expect(operation.ne).to eq [:logical, true]
31
31
 
32
- operation.ge.should eq [:logical, false]
33
- operation.gt.should eq [:logical, false]
34
- operation.eq.should eq [:logical, false]
32
+ expect(operation.ge).to eq [:logical, false]
33
+ expect(operation.gt).to eq [:logical, false]
34
+ expect(operation.eq).to eq [:logical, false]
35
35
  end
36
36
 
37
37
  it 'performs logical AND and OR' do
38
- logical.and.should eq [:logical, false]
39
- logical.or.should eq [:logical, true]
38
+ expect(logical.and).to eq [:logical, false]
39
+ expect(logical.or).to eq [:logical, true]
40
40
  end
41
41
 
42
42
  it 'mods two numbers' do
43
- operation.mod.should eq [:numeric, 2%3]
43
+ expect(operation.mod).to eq [:numeric, 2%3]
44
44
  end
45
45
  end
@@ -5,102 +5,102 @@ describe Dentaku::Calculator do
5
5
  let(:with_memory) { described_class.new.store(:apples => 3) }
6
6
 
7
7
  it 'evaluates an expression' do
8
- calculator.evaluate('7+3').should eq(10)
8
+ expect(calculator.evaluate('7+3')).to eq(10)
9
9
  end
10
10
 
11
11
  describe 'memory' do
12
- it { calculator.should be_empty }
13
- it { with_memory.should_not be_empty }
14
- it { with_memory.clear.should be_empty }
12
+ it { expect(calculator).to be_empty }
13
+ it { expect(with_memory).not_to be_empty }
14
+ it { expect(with_memory.clear).to be_empty }
15
15
 
16
- it { with_memory.memory(:apples).should eq(3) }
17
- it { with_memory.memory('apples').should eq(3) }
18
-
19
- it { calculator.store(:apples, 3).memory('apples').should eq(3) }
20
- it { calculator.store('apples', 3).memory(:apples).should eq(3) }
21
-
22
- it 'should discard local values' do
23
- calculator.evaluate('pears * 2', :pears => 5).should eq(10)
24
- calculator.should be_empty
25
- lambda { calculator.tokenize('pears * 2') }.should raise_error
16
+ it 'discards local values' do
17
+ expect(calculator.evaluate('pears * 2', :pears => 5)).to eq(10)
18
+ expect(calculator).to be_empty
26
19
  end
27
20
  end
28
21
 
29
- it 'should evaluate a statement with no variables' do
30
- calculator.evaluate('5+3').should eq(8)
31
- calculator.evaluate('(1+1+1)/3*100').should eq(100)
22
+ it 'evaluates a statement with no variables' do
23
+ expect(calculator.evaluate('5+3')).to eq(8)
24
+ expect(calculator.evaluate('(1+1+1)/3*100')).to eq(100)
32
25
  end
33
26
 
34
- it 'should fail to evaluate unbound statements' do
35
- lambda { calculator.evaluate('foo * 1.5') }.should raise_error
27
+ it 'fails to evaluate unbound statements' do
28
+ unbound = 'foo * 1.5'
29
+ expect { calculator.evaluate!(unbound) }.to raise_error(Dentaku::UnboundVariableError)
30
+ expect { calculator.evaluate!(unbound) }.to raise_error do |error|
31
+ expect(error.unbound_variables).to eq [:foo]
32
+ end
33
+ expect(calculator.evaluate(unbound)).to be_nil
34
+ expect(calculator.evaluate(unbound) { :bar }).to eq :bar
35
+ expect(calculator.evaluate(unbound) { |e| e }).to eq unbound
36
36
  end
37
37
 
38
- it 'should evaluate unbound statements given a binding in memory' do
39
- calculator.evaluate('foo * 1.5', :foo => 2).should eq(3)
40
- calculator.bind(:monkeys => 3).evaluate('monkeys < 7').should be_true
41
- calculator.evaluate('monkeys / 1.5').should eq(2)
38
+ it 'evaluates unbound statements given a binding in memory' do
39
+ expect(calculator.evaluate('foo * 1.5', :foo => 2)).to eq(3)
40
+ expect(calculator.bind(:monkeys => 3).evaluate('monkeys < 7')).to be_truthy
41
+ expect(calculator.evaluate('monkeys / 1.5')).to eq(2)
42
42
  end
43
43
 
44
- it 'should rebind for each evaluation' do
45
- calculator.evaluate('foo * 2', :foo => 2).should eq(4)
46
- calculator.evaluate('foo * 2', :foo => 4).should eq(8)
44
+ it 'rebinds for each evaluation' do
45
+ expect(calculator.evaluate('foo * 2', :foo => 2)).to eq(4)
46
+ expect(calculator.evaluate('foo * 2', :foo => 4)).to eq(8)
47
47
  end
48
48
 
49
- it 'should accept strings or symbols for binding keys' do
50
- calculator.evaluate('foo * 2', :foo => 2).should eq(4)
51
- calculator.evaluate('foo * 2', 'foo' => 4).should eq(8)
49
+ it 'accepts strings or symbols for binding keys' do
50
+ expect(calculator.evaluate('foo * 2', :foo => 2)).to eq(4)
51
+ expect(calculator.evaluate('foo * 2', 'foo' => 4)).to eq(8)
52
52
  end
53
53
 
54
- it 'should accept digits in identifiers' do
55
- calculator.evaluate('foo1 * 2', :foo1 => 2).should eq(4)
56
- calculator.evaluate('foo1 * 2', 'foo1' => 4).should eq(8)
57
- calculator.evaluate('1foo * 2', '1foo' => 2).should eq(4)
58
- calculator.evaluate('fo1o * 2', :fo1o => 4).should eq(8)
54
+ it 'accepts digits in identifiers' do
55
+ expect(calculator.evaluate('foo1 * 2', :foo1 => 2)).to eq(4)
56
+ expect(calculator.evaluate('foo1 * 2', 'foo1' => 4)).to eq(8)
57
+ expect(calculator.evaluate('1foo * 2', '1foo' => 2)).to eq(4)
58
+ expect(calculator.evaluate('fo1o * 2', :fo1o => 4)).to eq(8)
59
59
  end
60
60
 
61
- it 'should compare string literals with string variables' do
62
- calculator.evaluate('fruit = "apple"', :fruit => 'apple').should be_true
63
- calculator.evaluate('fruit = "apple"', :fruit => 'pear').should be_false
61
+ it 'compares string literals with string variables' do
62
+ expect(calculator.evaluate('fruit = "apple"', :fruit => 'apple')).to be_truthy
63
+ expect(calculator.evaluate('fruit = "apple"', :fruit => 'pear')).to be_falsey
64
64
  end
65
65
 
66
- it 'should do case-sensitive comparison' do
67
- calculator.evaluate('fruit = "Apple"', :fruit => 'apple').should be_false
68
- calculator.evaluate('fruit = "Apple"', :fruit => 'Apple').should be_true
66
+ it 'performs case-sensitive comparison' do
67
+ expect(calculator.evaluate('fruit = "Apple"', :fruit => 'apple')).to be_falsey
68
+ expect(calculator.evaluate('fruit = "Apple"', :fruit => 'Apple')).to be_truthy
69
69
  end
70
70
 
71
- it 'should allow binding logical values' do
72
- calculator.evaluate('some_boolean AND 7 > 5', :some_boolean => true).should be_true
73
- calculator.evaluate('some_boolean AND 7 < 5', :some_boolean => true).should be_false
74
- calculator.evaluate('some_boolean AND 7 > 5', :some_boolean => false).should be_false
71
+ it 'allows binding logical values' do
72
+ expect(calculator.evaluate('some_boolean AND 7 > 5', :some_boolean => true)).to be_truthy
73
+ expect(calculator.evaluate('some_boolean AND 7 < 5', :some_boolean => true)).to be_falsey
74
+ expect(calculator.evaluate('some_boolean AND 7 > 5', :some_boolean => false)).to be_falsey
75
75
 
76
- calculator.evaluate('some_boolean OR 7 > 5', :some_boolean => true).should be_true
77
- calculator.evaluate('some_boolean OR 7 < 5', :some_boolean => true).should be_true
78
- calculator.evaluate('some_boolean OR 7 < 5', :some_boolean => false).should be_false
76
+ expect(calculator.evaluate('some_boolean OR 7 > 5', :some_boolean => true)).to be_truthy
77
+ expect(calculator.evaluate('some_boolean OR 7 < 5', :some_boolean => true)).to be_truthy
78
+ expect(calculator.evaluate('some_boolean OR 7 < 5', :some_boolean => false)).to be_falsey
79
79
 
80
80
  end
81
81
 
82
82
  describe 'functions' do
83
- it 'should include IF' do
84
- calculator.evaluate('if(foo < 8, 10, 20)', :foo => 2).should eq(10)
85
- calculator.evaluate('if(foo < 8, 10, 20)', :foo => 9).should eq(20)
86
- calculator.evaluate('if (foo < 8, 10, 20)', :foo => 2).should eq(10)
87
- calculator.evaluate('if (foo < 8, 10, 20)', :foo => 9).should eq(20)
83
+ it 'include IF' do
84
+ expect(calculator.evaluate('if(foo < 8, 10, 20)', :foo => 2)).to eq(10)
85
+ expect(calculator.evaluate('if(foo < 8, 10, 20)', :foo => 9)).to eq(20)
86
+ expect(calculator.evaluate('if (foo < 8, 10, 20)', :foo => 2)).to eq(10)
87
+ expect(calculator.evaluate('if (foo < 8, 10, 20)', :foo => 9)).to eq(20)
88
88
  end
89
89
 
90
- it 'should include ROUND' do
91
- calculator.evaluate('round(8.2)').should eq(8)
92
- calculator.evaluate('round(8.8)').should eq(9)
93
- calculator.evaluate('round(8.75, 1)').should eq(8.8)
90
+ it 'include ROUND' do
91
+ expect(calculator.evaluate('round(8.2)')).to eq(8)
92
+ expect(calculator.evaluate('round(8.8)')).to eq(9)
93
+ expect(calculator.evaluate('round(8.75, 1)')).to eq(BigDecimal.new('8.8'))
94
94
 
95
- calculator.evaluate('ROUND(apples * 0.93)', { :apples => 10 }).should eq(9)
95
+ expect(calculator.evaluate('ROUND(apples * 0.93)', { :apples => 10 })).to eq(9)
96
96
  end
97
97
 
98
- it 'should include NOT' do
99
- calculator.evaluate('NOT(some_boolean)', :some_boolean => true).should be_false
100
- calculator.evaluate('NOT(some_boolean)', :some_boolean => false).should be_true
98
+ it 'include NOT' do
99
+ expect(calculator.evaluate('NOT(some_boolean)', :some_boolean => true)).to be_falsey
100
+ expect(calculator.evaluate('NOT(some_boolean)', :some_boolean => false)).to be_truthy
101
101
 
102
- calculator.evaluate('NOT(some_boolean) AND 7 > 5', :some_boolean => true).should be_false
103
- calculator.evaluate('NOT(some_boolean) OR 7 < 5', :some_boolean => false).should be_true
102
+ expect(calculator.evaluate('NOT(some_boolean) AND 7 > 5', :some_boolean => true)).to be_falsey
103
+ expect(calculator.evaluate('NOT(some_boolean) OR 7 < 5', :some_boolean => false)).to be_truthy
104
104
  end
105
105
  end
106
106
  end