delorean_lang 0.5.1 → 0.5.2
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 +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
|