dentaku 0.2.10 → 0.2.11

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