dentaku 0.2.10 → 0.2.11

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.
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  Dentaku
2
2
  =======
3
3
 
4
+ [![Gem Version](https://badge.fury.io/rb/dentaku.png)](http://badge.fury.io/rb/dentaku)
5
+ [![Build Status](https://travis-ci.org/rubysolo/dentaku.png?branch=master)](https://travis-ci.org/rubysolo/dentaku)
6
+ [![Code Climate](https://codeclimate.com/github/rubysolo/dentaku.png)](https://codeclimate.com/github/rubysolo/dentaku)
7
+
4
8
  http://github.com/rubysolo/dentaku
5
9
 
6
10
  DESCRIPTION
@@ -72,7 +76,7 @@ SUPPORTED OPERATORS AND FUNCTIONS
72
76
  ---------------------------------
73
77
 
74
78
  Math: `+ - * /`
75
- Logic: `< > <= >= <> != = AND OR`
79
+ Logic: `< > <= >= <> != = AND OR NOT`
76
80
  Functions: `IF ROUND`
77
81
 
78
82
  THANKS
@@ -0,0 +1,31 @@
1
+ module Dentaku
2
+ class BinaryOperation
3
+ attr_reader :left, :right
4
+
5
+ def initialize(left, right)
6
+ @left = left
7
+ @right = right
8
+ end
9
+
10
+ def pow; [:numeric, left ** right]; end
11
+ def add; [:numeric, left + right]; end
12
+ def subtract; [:numeric, left - right]; end
13
+ def multiply; [:numeric, left * right]; end
14
+
15
+ def divide
16
+ quotient, remainder = left.divmod(right)
17
+ return [:numeric, quotient] if remainder == 0
18
+ [:numeric, left.to_f / right.to_f]
19
+ end
20
+
21
+ def le; [:logical, left <= right]; end
22
+ def ge; [:logical, left >= right]; end
23
+ def lt; [:logical, left < right]; end
24
+ def gt; [:logical, left > right]; end
25
+ def ne; [:logical, left != right]; end
26
+ def eq; [:logical, left == right]; end
27
+
28
+ def and; [:logical, left && right]; end
29
+ def or; [:logical, left || right]; end
30
+ end
31
+ end
@@ -31,7 +31,7 @@ module Dentaku
31
31
  @memory[key_or_hash.to_sym] = value
32
32
  else
33
33
  key_or_hash.each do |key, value|
34
- @memory[key.to_sym] = value if value
34
+ @memory[key.to_sym] = value
35
35
  end
36
36
  end
37
37
 
@@ -69,7 +69,11 @@ module Dentaku
69
69
  end
70
70
 
71
71
  def type_for_value(value)
72
- value.is_a?(String) ? :string : :numeric
72
+ case value
73
+ when String then :string
74
+ when TrueClass, FalseClass then :logical
75
+ else :numeric
76
+ end
73
77
  end
74
78
  end
75
79
  end
@@ -1,85 +1,15 @@
1
- require 'dentaku/token'
2
- require 'dentaku/token_matcher'
1
+ require 'dentaku/rules'
2
+ require 'dentaku/binary_operation'
3
3
 
4
4
  module Dentaku
5
5
  class Evaluator
6
- # tokens
7
- T_NUMERIC = TokenMatcher.new(:numeric)
8
- T_STRING = TokenMatcher.new(:string)
9
- T_ADDSUB = TokenMatcher.new(:operator, [:add, :subtract])
10
- T_MULDIV = TokenMatcher.new(:operator, [:multiply, :divide])
11
- T_POW = TokenMatcher.new(:operator, :pow)
12
- T_COMPARATOR = TokenMatcher.new(:comparator)
13
- T_COMP_GT = TokenMatcher.new(:comparator, [:gt, :ge])
14
- T_COMP_LT = TokenMatcher.new(:comparator, [:lt, :le])
15
- T_OPEN = TokenMatcher.new(:grouping, :open)
16
- T_CLOSE = TokenMatcher.new(:grouping, :close)
17
- T_COMMA = TokenMatcher.new(:grouping, :comma)
18
- T_NON_GROUP = TokenMatcher.new(:grouping).invert
19
- T_LOGICAL = TokenMatcher.new(:logical)
20
- T_COMBINATOR = TokenMatcher.new(:combinator)
21
- T_IF = TokenMatcher.new(:function, :if)
22
- T_ROUND = TokenMatcher.new(:function, :round)
23
- T_ROUNDUP = TokenMatcher.new(:function, :roundup)
24
- T_ROUNDDOWN = TokenMatcher.new(:function, :rounddown)
25
- T_NOT = TokenMatcher.new(:function, :not)
26
-
27
- T_NON_GROUP_STAR = TokenMatcher.new(:grouping).invert.star
28
-
29
- # patterns
30
- P_GROUP = [T_OPEN, T_NON_GROUP_STAR, T_CLOSE]
31
- P_MATH_ADD = [T_NUMERIC, T_ADDSUB, T_NUMERIC]
32
- P_MATH_MUL = [T_NUMERIC, T_MULDIV, T_NUMERIC]
33
- P_MATH_POW = [T_NUMERIC, T_POW, T_NUMERIC]
34
- P_RANGE_ASC = [T_NUMERIC, T_COMP_LT, T_NUMERIC, T_COMP_LT, T_NUMERIC]
35
- P_RANGE_DESC = [T_NUMERIC, T_COMP_GT, T_NUMERIC, T_COMP_GT, T_NUMERIC]
36
- P_NUM_COMP = [T_NUMERIC, T_COMPARATOR, T_NUMERIC]
37
- P_STR_COMP = [T_STRING, T_COMPARATOR, T_STRING]
38
- P_COMBINE = [T_LOGICAL, T_COMBINATOR, T_LOGICAL]
39
-
40
- P_IF = [T_IF, T_OPEN, T_NON_GROUP, T_COMMA, T_NON_GROUP, T_COMMA, T_NON_GROUP, T_CLOSE]
41
- P_ROUND_ONE = [T_ROUND, T_OPEN, T_NON_GROUP_STAR, T_CLOSE]
42
- P_ROUND_TWO = [T_ROUND, T_OPEN, T_NON_GROUP_STAR, T_COMMA, T_NUMERIC, T_CLOSE]
43
- P_ROUNDUP = [T_ROUNDUP, T_OPEN, T_NON_GROUP_STAR, T_CLOSE]
44
- P_ROUNDDOWN = [T_ROUNDDOWN, T_OPEN, T_NON_GROUP_STAR, T_CLOSE]
45
- P_NOT = [T_NOT, T_OPEN, T_NON_GROUP_STAR, T_CLOSE]
46
-
47
- RULES = [
48
- [P_IF, :if],
49
- [P_ROUND_ONE, :round],
50
- [P_ROUND_TWO, :round],
51
- [P_ROUNDUP, :roundup],
52
- [P_ROUNDDOWN, :rounddown],
53
- [P_NOT, :not],
54
-
55
- [P_GROUP, :evaluate_group],
56
- [P_MATH_POW, :apply],
57
- [P_MATH_MUL, :apply],
58
- [P_MATH_ADD, :apply],
59
- [P_RANGE_ASC, :expand_range],
60
- [P_RANGE_DESC, :expand_range],
61
- [P_NUM_COMP, :apply],
62
- [P_STR_COMP, :apply],
63
- [P_COMBINE, :apply]
64
- ]
65
-
66
6
  def evaluate(tokens)
67
7
  evaluate_token_stream(tokens).value
68
8
  end
69
9
 
70
10
  def evaluate_token_stream(tokens)
71
11
  while tokens.length > 1
72
- matched = false
73
- RULES.each do |pattern, evaluator|
74
- pos, match = find_rule_match(pattern, tokens)
75
-
76
- if pos
77
- tokens = evaluate_step(tokens, pos, match.length, evaluator)
78
- matched = true
79
- break
80
- end
81
- end
82
-
12
+ matched, tokens = match_rule_pattern(tokens)
83
13
  raise "no rule matched #{ tokens.map(&:category).inspect }" unless matched
84
14
  end
85
15
 
@@ -88,9 +18,19 @@ module Dentaku
88
18
  tokens.first
89
19
  end
90
20
 
91
- def evaluate_step(token_stream, start, length, evaluator)
92
- expr = token_stream.slice!(start, length)
93
- token_stream.insert start, *self.send(evaluator, *expr)
21
+ def match_rule_pattern(tokens)
22
+ matched = false
23
+ Rules.each do |pattern, evaluator|
24
+ pos, match = find_rule_match(pattern, tokens)
25
+
26
+ if pos
27
+ tokens = evaluate_step(tokens, pos, match.length, evaluator)
28
+ matched = true
29
+ break
30
+ end
31
+ end
32
+
33
+ [matched, tokens]
94
34
  end
95
35
 
96
36
  def find_rule_match(pattern, token_stream)
@@ -113,40 +53,19 @@ module Dentaku
113
53
  nil
114
54
  end
115
55
 
56
+ def evaluate_step(token_stream, start, length, evaluator)
57
+ expr = token_stream.slice!(start, length)
58
+ token_stream.insert start, *self.send(evaluator, *expr)
59
+ end
60
+
116
61
  def evaluate_group(*args)
117
62
  evaluate_token_stream(args[1..-2])
118
63
  end
119
64
 
120
65
  def apply(lvalue, operator, rvalue)
121
- l = lvalue.value
122
- r = rvalue.value
123
-
124
- case operator.value
125
- when :pow then Token.new(:numeric, l ** r)
126
- when :add then Token.new(:numeric, l + r)
127
- when :subtract then Token.new(:numeric, l - r)
128
- when :multiply then Token.new(:numeric, l * r)
129
- when :divide then Token.new(:numeric, divide(l, r))
130
-
131
- when :le then Token.new(:logical, l <= r)
132
- when :ge then Token.new(:logical, l >= r)
133
- when :lt then Token.new(:logical, l < r)
134
- when :gt then Token.new(:logical, l > r)
135
- when :ne then Token.new(:logical, l != r)
136
- when :eq then Token.new(:logical, l == r)
137
-
138
- when :and then Token.new(:logical, l && r)
139
- when :or then Token.new(:logical, l || r)
140
-
141
- else
142
- raise "unknown comparator '#{ comparator }'"
143
- end
144
- end
145
-
146
- def divide(numerator, denominator)
147
- quotient, remainder = numerator.divmod(denominator)
148
- return quotient if remainder == 0
149
- numerator.to_f / denominator.to_f
66
+ operation = BinaryOperation.new(lvalue.value, rvalue.value)
67
+ raise "unknown operation #{ operator.value }" unless operation.respond_to?(operator.value)
68
+ Token.new(*operation.send(operator.value))
150
69
  end
151
70
 
152
71
  def expand_range(left, oper1, middle, oper2, right)
@@ -166,35 +85,29 @@ module Dentaku
166
85
  def round(*args)
167
86
  _, _, *tokens, _ = args
168
87
 
169
- input_tokens = tokens.take_while { |a| a.category != :grouping }
170
- input_value = evaluate_token_stream(input_tokens).value
171
- places = 0
88
+ input_tokens, places_tokens = tokens.chunk { |t| t.category == :grouping }.
89
+ reject { |flag, tokens| flag }.
90
+ map { |flag, tokens| tokens }
172
91
 
173
- if places_token = tokens.drop_while { |a| a.category != :grouping }.last
174
- places = places_token.value
175
- end
92
+ input_value = evaluate_token_stream(input_tokens).value
93
+ places = places_tokens ? evaluate_token_stream(places_tokens).value : 0
176
94
 
177
- begin
178
- value = input_value.round(places)
179
- rescue ArgumentError
180
- value = (input * 10 ** places).round / (10 ** places).to_f
181
- end
95
+ value = input_value.round(places)
182
96
 
183
97
  Token.new(:numeric, value)
184
98
  end
185
99
 
186
- def roundup(*args)
187
- _, _, *tokens, _ = args
100
+ def round_int(*args)
101
+ function, _, *tokens, _ = args
188
102
 
189
103
  value = evaluate_token_stream(tokens).value
190
- Token.new(:numeric, value.ceil)
191
- end
192
-
193
- def rounddown(*args)
194
- _, _, *tokens, _ = args
104
+ rounded = if function.value == :roundup
105
+ value.ceil
106
+ else
107
+ value.floor
108
+ end
195
109
 
196
- value = evaluate_token_stream(tokens).value
197
- Token.new(:numeric, value.floor)
110
+ Token.new(:numeric, rounded)
198
111
  end
199
112
 
200
113
  def not(*args)
@@ -0,0 +1,75 @@
1
+ require 'dentaku/token'
2
+ require 'dentaku/token_matcher'
3
+
4
+ module Dentaku
5
+ class Rules
6
+ def self.each
7
+ @rules ||= [
8
+ [ p(:if), :if ],
9
+ [ p(:round_one), :round ],
10
+ [ p(:round_two), :round ],
11
+ [ p(:roundup), :round_int ],
12
+ [ p(:rounddown), :round_int ],
13
+ [ p(:not), :not ],
14
+
15
+ [ p(:group), :evaluate_group ],
16
+ [ p(:math_pow), :apply ],
17
+ [ p(:math_mul), :apply ],
18
+ [ p(:math_add), :apply ],
19
+ [ p(:range_asc), :expand_range ],
20
+ [ p(:range_desc), :expand_range ],
21
+ [ p(:num_comp), :apply ],
22
+ [ p(:str_comp), :apply ],
23
+ [ p(:combine), :apply ]
24
+ ]
25
+
26
+ @rules.each { |r| yield r }
27
+ end
28
+
29
+ def self.t(name)
30
+ @matchers ||= [
31
+ :numeric, :string, :addsub, :muldiv, :pow,
32
+ :comparator, :comp_gt, :comp_lt,
33
+ :open, :close, :comma,
34
+ :non_group, :non_group_star,
35
+ :logical, :combinator,
36
+ :if, :round, :roundup, :rounddown, :not
37
+ ].each_with_object({}) do |name, matchers|
38
+ matchers[name] = TokenMatcher.send(name)
39
+ end
40
+
41
+ @matchers[name]
42
+ end
43
+
44
+ def self.p(name)
45
+ @patterns ||= {
46
+ group: pattern(:open, :non_group_star, :close),
47
+ math_add: pattern(:numeric, :addsub, :numeric),
48
+ math_mul: pattern(:numeric, :muldiv, :numeric),
49
+ math_pow: pattern(:numeric, :pow, :numeric),
50
+ range_asc: pattern(:numeric, :comp_lt, :numeric, :comp_lt, :numeric),
51
+ range_desc: pattern(:numeric, :comp_gt, :numeric, :comp_gt, :numeric),
52
+ num_comp: pattern(:numeric, :comparator, :numeric),
53
+ str_comp: pattern(:string, :comparator, :string),
54
+ combine: pattern(:logical, :combinator, :logical),
55
+
56
+ if: func_pattern(:if, :non_group, :comma, :non_group, :comma, :non_group),
57
+ round_one: func_pattern(:round, :non_group_star),
58
+ round_two: func_pattern(:round, :non_group_star, :comma, :numeric),
59
+ roundup: func_pattern(:roundup, :non_group_star),
60
+ rounddown: func_pattern(:rounddown, :non_group_star),
61
+ not: func_pattern(:not, :non_group_star)
62
+ }
63
+
64
+ @patterns[name]
65
+ end
66
+
67
+ def self.pattern(*symbols)
68
+ symbols.map { |s| t(s) }
69
+ end
70
+
71
+ def self.func_pattern(func, *tokens)
72
+ pattern(func, :open, *tokens, :close)
73
+ end
74
+ end
75
+ end
@@ -57,6 +57,27 @@ module Dentaku
57
57
  def value_match(value)
58
58
  @values.empty? || @values.include?(value)
59
59
  end
60
+
61
+ def self.numeric; new(:numeric); end
62
+ def self.string; new(:string); end
63
+ def self.addsub; new(:operator, [:add, :subtract]); end
64
+ def self.muldiv; new(:operator, [:multiply, :divide]); end
65
+ def self.pow; new(:operator, :pow); end
66
+ def self.comparator; new(:comparator); end
67
+ def self.comp_gt; new(:comparator, [:gt, :ge]); end
68
+ def self.comp_lt; new(:comparator, [:lt, :le]); end
69
+ def self.open; new(:grouping, :open); end
70
+ def self.close; new(:grouping, :close); end
71
+ def self.comma; new(:grouping, :comma); end
72
+ def self.logical; new(:logical); end
73
+ def self.combinator; new(:combinator); end
74
+ def self.if; new(:function, :if); end
75
+ def self.round; new(:function, :round); end
76
+ def self.roundup; new(:function, :roundup); end
77
+ def self.rounddown; new(:function, :rounddown); end
78
+ def self.not; new(:function, :not); end
79
+ def self.non_group; new(:grouping).invert; end
80
+ def self.non_group_star; new(:grouping).invert.star; end
60
81
  end
61
82
  end
62
83
 
@@ -18,5 +18,66 @@ module Dentaku
18
18
 
19
19
  false
20
20
  end
21
+
22
+ class << self
23
+ def scanners
24
+ @scanners ||= [
25
+ whitespace,
26
+ numeric,
27
+ double_quoted_string,
28
+ single_quoted_string,
29
+ operator,
30
+ grouping,
31
+ comparator,
32
+ combinator,
33
+ function,
34
+ identifier
35
+ ]
36
+ end
37
+
38
+ def whitespace
39
+ new(:whitespace, '\s+')
40
+ end
41
+
42
+ def numeric
43
+ new(:numeric, '(\d+(\.\d+)?|\.\d+)\b', lambda { |raw| raw =~ /\./ ? raw.to_f : raw.to_i })
44
+ end
45
+
46
+ def double_quoted_string
47
+ new(:string, '"[^"]*"', lambda { |raw| raw.gsub(/^"|"$/, '') })
48
+ end
49
+
50
+ def single_quoted_string
51
+ new(:string, "'[^']*'", lambda { |raw| raw.gsub(/^'|'$/, '') })
52
+ end
53
+
54
+ def operator
55
+ names = { pow: '^', add: '+', subtract: '-', multiply: '*', divide: '/' }.invert
56
+ new(:operator, '\^|\+|-|\*|\/', lambda { |raw| names[raw] })
57
+ end
58
+
59
+ def grouping
60
+ names = { open: '(', close: ')', comma: ',' }.invert
61
+ new(:grouping, '\(|\)|,', lambda { |raw| names[raw] })
62
+ end
63
+
64
+ def comparator
65
+ names = { le: '<=', ge: '>=', ne: '!=', lt: '<', gt: '>', eq: '=' }.invert
66
+ alternate = { ne: '<>' }.invert
67
+ new(:comparator, '<=|>=|!=|<>|<|>|=', lambda { |raw| names[raw] || alternate[raw] })
68
+ end
69
+
70
+ def combinator
71
+ new(:combinator, '(and|or)\b', lambda {|raw| raw.strip.downcase.to_sym })
72
+ end
73
+
74
+ def function
75
+ new(:function, '(if|round(up|down)?|not)\b', lambda {|raw| raw.strip.downcase.to_sym })
76
+ end
77
+
78
+ def identifier
79
+ new(:identifier, '\w+\b', lambda {|raw| raw.strip.downcase.to_sym })
80
+ end
81
+ end
21
82
  end
22
83
  end
@@ -4,73 +4,40 @@ require 'dentaku/token_scanner'
4
4
 
5
5
  module Dentaku
6
6
  class Tokenizer
7
- SCANNERS = [
8
- TokenScanner.new(:whitespace, '\s+'),
9
- TokenScanner.new(:numeric, '(\d+(\.\d+)?|\.\d+)\b', lambda { |raw| raw =~ /\./ ? raw.to_f : raw.to_i }),
10
- TokenScanner.new(:string, '"[^"]*"', lambda { |raw| raw.gsub(/^"|"$/, '') }),
11
- TokenScanner.new(:string, "'[^']*'", lambda { |raw| raw.gsub(/^'|'$/, '') }),
12
- TokenScanner.new(:operator, '\^|\+|-|\*|\/', lambda do |raw|
13
- case raw
14
- when '^' then :pow
15
- when '+' then :add
16
- when '-' then :subtract
17
- when '*' then :multiply
18
- when '/' then :divide
19
- end
20
- end),
21
- TokenScanner.new(:grouping, '\(|\)|,', lambda do |raw|
22
- case raw
23
- when '(' then :open
24
- when ')' then :close
25
- when ',' then :comma
26
- end
27
- end),
28
- TokenScanner.new(:comparator, '<=|>=|!=|<>|<|>|=', lambda do |raw|
29
- case raw
30
- when '<=' then :le
31
- when '>=' then :ge
32
- when '!=' then :ne
33
- when '<>' then :ne
34
- when '<' then :lt
35
- when '>' then :gt
36
- when '=' then :eq
37
- end
38
- end),
39
- TokenScanner.new(:combinator, '(and|or)\b', lambda {|raw| raw.strip.downcase.to_sym }),
40
- TokenScanner.new(:function, '(if|round(up|down)?|not)\b',
41
- lambda {|raw| raw.strip.downcase.to_sym }),
42
- TokenScanner.new(:identifier, '\w+\b', lambda {|raw| raw.strip.downcase.to_sym })
43
- ]
44
-
45
7
  LPAREN = TokenMatcher.new(:grouping, :open)
46
8
  RPAREN = TokenMatcher.new(:grouping, :close)
47
9
 
48
10
  def tokenize(string)
49
- nesting = 0
50
- tokens = []
51
- input = string.dup
11
+ @nesting = 0
12
+ @tokens = []
13
+ input = string.dup
52
14
 
53
15
  until input.empty?
54
- raise "parse error at: '#{ input }'" unless SCANNERS.any? do |scanner|
55
- if token = scanner.scan(input)
56
- raise "unexpected zero-width match (:#{ token.category }) at '#{ input }'" if token.length == 0
16
+ raise "parse error at: '#{ input }'" unless TokenScanner.scanners.any? do |scanner|
17
+ scanned, input = scan(input, scanner)
18
+ scanned
19
+ end
20
+ end
57
21
 
58
- nesting += 1 if LPAREN == token
59
- nesting -= 1 if RPAREN == token
60
- raise "too many closing parentheses" if nesting < 0
22
+ raise "too many opening parentheses" if @nesting > 0
61
23
 
62
- tokens << token unless token.is?(:whitespace)
63
- input.slice!(0, token.length)
24
+ @tokens
25
+ end
64
26
 
65
- true
66
- else
67
- false
68
- end
69
- end
70
- end
27
+ def scan(string, scanner)
28
+ if token = scanner.scan(string)
29
+ raise "unexpected zero-width match (:#{ token.category }) at '#{ string }'" if token.length == 0
71
30
 
72
- raise "too many opening parentheses" if nesting > 0
73
- tokens
31
+ @nesting += 1 if LPAREN == token
32
+ @nesting -= 1 if RPAREN == token
33
+ raise "too many closing parentheses" if @nesting < 0
34
+
35
+ @tokens << token unless token.is?(:whitespace)
36
+
37
+ [true, string[token.length..-1]]
38
+ else
39
+ [false, string]
40
+ end
74
41
  end
75
42
  end
76
43
  end
@@ -1,3 +1,3 @@
1
1
  module Dentaku
2
- VERSION = "0.2.10"
2
+ VERSION = "0.2.11"
3
3
  end
@@ -0,0 +1,41 @@
1
+ require 'dentaku/binary_operation'
2
+
3
+ describe Dentaku::BinaryOperation do
4
+ let(:operation) { described_class.new(2, 3) }
5
+ let(:logical) { described_class.new(true, false) }
6
+
7
+ it 'raises a number to a power' do
8
+ operation.pow.should eq [:numeric, 8]
9
+ end
10
+
11
+ it 'adds two numbers' do
12
+ operation.add.should eq [:numeric, 5]
13
+ end
14
+
15
+ it 'subtracts two numbers' do
16
+ operation.subtract.should eq [:numeric, -1]
17
+ end
18
+
19
+ it 'multiplies two numbers' do
20
+ operation.multiply.should eq [:numeric, 6]
21
+ end
22
+
23
+ it 'divides two numbers' do
24
+ operation.divide.should eq [:numeric, (2.0/3.0)]
25
+ end
26
+
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]
31
+
32
+ operation.ge.should eq [:logical, false]
33
+ operation.gt.should eq [:logical, false]
34
+ operation.eq.should eq [:logical, false]
35
+ end
36
+
37
+ it 'performs logical AND and OR' do
38
+ logical.and.should eq [:logical, false]
39
+ logical.or.should eq [:logical, true]
40
+ end
41
+ end
@@ -68,6 +68,12 @@ describe Dentaku::Calculator do
68
68
  calculator.evaluate('fruit = "Apple"', :fruit => 'Apple').should be_true
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
75
+ end
76
+
71
77
  describe 'functions' do
72
78
  it 'should include IF' do
73
79
  calculator.evaluate('if (foo < 8, 10, 20)', :foo => 2).should eq(10)
@@ -60,8 +60,9 @@ describe Dentaku::Evaluator do
60
60
  end
61
61
 
62
62
  describe 'functions' do
63
- it 'should evaluate function' do
63
+ it 'should be evaluated' do
64
64
  evaluator.evaluate(token_stream(:round, :open, 5, :divide, 3.0, :close)).should eq 2
65
+ evaluator.evaluate(token_stream(:round, :open, 5, :divide, 3.0, :comma, 2, :close)).should eq 1.67
65
66
  evaluator.evaluate(token_stream(:roundup, :open, 5, :divide, 1.2, :close)).should eq 5
66
67
  evaluator.evaluate(token_stream(:rounddown, :open, 5, :divide, 1.2, :close)).should eq 4
67
68
  end
data/spec/spec_helper.rb CHANGED
@@ -14,7 +14,7 @@ def type_for(value)
14
14
  :numeric
15
15
  when :add, :subtract, :multiply, :divide
16
16
  :operator
17
- when :open, :close
17
+ when :open, :close, :comma
18
18
  :grouping
19
19
  when :le, :ge, :ne, :ne, :lt, :gt, :eq
20
20
  :comparator
@@ -19,5 +19,9 @@ describe Dentaku::TokenScanner do
19
19
  token.category.should eq(:numeric)
20
20
  token.value.should eq(5)
21
21
  end
22
+
23
+ it 'should return a list of all configured scanners' do
24
+ described_class.scanners.length.should eq 10
25
+ end
22
26
  end
23
27
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dentaku
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.10
4
+ version: 0.2.11
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-10 00:00:00.000000000 Z
12
+ date: 2013-08-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -53,18 +53,22 @@ extensions: []
53
53
  extra_rdoc_files: []
54
54
  files:
55
55
  - .gitignore
56
+ - .travis.yml
56
57
  - Gemfile
57
58
  - README.md
58
59
  - Rakefile
59
60
  - dentaku.gemspec
60
61
  - lib/dentaku.rb
62
+ - lib/dentaku/binary_operation.rb
61
63
  - lib/dentaku/calculator.rb
62
64
  - lib/dentaku/evaluator.rb
65
+ - lib/dentaku/rules.rb
63
66
  - lib/dentaku/token.rb
64
67
  - lib/dentaku/token_matcher.rb
65
68
  - lib/dentaku/token_scanner.rb
66
69
  - lib/dentaku/tokenizer.rb
67
70
  - lib/dentaku/version.rb
71
+ - spec/binary_operation_spec.rb
68
72
  - spec/calculator_spec.rb
69
73
  - spec/dentaku_spec.rb
70
74
  - spec/evaluator_spec.rb
@@ -85,18 +89,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
85
89
  - - ! '>='
86
90
  - !ruby/object:Gem::Version
87
91
  version: '0'
88
- segments:
89
- - 0
90
- hash: -662526630847863998
91
92
  required_rubygems_version: !ruby/object:Gem::Requirement
92
93
  none: false
93
94
  requirements:
94
95
  - - ! '>='
95
96
  - !ruby/object:Gem::Version
96
97
  version: '0'
97
- segments:
98
- - 0
99
- hash: -662526630847863998
100
98
  requirements: []
101
99
  rubyforge_project: dentaku
102
100
  rubygems_version: 1.8.23
@@ -104,6 +102,7 @@ signing_key:
104
102
  specification_version: 3
105
103
  summary: A formula language parser and evaluator
106
104
  test_files:
105
+ - spec/binary_operation_spec.rb
107
106
  - spec/calculator_spec.rb
108
107
  - spec/dentaku_spec.rb
109
108
  - spec/evaluator_spec.rb