delorean_lang 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitlab-ci.yml +1 -2
- data/.rubocop.yml +45 -5
- data/.rubocop_todo.yml +1 -634
- data/Gemfile +3 -1
- data/README.md +22 -0
- data/Rakefile +3 -1
- data/delorean.gemspec +18 -17
- data/lib/delorean/abstract_container.rb +4 -2
- data/lib/delorean/base.rb +30 -27
- data/lib/delorean/cache.rb +2 -0
- data/lib/delorean/cache/adapters.rb +2 -0
- data/lib/delorean/cache/adapters/base.rb +2 -0
- data/lib/delorean/cache/adapters/ruby_cache.rb +5 -0
- data/lib/delorean/const.rb +5 -3
- data/lib/delorean/debug.rb +6 -5
- data/lib/delorean/delorean.rb +466 -147
- data/lib/delorean/delorean.treetop +13 -1
- data/lib/delorean/engine.rb +61 -50
- data/lib/delorean/error.rb +2 -1
- data/lib/delorean/model.rb +12 -9
- data/lib/delorean/nodes.rb +130 -67
- data/lib/delorean/ruby.rb +2 -0
- data/lib/delorean/ruby/whitelists.rb +2 -0
- data/lib/delorean/ruby/whitelists/base.rb +7 -3
- data/lib/delorean/ruby/whitelists/default.rb +6 -6
- data/lib/delorean/ruby/whitelists/empty.rb +3 -2
- data/lib/delorean/ruby/whitelists/matchers.rb +2 -0
- data/lib/delorean/ruby/whitelists/matchers/arguments.rb +2 -0
- data/lib/delorean/ruby/whitelists/matchers/method.rb +5 -2
- data/lib/delorean/ruby/whitelists/whitelist_error.rb +2 -0
- data/lib/delorean/version.rb +3 -1
- data/lib/delorean_lang.rb +3 -1
- data/spec/cache_spec.rb +4 -2
- data/spec/dev_spec.rb +68 -69
- data/spec/eval_spec.rb +824 -729
- data/spec/func_spec.rb +172 -176
- data/spec/parse_spec.rb +516 -522
- data/spec/ruby/whitelist_spec.rb +6 -3
- data/spec/spec_helper.rb +26 -23
- metadata +27 -27
data/spec/func_spec.rb
CHANGED
@@ -1,298 +1,294 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
describe 'Delorean' do
|
6
|
+
let(:engine) do
|
7
|
+
Delorean::Engine.new('ZZZ')
|
8
|
+
end
|
8
9
|
|
9
|
-
it
|
10
|
-
engine.parse defn(
|
11
|
-
|
12
|
-
|
10
|
+
it 'should handle MAX as a node name' do
|
11
|
+
engine.parse defn('MAX:',
|
12
|
+
' a = [1, 2, 3, 0, -10].max()',
|
13
|
+
)
|
13
14
|
|
14
|
-
r = engine.evaluate(
|
15
|
+
r = engine.evaluate('MAX', 'a')
|
15
16
|
r.should == 3
|
16
17
|
end
|
17
18
|
|
18
|
-
it
|
19
|
-
engine.parse defn(
|
20
|
-
|
19
|
+
it 'should handle COMPACT' do
|
20
|
+
engine.parse defn('A:',
|
21
|
+
' a = [1, 2, nil, -3, 4].compact',
|
21
22
|
" b = {'a': 1, 'b': nil, 'c': nil}.compact()",
|
22
|
-
|
23
|
+
)
|
23
24
|
|
24
|
-
expect(engine.evaluate(
|
25
|
-
expect(engine.evaluate(
|
25
|
+
expect(engine.evaluate('A', 'a')).to eq([1, 2, -3, 4])
|
26
|
+
expect(engine.evaluate('A', 'b')).to eq('a' => 1)
|
26
27
|
end
|
27
28
|
|
28
|
-
it
|
29
|
-
engine.parse defn(
|
30
|
-
|
31
|
-
|
29
|
+
it 'should handle MIN' do
|
30
|
+
engine.parse defn('A:',
|
31
|
+
' a = [1, 2, -3, 4].min()',
|
32
|
+
)
|
32
33
|
|
33
|
-
r = engine.evaluate(
|
34
|
+
r = engine.evaluate('A', 'a')
|
34
35
|
r.should == -3
|
35
36
|
end
|
36
37
|
|
37
|
-
it
|
38
|
-
engine.parse defn(
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
it 'should handle ROUND' do
|
39
|
+
engine.parse defn('A:',
|
40
|
+
' a = 12.3456.round(2)',
|
41
|
+
' b = 12.3456.round(1)',
|
42
|
+
' c = 12.3456.round()',
|
43
|
+
)
|
43
44
|
|
44
|
-
r = engine.evaluate(
|
45
|
+
r = engine.evaluate('A', ['a', 'b', 'c'])
|
45
46
|
r.should == [12.35, 12.3, 12]
|
46
47
|
end
|
47
48
|
|
48
|
-
it
|
49
|
-
engine.parse defn(
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
it 'should handle TRUNCATE' do
|
50
|
+
engine.parse defn('A:',
|
51
|
+
' a = 12.3456.truncate(2)',
|
52
|
+
' b = 12.3456.truncate(1)',
|
53
|
+
' c = 12.3456.truncate()',
|
54
|
+
)
|
54
55
|
|
55
|
-
r = engine.evaluate(
|
56
|
+
r = engine.evaluate('A', ['a', 'b', 'c'])
|
56
57
|
r.should == [12.34, 12.3, 12]
|
57
58
|
end
|
58
59
|
|
59
|
-
it
|
60
|
-
engine.parse defn(
|
61
|
-
|
62
|
-
|
60
|
+
it 'should handle FLOOR' do
|
61
|
+
engine.parse defn('A:',
|
62
|
+
' a = [12.3456.floor(), 13.7890.floor()]',
|
63
|
+
)
|
63
64
|
|
64
|
-
r = engine.evaluate(
|
65
|
+
r = engine.evaluate('A', 'a')
|
65
66
|
r.should == [12, 13]
|
66
67
|
end
|
67
68
|
|
68
|
-
it
|
69
|
-
engine.parse defn(
|
70
|
-
|
69
|
+
it 'should handle TO_F' do
|
70
|
+
engine.parse defn('A:',
|
71
|
+
' a = 12.3456.to_f()',
|
71
72
|
" b = '12.3456'.to_f()",
|
72
73
|
" c = '12'.to_f()",
|
73
74
|
" d = '2018-05-04 10:56:27 -0700'.to_time.to_f",
|
74
|
-
|
75
|
+
)
|
75
76
|
|
76
|
-
r = engine.evaluate(
|
77
|
-
r.should == [12.3456, 12.3456, 12,
|
77
|
+
r = engine.evaluate('A', ['a', 'b', 'c', 'd'])
|
78
|
+
r.should == [12.3456, 12.3456, 12, 1_525_456_587.0]
|
78
79
|
end
|
79
80
|
|
80
|
-
it
|
81
|
-
engine.parse defn(
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
81
|
+
it 'should handle ABS' do
|
82
|
+
engine.parse defn('A:',
|
83
|
+
' a = (-123).abs()',
|
84
|
+
' b = (-1.1).abs()',
|
85
|
+
' c = 2.3.abs()',
|
86
|
+
' d = 0.abs()',
|
87
|
+
)
|
87
88
|
|
88
|
-
r = engine.evaluate(
|
89
|
+
r = engine.evaluate('A', ['a', 'b', 'c', 'd'])
|
89
90
|
r.should == [123, 1.1, 2.3, 0]
|
90
91
|
end
|
91
92
|
|
92
|
-
it
|
93
|
-
engine.parse defn(
|
93
|
+
it 'should handle STRING' do
|
94
|
+
engine.parse defn('A:',
|
94
95
|
" a = 'hello'.to_s()",
|
95
|
-
|
96
|
-
|
97
|
-
|
96
|
+
' b = 12.3456.to_s()',
|
97
|
+
' c = [1,2,3].to_s()',
|
98
|
+
)
|
98
99
|
|
99
|
-
r = engine.evaluate(
|
100
|
-
r.should == [
|
100
|
+
r = engine.evaluate('A', ['a', 'b', 'c'])
|
101
|
+
r.should == ['hello', '12.3456', [1, 2, 3].to_s]
|
101
102
|
end
|
102
103
|
|
103
|
-
it
|
104
|
-
engine.parse defn(
|
104
|
+
it 'should handle FETCH' do
|
105
|
+
engine.parse defn('A:',
|
105
106
|
" h = {'a':123, 1:111}",
|
106
107
|
" a = h.fetch('a')",
|
107
|
-
|
108
|
+
' b = h.fetch(1)',
|
108
109
|
" c = h.fetch('xxx', 456)",
|
109
|
-
|
110
|
+
)
|
110
111
|
|
111
|
-
r = engine.evaluate(
|
112
|
+
r = engine.evaluate('A', ['a', 'b', 'c'])
|
112
113
|
r.should == [123, 111, 456]
|
113
114
|
end
|
114
115
|
|
115
|
-
it
|
116
|
-
engine.parse defn(
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
116
|
+
it 'should handle TIMEPART' do
|
117
|
+
engine.parse defn('A:',
|
118
|
+
' p =?',
|
119
|
+
' h = p.hour()',
|
120
|
+
' m = p.min()',
|
121
|
+
' s = p.sec()',
|
122
|
+
' d = p.to_date()',
|
123
|
+
' e = p.to_date.to_s.to_date',
|
124
|
+
)
|
124
125
|
|
125
126
|
p = Time.now
|
126
|
-
params = {
|
127
|
-
r = engine.evaluate(
|
127
|
+
params = { 'p' => p }
|
128
|
+
r = engine.evaluate('A', %w[h m s d e], params)
|
128
129
|
r.should == [p.hour, p.min, p.sec, p.to_date, p.to_date]
|
129
130
|
|
130
131
|
# Non time argument should raise an error
|
131
|
-
expect { engine.evaluate(
|
132
|
-
|
132
|
+
expect { engine.evaluate('A', ['m'], 'p' => 123) }.to raise_error
|
133
133
|
end
|
134
134
|
|
135
|
-
it
|
136
|
-
engine.parse defn(
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
135
|
+
it 'should handle DATEPART' do
|
136
|
+
engine.parse defn('A:',
|
137
|
+
' p =?',
|
138
|
+
' y = p.year()',
|
139
|
+
' d = p.day()',
|
140
|
+
' m = p.month()',
|
141
|
+
)
|
142
142
|
|
143
143
|
p = Date.today
|
144
|
-
r = engine.evaluate(
|
144
|
+
r = engine.evaluate('A', ['y', 'd', 'm'], 'p' => p)
|
145
145
|
r.should == [p.year, p.day, p.month]
|
146
146
|
|
147
147
|
# Non date argument should raise an error
|
148
|
-
expect
|
149
|
-
engine.evaluate(
|
150
|
-
|
148
|
+
expect do
|
149
|
+
engine.evaluate('A', ['y', 'd', 'm'], 'p' => 123)
|
150
|
+
end.to raise_error
|
151
151
|
end
|
152
152
|
|
153
|
-
it
|
154
|
-
x = [[1,2,[3]], 4, 5, [6]]
|
153
|
+
it 'should handle FLATTEN' do
|
154
|
+
x = [[1, 2, [3]], 4, 5, [6]]
|
155
155
|
|
156
|
-
engine.parse defn(
|
156
|
+
engine.parse defn('A:',
|
157
157
|
" a = #{x}",
|
158
|
-
|
159
|
-
|
158
|
+
' b = a.flatten() + a.flatten(1)'
|
159
|
+
)
|
160
160
|
|
161
|
-
engine.evaluate(
|
161
|
+
engine.evaluate('A', 'b').should == x.flatten + x.flatten(1)
|
162
162
|
end
|
163
163
|
|
164
|
-
it
|
164
|
+
it 'should handle ZIP' do
|
165
165
|
a = [1, 2]
|
166
166
|
b = [4, 5, 6]
|
167
167
|
c = [7, 8]
|
168
168
|
|
169
|
-
engine.parse defn(
|
169
|
+
engine.parse defn('A:',
|
170
170
|
" a = #{a}",
|
171
171
|
" b = #{b}",
|
172
172
|
" c = #{c}",
|
173
|
-
|
174
|
-
|
173
|
+
' d = a.zip(b) + a.zip(b, c)',
|
174
|
+
)
|
175
175
|
|
176
|
-
expect(engine.evaluate(
|
176
|
+
expect(engine.evaluate('A', 'd')).to eq a.zip(b) + a.zip(b, c)
|
177
177
|
end
|
178
178
|
|
179
|
-
it
|
180
|
-
engine.parse defn(
|
179
|
+
it 'should handle ERR' do
|
180
|
+
engine.parse defn('A:',
|
181
181
|
" a = ERR('hello')",
|
182
182
|
" b = ERR('xx', 1, 2, 3)",
|
183
|
-
|
183
|
+
)
|
184
184
|
|
185
|
-
expect { engine.evaluate(
|
185
|
+
expect { engine.evaluate('A', 'a') }.to raise_error('hello')
|
186
186
|
|
187
187
|
lambda {
|
188
|
-
|
189
|
-
}.should raise_error(
|
188
|
+
engine.evaluate('A', 'b')
|
189
|
+
}.should raise_error('xx, 1, 2, 3')
|
190
190
|
end
|
191
191
|
|
192
|
-
it
|
192
|
+
it 'should handle RUBY' do
|
193
193
|
x = [[1, 2, [-3]], 4, 5, [6], -3, 4, 5, 0]
|
194
194
|
|
195
|
-
engine.parse defn(
|
195
|
+
engine.parse defn('A:',
|
196
196
|
" a = #{x}",
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
197
|
+
' b = a.flatten()',
|
198
|
+
' c = a.flatten(1)',
|
199
|
+
' d = b+c',
|
200
|
+
' dd = d.flatten()',
|
201
|
+
' e = dd.sort()',
|
202
|
+
' f = e.uniq()',
|
203
|
+
' g = e.length',
|
204
|
+
' gg = a.length()',
|
205
|
+
' l = a.member(5)',
|
206
|
+
' m = [a.member(5), a.member(55)]',
|
207
207
|
" n = {'a':1, 'b':2, 'c':3}.length()",
|
208
208
|
" o = 'hello'.length",
|
209
|
-
|
210
|
-
|
211
|
-
engine.evaluate(
|
212
|
-
|
213
|
-
dd = engine.evaluate(
|
214
|
-
engine.evaluate(
|
215
|
-
engine.evaluate(
|
216
|
-
engine.evaluate(
|
217
|
-
engine.evaluate(
|
218
|
-
engine.evaluate(
|
219
|
-
engine.evaluate(
|
220
|
-
engine.evaluate(
|
209
|
+
)
|
210
|
+
|
211
|
+
engine.evaluate('A', 'c').should == x.flatten(1)
|
212
|
+
engine.evaluate('A', 'd').should == x.flatten + x.flatten(1)
|
213
|
+
dd = engine.evaluate('A', 'dd')
|
214
|
+
engine.evaluate('A', 'e').should == dd.sort
|
215
|
+
engine.evaluate('A', 'f').should == dd.sort.uniq
|
216
|
+
engine.evaluate('A', 'g').should == dd.length
|
217
|
+
engine.evaluate('A', 'gg').should == x.length
|
218
|
+
engine.evaluate('A', 'm').should == [x.member?(5), x.member?(55)]
|
219
|
+
engine.evaluate('A', 'n').should == 3
|
220
|
+
engine.evaluate('A', 'o').should == 5
|
221
221
|
end
|
222
222
|
|
223
|
-
it
|
223
|
+
it 'should be able to call function on hash' do
|
224
224
|
# FIXME: this is actually a Delorean design issue. How do
|
225
225
|
# whitelisted functions interact with attrs? In this case, we
|
226
226
|
# return nil since there is no Delorean 'length' attr in the hash.
|
227
227
|
skip 'Delorean design issue to be resolved'
|
228
228
|
|
229
|
-
engine.parse defn(
|
230
|
-
|
229
|
+
engine.parse defn('A:',
|
230
|
+
' n = {}.length',
|
231
231
|
" m = {'length':100}.length",
|
232
|
-
|
233
|
-
engine.evaluate(
|
234
|
-
engine.evaluate(
|
232
|
+
)
|
233
|
+
engine.evaluate('A', 'n').should == 0
|
234
|
+
engine.evaluate('A', 'm').should == 100
|
235
235
|
end
|
236
236
|
|
237
|
-
it
|
238
|
-
engine.parse defn(
|
237
|
+
it 'should be able to call hash except' do
|
238
|
+
engine.parse defn('A:',
|
239
239
|
" h = {'a': 1, 'b':2, 'c': 3}",
|
240
240
|
" e = h.except('a', 'c')",
|
241
|
-
|
242
|
-
expect(engine.evaluate(
|
241
|
+
)
|
242
|
+
expect(engine.evaluate('A', 'e')).to eq('b' => 2)
|
243
243
|
end
|
244
244
|
|
245
|
-
it
|
245
|
+
it 'should handle RUBY slice function' do
|
246
246
|
x = [[1, 2, [-3]], 4, [5, 6], -3, 4, 5, 0]
|
247
247
|
|
248
|
-
engine.parse defn(
|
248
|
+
engine.parse defn('A:',
|
249
249
|
" a = #{x}",
|
250
|
-
|
251
|
-
|
252
|
-
engine.evaluate(
|
250
|
+
' b = a.slice(0, 4)',
|
251
|
+
)
|
252
|
+
engine.evaluate('A', 'b').should == x.slice(0, 4)
|
253
253
|
end
|
254
254
|
|
255
|
-
it
|
256
|
-
engine.parse defn(
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
255
|
+
it 'should handle RUBY empty? function' do
|
256
|
+
engine.parse defn('A:',
|
257
|
+
' a0 = []',
|
258
|
+
' b0 = {}',
|
259
|
+
' c0 = {-}',
|
260
|
+
' a1 = [1,2,3]',
|
261
261
|
" b1 = {'a': 1, 'b':2}",
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
engine.evaluate(
|
262
|
+
' c1 = {1,2,3}',
|
263
|
+
' res = [a0.empty, b0.empty(), c0.empty, a1.empty, b1.empty(), c1.empty]',
|
264
|
+
)
|
265
|
+
engine.evaluate('A', 'res').should == [true, true, true, false, false, false]
|
266
266
|
end
|
267
267
|
|
268
|
-
it
|
269
|
-
engine.parse defn(
|
270
|
-
|
271
|
-
|
272
|
-
|
268
|
+
it 'should handle BETWEEN' do
|
269
|
+
engine.parse defn('A:',
|
270
|
+
' a = 1.23',
|
271
|
+
' number_between = [a.between(10,20), a.between(1,3)]',
|
273
272
|
" c = 'c'",
|
274
273
|
" string_between = [c.between('a', 'd'), c.between('d', 'e')]",
|
275
|
-
|
276
274
|
" types_mismatch1 = [a.between('a', 'd')]",
|
277
|
-
|
278
|
-
|
275
|
+
' types_mismatch2 = [c.between(1, 3)]'
|
276
|
+
)
|
279
277
|
|
280
|
-
expect(engine.evaluate(
|
281
|
-
expect(engine.evaluate(
|
278
|
+
expect(engine.evaluate('A', 'number_between')).to eq([false, true])
|
279
|
+
expect(engine.evaluate('A', 'string_between')).to eq([true, false])
|
282
280
|
|
283
|
-
expect { engine.evaluate(
|
284
|
-
expect { engine.evaluate(
|
281
|
+
expect { engine.evaluate('A', 'types_mismatch1') }.to raise_error(/bad arg/)
|
282
|
+
expect { engine.evaluate('A', 'types_mismatch2') }.to raise_error(/bad arg/)
|
285
283
|
end
|
286
284
|
|
287
|
-
|
288
|
-
|
289
|
-
engine.parse defn("A:",
|
285
|
+
it 'should handle MATCH' do
|
286
|
+
engine.parse defn('A:',
|
290
287
|
" a = 'this is a test'.match('(.*)( is )(.*)')",
|
291
|
-
|
292
|
-
|
288
|
+
' b = [a[0], a[1], a[2], a[3], a[4]]',
|
289
|
+
)
|
293
290
|
|
294
|
-
expect(engine.evaluate(
|
295
|
-
to eq([
|
291
|
+
expect(engine.evaluate('A', 'b'))
|
292
|
+
.to eq(['this is a test', 'this', ' is ', 'a test', nil])
|
296
293
|
end
|
297
|
-
|
298
294
|
end
|