dentaku 3.2.1 → 3.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -1
- data/lib/dentaku/ast.rb +1 -0
- data/lib/dentaku/ast/array.rb +23 -0
- data/lib/dentaku/ast/functions/string_functions.rb +37 -8
- data/lib/dentaku/parser.rb +19 -2
- data/lib/dentaku/token_scanner.rb +6 -0
- data/lib/dentaku/version.rb +1 -1
- data/spec/ast/string_functions_spec.rb +38 -0
- data/spec/calculator_spec.rb +2 -0
- data/spec/tokenizer_spec.rb +6 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7a7a3fa41f933a1f88a76e736c32e71dcf87f391a7a4ba09d37b2abad8269b2
|
|
4
|
+
data.tar.gz: 3045bd6715d486669c2fe015165a2bef31928fccdf4e6a07f59a67257b9f4867
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f8b4a5d727ec64a0308a57570fc51ff09864174fd0669956acb6b26b0d6b5694d6a0ec563eb107b577db79dd37ddd383fc36ec47bd17ebb11008981e2d5c729
|
|
7
|
+
data.tar.gz: 1cb16ed5d1bbad1c896d3907761206178093531ef02fe27eabaa19e1b5187f66f1d9d6586d410107133c08c520596dd7b9a6d5b47f23decb47e80ed2cbdd0e28
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## [v3.3.0] 2018-12-04
|
|
4
|
+
- add array literal syntax
|
|
5
|
+
- return correct type from string function AST nodes
|
|
6
|
+
|
|
3
7
|
## [v3.2.1] 2018-10-24
|
|
4
8
|
- make `evaluate` rescue more exceptions
|
|
5
9
|
|
|
@@ -162,7 +166,8 @@
|
|
|
162
166
|
## [v0.1.0] 2012-01-20
|
|
163
167
|
- initial release
|
|
164
168
|
|
|
165
|
-
[HEAD]: https://github.com/rubysolo/dentaku/compare/v3.
|
|
169
|
+
[HEAD]: https://github.com/rubysolo/dentaku/compare/v3.3.0...HEAD
|
|
170
|
+
[v3.3.0]: https://github.com/rubysolo/dentaku/compare/v3.2.1...v3.3.0
|
|
166
171
|
[v3.2.1]: https://github.com/rubysolo/dentaku/compare/v3.2.0...v3.2.1
|
|
167
172
|
[v3.2.0]: https://github.com/rubysolo/dentaku/compare/v3.1.0...v3.2.0
|
|
168
173
|
[v3.1.0]: https://github.com/rubysolo/dentaku/compare/v3.0.0...v3.1.0
|
data/lib/dentaku/ast.rb
CHANGED
|
@@ -11,6 +11,7 @@ require_relative './ast/negation'
|
|
|
11
11
|
require_relative './ast/comparators'
|
|
12
12
|
require_relative './ast/combinators'
|
|
13
13
|
require_relative './ast/access'
|
|
14
|
+
require_relative './ast/array'
|
|
14
15
|
require_relative './ast/grouping'
|
|
15
16
|
require_relative './ast/case'
|
|
16
17
|
require_relative './ast/function_registry'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Dentaku
|
|
2
|
+
module AST
|
|
3
|
+
class Array
|
|
4
|
+
def self.arity
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.peek(*)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(*elements)
|
|
11
|
+
@elements = *elements
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def value(context = {})
|
|
15
|
+
@elements.map { |el| el.value(context) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def dependencies(context = {})
|
|
19
|
+
@elements.flat_map { |el| el.dependencies(context) }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -3,7 +3,20 @@ require_relative '../function'
|
|
|
3
3
|
module Dentaku
|
|
4
4
|
module AST
|
|
5
5
|
module StringFunctions
|
|
6
|
-
class
|
|
6
|
+
class Base < Function
|
|
7
|
+
def type
|
|
8
|
+
:string
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def negative_argument_failure(fun, arg = 'length')
|
|
12
|
+
raise Dentaku::ArgumentError.for(
|
|
13
|
+
:invalid_value,
|
|
14
|
+
function_name: "#{fun}()"
|
|
15
|
+
), "#{fun}() requires #{arg} to be positive"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Left < Base
|
|
7
20
|
def initialize(*args)
|
|
8
21
|
super
|
|
9
22
|
@string, @length = *@args
|
|
@@ -12,11 +25,12 @@ module Dentaku
|
|
|
12
25
|
def value(context = {})
|
|
13
26
|
string = @string.value(context).to_s
|
|
14
27
|
length = @length.value(context)
|
|
28
|
+
negative_argument_failure('LEFT') if length < 0
|
|
15
29
|
string[0, length]
|
|
16
30
|
end
|
|
17
31
|
end
|
|
18
32
|
|
|
19
|
-
class Right <
|
|
33
|
+
class Right < Base
|
|
20
34
|
def initialize(*args)
|
|
21
35
|
super
|
|
22
36
|
@string, @length = *@args
|
|
@@ -25,11 +39,12 @@ module Dentaku
|
|
|
25
39
|
def value(context = {})
|
|
26
40
|
string = @string.value(context).to_s
|
|
27
41
|
length = @length.value(context)
|
|
42
|
+
negative_argument_failure('RIGHT') if length < 0
|
|
28
43
|
string[length * -1, length] || string
|
|
29
44
|
end
|
|
30
45
|
end
|
|
31
46
|
|
|
32
|
-
class Mid <
|
|
47
|
+
class Mid < Base
|
|
33
48
|
def initialize(*args)
|
|
34
49
|
super
|
|
35
50
|
@string, @offset, @length = *@args
|
|
@@ -38,12 +53,14 @@ module Dentaku
|
|
|
38
53
|
def value(context = {})
|
|
39
54
|
string = @string.value(context).to_s
|
|
40
55
|
offset = @offset.value(context)
|
|
56
|
+
negative_argument_failure('MID', 'offset') if offset < 0
|
|
41
57
|
length = @length.value(context)
|
|
58
|
+
negative_argument_failure('MID') if length < 0
|
|
42
59
|
string[offset - 1, length].to_s
|
|
43
60
|
end
|
|
44
61
|
end
|
|
45
62
|
|
|
46
|
-
class Len <
|
|
63
|
+
class Len < Base
|
|
47
64
|
def initialize(*args)
|
|
48
65
|
super
|
|
49
66
|
@string = @args[0]
|
|
@@ -53,9 +70,13 @@ module Dentaku
|
|
|
53
70
|
string = @string.value(context).to_s
|
|
54
71
|
string.length
|
|
55
72
|
end
|
|
73
|
+
|
|
74
|
+
def type
|
|
75
|
+
:numeric
|
|
76
|
+
end
|
|
56
77
|
end
|
|
57
78
|
|
|
58
|
-
class Find <
|
|
79
|
+
class Find < Base
|
|
59
80
|
def initialize(*args)
|
|
60
81
|
super
|
|
61
82
|
@needle, @haystack = *@args
|
|
@@ -68,9 +89,13 @@ module Dentaku
|
|
|
68
89
|
pos = haystack.index(needle)
|
|
69
90
|
pos && pos + 1
|
|
70
91
|
end
|
|
92
|
+
|
|
93
|
+
def type
|
|
94
|
+
:numeric
|
|
95
|
+
end
|
|
71
96
|
end
|
|
72
97
|
|
|
73
|
-
class Substitute <
|
|
98
|
+
class Substitute < Base
|
|
74
99
|
def initialize(*args)
|
|
75
100
|
super
|
|
76
101
|
@original, @search, @replacement = *@args
|
|
@@ -85,7 +110,7 @@ module Dentaku
|
|
|
85
110
|
end
|
|
86
111
|
end
|
|
87
112
|
|
|
88
|
-
class Concat <
|
|
113
|
+
class Concat < Base
|
|
89
114
|
def initialize(*args)
|
|
90
115
|
super
|
|
91
116
|
end
|
|
@@ -95,7 +120,7 @@ module Dentaku
|
|
|
95
120
|
end
|
|
96
121
|
end
|
|
97
122
|
|
|
98
|
-
class Contains <
|
|
123
|
+
class Contains < Base
|
|
99
124
|
def initialize(*args)
|
|
100
125
|
super
|
|
101
126
|
@needle, @haystack = *args
|
|
@@ -104,6 +129,10 @@ module Dentaku
|
|
|
104
129
|
def value(context = {})
|
|
105
130
|
@haystack.value(context).to_s.include? @needle.value(context).to_s
|
|
106
131
|
end
|
|
132
|
+
|
|
133
|
+
def type
|
|
134
|
+
:logical
|
|
135
|
+
end
|
|
107
136
|
end
|
|
108
137
|
end
|
|
109
138
|
end
|
data/lib/dentaku/parser.rb
CHANGED
|
@@ -205,11 +205,28 @@ module Dentaku
|
|
|
205
205
|
end
|
|
206
206
|
|
|
207
207
|
unless operations.last == AST::Access
|
|
208
|
-
fail! :unbalanced_bracket, token
|
|
208
|
+
fail! :unbalanced_bracket, token: token
|
|
209
209
|
end
|
|
210
210
|
consume
|
|
211
211
|
end
|
|
212
212
|
|
|
213
|
+
when :array
|
|
214
|
+
case token.value
|
|
215
|
+
when :array_start
|
|
216
|
+
operations.push AST::Array
|
|
217
|
+
arities.push 0
|
|
218
|
+
when :array_end
|
|
219
|
+
while operations.any? && operations.last != AST::Array
|
|
220
|
+
consume
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
unless operations.last == AST::Array
|
|
224
|
+
fail! :unbalanced_bracket, token: token
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
consume(arities.pop.succ)
|
|
228
|
+
end
|
|
229
|
+
|
|
213
230
|
when :grouping
|
|
214
231
|
case token.value
|
|
215
232
|
when :open
|
|
@@ -237,7 +254,7 @@ module Dentaku
|
|
|
237
254
|
|
|
238
255
|
when :comma
|
|
239
256
|
arities[-1] += 1
|
|
240
|
-
while operations.any? && operations.last != AST::Grouping
|
|
257
|
+
while operations.any? && operations.last != AST::Grouping && operations.last != AST::Array
|
|
241
258
|
consume
|
|
242
259
|
end
|
|
243
260
|
|
|
@@ -43,6 +43,7 @@ module Dentaku
|
|
|
43
43
|
:combinator,
|
|
44
44
|
:operator,
|
|
45
45
|
:grouping,
|
|
46
|
+
:array,
|
|
46
47
|
:access,
|
|
47
48
|
:case_statement,
|
|
48
49
|
:comparator,
|
|
@@ -129,6 +130,11 @@ module Dentaku
|
|
|
129
130
|
new(:grouping, '\(|\)|,', lambda { |raw| names[raw] })
|
|
130
131
|
end
|
|
131
132
|
|
|
133
|
+
def array
|
|
134
|
+
names = { array_start: '{', array_end: '}', }.invert
|
|
135
|
+
new(:array, '\{|\}|,', lambda { |raw| names[raw] })
|
|
136
|
+
end
|
|
137
|
+
|
|
132
138
|
def access
|
|
133
139
|
names = { lbracket: '[', rbracket: ']' }.invert
|
|
134
140
|
new(:access, '\[|\]', lambda { |raw| names[raw] })
|
data/lib/dentaku/version.rb
CHANGED
|
@@ -25,6 +25,16 @@ describe Dentaku::AST::StringFunctions::Left do
|
|
|
25
25
|
it 'handles size greater than input string length correctly' do
|
|
26
26
|
expect(subject.value('string' => 'abcdefg', 'length' => 40)).to eq 'abcdefg'
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
it 'has the proper type' do
|
|
30
|
+
expect(subject.type).to eq(:string)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'raises an error if given invalid length' do
|
|
34
|
+
expect {
|
|
35
|
+
subject.value('string' => 'abcdefg', 'length' => -2)
|
|
36
|
+
}.to raise_error(Dentaku::ArgumentError, /LEFT\(\) requires length to be positive/)
|
|
37
|
+
end
|
|
28
38
|
end
|
|
29
39
|
|
|
30
40
|
describe Dentaku::AST::StringFunctions::Right do
|
|
@@ -42,6 +52,10 @@ describe Dentaku::AST::StringFunctions::Right do
|
|
|
42
52
|
subject = described_class.new(literal('abcdefg'), literal(40))
|
|
43
53
|
expect(subject.value).to eq 'abcdefg'
|
|
44
54
|
end
|
|
55
|
+
|
|
56
|
+
it 'has the proper type' do
|
|
57
|
+
expect(subject.type).to eq(:string)
|
|
58
|
+
end
|
|
45
59
|
end
|
|
46
60
|
|
|
47
61
|
describe Dentaku::AST::StringFunctions::Mid do
|
|
@@ -64,6 +78,10 @@ describe Dentaku::AST::StringFunctions::Mid do
|
|
|
64
78
|
subject = described_class.new(literal('abcdefg'), literal(4), literal(40))
|
|
65
79
|
expect(subject.value).to eq 'defg'
|
|
66
80
|
end
|
|
81
|
+
|
|
82
|
+
it 'has the proper type' do
|
|
83
|
+
expect(subject.type).to eq(:string)
|
|
84
|
+
end
|
|
67
85
|
end
|
|
68
86
|
|
|
69
87
|
describe Dentaku::AST::StringFunctions::Len do
|
|
@@ -76,6 +94,10 @@ describe Dentaku::AST::StringFunctions::Len do
|
|
|
76
94
|
subject = described_class.new(literal(''))
|
|
77
95
|
expect(subject.value).to eq 0
|
|
78
96
|
end
|
|
97
|
+
|
|
98
|
+
it 'has the proper type' do
|
|
99
|
+
expect(subject.type).to eq(:numeric)
|
|
100
|
+
end
|
|
79
101
|
end
|
|
80
102
|
|
|
81
103
|
describe Dentaku::AST::StringFunctions::Find do
|
|
@@ -93,6 +115,10 @@ describe Dentaku::AST::StringFunctions::Find do
|
|
|
93
115
|
subject = described_class.new(literal('DE'), literal(''))
|
|
94
116
|
expect(subject.value).to be_nil
|
|
95
117
|
end
|
|
118
|
+
|
|
119
|
+
it 'has the proper type' do
|
|
120
|
+
expect(subject.type).to eq(:numeric)
|
|
121
|
+
end
|
|
96
122
|
end
|
|
97
123
|
|
|
98
124
|
describe Dentaku::AST::StringFunctions::Substitute do
|
|
@@ -110,6 +136,10 @@ describe Dentaku::AST::StringFunctions::Substitute do
|
|
|
110
136
|
subject = described_class.new(literal('ABCDEFG'), literal('DE'), literal(''))
|
|
111
137
|
expect(subject.value).to eq 'ABCFG'
|
|
112
138
|
end
|
|
139
|
+
|
|
140
|
+
it 'has the proper type' do
|
|
141
|
+
expect(subject.type).to eq(:string)
|
|
142
|
+
end
|
|
113
143
|
end
|
|
114
144
|
|
|
115
145
|
describe Dentaku::AST::StringFunctions::Concat do
|
|
@@ -132,6 +162,10 @@ describe Dentaku::AST::StringFunctions::Concat do
|
|
|
132
162
|
subject = described_class.new(literal(''), literal(''))
|
|
133
163
|
expect(subject.value).to eq ''
|
|
134
164
|
end
|
|
165
|
+
|
|
166
|
+
it 'has the proper type' do
|
|
167
|
+
expect(subject.type).to eq(:string)
|
|
168
|
+
end
|
|
135
169
|
end
|
|
136
170
|
|
|
137
171
|
describe Dentaku::AST::StringFunctions::Contains do
|
|
@@ -141,4 +175,8 @@ describe Dentaku::AST::StringFunctions::Contains do
|
|
|
141
175
|
subject = described_class.new(literal('app'), literal('orange'))
|
|
142
176
|
expect(subject.value).to be_falsy
|
|
143
177
|
end
|
|
178
|
+
|
|
179
|
+
it 'has the proper type' do
|
|
180
|
+
expect(subject.type).to eq(:logical)
|
|
181
|
+
end
|
|
144
182
|
end
|
data/spec/calculator_spec.rb
CHANGED
|
@@ -79,6 +79,7 @@ describe Dentaku::Calculator do
|
|
|
79
79
|
describe 'evaluate!' do
|
|
80
80
|
it 'raises exception when formula has error' do
|
|
81
81
|
expect { calculator.evaluate!('1 + + 1') }.to raise_error(Dentaku::ParseError)
|
|
82
|
+
expect { calculator.evaluate!('(1 > 5) OR LEFT("abc", 1)') }.to raise_error(Dentaku::ParseError)
|
|
82
83
|
end
|
|
83
84
|
|
|
84
85
|
it 'raises unbound variable errors' do
|
|
@@ -149,6 +150,7 @@ describe Dentaku::Calculator do
|
|
|
149
150
|
|
|
150
151
|
it 'evaluates arrays' do
|
|
151
152
|
expect(calculator.evaluate([1, 2, 3])).to eq([1, 2, 3])
|
|
153
|
+
expect(calculator.evaluate!('{1,2,3}')).to eq([1, 2, 3])
|
|
152
154
|
end
|
|
153
155
|
end
|
|
154
156
|
|
data/spec/tokenizer_spec.rb
CHANGED
|
@@ -179,6 +179,12 @@ describe Dentaku::Tokenizer do
|
|
|
179
179
|
expect(tokens.map(&:value)).to eq(['size', :lt, 3, :or, 'admin', :eq, 1])
|
|
180
180
|
end
|
|
181
181
|
|
|
182
|
+
it 'tokenizes curly brackets for array literals' do
|
|
183
|
+
tokens = tokenizer.tokenize('{}')
|
|
184
|
+
expect(tokens.map(&:category)).to eq(%i(array array))
|
|
185
|
+
expect(tokens.map(&:value)).to eq(%i(array_start array_end))
|
|
186
|
+
end
|
|
187
|
+
|
|
182
188
|
it 'tokenizes square brackets for data structure access' do
|
|
183
189
|
tokens = tokenizer.tokenize('a[1]')
|
|
184
190
|
expect(tokens.map(&:category)).to eq(%i(identifier access numeric access))
|
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.
|
|
4
|
+
version: 3.3.0
|
|
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-
|
|
11
|
+
date: 2018-12-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: codecov
|
|
@@ -143,6 +143,7 @@ files:
|
|
|
143
143
|
- lib/dentaku/ast.rb
|
|
144
144
|
- lib/dentaku/ast/access.rb
|
|
145
145
|
- lib/dentaku/ast/arithmetic.rb
|
|
146
|
+
- lib/dentaku/ast/array.rb
|
|
146
147
|
- lib/dentaku/ast/bitwise.rb
|
|
147
148
|
- lib/dentaku/ast/case.rb
|
|
148
149
|
- lib/dentaku/ast/case/case_conditional.rb
|