dentaku 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +13 -10
- data/lib/dentaku.rb +0 -8
- data/lib/dentaku/binary_operation.rb +2 -0
- data/lib/dentaku/calculator.rb +1 -0
- data/lib/dentaku/exceptions.rb +9 -0
- data/lib/dentaku/rules.rb +11 -13
- data/lib/dentaku/token_matcher.rb +46 -18
- data/lib/dentaku/token_scanner.rb +9 -4
- data/lib/dentaku/tokenizer.rb +11 -8
- data/lib/dentaku/version.rb +1 -1
- data/spec/dentaku_spec.rb +4 -0
- data/spec/evaluator_spec.rb +17 -8
- data/spec/external_function_spec.rb +3 -2
- data/spec/spec_helper.rb +5 -4
- data/spec/token_matcher_spec.rb +30 -0
- data/spec/token_scanner_spec.rb +2 -2
- data/spec/tokenizer_spec.rb +13 -6
- metadata +29 -15
- checksums.yaml +0 -7
data/README.md
CHANGED
@@ -199,9 +199,9 @@ As an example, the exponentiation function takes two parameters, the mantissa
|
|
199
199
|
and the exponent, so the token list could be defined as: `[:numeric,
|
200
200
|
:numeric]`. Other functions might be variadic -- consider `max`, a function
|
201
201
|
that takes any number of numeric inputs and returns the largest one. Its token
|
202
|
-
list could be defined as: `[:
|
203
|
-
|
204
|
-
[rules definitions](https://github.com/rubysolo/dentaku/blob/master/lib/dentaku/token_matcher.rb#
|
202
|
+
list could be defined as: `[:arguments]` (one or more numeric, string, or logical
|
203
|
+
values, separated by commas). See the
|
204
|
+
[rules definitions](https://github.com/rubysolo/dentaku/blob/master/lib/dentaku/token_matcher.rb#L87)
|
205
205
|
for the names of token patterns you can use.
|
206
206
|
|
207
207
|
Functions can be added individually using Calculator#add_function, or en masse using
|
@@ -230,10 +230,10 @@ Here's an example of adding the `max` function:
|
|
230
230
|
> c.add_function(
|
231
231
|
name: :max,
|
232
232
|
type: :numeric,
|
233
|
-
signature: [:
|
233
|
+
signature: [:arguments],
|
234
234
|
body: ->(*args) { args.max }
|
235
235
|
)
|
236
|
-
> c.evaluate 'MAX(5,3,9
|
236
|
+
> c.evaluate 'MAX(8,6,7,5,3,0,9)'
|
237
237
|
#=> 9
|
238
238
|
```
|
239
239
|
|
@@ -245,14 +245,17 @@ Big thanks to [ElkStone Basements](http://www.elkstonebasements.com/) for
|
|
245
245
|
allowing me to extract and open source this code. Thanks also to all the
|
246
246
|
contributors:
|
247
247
|
|
248
|
+
* [0xCCD](https://github.com/0xCCD)
|
249
|
+
* [AlexeyMK](https://github.com/AlexeyMK)
|
248
250
|
* [CraigCottingham](https://github.com/CraigCottingham)
|
249
|
-
* [arnaudl](https://github.com/arnaudl)
|
250
|
-
* [thbar](https://github.com/thbar) / [BoxCar](https://www.boxcar.io)
|
251
251
|
* [antonversal](https://github.com/antonversal)
|
252
|
-
* [
|
252
|
+
* [arnaudl](https://github.com/arnaudl)
|
253
|
+
* [bernardofire](https://github.com/bernardofire)
|
253
254
|
* [brixen](https://github.com/brixen)
|
254
|
-
* [
|
255
|
-
* [
|
255
|
+
* [jasonhutchens](https://github.com/jasonhutchens)
|
256
|
+
* [jmangs](https://github.com/jmangs)
|
257
|
+
* [mvbrocato](https://github.com/mvbrocato)
|
258
|
+
* [thbar](https://github.com/thbar) / [BoxCar](https://www.boxcar.io)
|
256
259
|
|
257
260
|
|
258
261
|
LICENSE
|
data/lib/dentaku.rb
CHANGED
@@ -7,14 +7,6 @@ module Dentaku
|
|
7
7
|
calculator.evaluate(expression, data)
|
8
8
|
end
|
9
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
|
-
|
18
10
|
private
|
19
11
|
|
20
12
|
def self.calculator
|
data/lib/dentaku/calculator.rb
CHANGED
data/lib/dentaku/rules.rb
CHANGED
@@ -42,7 +42,7 @@ module Dentaku
|
|
42
42
|
@rules.unshift [
|
43
43
|
[
|
44
44
|
TokenMatcher.send(ext.name),
|
45
|
-
t(:
|
45
|
+
t(:fopen),
|
46
46
|
*pattern(*ext.tokens),
|
47
47
|
t(:close)
|
48
48
|
],
|
@@ -53,22 +53,20 @@ module Dentaku
|
|
53
53
|
|
54
54
|
def self.func(name)
|
55
55
|
@funcs ||= {}
|
56
|
-
@funcs
|
56
|
+
@funcs.fetch(name)
|
57
57
|
end
|
58
58
|
|
59
59
|
def self.t(name)
|
60
60
|
@matchers ||= generate_matchers
|
61
|
-
@matchers
|
61
|
+
@matchers.fetch(name)
|
62
62
|
end
|
63
63
|
|
64
64
|
def self.generate_matchers
|
65
65
|
[
|
66
66
|
:numeric, :string, :addsub, :subtract, :muldiv, :pow, :mod,
|
67
|
-
:comparator, :comp_gt, :comp_lt,
|
68
|
-
:
|
69
|
-
:
|
70
|
-
:logical, :combinator,
|
71
|
-
:if, :round, :roundup, :rounddown, :not
|
67
|
+
:comparator, :comp_gt, :comp_lt, :fopen, :open, :close, :comma,
|
68
|
+
:non_close_plus, :non_group, :non_group_star, :arguments,
|
69
|
+
:logical, :combinator, :if, :round, :roundup, :rounddown, :not
|
72
70
|
].each_with_object({}) do |name, matchers|
|
73
71
|
matchers[name] = TokenMatcher.send(name)
|
74
72
|
end
|
@@ -90,10 +88,10 @@ module Dentaku
|
|
90
88
|
combine: pattern(:logical, :combinator, :logical),
|
91
89
|
|
92
90
|
if: func_pattern(:if, :non_group, :comma, :non_group, :comma, :non_group),
|
93
|
-
round: func_pattern(:round, :
|
94
|
-
roundup: func_pattern(:roundup, :
|
95
|
-
rounddown: func_pattern(:rounddown, :
|
96
|
-
not: func_pattern(:not, :
|
91
|
+
round: func_pattern(:round, :arguments),
|
92
|
+
roundup: func_pattern(:roundup, :arguments),
|
93
|
+
rounddown: func_pattern(:rounddown, :arguments),
|
94
|
+
not: func_pattern(:not, :arguments)
|
97
95
|
}
|
98
96
|
|
99
97
|
@patterns[name]
|
@@ -104,7 +102,7 @@ module Dentaku
|
|
104
102
|
end
|
105
103
|
|
106
104
|
def self.func_pattern(func, *tokens)
|
107
|
-
pattern(func, :
|
105
|
+
pattern(func, :fopen, *tokens, :close)
|
108
106
|
end
|
109
107
|
end
|
110
108
|
end
|
@@ -2,27 +2,31 @@ require 'dentaku/token'
|
|
2
2
|
|
3
3
|
module Dentaku
|
4
4
|
class TokenMatcher
|
5
|
-
|
6
|
-
@categories = [categories].compact.flatten
|
7
|
-
@values = [values].compact.flatten
|
8
|
-
@invert = false
|
5
|
+
attr_reader :children
|
9
6
|
|
10
|
-
|
11
|
-
|
7
|
+
def initialize(categories=nil, values=nil, children=[])
|
8
|
+
# store categories and values as hash to optimize key lookup, h/t @jan-mangs
|
9
|
+
@categories = [categories].compact.flatten.each_with_object({}) { |c,h| h[c] = 1 }
|
10
|
+
@values = [values].compact.flatten.each_with_object({}) { |v,h| h[v] = 1 }
|
11
|
+
@children = children.compact
|
12
|
+
@invert = false
|
12
13
|
|
13
14
|
@min = 1
|
14
15
|
@max = 1
|
15
16
|
@range = (@min..@max)
|
16
17
|
end
|
17
18
|
|
19
|
+
def | (other_matcher)
|
20
|
+
self.class.new(:nomatch, :nomatch, leaf_matchers + other_matcher.leaf_matchers)
|
21
|
+
end
|
22
|
+
|
18
23
|
def invert
|
19
24
|
@invert = ! @invert
|
20
25
|
self
|
21
26
|
end
|
22
27
|
|
23
28
|
def ==(token)
|
24
|
-
|
25
|
-
(category_match(token.category) && value_match(token.value)) ^ @invert
|
29
|
+
leaf_matcher? ? matches_token?(token) : any_child_matches_token?(token)
|
26
30
|
end
|
27
31
|
|
28
32
|
def match(token_stream, offset=0)
|
@@ -53,40 +57,65 @@ module Dentaku
|
|
53
57
|
self
|
54
58
|
end
|
55
59
|
|
60
|
+
def leaf_matcher?
|
61
|
+
children.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
def leaf_matchers
|
65
|
+
leaf_matcher? ? [self] : children
|
66
|
+
end
|
67
|
+
|
56
68
|
private
|
57
69
|
|
70
|
+
def any_child_matches_token?(token)
|
71
|
+
children.any? { |child| child == token }
|
72
|
+
end
|
73
|
+
|
74
|
+
def matches_token?(token)
|
75
|
+
return false if token.nil?
|
76
|
+
(category_match(token.category) && value_match(token.value)) ^ @invert
|
77
|
+
end
|
78
|
+
|
58
79
|
def category_match(category)
|
59
|
-
@
|
80
|
+
@categories.empty? || @categories.key?(category)
|
60
81
|
end
|
61
82
|
|
62
83
|
def value_match(value)
|
63
|
-
@values.empty? || @
|
84
|
+
@values.empty? || @values.key?(value)
|
64
85
|
end
|
65
86
|
|
66
87
|
def self.numeric; new(:numeric); end
|
67
88
|
def self.string; new(:string); end
|
89
|
+
def self.logical; new(:logical); end
|
90
|
+
def self.value
|
91
|
+
new(:numeric) | new(:string) | new(:logical)
|
92
|
+
end
|
93
|
+
|
68
94
|
def self.addsub; new(:operator, [:add, :subtract]); end
|
69
95
|
def self.subtract; new(:operator, :subtract); end
|
70
96
|
def self.muldiv; new(:operator, [:multiply, :divide]); end
|
71
97
|
def self.pow; new(:operator, :pow); end
|
72
98
|
def self.mod; new(:operator, :mod); end
|
99
|
+
def self.combinator; new(:combinator); end
|
100
|
+
|
73
101
|
def self.comparator; new(:comparator); end
|
74
102
|
def self.comp_gt; new(:comparator, [:gt, :ge]); end
|
75
103
|
def self.comp_lt; new(:comparator, [:lt, :le]); end
|
104
|
+
|
105
|
+
def self.fopen; new(:grouping, :fopen); end
|
76
106
|
def self.open; new(:grouping, :open); end
|
77
107
|
def self.close; new(:grouping, :close); end
|
78
108
|
def self.comma; new(:grouping, :comma); end
|
79
|
-
def self.
|
80
|
-
def self.
|
109
|
+
def self.non_group; new(:grouping).invert; end
|
110
|
+
def self.non_group_star; new(:grouping).invert.star; end
|
111
|
+
def self.non_close_plus; new(:grouping, :close).invert.plus; end
|
112
|
+
def self.arguments; (value | comma).plus; end
|
113
|
+
|
81
114
|
def self.if; new(:function, :if); end
|
82
115
|
def self.round; new(:function, :round); end
|
83
116
|
def self.roundup; new(:function, :roundup); end
|
84
117
|
def self.rounddown; new(:function, :rounddown); end
|
85
118
|
def self.not; new(:function, :not); end
|
86
|
-
def self.non_close_plus; new(:grouping, :close).invert.plus; end
|
87
|
-
def self.non_group; new(:grouping).invert; end
|
88
|
-
def self.non_group_star; new(:grouping).invert.star; end
|
89
|
-
|
90
119
|
|
91
120
|
def self.method_missing(name, *args, &block)
|
92
121
|
new(:function, name)
|
@@ -95,6 +124,5 @@ module Dentaku
|
|
95
124
|
def self.respond_to_missing?(name, include_priv)
|
96
125
|
true
|
97
126
|
end
|
98
|
-
|
99
127
|
end
|
100
|
-
end
|
128
|
+
end
|
@@ -13,7 +13,9 @@ module Dentaku
|
|
13
13
|
value = raw = m.to_s
|
14
14
|
value = @converter.call(raw) if @converter
|
15
15
|
|
16
|
-
return
|
16
|
+
return Array(value).map do |v|
|
17
|
+
Token === v ? v : Token.new(@category, v, raw)
|
18
|
+
end
|
17
19
|
end
|
18
20
|
|
19
21
|
false
|
@@ -68,15 +70,18 @@ module Dentaku
|
|
68
70
|
end
|
69
71
|
|
70
72
|
def combinator
|
71
|
-
new(:combinator, '(and|or)\b', lambda {|raw| raw.strip.downcase.to_sym })
|
73
|
+
new(:combinator, '(and|or)\b', lambda { |raw| raw.strip.downcase.to_sym })
|
72
74
|
end
|
73
75
|
|
74
76
|
def function
|
75
|
-
new(:function, '
|
77
|
+
new(:function, '\w+\s*\(', lambda do |raw|
|
78
|
+
function_name = raw.gsub('(', '')
|
79
|
+
[Token.new(:function, function_name.strip.downcase.to_sym, function_name), Token.new(:grouping, :fopen, '(')]
|
80
|
+
end)
|
76
81
|
end
|
77
82
|
|
78
83
|
def identifier
|
79
|
-
new(:identifier, '\w+\b', lambda {|raw| raw.strip.downcase.to_sym })
|
84
|
+
new(:identifier, '\w+\b', lambda { |raw| raw.strip.downcase.to_sym })
|
80
85
|
end
|
81
86
|
end
|
82
87
|
end
|
data/lib/dentaku/tokenizer.rb
CHANGED
@@ -4,7 +4,7 @@ require 'dentaku/token_scanner'
|
|
4
4
|
|
5
5
|
module Dentaku
|
6
6
|
class Tokenizer
|
7
|
-
LPAREN = TokenMatcher.new(:grouping, :open)
|
7
|
+
LPAREN = TokenMatcher.new(:grouping, [:open, :fopen])
|
8
8
|
RPAREN = TokenMatcher.new(:grouping, :close)
|
9
9
|
|
10
10
|
def tokenize(string)
|
@@ -25,16 +25,19 @@ module Dentaku
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def scan(string, scanner)
|
28
|
-
if
|
29
|
-
|
28
|
+
if tokens = scanner.scan(string)
|
29
|
+
tokens.each do |token|
|
30
|
+
raise "unexpected zero-width match (:#{ token.category }) at '#{ string }'" if token.length == 0
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
@nesting += 1 if LPAREN == token
|
33
|
+
@nesting -= 1 if RPAREN == token
|
34
|
+
raise "too many closing parentheses" if @nesting < 0
|
34
35
|
|
35
|
-
|
36
|
+
@tokens << token unless token.is?(:whitespace)
|
37
|
+
end
|
36
38
|
|
37
|
-
|
39
|
+
match_length = tokens.map(&:length).reduce(:+)
|
40
|
+
[true, string[match_length..-1]]
|
38
41
|
else
|
39
42
|
[false, string]
|
40
43
|
end
|
data/lib/dentaku/version.rb
CHANGED
data/spec/dentaku_spec.rb
CHANGED
@@ -8,4 +8,8 @@ describe Dentaku do
|
|
8
8
|
it 'binds values to variables' do
|
9
9
|
expect(Dentaku('oranges > 7', {:oranges => 10})).to be_truthy
|
10
10
|
end
|
11
|
+
|
12
|
+
it 'evaulates a nested function' do
|
13
|
+
expect(Dentaku('roundup(roundup(3 * cherries) + raspberries)', cherries: 1.5, raspberries: 0.9)).to eql(6)
|
14
|
+
end
|
11
15
|
end
|
data/spec/evaluator_spec.rb
CHANGED
@@ -70,12 +70,21 @@ describe Dentaku::Evaluator do
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
+
describe 'find_rule_match' do
|
74
|
+
it 'matches a function call' do
|
75
|
+
if_pattern, _ = *Dentaku::Rules.core_rules.first
|
76
|
+
position, tokens = evaluator.find_rule_match(if_pattern, token_stream(:if, :fopen, true, :comma, 1, :comma, 2, :close))
|
77
|
+
expect(position).to eq 0
|
78
|
+
expect(tokens.length).to eq 8
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
73
82
|
describe 'functions' do
|
74
83
|
it 'is evaluated' do
|
75
|
-
expect(evaluator.evaluate(token_stream(:round, :
|
76
|
-
expect(evaluator.evaluate(token_stream(:round, :
|
77
|
-
expect(evaluator.evaluate(token_stream(:roundup, :
|
78
|
-
expect(evaluator.evaluate(token_stream(:rounddown, :
|
84
|
+
expect(evaluator.evaluate(token_stream(:round, :fopen, 5, :divide, 3.0, :close))).to eq 2
|
85
|
+
expect(evaluator.evaluate(token_stream(:round, :fopen, 5, :divide, 3.0, :comma, 2, :close))).to eq 1.67
|
86
|
+
expect(evaluator.evaluate(token_stream(:roundup, :fopen, 5, :divide, 1.2, :close))).to eq 5
|
87
|
+
expect(evaluator.evaluate(token_stream(:rounddown, :fopen, 5, :divide, 1.2, :close))).to eq 4
|
79
88
|
end
|
80
89
|
end
|
81
90
|
|
@@ -96,13 +105,13 @@ describe Dentaku::Evaluator do
|
|
96
105
|
end
|
97
106
|
|
98
107
|
it 'evaluates combined conditionals' do
|
99
|
-
expect(evaluator.evaluate(token_stream(5, :gt, 1, :or,
|
100
|
-
expect(evaluator.evaluate(token_stream(5, :gt, 1, :and,
|
108
|
+
expect(evaluator.evaluate(token_stream(5, :gt, 1, :or, false))).to be_truthy
|
109
|
+
expect(evaluator.evaluate(token_stream(5, :gt, 1, :and, false))).to be_falsey
|
101
110
|
end
|
102
111
|
|
103
112
|
it 'negates a logical value' do
|
104
|
-
expect(evaluator.evaluate(token_stream(:not, :
|
105
|
-
expect(evaluator.evaluate(token_stream(:not, :
|
113
|
+
expect(evaluator.evaluate(token_stream(:not, :fopen, 5, :gt, 1, :or, false, :close))).to be_falsey
|
114
|
+
expect(evaluator.evaluate(token_stream(:not, :fopen, 5, :gt, 1, :and, false, :close))).to be_truthy
|
106
115
|
end
|
107
116
|
end
|
108
117
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'spec_helper'
|
1
2
|
require 'dentaku/calculator'
|
2
3
|
|
3
4
|
describe Dentaku::Calculator do
|
@@ -20,13 +21,13 @@ describe Dentaku::Calculator do
|
|
20
21
|
{
|
21
22
|
name: :max,
|
22
23
|
type: :numeric,
|
23
|
-
signature: [ :
|
24
|
+
signature: [ :arguments ],
|
24
25
|
body: ->(*args) { args.max }
|
25
26
|
},
|
26
27
|
{
|
27
28
|
name: :min,
|
28
29
|
type: :numeric,
|
29
|
-
signature: [ :
|
30
|
+
signature: [ :arguments ],
|
30
31
|
body: ->(*args) { args.min }
|
31
32
|
}
|
32
33
|
]
|
data/spec/spec_helper.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
def token_stream(*args)
|
3
3
|
args.map do |value|
|
4
4
|
type = type_for(value)
|
5
|
-
value = (value == :true) if type == :logical
|
6
5
|
Dentaku::Token.new(type, value)
|
7
6
|
end
|
8
7
|
end
|
@@ -12,16 +11,18 @@ def type_for(value)
|
|
12
11
|
case value
|
13
12
|
when Numeric
|
14
13
|
:numeric
|
14
|
+
when String
|
15
|
+
:string
|
16
|
+
when true, false
|
17
|
+
:logical
|
15
18
|
when :add, :subtract, :multiply, :divide, :mod
|
16
19
|
:operator
|
17
|
-
when :open, :close, :comma
|
20
|
+
when :fopen, :open, :close, :comma
|
18
21
|
:grouping
|
19
22
|
when :le, :ge, :ne, :ne, :lt, :gt, :eq
|
20
23
|
:comparator
|
21
24
|
when :and, :or
|
22
25
|
:combinator
|
23
|
-
when :true, :false
|
24
|
-
:logical
|
25
26
|
when :if, :round, :roundup, :rounddown, :not
|
26
27
|
:function
|
27
28
|
else
|
data/spec/token_matcher_spec.rb
CHANGED
@@ -53,6 +53,26 @@ describe Dentaku::TokenMatcher do
|
|
53
53
|
expect(matcher).to eq(cmp)
|
54
54
|
end
|
55
55
|
|
56
|
+
describe 'combining multiple tokens' do
|
57
|
+
let(:numeric) { described_class.new(:numeric) }
|
58
|
+
let(:string) { described_class.new(:string) }
|
59
|
+
|
60
|
+
it 'matches either' do
|
61
|
+
either = numeric | string
|
62
|
+
expect(either).to eq(Dentaku::Token.new(:numeric, 5))
|
63
|
+
expect(either).to eq(Dentaku::Token.new(:string, 'rhubarb'))
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'matches any value' do
|
67
|
+
value = described_class.value
|
68
|
+
expect(value).to eq(Dentaku::Token.new(:numeric, 8))
|
69
|
+
expect(value).to eq(Dentaku::Token.new(:string, 'apricot'))
|
70
|
+
expect(value).to eq(Dentaku::Token.new(:logical, false))
|
71
|
+
expect(value).not_to eq(Dentaku::Token.new(:function, :round))
|
72
|
+
expect(value).not_to eq(Dentaku::Token.new(:identifier, :hello))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
56
76
|
describe 'stream matching' do
|
57
77
|
let(:stream) { token_stream(5, 11, 9, 24, :hello, 8) }
|
58
78
|
|
@@ -100,6 +120,16 @@ describe Dentaku::TokenMatcher do
|
|
100
120
|
expect(matched).not_to be_truthy
|
101
121
|
end
|
102
122
|
end
|
123
|
+
|
124
|
+
describe 'arguments' do
|
125
|
+
it 'matches comma-separated values' do
|
126
|
+
stream = token_stream(1, :comma, 2, :comma, true, :comma, 'olive', :comma, :'(')
|
127
|
+
matched, substream = described_class.arguments.match(stream)
|
128
|
+
expect(matched).to be_truthy
|
129
|
+
expect(substream.length).to eq 8
|
130
|
+
expect(substream.map(&:value)).to eq [1, :comma, 2, :comma, true, :comma, 'olive', :comma]
|
131
|
+
end
|
132
|
+
end
|
103
133
|
end
|
104
134
|
end
|
105
135
|
|
data/spec/token_scanner_spec.rb
CHANGED
@@ -5,7 +5,7 @@ describe Dentaku::TokenScanner do
|
|
5
5
|
let(:numeric) { described_class.new(:numeric, '(\d+(\.\d+)?|\.\d+)', lambda{|raw| raw =~ /\./ ? BigDecimal.new(raw) : raw.to_i }) }
|
6
6
|
|
7
7
|
it 'returns a token for a matching string' do
|
8
|
-
token = whitespace.scan(' ')
|
8
|
+
token = whitespace.scan(' ').first
|
9
9
|
expect(token.category).to eq(:whitespace)
|
10
10
|
expect(token.value).to eq(' ')
|
11
11
|
end
|
@@ -15,7 +15,7 @@ describe Dentaku::TokenScanner do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'performs raw value conversion' do
|
18
|
-
token = numeric.scan('5')
|
18
|
+
token = numeric.scan('5').first
|
19
19
|
expect(token.category).to eq(:numeric)
|
20
20
|
expect(token.value).to eq(5)
|
21
21
|
end
|
data/spec/tokenizer_spec.rb
CHANGED
@@ -102,36 +102,43 @@ describe Dentaku::Tokenizer do
|
|
102
102
|
tokens = tokenizer.tokenize('if(x < 10, y, z)')
|
103
103
|
expect(tokens.length).to eq(10)
|
104
104
|
expect(tokens.map(&:category)).to eq([:function, :grouping, :identifier, :comparator, :numeric, :grouping, :identifier, :grouping, :identifier, :grouping])
|
105
|
-
expect(tokens.map(&:value)).to eq([:if, :
|
105
|
+
expect(tokens.map(&:value)).to eq([:if, :fopen, :x, :lt, 10, :comma, :y, :comma, :z, :close])
|
106
106
|
end
|
107
107
|
|
108
108
|
it 'include ROUND/UP/DOWN' do
|
109
109
|
tokens = tokenizer.tokenize('round(8.2)')
|
110
110
|
expect(tokens.length).to eq(4)
|
111
111
|
expect(tokens.map(&:category)).to eq([:function, :grouping, :numeric, :grouping])
|
112
|
-
expect(tokens.map(&:value)).to eq([:round, :
|
112
|
+
expect(tokens.map(&:value)).to eq([:round, :fopen, BigDecimal.new('8.2'), :close])
|
113
113
|
|
114
114
|
tokens = tokenizer.tokenize('round(8.75, 1)')
|
115
115
|
expect(tokens.length).to eq(6)
|
116
116
|
expect(tokens.map(&:category)).to eq([:function, :grouping, :numeric, :grouping, :numeric, :grouping])
|
117
|
-
expect(tokens.map(&:value)).to eq([:round, :
|
117
|
+
expect(tokens.map(&:value)).to eq([:round, :fopen, BigDecimal.new('8.75'), :comma, 1, :close])
|
118
118
|
|
119
119
|
tokens = tokenizer.tokenize('ROUNDUP(8.2)')
|
120
120
|
expect(tokens.length).to eq(4)
|
121
121
|
expect(tokens.map(&:category)).to eq([:function, :grouping, :numeric, :grouping])
|
122
|
-
expect(tokens.map(&:value)).to eq([:roundup, :
|
122
|
+
expect(tokens.map(&:value)).to eq([:roundup, :fopen, BigDecimal.new('8.2'), :close])
|
123
123
|
|
124
124
|
tokens = tokenizer.tokenize('RoundDown(8.2)')
|
125
125
|
expect(tokens.length).to eq(4)
|
126
126
|
expect(tokens.map(&:category)).to eq([:function, :grouping, :numeric, :grouping])
|
127
|
-
expect(tokens.map(&:value)).to eq([:rounddown, :
|
127
|
+
expect(tokens.map(&:value)).to eq([:rounddown, :fopen, BigDecimal.new('8.2'), :close])
|
128
128
|
end
|
129
129
|
|
130
130
|
it 'include NOT' do
|
131
131
|
tokens = tokenizer.tokenize('not(8 < 5)')
|
132
132
|
expect(tokens.length).to eq(6)
|
133
133
|
expect(tokens.map(&:category)).to eq([:function, :grouping, :numeric, :comparator, :numeric, :grouping])
|
134
|
-
expect(tokens.map(&:value)).to eq([:not, :
|
134
|
+
expect(tokens.map(&:value)).to eq([:not, :fopen, 8, :lt, 5, :close])
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'handles whitespace after function name' do
|
138
|
+
tokens = tokenizer.tokenize('not (8 < 5)')
|
139
|
+
expect(tokens.length).to eq(6)
|
140
|
+
expect(tokens.map(&:category)).to eq([:function, :grouping, :numeric, :comparator, :numeric, :grouping])
|
141
|
+
expect(tokens.map(&:value)).to eq([:not, :fopen, 8, :lt, 5, :close])
|
135
142
|
end
|
136
143
|
end
|
137
144
|
end
|
metadata
CHANGED
@@ -1,53 +1,59 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dentaku
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.1
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Solomon White
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2014-10-
|
12
|
+
date: 2014-10-22 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: rake
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
|
-
- -
|
19
|
+
- - ! '>='
|
18
20
|
- !ruby/object:Gem::Version
|
19
21
|
version: '0'
|
20
22
|
type: :development
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
|
-
- -
|
27
|
+
- - ! '>='
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: '0'
|
27
30
|
- !ruby/object:Gem::Dependency
|
28
31
|
name: rspec
|
29
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
30
34
|
requirements:
|
31
|
-
- -
|
35
|
+
- - ! '>='
|
32
36
|
- !ruby/object:Gem::Version
|
33
37
|
version: '0'
|
34
38
|
type: :development
|
35
39
|
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
37
42
|
requirements:
|
38
|
-
- -
|
43
|
+
- - ! '>='
|
39
44
|
- !ruby/object:Gem::Version
|
40
45
|
version: '0'
|
41
|
-
description:
|
42
|
-
|
46
|
+
description: ! ' Dentaku is a parser and evaluator for mathematical formulas
|
47
|
+
|
48
|
+
'
|
43
49
|
email:
|
44
50
|
- rubysolo@gmail.com
|
45
51
|
executables: []
|
46
52
|
extensions: []
|
47
53
|
extra_rdoc_files: []
|
48
54
|
files:
|
49
|
-
-
|
50
|
-
-
|
55
|
+
- .gitignore
|
56
|
+
- .travis.yml
|
51
57
|
- Gemfile
|
52
58
|
- README.md
|
53
59
|
- Rakefile
|
@@ -57,6 +63,7 @@ files:
|
|
57
63
|
- lib/dentaku/calculator.rb
|
58
64
|
- lib/dentaku/dependency_resolver.rb
|
59
65
|
- lib/dentaku/evaluator.rb
|
66
|
+
- lib/dentaku/exceptions.rb
|
60
67
|
- lib/dentaku/expression.rb
|
61
68
|
- lib/dentaku/external_function.rb
|
62
69
|
- lib/dentaku/rules.rb
|
@@ -79,26 +86,33 @@ files:
|
|
79
86
|
homepage: http://github.com/rubysolo/dentaku
|
80
87
|
licenses:
|
81
88
|
- MIT
|
82
|
-
metadata: {}
|
83
89
|
post_install_message:
|
84
90
|
rdoc_options: []
|
85
91
|
require_paths:
|
86
92
|
- lib
|
87
93
|
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
88
95
|
requirements:
|
89
|
-
- -
|
96
|
+
- - ! '>='
|
90
97
|
- !ruby/object:Gem::Version
|
91
98
|
version: '0'
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
hash: -1375666282720046081
|
92
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
93
104
|
requirements:
|
94
|
-
- -
|
105
|
+
- - ! '>='
|
95
106
|
- !ruby/object:Gem::Version
|
96
107
|
version: '0'
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
hash: -1375666282720046081
|
97
111
|
requirements: []
|
98
112
|
rubyforge_project: dentaku
|
99
|
-
rubygems_version:
|
113
|
+
rubygems_version: 1.8.23.2
|
100
114
|
signing_key:
|
101
|
-
specification_version:
|
115
|
+
specification_version: 3
|
102
116
|
summary: A formula language parser and evaluator
|
103
117
|
test_files:
|
104
118
|
- spec/binary_operation_spec.rb
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: 064d1f2a40ca1fa6caf9b07f5fb535a673fcbe32
|
4
|
-
data.tar.gz: 0fe2cb4349c5423da447fa81173d3b70636bacff
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: 55fdf11b6a81a851c4d5db09bd2d1ee3385d7ca0f08d2ba806bb9cf9dc914acda0b90d38b817353d28ab99c1b89b198a7d10e998360b2870ca04d485616a97cf
|
7
|
-
data.tar.gz: f68bc08dd8d1ff2f3d4e79f5af7f3131b59ce539d2001360e78c8fe340421f993c6e67d4ea1490b69928861c19b74615caea09cfd0e74cd251f0affd142eb0bd
|