dentaku 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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