dentaku 3.3.0 → 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -66,8 +66,8 @@ describe Dentaku::Calculator do
66
66
  calculator2 = Dentaku::Calculator.new
67
67
  calculator2.add_function(:my_function, :numeric, ->(x) { 4 * x + 3 })
68
68
 
69
- expect(calculator1.evaluate("1 + my_function(2)")). to eq (1 + 2 * 2 + 1)
70
- expect(calculator2.evaluate("1 + my_function(2)")). to eq (1 + 4 * 2 + 3)
69
+ expect(calculator1.evaluate("1 + my_function(2)")). to eq(1 + 2 * 2 + 1)
70
+ expect(calculator2.evaluate("1 + my_function(2)")). to eq(1 + 4 * 2 + 3)
71
71
 
72
72
  expect {
73
73
  Dentaku::Calculator.new.evaluate!("1 + my_function(2)")
@@ -76,7 +76,7 @@ describe Dentaku::Calculator do
76
76
 
77
77
  it 'self.add_function adds to default/global function registry' do
78
78
  Dentaku::Calculator.add_function(:global_function, :numeric, ->(x) { 10 + x**2 })
79
- expect(Dentaku::Calculator.new.evaluate("global_function(3) + 5")).to eq (10 + 3**2 + 5)
79
+ expect(Dentaku::Calculator.new.evaluate("global_function(3) + 5")).to eq(10 + 3**2 + 5)
80
80
  end
81
81
  end
82
82
  end
@@ -1,189 +1,138 @@
1
1
  require 'spec_helper'
2
2
  require 'dentaku/token'
3
+ require 'dentaku/tokenizer'
3
4
  require 'dentaku/parser'
4
5
 
5
6
  describe Dentaku::Parser do
6
- it 'is constructed from a token' do
7
- token = Dentaku::Token.new(:numeric, 5)
8
- node = described_class.new([token]).parse
9
- expect(node.value).to eq 5
7
+ it 'parses an integer literal' do
8
+ node = parse('5')
9
+ expect(node.value).to eq(5)
10
10
  end
11
11
 
12
12
  it 'performs simple addition' do
13
- five = Dentaku::Token.new(:numeric, 5)
14
- plus = Dentaku::Token.new(:operator, :add)
15
- four = Dentaku::Token.new(:numeric, 4)
16
-
17
- node = described_class.new([five, plus, four]).parse
18
- expect(node.value).to eq 9
13
+ node = parse('5 + 4')
14
+ expect(node.value).to eq(9)
19
15
  end
20
16
 
21
17
  it 'compares two numbers' do
22
- five = Dentaku::Token.new(:numeric, 5)
23
- lt = Dentaku::Token.new(:comparator, :lt)
24
- four = Dentaku::Token.new(:numeric, 4)
25
-
26
- node = described_class.new([five, lt, four]).parse
27
- expect(node.value).to eq false
18
+ node = parse('5 < 4')
19
+ expect(node.value).to eq(false)
28
20
  end
29
21
 
30
22
  it 'calculates unary percentage' do
31
- five = Dentaku::Token.new(:numeric, 5)
32
- mod = Dentaku::Token.new(:operator, :mod)
33
-
34
- node = described_class.new([five, mod]).parse
35
- expect(node.value).to eq 0.05
23
+ node = parse('5%')
24
+ expect(node.value).to eq(0.05)
36
25
  end
37
26
 
38
27
  it 'calculates bitwise OR' do
39
- two = Dentaku::Token.new(:numeric, 2)
40
- bitor = Dentaku::Token.new(:operator, :bitor)
41
- three = Dentaku::Token.new(:numeric, 3)
42
-
43
- node = described_class.new([two, bitor, three]).parse
44
- expect(node.value).to eq 3
28
+ node = parse('2|3')
29
+ expect(node.value).to eq(3)
45
30
  end
46
31
 
47
32
  it 'performs multiple operations in one stream' do
48
- five = Dentaku::Token.new(:numeric, 5)
49
- plus = Dentaku::Token.new(:operator, :add)
50
- four = Dentaku::Token.new(:numeric, 4)
51
- times = Dentaku::Token.new(:operator, :multiply)
52
- three = Dentaku::Token.new(:numeric, 3)
53
-
54
- node = described_class.new([five, plus, four, times, three]).parse
55
- expect(node.value).to eq 17
33
+ node = parse('5 * 4 + 3')
34
+ expect(node.value).to eq(23)
56
35
  end
57
36
 
58
37
  it 'respects order of operations' do
59
- five = Dentaku::Token.new(:numeric, 5)
60
- times = Dentaku::Token.new(:operator, :multiply)
61
- four = Dentaku::Token.new(:numeric, 4)
62
- plus = Dentaku::Token.new(:operator, :add)
63
- three = Dentaku::Token.new(:numeric, 3)
64
-
65
- node = described_class.new([five, times, four, plus, three]).parse
66
- expect(node.value).to eq 23
38
+ node = parse('5 + 4*3')
39
+ expect(node.value).to eq(17)
67
40
  end
68
41
 
69
42
  it 'respects grouping by parenthesis' do
70
- lpar = Dentaku::Token.new(:grouping, :open)
71
- five = Dentaku::Token.new(:numeric, 5)
72
- plus = Dentaku::Token.new(:operator, :add)
73
- four = Dentaku::Token.new(:numeric, 4)
74
- rpar = Dentaku::Token.new(:grouping, :close)
75
- times = Dentaku::Token.new(:operator, :multiply)
76
- three = Dentaku::Token.new(:numeric, 3)
77
-
78
- node = described_class.new([lpar, five, plus, four, rpar, times, three]).parse
79
- expect(node.value).to eq 27
43
+ node = parse('(5 + 4) * 3')
44
+ expect(node.value).to eq(27)
80
45
  end
81
46
 
82
47
  it 'evaluates functions' do
83
- fn = Dentaku::Token.new(:function, :if)
84
- fopen = Dentaku::Token.new(:grouping, :open)
85
- five = Dentaku::Token.new(:numeric, 5)
86
- lt = Dentaku::Token.new(:comparator, :lt)
87
- four = Dentaku::Token.new(:numeric, 4)
88
- comma = Dentaku::Token.new(:grouping, :comma)
89
- three = Dentaku::Token.new(:numeric, 3)
90
- two = Dentaku::Token.new(:numeric, 2)
91
- rpar = Dentaku::Token.new(:grouping, :close)
92
-
93
- node = described_class.new([fn, fopen, five, lt, four, comma, three, comma, two, rpar]).parse
94
- expect(node.value).to eq 2
48
+ node = parse('IF(5 < 4, 3, 2)')
49
+ expect(node.value).to eq(2)
95
50
  end
96
51
 
97
52
  it 'represents formulas with variables' do
98
- five = Dentaku::Token.new(:numeric, 5)
99
- times = Dentaku::Token.new(:operator, :multiply)
100
- x = Dentaku::Token.new(:identifier, :x)
101
-
102
- node = described_class.new([five, times, x]).parse
53
+ node = parse('5 * x')
103
54
  expect { node.value }.to raise_error(Dentaku::UnboundVariableError)
104
- expect(node.value(x: 3)).to eq 15
55
+ expect(node.value("x" => 3)).to eq(15)
105
56
  end
106
57
 
107
58
  it 'evaluates access into data structures' do
108
- a = token(:a)
109
- lbracket = token(:lbracket)
110
- one = token(1)
111
- rbracket = token(:rbracket)
112
-
113
- node = described_class.new([a, lbracket, one, rbracket]).parse
59
+ node = parse('a[1]')
114
60
  expect { node.value }.to raise_error(Dentaku::UnboundVariableError)
115
- expect(node.value(a: [1, 2, 3])).to eq 2
61
+ expect(node.value("a" => [1, 2, 3])).to eq(2)
116
62
  end
117
63
 
118
64
  it 'evaluates boolean expressions' do
119
- d_true = Dentaku::Token.new(:logical, true)
120
- d_and = Dentaku::Token.new(:combinator, :and)
121
- d_false = Dentaku::Token.new(:logical, false)
122
-
123
- node = described_class.new([d_true, d_and, d_false]).parse
124
- expect(node.value).to eq false
65
+ node = parse('true AND false')
66
+ expect(node.value).to eq(false)
125
67
  end
126
68
 
127
69
  it 'evaluates a case statement' do
128
- case_start = Dentaku::Token.new(:case, :open)
129
- x = Dentaku::Token.new(:identifier, :x)
130
- case_when1 = Dentaku::Token.new(:case, :when)
131
- one = Dentaku::Token.new(:numeric, 1)
132
- case_then1 = Dentaku::Token.new(:case, :then)
133
- two = Dentaku::Token.new(:numeric, 2)
134
- case_when2 = Dentaku::Token.new(:case, :when)
135
- three = Dentaku::Token.new(:numeric, 3)
136
- case_then2 = Dentaku::Token.new(:case, :then)
137
- four = Dentaku::Token.new(:numeric, 4)
138
- case_close = Dentaku::Token.new(:case, :close)
139
-
140
- node = described_class.new(
141
- [case_start,
142
- x,
143
- case_when1,
144
- one,
145
- case_then1,
146
- two,
147
- case_when2,
148
- three,
149
- case_then2,
150
- four,
151
- case_close]).parse
152
- expect(node.value(x: 3)).to eq(4)
70
+ node = parse('CASE x WHEN 1 THEN 2 WHEN 3 THEN 4 END')
71
+ expect(node.value("x" => 3)).to eq(4)
153
72
  end
154
73
 
155
74
  context 'invalid expression' do
156
75
  it 'raises a parse error for bad math' do
157
- five = Dentaku::Token.new(:numeric, 5)
158
- times = Dentaku::Token.new(:operator, :multiply)
159
- minus = Dentaku::Token.new(:operator, :subtract)
160
-
161
76
  expect {
162
- described_class.new([five, times, minus]).parse
77
+ parse("5 * -")
163
78
  }.to raise_error(Dentaku::ParseError)
164
79
  end
165
80
 
166
81
  it 'raises a parse error for bad logic' do
167
- this = Dentaku::Token.new(:logical, true)
168
- also = Dentaku::Token.new(:combinator, :and)
82
+ expect {
83
+ parse("TRUE AND")
84
+ }.to raise_error(Dentaku::ParseError)
85
+ end
86
+
87
+ it 'raises a parse error for bad grouping structure' do
88
+ expect {
89
+ parse(",")
90
+ }.to raise_error(Dentaku::ParseError)
169
91
 
170
92
  expect {
171
- described_class.new([this, also]).parse
93
+ parse("5, x")
94
+ described_class.new([five, comma, x]).parse
95
+ }.to raise_error(Dentaku::ParseError)
96
+
97
+ expect {
98
+ parse("5 + 5, x")
172
99
  }.to raise_error(Dentaku::ParseError)
173
100
  end
174
101
 
175
- it 'raises an exception when trying to access an undefined function' do
176
- fn = Dentaku::Token.new(:function, 'non_exists_func')
102
+ it 'raises parse errors for malformed case statements' do
103
+ expect {
104
+ parse("CASE a when 'one' then 1")
105
+ }.to raise_error(Dentaku::ParseError)
106
+
107
+ expect {
108
+ parse("case a whend 'one' then 1 end")
109
+ }.to raise_error(Dentaku::ParseError)
110
+
111
+ expect {
112
+ parse("CASE a WHEN 'one' THEND 1 END")
113
+ }.to raise_error(Dentaku::ParseError)
177
114
 
178
115
  expect {
179
- described_class.new([fn]).parse
116
+ parse("CASE a when 'one' then end")
117
+ }.to raise_error(Dentaku::ParseError)
118
+ end
119
+
120
+ it 'raises a parse error when trying to access an undefined function' do
121
+ expect {
122
+ parse("undefined()")
180
123
  }.to raise_error(Dentaku::ParseError)
181
124
  end
182
125
  end
183
126
 
184
- it "evaluates explicit 'NULL' as a Nil" do
185
- null = Dentaku::Token.new(:null, nil)
186
- node = described_class.new([null]).parse
127
+ it "evaluates explicit 'NULL' as nil" do
128
+ node = parse("NULL")
187
129
  expect(node.value).to eq(nil)
188
130
  end
131
+
132
+ private
133
+
134
+ def parse(expr)
135
+ tokens = Dentaku::Tokenizer.new.tokenize(expr)
136
+ described_class.new(tokens).parse
137
+ end
189
138
  end
@@ -16,9 +16,11 @@ end
16
16
 
17
17
  RSpec.configure do |c|
18
18
  c.before(:all) {
19
- # add example for alias because we can set aliases just once
20
- # before `calculator` method called
21
- Dentaku.aliases = { roundup: ['roundupup'] }
19
+ if Dentaku.respond_to?(:aliases=)
20
+ # add example for alias because we can set aliases just once
21
+ # before `calculator` method called
22
+ Dentaku.aliases = { roundup: ['roundupup'] }
23
+ end
22
24
  }
23
25
  end
24
26
 
@@ -45,7 +47,7 @@ def type_for(value)
45
47
  :grouping
46
48
  when :lbracket, :rbracket
47
49
  :access
48
- when :le, :ge, :ne, :ne, :lt, :gt, :eq
50
+ when :le, :ge, :ne, :lt, :gt, :eq
49
51
  :comparator
50
52
  when :and, :or
51
53
  :combinator
@@ -82,8 +82,8 @@ describe Dentaku::TokenMatcher do
82
82
  it 'matches zero or more occurrences in a token stream' do
83
83
  matched, substream = standard.match(stream)
84
84
  expect(matched).to be_truthy
85
- expect(substream.length).to eq 1
86
- expect(substream.map(&:value)).to eq [5]
85
+ expect(substream.length).to eq(1)
86
+ expect(substream.map(&:value)).to eq([5])
87
87
 
88
88
  matched, substream = standard.match(stream, 4)
89
89
  expect(substream).to be_empty
@@ -97,8 +97,8 @@ describe Dentaku::TokenMatcher do
97
97
  it 'matches zero or more occurrences in a token stream' do
98
98
  matched, substream = star.match(stream)
99
99
  expect(matched).to be_truthy
100
- expect(substream.length).to eq 4
101
- expect(substream.map(&:value)).to eq [5, 11, 9, 24]
100
+ expect(substream.length).to eq(4)
101
+ expect(substream.map(&:value)).to eq([5, 11, 9, 24])
102
102
 
103
103
  matched, substream = star.match(stream, 4)
104
104
  expect(substream).to be_empty
@@ -112,8 +112,8 @@ describe Dentaku::TokenMatcher do
112
112
  it 'matches one or more occurrences in a token stream' do
113
113
  matched, substream = plus.match(stream)
114
114
  expect(matched).to be_truthy
115
- expect(substream.length).to eq 4
116
- expect(substream.map(&:value)).to eq [5, 11, 9, 24]
115
+ expect(substream.length).to eq(4)
116
+ expect(substream.map(&:value)).to eq([5, 11, 9, 24])
117
117
 
118
118
  matched, substream = plus.match(stream, 4)
119
119
  expect(substream).to be_empty
@@ -126,8 +126,8 @@ describe Dentaku::TokenMatcher do
126
126
  stream = token_stream(1, :comma, 2, :comma, true, :comma, 'olive', :comma, :'(')
127
127
  matched, substream = described_class.arguments.match(stream)
128
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]
129
+ expect(substream.length).to eq(8)
130
+ expect(substream.map(&:value)).to eq([1, :comma, 2, :comma, true, :comma, 'olive', :comma])
131
131
  end
132
132
  end
133
133
  end
@@ -3,7 +3,7 @@ require 'dentaku/token_scanner'
3
3
  describe Dentaku::TokenScanner do
4
4
  let(:whitespace) { described_class.new(:whitespace, '\s') }
5
5
  let(:numeric) { described_class.new(:numeric, '(\d+(\.\d+)?|\.\d+)',
6
- ->(raw) { raw =~ /\./ ? BigDecimal.new(raw) : raw.to_i })
6
+ ->(raw) { raw =~ /\./ ? BigDecimal(raw) : raw.to_i })
7
7
  }
8
8
  let(:custom) { described_class.new(:identifier, '#\w+\b',
9
9
  ->(raw) { raw.gsub('#', '').to_sym })
@@ -29,18 +29,18 @@ describe Dentaku::TokenScanner do
29
29
 
30
30
  it 'allows customizing available scanners' do
31
31
  described_class.scanners = [:whitespace, :numeric]
32
- expect(described_class.scanners.length).to eq 2
32
+ expect(described_class.scanners.length).to eq(2)
33
33
  end
34
34
 
35
35
  it 'ignores invalid scanners' do
36
36
  described_class.scanners = [:whitespace, :numeric, :fake]
37
- expect(described_class.scanners.length).to eq 2
37
+ expect(described_class.scanners.length).to eq(2)
38
38
  end
39
39
 
40
40
  it 'uses a custom scanner' do
41
41
  described_class.scanners = [:whitespace, :numeric]
42
42
  described_class.register_scanner(:custom, custom)
43
- expect(described_class.scanners.length).to eq 3
43
+ expect(described_class.scanners.length).to eq(3)
44
44
 
45
45
  token = custom.scan('#apple + #pear').first
46
46
  expect(token.category).to eq(:identifier)
@@ -243,22 +243,22 @@ describe Dentaku::Tokenizer do
243
243
  tokens = tokenizer.tokenize('round(8.2)')
244
244
  expect(tokens.length).to eq(4)
245
245
  expect(tokens.map(&:category)).to eq([:function, :grouping, :numeric, :grouping])
246
- expect(tokens.map(&:value)).to eq([:round, :open, BigDecimal.new('8.2'), :close])
246
+ expect(tokens.map(&:value)).to eq([:round, :open, BigDecimal('8.2'), :close])
247
247
 
248
248
  tokens = tokenizer.tokenize('round(8.75, 1)')
249
249
  expect(tokens.length).to eq(6)
250
250
  expect(tokens.map(&:category)).to eq([:function, :grouping, :numeric, :grouping, :numeric, :grouping])
251
- expect(tokens.map(&:value)).to eq([:round, :open, BigDecimal.new('8.75'), :comma, 1, :close])
251
+ expect(tokens.map(&:value)).to eq([:round, :open, BigDecimal('8.75'), :comma, 1, :close])
252
252
 
253
253
  tokens = tokenizer.tokenize('ROUNDUP(8.2)')
254
254
  expect(tokens.length).to eq(4)
255
255
  expect(tokens.map(&:category)).to eq([:function, :grouping, :numeric, :grouping])
256
- expect(tokens.map(&:value)).to eq([:roundup, :open, BigDecimal.new('8.2'), :close])
256
+ expect(tokens.map(&:value)).to eq([:roundup, :open, BigDecimal('8.2'), :close])
257
257
 
258
258
  tokens = tokenizer.tokenize('RoundDown(8.2)')
259
259
  expect(tokens.length).to eq(4)
260
260
  expect(tokens.map(&:category)).to eq([:function, :grouping, :numeric, :grouping])
261
- expect(tokens.map(&:value)).to eq([:rounddown, :open, BigDecimal.new('8.2'), :close])
261
+ expect(tokens.map(&:value)).to eq([:rounddown, :open, BigDecimal('8.2'), :close])
262
262
  end
263
263
 
264
264
  it 'include NOT' do
@@ -298,25 +298,25 @@ describe Dentaku::Tokenizer do
298
298
  it 'replaced with function name' do
299
299
  input = 'rrrrround!(8.2) + minimo(4,6,2)'
300
300
  tokenizer.tokenize(input, aliases: aliases)
301
- expect(tokenizer.replace_aliases(input)).to eq 'round(8.2) + min(4,6,2)'
301
+ expect(tokenizer.replace_aliases(input)).to eq('round(8.2) + min(4,6,2)')
302
302
  end
303
303
 
304
304
  it 'case insensitive' do
305
305
  input = 'MinImO(4,6,2)'
306
306
  tokenizer.tokenize(input, aliases: aliases)
307
- expect(tokenizer.replace_aliases(input)).to eq 'min(4,6,2)'
307
+ expect(tokenizer.replace_aliases(input)).to eq('min(4,6,2)')
308
308
  end
309
309
 
310
310
  it 'replace only whole aliases without word parts' do
311
311
  input = 'maximo(2,minimoooo())' # `minimoooo` doesn't match `minimo`
312
312
  tokenizer.tokenize(input, aliases: aliases)
313
- expect(tokenizer.replace_aliases(input)).to eq 'max(2,minimoooo())'
313
+ expect(tokenizer.replace_aliases(input)).to eq('max(2,minimoooo())')
314
314
  end
315
315
 
316
316
  it 'work with non-latin symbols' do
317
317
  input = '如果(1,2,3)'
318
318
  tokenizer.tokenize(input, aliases: aliases)
319
- expect(tokenizer.replace_aliases(input)).to eq 'if(1,2,3)'
319
+ expect(tokenizer.replace_aliases(input)).to eq('if(1,2,3)')
320
320
  end
321
321
  end
322
322
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dentaku
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 3.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Solomon White
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-04 00:00:00.000000000 Z
11
+ date: 2019-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: codecov