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/parse_spec.rb
CHANGED
@@ -1,838 +1,832 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
4
|
|
3
|
-
describe
|
4
|
-
let(:sset)
|
5
|
+
describe 'Delorean' do
|
6
|
+
let(:sset) do
|
5
7
|
TestContainer.new(
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
'AAA' =>
|
9
|
+
defn('X:',
|
10
|
+
' a = 123',
|
11
|
+
' b = a',
|
12
|
+
)
|
13
|
+
)
|
14
|
+
end
|
13
15
|
|
14
|
-
let(:engine)
|
15
|
-
Delorean::Engine.new
|
16
|
-
|
16
|
+
let(:engine) do
|
17
|
+
Delorean::Engine.new 'YYY', sset
|
18
|
+
end
|
17
19
|
|
18
|
-
it
|
19
|
-
engine.parse defn(
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
it 'can parse very simple calls' do
|
21
|
+
engine.parse defn('X:',
|
22
|
+
' a = 123',
|
23
|
+
' b = a',
|
24
|
+
)
|
23
25
|
end
|
24
26
|
|
25
|
-
it
|
26
|
-
engine.parse defn(
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
it 'can parse simple expressions - 1' do
|
28
|
+
engine.parse defn('A:',
|
29
|
+
' a = 123',
|
30
|
+
' x = -(a*2)',
|
31
|
+
' b = -(a + 1)',
|
32
|
+
)
|
31
33
|
end
|
32
34
|
|
33
|
-
it
|
34
|
-
engine.parse defn(
|
35
|
-
|
36
|
-
|
35
|
+
it 'can parse simple expressions - 2' do
|
36
|
+
engine.parse defn('A:',
|
37
|
+
' a = 1 + 2 * -3 - -4',
|
38
|
+
)
|
37
39
|
end
|
38
40
|
|
39
|
-
it
|
40
|
-
engine.parse defn(
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
it 'can parse params' do
|
42
|
+
engine.parse defn('A:',
|
43
|
+
' a =?',
|
44
|
+
' b =? a*2',
|
45
|
+
)
|
44
46
|
end
|
45
47
|
|
46
|
-
it
|
47
|
-
engine.parse defn(
|
48
|
-
|
49
|
-
|
48
|
+
it 'can parse indexing' do
|
49
|
+
engine.parse defn('A:',
|
50
|
+
' b = [1,2,3][1]',
|
51
|
+
)
|
50
52
|
end
|
51
53
|
|
52
|
-
it
|
53
|
-
engine.parse defn(
|
54
|
+
it 'can parse indexing with getattr' do
|
55
|
+
engine.parse defn('A:',
|
54
56
|
" a = {'x': [1,2,3]}",
|
55
|
-
|
56
|
-
|
57
|
+
' b = a.x[1]',
|
58
|
+
)
|
57
59
|
end
|
58
60
|
|
59
|
-
it
|
61
|
+
it 'should accept default param definitions' do
|
60
62
|
lambda {
|
61
|
-
engine.parse defn(
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
63
|
+
engine.parse defn('A:',
|
64
|
+
' a =? 0.0123',
|
65
|
+
' b =? 0',
|
66
|
+
' c =? -1.1',
|
67
|
+
' d = b + c',
|
68
|
+
)
|
67
69
|
}.should_not raise_error
|
68
70
|
end
|
69
71
|
|
70
|
-
it
|
72
|
+
it 'gives errors with attrs not in node' do
|
71
73
|
lambda {
|
72
|
-
engine.parse defn(
|
73
|
-
|
74
|
-
|
74
|
+
engine.parse defn('a = 123',
|
75
|
+
'b = a * 2',
|
76
|
+
)
|
75
77
|
}.should raise_error(Delorean::ParseError)
|
76
78
|
end
|
77
79
|
|
78
|
-
it
|
80
|
+
it 'should disallow .<digits> literals' do
|
79
81
|
lambda {
|
80
|
-
engine.parse defn(
|
81
|
-
|
82
|
-
|
82
|
+
engine.parse defn('A:',
|
83
|
+
' a = .123',
|
84
|
+
)
|
83
85
|
}.should raise_error(Delorean::ParseError)
|
84
86
|
end
|
85
87
|
|
86
|
-
it
|
88
|
+
it 'should disallow leading 0s in numbers' do
|
87
89
|
lambda {
|
88
|
-
engine.parse defn(
|
89
|
-
|
90
|
-
|
90
|
+
engine.parse defn('A:',
|
91
|
+
' a = 00.123',
|
92
|
+
)
|
91
93
|
}.should raise_error(Delorean::ParseError)
|
92
94
|
end
|
93
95
|
|
94
|
-
it
|
96
|
+
it 'should disallow leading 0s in numbers (2)' do
|
95
97
|
lambda {
|
96
|
-
engine.parse defn(
|
97
|
-
|
98
|
-
|
98
|
+
engine.parse defn('A:',
|
99
|
+
' a = 0123',
|
100
|
+
)
|
99
101
|
}.should raise_error(Delorean::ParseError)
|
100
102
|
end
|
101
103
|
|
102
|
-
it
|
104
|
+
it 'should disallow bad attr names' do
|
103
105
|
lambda {
|
104
|
-
engine.parse defn(
|
105
|
-
|
106
|
-
|
106
|
+
engine.parse defn('A:',
|
107
|
+
' B = 1',
|
108
|
+
)
|
107
109
|
}.should raise_error(Delorean::ParseError)
|
108
110
|
|
109
111
|
engine.reset
|
110
112
|
|
111
113
|
lambda {
|
112
|
-
engine.parse defn(
|
113
|
-
|
114
|
-
|
114
|
+
engine.parse defn('A:',
|
115
|
+
' _b = 1',
|
116
|
+
)
|
115
117
|
}.should raise_error(Delorean::ParseError)
|
116
118
|
end
|
117
119
|
|
118
|
-
it
|
120
|
+
it 'should disallow bad node names' do
|
119
121
|
lambda {
|
120
|
-
engine.parse defn(
|
121
|
-
|
122
|
+
engine.parse defn('a:',
|
123
|
+
)
|
122
124
|
}.should raise_error(Delorean::ParseError)
|
123
125
|
|
124
126
|
engine.reset
|
125
127
|
|
126
128
|
lambda {
|
127
|
-
engine.parse defn(
|
128
|
-
|
129
|
+
engine.parse defn('_A:',
|
130
|
+
)
|
129
131
|
}.should raise_error(Delorean::ParseError)
|
130
132
|
end
|
131
133
|
|
132
|
-
it
|
134
|
+
it 'should disallow recursion' do
|
133
135
|
lambda {
|
134
|
-
engine.parse defn(
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
136
|
+
engine.parse defn('A:',
|
137
|
+
' a = 1',
|
138
|
+
'B: A',
|
139
|
+
' a = a + 1',
|
140
|
+
)
|
139
141
|
}.should raise_error(Delorean::RecursionError)
|
140
142
|
|
141
143
|
engine.reset
|
142
144
|
|
143
145
|
lambda {
|
144
|
-
engine.parse defn(
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
146
|
+
engine.parse defn('A:',
|
147
|
+
' a = 1',
|
148
|
+
'B: A',
|
149
|
+
' b = a',
|
150
|
+
' a = b',
|
151
|
+
)
|
150
152
|
}.should raise_error(Delorean::RecursionError)
|
151
|
-
|
152
153
|
end
|
153
154
|
|
154
|
-
it
|
155
|
-
engine.parse defn(
|
156
|
-
|
157
|
-
|
158
|
-
|
155
|
+
it 'should allow getattr in expressions' do
|
156
|
+
engine.parse defn('A:',
|
157
|
+
' a = 1',
|
158
|
+
' b = A.a * A.a - A.a',
|
159
|
+
)
|
159
160
|
end
|
160
161
|
|
161
|
-
it
|
162
|
-
engine.parse defn(
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
162
|
+
it 'should allow in expressions' do
|
163
|
+
engine.parse defn('A:',
|
164
|
+
' int =? 1',
|
165
|
+
' a = if int>1 then int*2 else int/2',
|
166
|
+
' b = int in [1,2,3]',
|
167
|
+
)
|
167
168
|
end
|
168
169
|
|
169
|
-
it
|
170
|
+
it 'should allow non-recursive code 1' do
|
170
171
|
# this is not a recursion error
|
171
|
-
engine.parse defn(
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
)
|
172
|
+
engine.parse defn('A:',
|
173
|
+
' a = 1',
|
174
|
+
' b = 2',
|
175
|
+
'B: A',
|
176
|
+
' a = A.b',
|
177
|
+
' b = a',
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'should allow non-recursive code 2' do
|
182
|
+
engine.parse defn('A:',
|
183
|
+
' a = 1',
|
184
|
+
' b = 2',
|
185
|
+
'B: A',
|
186
|
+
' a = A.b',
|
187
|
+
' b = A.b + B.a',
|
188
|
+
)
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'should allow non-recursive code 3' do
|
192
|
+
engine.parse defn('A:',
|
193
|
+
' b = 2',
|
194
|
+
' a = A.b + A.b',
|
195
|
+
' c = a + b + a + b',
|
196
|
+
)
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should check for recursion with default params 1' do
|
200
|
+
lambda {
|
201
|
+
engine.parse defn('A:',
|
202
|
+
' a =? a',
|
203
|
+
)
|
204
204
|
}.should raise_error(Delorean::UndefinedError)
|
205
205
|
end
|
206
206
|
|
207
|
-
it
|
207
|
+
it 'should check for recursion with default params 2' do
|
208
208
|
lambda {
|
209
|
-
engine.parse defn(
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
209
|
+
engine.parse defn('A:',
|
210
|
+
' a = 1',
|
211
|
+
'B: A',
|
212
|
+
' b =? a',
|
213
|
+
' a =? b',
|
214
|
+
)
|
215
215
|
}.should raise_error(Delorean::RecursionError)
|
216
216
|
end
|
217
217
|
|
218
|
-
it
|
218
|
+
it 'gives errors for attrs defined more than once in a node' do
|
219
219
|
lambda {
|
220
|
-
engine.parse defn(
|
221
|
-
|
222
|
-
|
223
|
-
|
220
|
+
engine.parse defn('B:',
|
221
|
+
' b = 1 + 1',
|
222
|
+
' b = 123',
|
223
|
+
)
|
224
224
|
}.should raise_error(Delorean::RedefinedError)
|
225
225
|
|
226
226
|
engine.reset
|
227
227
|
|
228
228
|
lambda {
|
229
|
-
engine.parse defn(
|
230
|
-
|
231
|
-
|
232
|
-
|
229
|
+
engine.parse defn('B:',
|
230
|
+
' b =?',
|
231
|
+
' b = 123',
|
232
|
+
)
|
233
233
|
}.should raise_error(Delorean::RedefinedError)
|
234
234
|
|
235
235
|
engine.reset
|
236
236
|
|
237
237
|
lambda {
|
238
|
-
engine.parse defn(
|
239
|
-
|
240
|
-
|
241
|
-
|
238
|
+
engine.parse defn('B:',
|
239
|
+
' b =? 22',
|
240
|
+
' b = 123',
|
241
|
+
)
|
242
242
|
}.should raise_error(Delorean::RedefinedError)
|
243
243
|
end
|
244
244
|
|
245
|
-
it
|
245
|
+
it 'should raise error for nodes defined more than once' do
|
246
246
|
lambda {
|
247
|
-
engine.parse defn(
|
248
|
-
|
249
|
-
|
250
|
-
|
247
|
+
engine.parse defn('B:',
|
248
|
+
' b =?',
|
249
|
+
'B:',
|
250
|
+
)
|
251
251
|
}.should raise_error(Delorean::RedefinedError)
|
252
252
|
|
253
253
|
engine.reset
|
254
254
|
|
255
255
|
lambda {
|
256
|
-
engine.parse defn(
|
257
|
-
|
258
|
-
|
259
|
-
|
256
|
+
engine.parse defn('B:',
|
257
|
+
'A:',
|
258
|
+
'B:',
|
259
|
+
)
|
260
260
|
}.should raise_error(Delorean::RedefinedError)
|
261
261
|
end
|
262
262
|
|
263
|
-
it
|
263
|
+
it 'should not be valid to derive from undefined nodes' do
|
264
264
|
lambda {
|
265
|
-
engine.parse defn(
|
266
|
-
|
267
|
-
|
265
|
+
engine.parse defn('A: B',
|
266
|
+
' a = 456 * 123',
|
267
|
+
)
|
268
268
|
}.should raise_error(Delorean::UndefinedError)
|
269
269
|
end
|
270
270
|
|
271
|
-
it
|
271
|
+
it 'should not be valid to use an undefined attr' do
|
272
272
|
lambda {
|
273
|
-
engine.parse defn(
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
273
|
+
engine.parse defn('A:',
|
274
|
+
' a = 456 * 123',
|
275
|
+
'B: A',
|
276
|
+
' b = a',
|
277
|
+
' c = d',
|
278
|
+
)
|
279
279
|
}.should raise_error(Delorean::UndefinedError)
|
280
280
|
end
|
281
281
|
|
282
|
-
it
|
282
|
+
it 'should not be possible to use a forward definition in hash' do
|
283
283
|
lambda {
|
284
|
-
engine.parse defn(
|
284
|
+
engine.parse defn('A:',
|
285
285
|
" c = {'b': 1, 'd' : d}",
|
286
|
-
|
287
|
-
|
286
|
+
' d = 789',
|
287
|
+
)
|
288
288
|
}.should raise_error(Delorean::UndefinedError)
|
289
289
|
end
|
290
290
|
|
291
|
-
it
|
291
|
+
it 'should not be possible to use a forward definition in node call' do
|
292
292
|
lambda {
|
293
|
-
engine.parse defn(
|
294
|
-
|
295
|
-
|
296
|
-
|
293
|
+
engine.parse defn('A:',
|
294
|
+
' c = A(b=1, d=d)',
|
295
|
+
' d = 789',
|
296
|
+
)
|
297
297
|
}.should raise_error(Delorean::UndefinedError)
|
298
298
|
end
|
299
299
|
|
300
|
-
it
|
300
|
+
it 'should not be possible to use a forward definition in array' do
|
301
301
|
lambda {
|
302
|
-
engine.parse defn(
|
303
|
-
|
304
|
-
|
305
|
-
|
302
|
+
engine.parse defn('A:',
|
303
|
+
' c = [123, 456, d]',
|
304
|
+
' d = 789',
|
305
|
+
)
|
306
306
|
}.should raise_error(Delorean::UndefinedError)
|
307
307
|
end
|
308
308
|
|
309
|
-
it
|
309
|
+
it 'should be able to use ruby keywords as identifier' do
|
310
310
|
lambda {
|
311
|
-
engine.parse defn(
|
312
|
-
|
313
|
-
|
311
|
+
engine.parse defn('A:',
|
312
|
+
' in = 123',
|
313
|
+
)
|
314
314
|
}.should_not raise_error
|
315
315
|
|
316
316
|
engine.reset
|
317
317
|
|
318
318
|
lambda {
|
319
|
-
engine.parse defn(
|
320
|
-
|
321
|
-
|
319
|
+
engine.parse defn('B:',
|
320
|
+
' in1 = 123',
|
321
|
+
)
|
322
322
|
}.should_not raise_error
|
323
323
|
|
324
324
|
engine.reset
|
325
325
|
|
326
326
|
lambda {
|
327
|
-
engine.parse defn(
|
328
|
-
|
329
|
-
|
330
|
-
|
327
|
+
engine.parse defn('C:',
|
328
|
+
' ifx = 123',
|
329
|
+
' elsey = ifx + 1',
|
330
|
+
)
|
331
331
|
}.should_not raise_error
|
332
332
|
|
333
333
|
engine.reset
|
334
334
|
|
335
335
|
lambda {
|
336
|
-
engine.parse defn(
|
337
|
-
|
338
|
-
|
336
|
+
engine.parse defn('D:',
|
337
|
+
' true = false',
|
338
|
+
)
|
339
339
|
}.should_not raise_error
|
340
340
|
|
341
341
|
engine.reset
|
342
342
|
|
343
343
|
lambda {
|
344
|
-
engine.parse defn(
|
345
|
-
|
346
|
-
|
347
|
-
|
344
|
+
engine.parse defn('E:',
|
345
|
+
' a = 1',
|
346
|
+
' return=a',
|
347
|
+
)
|
348
348
|
}.should_not raise_error
|
349
349
|
|
350
350
|
engine.reset
|
351
351
|
|
352
|
-
skip
|
352
|
+
skip 'need to fix'
|
353
353
|
|
354
354
|
lambda {
|
355
|
-
engine.parse defn(
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
355
|
+
engine.parse defn('D:',
|
356
|
+
' true_1 = false',
|
357
|
+
' false_1 = true_1',
|
358
|
+
' nil_1 = false_1',
|
359
|
+
)
|
360
360
|
}.should_not raise_error
|
361
361
|
|
362
362
|
engine.reset
|
363
363
|
end
|
364
364
|
|
365
|
-
it
|
365
|
+
it 'should parse calls followed by getattr' do
|
366
366
|
lambda {
|
367
|
-
engine.parse defn(
|
368
|
-
|
369
|
-
|
370
|
-
|
367
|
+
engine.parse defn('A:',
|
368
|
+
' a = -1',
|
369
|
+
' b = A().a',
|
370
|
+
)
|
371
371
|
}.should_not raise_error
|
372
372
|
end
|
373
373
|
|
374
|
-
it
|
374
|
+
it 'should be able to chain method calls on model functions' do
|
375
375
|
lambda {
|
376
|
-
engine.parse defn(
|
376
|
+
engine.parse defn('A:',
|
377
377
|
" b = Dummy.i_just_met_you('CRJ', 123).name"
|
378
|
-
|
378
|
+
)
|
379
379
|
}.should_not raise_error
|
380
380
|
end
|
381
381
|
|
382
|
-
it
|
382
|
+
it 'should be able to pass model class to model functions' do
|
383
383
|
lambda {
|
384
|
-
engine.parse defn(
|
385
|
-
|
386
|
-
|
384
|
+
engine.parse defn('A:',
|
385
|
+
' b = Dummy.i_just_met_you(Dummy, 1)'
|
386
|
+
)
|
387
387
|
}.should_not raise_error
|
388
388
|
end
|
389
389
|
|
390
|
-
it
|
391
|
-
engine.parse defn(
|
392
|
-
|
393
|
-
|
390
|
+
it 'should be able to call class methods on ActiveRecord classes' do
|
391
|
+
engine.parse defn('A:',
|
392
|
+
' b = Dummy.call_me_maybe()',
|
393
|
+
)
|
394
394
|
end
|
395
395
|
|
396
|
-
it
|
396
|
+
it 'should get exception on arg count to class method call' do
|
397
397
|
lambda {
|
398
|
-
engine.parse defn(
|
398
|
+
engine.parse defn('A:',
|
399
399
|
' b = Dummy.i_just_met_you(1, 2, 3)',
|
400
|
-
|
400
|
+
)
|
401
401
|
}.should raise_error(Delorean::BadCallError)
|
402
402
|
end
|
403
403
|
|
404
404
|
it "shouldn't be able to call ActiveRecord methods without signature" do
|
405
405
|
lambda {
|
406
|
-
engine.parse defn(
|
407
|
-
|
408
|
-
|
406
|
+
engine.parse defn('A:',
|
407
|
+
' b = Dummy.this_is_crazy()',
|
408
|
+
)
|
409
409
|
}.should raise_error(Delorean::UndefinedFunctionError)
|
410
410
|
end
|
411
411
|
|
412
|
-
it
|
413
|
-
engine.parse defn(
|
414
|
-
|
415
|
-
|
412
|
+
it 'should be able to call class methods on ActiveRecord classes in modules' do
|
413
|
+
engine.parse defn('A:',
|
414
|
+
' b = M::LittleDummy.heres_my_number(867, 5309)',
|
415
|
+
)
|
416
416
|
end
|
417
417
|
|
418
|
-
it
|
419
|
-
engine.parse defn(
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
418
|
+
it 'should be able to override parameters with attribute definitions' do
|
419
|
+
engine.parse defn('A:',
|
420
|
+
' b =? 22',
|
421
|
+
'B: A',
|
422
|
+
' b = 123',
|
423
|
+
'C: B',
|
424
|
+
' b =? 11',
|
425
|
+
)
|
426
426
|
end
|
427
427
|
|
428
|
-
it
|
429
|
-
engine.parse defn(
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
428
|
+
it 'should be able to access derived attrs' do
|
429
|
+
engine.parse defn('A:',
|
430
|
+
' b =? 22',
|
431
|
+
'B: A',
|
432
|
+
' c = b * 123',
|
433
|
+
'C: B',
|
434
|
+
' d =? c * b + 11',
|
435
|
+
)
|
436
436
|
end
|
437
437
|
|
438
|
-
it
|
438
|
+
it 'should not be able to access attrs not defined in ancestors' do
|
439
439
|
lambda {
|
440
|
-
engine.parse defn(
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
440
|
+
engine.parse defn('A:',
|
441
|
+
' b =? 22',
|
442
|
+
'B: A',
|
443
|
+
' c = b * 123',
|
444
|
+
'C: A',
|
445
|
+
' d =? c * b + 11',
|
446
|
+
)
|
447
447
|
}.should raise_error(Delorean::UndefinedError)
|
448
448
|
end
|
449
449
|
|
450
|
-
it
|
451
|
-
engine.parse defn(
|
452
|
-
|
453
|
-
|
454
|
-
|
450
|
+
it 'should be able to access specific node attrs ' do
|
451
|
+
engine.parse defn('A:',
|
452
|
+
' b = 123',
|
453
|
+
' c = A.b',
|
454
|
+
)
|
455
455
|
|
456
456
|
engine.reset
|
457
457
|
|
458
|
-
engine.parse defn(
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
458
|
+
engine.parse defn('A:',
|
459
|
+
' b = 123',
|
460
|
+
'B: A',
|
461
|
+
' b = 111',
|
462
|
+
' c = A.b * 123',
|
463
|
+
' d = B.b',
|
464
|
+
)
|
465
465
|
end
|
466
466
|
|
467
|
-
it
|
468
|
-
engine.parse defn(
|
469
|
-
|
470
|
-
|
471
|
-
|
467
|
+
it 'should be able to perform arbitrary getattr' do
|
468
|
+
engine.parse defn('A:',
|
469
|
+
' b = 22',
|
470
|
+
' c = b.x.y.z',
|
471
|
+
)
|
472
472
|
|
473
473
|
engine.reset
|
474
474
|
|
475
475
|
lambda {
|
476
|
-
engine.parse defn(
|
477
|
-
|
478
|
-
|
476
|
+
engine.parse defn('B:',
|
477
|
+
' c = b.x.y.z',
|
478
|
+
)
|
479
479
|
}.should raise_error(Delorean::UndefinedError)
|
480
|
-
|
481
480
|
end
|
482
481
|
|
483
|
-
it
|
484
|
-
engine.parse defn(
|
485
|
-
|
486
|
-
|
487
|
-
|
482
|
+
it 'should handle lines with comments' do
|
483
|
+
engine.parse defn('A: # kaka',
|
484
|
+
' b = 22 # testing #',
|
485
|
+
' c = b',
|
486
|
+
)
|
488
487
|
end
|
489
488
|
|
490
|
-
it
|
489
|
+
it 'should be able to report error line during parse' do
|
491
490
|
begin
|
492
|
-
engine.parse defn(
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
rescue => exc
|
491
|
+
engine.parse defn('A:',
|
492
|
+
' b = 123',
|
493
|
+
'B: .A',
|
494
|
+
)
|
495
|
+
rescue StandardError => exc
|
497
496
|
end
|
498
497
|
|
499
|
-
exc.module_name.should ==
|
498
|
+
exc.module_name.should == 'YYY'
|
500
499
|
exc.line.should == 3
|
501
500
|
|
502
501
|
engine.reset
|
503
502
|
|
504
503
|
begin
|
505
|
-
engine.parse defn(
|
506
|
-
|
507
|
-
|
508
|
-
rescue => exc
|
504
|
+
engine.parse defn('A:',
|
505
|
+
' b = 3 % b',
|
506
|
+
)
|
507
|
+
rescue StandardError => exc
|
509
508
|
end
|
510
509
|
|
511
|
-
exc.module_name.should ==
|
510
|
+
exc.module_name.should == 'YYY'
|
512
511
|
exc.line.should == 2
|
513
512
|
end
|
514
513
|
|
515
|
-
it
|
514
|
+
it 'correctly report error line during parse' do
|
516
515
|
begin
|
517
|
-
engine.parse defn(
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
rescue => exc
|
516
|
+
engine.parse defn('A:',
|
517
|
+
' b = [yyy',
|
518
|
+
' ]',
|
519
|
+
'B:',
|
520
|
+
)
|
521
|
+
rescue StandardError => exc
|
523
522
|
end
|
524
523
|
|
525
|
-
exc.module_name.should ==
|
524
|
+
exc.module_name.should == 'YYY'
|
526
525
|
exc.line.should == 2
|
527
526
|
end
|
528
527
|
|
529
|
-
it
|
528
|
+
it 'should raise error on malformed string' do
|
530
529
|
lambda {
|
531
|
-
engine.parse defn(
|
530
|
+
engine.parse defn('A:',
|
532
531
|
' d = "testing"" ',
|
533
|
-
|
532
|
+
)
|
534
533
|
}.should raise_error(Delorean::ParseError)
|
535
534
|
end
|
536
535
|
|
537
|
-
it
|
536
|
+
it 'should not allow inherited ruby methods as attrs' do
|
538
537
|
lambda {
|
539
|
-
engine.parse defn(
|
540
|
-
|
541
|
-
|
538
|
+
engine.parse defn('A:',
|
539
|
+
' a = name',
|
540
|
+
)
|
542
541
|
}.should raise_error(Delorean::UndefinedError)
|
543
542
|
|
544
543
|
engine.reset
|
545
544
|
|
546
545
|
lambda {
|
547
|
-
engine.parse defn(
|
548
|
-
|
549
|
-
|
546
|
+
engine.parse defn('A:',
|
547
|
+
' a = new',
|
548
|
+
)
|
550
549
|
}.should raise_error(Delorean::UndefinedError)
|
551
550
|
end
|
552
551
|
|
553
|
-
it
|
554
|
-
engine.parse defn(
|
555
|
-
|
556
|
-
|
552
|
+
it 'should be able to parse lists' do
|
553
|
+
engine.parse defn('A:',
|
554
|
+
' b = []',
|
555
|
+
' c = [1,2,3]',
|
557
556
|
" d = [b, c, b, c, 1, 2, '123', 1.1]",
|
558
|
-
|
559
|
-
|
557
|
+
' e = [1, 1+1, 1+1+1]',
|
558
|
+
)
|
560
559
|
|
561
560
|
engine.reset
|
562
561
|
|
563
562
|
lambda {
|
564
|
-
engine.parse defn(
|
565
|
-
|
566
|
-
|
563
|
+
engine.parse defn('A:',
|
564
|
+
' a = [',
|
565
|
+
)
|
567
566
|
}.should raise_error(Delorean::ParseError)
|
568
567
|
|
569
568
|
engine.reset
|
570
569
|
|
571
570
|
lambda {
|
572
|
-
engine.parse defn(
|
573
|
-
|
574
|
-
|
571
|
+
engine.parse defn('A:',
|
572
|
+
' a = []-',
|
573
|
+
)
|
575
574
|
}.should raise_error(Delorean::ParseError)
|
576
|
-
|
577
575
|
end
|
578
576
|
|
579
577
|
it "should handle trailing ',' with lists" do
|
580
|
-
engine.parse defn(
|
581
|
-
|
582
|
-
|
578
|
+
engine.parse defn('A:',
|
579
|
+
' b = [1,2,3,]',
|
580
|
+
)
|
583
581
|
|
584
582
|
engine.reset
|
585
583
|
|
586
584
|
lambda {
|
587
|
-
engine.parse defn(
|
588
|
-
|
589
|
-
|
585
|
+
engine.parse defn('A:',
|
586
|
+
' a = [,]',
|
587
|
+
)
|
590
588
|
}.should raise_error(Delorean::ParseError)
|
591
589
|
|
592
590
|
engine.reset
|
593
591
|
|
594
592
|
lambda {
|
595
|
-
engine.parse defn(
|
596
|
-
|
597
|
-
|
593
|
+
engine.parse defn('A:',
|
594
|
+
' a = [1,2,,]',
|
595
|
+
)
|
598
596
|
}.should raise_error(Delorean::ParseError)
|
599
597
|
end
|
600
598
|
|
601
|
-
it
|
602
|
-
engine.parse defn(
|
603
|
-
|
599
|
+
it 'should be able to parse hashes' do
|
600
|
+
engine.parse defn('A:',
|
601
|
+
' b = {}',
|
604
602
|
" c = {'a':1, 'b': 2, 'c':-3}",
|
605
|
-
|
606
|
-
|
603
|
+
' d = [{1:11}, {2: 22}]',
|
604
|
+
)
|
607
605
|
|
608
606
|
engine.reset
|
609
607
|
|
610
608
|
lambda {
|
611
|
-
engine.parse defn(
|
612
|
-
|
613
|
-
|
609
|
+
engine.parse defn('A:',
|
610
|
+
' a = {',
|
611
|
+
)
|
614
612
|
}.should raise_error(Delorean::ParseError)
|
615
613
|
|
616
614
|
engine.reset
|
617
615
|
|
618
616
|
lambda {
|
619
|
-
engine.parse defn(
|
620
|
-
|
621
|
-
|
617
|
+
engine.parse defn('A:',
|
618
|
+
' a = {}+',
|
619
|
+
)
|
622
620
|
}.should raise_error(Delorean::ParseError)
|
623
621
|
end
|
624
622
|
|
625
|
-
it
|
626
|
-
engine.parse defn(
|
627
|
-
|
623
|
+
it 'should be able to parse conditional hash literals' do
|
624
|
+
engine.parse defn('A:',
|
625
|
+
' a = {}',
|
628
626
|
" c = {'a':a if a, 'b': 2, 'c':-3 if 123}",
|
629
|
-
|
627
|
+
)
|
630
628
|
end
|
631
629
|
|
632
630
|
it "should handle trailing ',' with hashes" do
|
633
|
-
engine.parse defn(
|
634
|
-
|
635
|
-
|
631
|
+
engine.parse defn('A:',
|
632
|
+
' b = {-1:1,}',
|
633
|
+
)
|
636
634
|
|
637
635
|
engine.reset
|
638
636
|
|
639
637
|
lambda {
|
640
|
-
engine.parse defn(
|
641
|
-
|
642
|
-
|
638
|
+
engine.parse defn('A:',
|
639
|
+
' a = {,}',
|
640
|
+
)
|
643
641
|
}.should raise_error(Delorean::ParseError)
|
644
642
|
|
645
643
|
engine.reset
|
646
644
|
|
647
645
|
lambda {
|
648
|
-
engine.parse defn(
|
649
|
-
|
650
|
-
|
646
|
+
engine.parse defn('A:',
|
647
|
+
' a = {-1:1,,}',
|
648
|
+
)
|
651
649
|
}.should raise_error(Delorean::ParseError)
|
652
650
|
end
|
653
651
|
|
654
|
-
it
|
655
|
-
engine.parse defn(
|
656
|
-
|
657
|
-
|
652
|
+
it 'should be able to parse list operations ' do
|
653
|
+
engine.parse defn('A:',
|
654
|
+
' b = [] + []',
|
655
|
+
)
|
658
656
|
end
|
659
657
|
|
660
|
-
it
|
661
|
-
engine.parse defn(
|
662
|
-
|
663
|
-
|
664
|
-
|
658
|
+
it 'should parse list comprehension' do
|
659
|
+
engine.parse defn('A:',
|
660
|
+
' b = [123 for i in 123]',
|
661
|
+
)
|
665
662
|
end
|
666
663
|
|
667
|
-
it
|
668
|
-
engine.parse defn(
|
669
|
-
|
670
|
-
|
671
|
-
|
664
|
+
it 'should parse list comprehension (2)' do
|
665
|
+
engine.parse defn('A:',
|
666
|
+
' b = [i+1 for i in [1,2,3]]',
|
667
|
+
)
|
672
668
|
end
|
673
669
|
|
674
|
-
it
|
675
|
-
engine.parse defn(
|
676
|
-
|
677
|
-
|
678
|
-
|
670
|
+
it 'should parse nested list comprehension' do
|
671
|
+
engine.parse defn('A:',
|
672
|
+
' b = [[a+c for c in [4,5]] for a in [1,2,3]]',
|
673
|
+
)
|
679
674
|
end
|
680
675
|
|
681
|
-
xit
|
682
|
-
engine.parse defn(
|
683
|
-
|
684
|
-
|
685
|
-
|
676
|
+
xit 'should parse cross list comprehension' do
|
677
|
+
engine.parse defn('A:',
|
678
|
+
' b = [a+c for c in [4,5] for a in [1,2,3]]',
|
679
|
+
)
|
686
680
|
end
|
687
681
|
|
688
|
-
it
|
689
|
-
engine.parse defn(
|
690
|
-
|
691
|
-
|
682
|
+
it 'should accept list comprehension variable override' do
|
683
|
+
engine.parse defn('A:',
|
684
|
+
' b = [b+1 for b in [1,2,3]]',
|
685
|
+
)
|
692
686
|
end
|
693
687
|
|
694
|
-
it
|
695
|
-
engine.parse defn(
|
696
|
-
|
697
|
-
|
698
|
-
|
688
|
+
it 'should accept list comprehension variable override (2)' do
|
689
|
+
engine.parse defn('A:',
|
690
|
+
' a = 1',
|
691
|
+
' b = [a+1 for a in [1,2,3]]',
|
692
|
+
)
|
699
693
|
end
|
700
694
|
|
701
|
-
it
|
695
|
+
it 'errors out on bad list comprehension' do
|
702
696
|
lambda {
|
703
|
-
engine.parse defn(
|
704
|
-
|
705
|
-
|
697
|
+
engine.parse defn('A:',
|
698
|
+
' b = [i+1 for x in [1,2,3]]',
|
699
|
+
)
|
706
700
|
}.should raise_error(Delorean::UndefinedError)
|
707
701
|
engine.reset
|
708
702
|
|
709
703
|
lambda {
|
710
|
-
engine.parse defn(
|
711
|
-
|
712
|
-
|
704
|
+
engine.parse defn('A:',
|
705
|
+
' a = [123 for b in b]',
|
706
|
+
)
|
713
707
|
}.should raise_error(Delorean::UndefinedError)
|
714
708
|
engine.reset
|
715
709
|
|
716
710
|
# disallow nested comprehension var reuse
|
717
711
|
lambda {
|
718
|
-
engine.parse defn(
|
719
|
-
|
720
|
-
|
712
|
+
engine.parse defn('A:',
|
713
|
+
' b = [[a+1 for a in [4,5]] for a in [1,2,3]]',
|
714
|
+
)
|
721
715
|
}.should raise_error(Delorean::RedefinedError)
|
722
716
|
engine.reset
|
723
717
|
end
|
724
718
|
|
725
|
-
it
|
726
|
-
engine.parse defn(
|
727
|
-
|
728
|
-
|
719
|
+
it 'should handle nested comprehension variables' do
|
720
|
+
engine.parse defn('A:',
|
721
|
+
' b = [ a+b for a, b in [] ]',
|
722
|
+
)
|
729
723
|
end
|
730
724
|
|
731
|
-
it
|
732
|
-
engine.parse defn(
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
725
|
+
it 'should allow nodes as values' do
|
726
|
+
engine.parse defn('A:',
|
727
|
+
' a = 123',
|
728
|
+
'B:',
|
729
|
+
' a = A',
|
730
|
+
)
|
737
731
|
end
|
738
732
|
|
739
|
-
it
|
740
|
-
engine.parse defn(
|
741
|
-
|
742
|
-
|
733
|
+
it 'should parse module calls' do
|
734
|
+
engine.parse defn('A:',
|
735
|
+
' a = 123',
|
736
|
+
' b = 456 + a',
|
743
737
|
" n = 'A'",
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
738
|
+
' c = nil(x = 123, y = 456)',
|
739
|
+
' d = n(x = 123,',
|
740
|
+
' y = 456,',
|
741
|
+
' )',
|
742
|
+
)
|
749
743
|
end
|
750
744
|
|
751
|
-
it
|
752
|
-
engine.parse defn(
|
753
|
-
|
754
|
-
|
755
|
-
|
745
|
+
it 'should parse module calls by node name' do
|
746
|
+
engine.parse defn('A:',
|
747
|
+
' a = 123',
|
748
|
+
' d = A()',
|
749
|
+
)
|
756
750
|
end
|
757
751
|
|
758
|
-
it
|
759
|
-
engine.parse defn(
|
760
|
-
|
761
|
-
|
752
|
+
it 'should allow positional args to node calls' do
|
753
|
+
engine.parse defn('A:',
|
754
|
+
' d = A(1, 2, 3, a=123, b=456)',
|
755
|
+
)
|
762
756
|
end
|
763
757
|
|
764
|
-
it
|
765
|
-
engine.parse defn(
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
758
|
+
it 'should allow node calls to attrs' do
|
759
|
+
engine.parse defn('A:',
|
760
|
+
' x=?',
|
761
|
+
' a = A(x=123)',
|
762
|
+
' d = a(x=456).x',
|
763
|
+
)
|
770
764
|
end
|
771
765
|
|
772
|
-
it
|
773
|
-
engine.parse defn(
|
774
|
-
|
775
|
-
|
766
|
+
it 'allow conditional args to node calls' do
|
767
|
+
engine.parse defn('A:',
|
768
|
+
' d = A(a=1, b=4 if true, c=4 if false)',
|
769
|
+
)
|
776
770
|
end
|
777
771
|
|
778
|
-
it
|
779
|
-
engine.parse defn(
|
780
|
-
|
781
|
-
|
782
|
-
|
772
|
+
it 'allow double splats in node calls' do
|
773
|
+
engine.parse defn('A:',
|
774
|
+
' a =?',
|
775
|
+
' d = A(**a, **(a+a), a=123, b=456)',
|
776
|
+
)
|
783
777
|
end
|
784
778
|
|
785
|
-
it
|
786
|
-
engine.parse defn(
|
787
|
-
|
779
|
+
it 'allow double splats in literal hashes' do
|
780
|
+
engine.parse defn('A:',
|
781
|
+
' a =?',
|
788
782
|
" d = {'a':1, 2:2, **a, **(a+a)}",
|
789
|
-
|
783
|
+
)
|
790
784
|
end
|
791
785
|
|
792
|
-
it
|
793
|
-
engine.parse defn(
|
794
|
-
|
795
|
-
|
786
|
+
it 'should parse instance calls' do
|
787
|
+
engine.parse defn('A:',
|
788
|
+
' a = [1,2,[4]].flatten(1)',
|
789
|
+
)
|
796
790
|
end
|
797
791
|
|
798
|
-
it
|
799
|
-
engine.parse defn(
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
792
|
+
it 'should parse multiline attr defs' do
|
793
|
+
engine.parse defn('A:',
|
794
|
+
' a = [1,',
|
795
|
+
' 2,',
|
796
|
+
' 3]',
|
797
|
+
' b = 456',
|
798
|
+
)
|
805
799
|
end
|
806
800
|
|
807
|
-
xit
|
808
|
-
engine.parse defn(
|
809
|
-
|
810
|
-
|
811
|
-
|
801
|
+
xit 'should parse multiline empty list' do
|
802
|
+
engine.parse defn('A:',
|
803
|
+
' a = [',
|
804
|
+
' ]',
|
805
|
+
)
|
812
806
|
end
|
813
807
|
|
814
|
-
it
|
808
|
+
it 'should give proper errors on parse multiline attr defs' do
|
815
809
|
begin
|
816
|
-
engine.parse defn(
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
810
|
+
engine.parse defn('A:',
|
811
|
+
' a = [1,',
|
812
|
+
' 2,',
|
813
|
+
' 3];',
|
814
|
+
' b = 456',
|
815
|
+
)
|
816
|
+
raise
|
823
817
|
rescue Delorean::ParseError => exc
|
824
818
|
exc.line.should == 2
|
825
819
|
end
|
826
820
|
end
|
827
821
|
|
828
|
-
it
|
822
|
+
it 'should give proper errors when multiline error falls off the end' do
|
829
823
|
begin
|
830
|
-
engine.parse defn(
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
824
|
+
engine.parse defn('A:',
|
825
|
+
' x = 123',
|
826
|
+
' a = 1 +',
|
827
|
+
' 2 +',
|
828
|
+
)
|
829
|
+
raise
|
836
830
|
rescue Delorean::ParseError => exc
|
837
831
|
exc.line.should == 3
|
838
832
|
end
|
@@ -840,69 +834,69 @@ describe "Delorean" do
|
|
840
834
|
|
841
835
|
it "should give proper errors when multiline doesn't end properly" do
|
842
836
|
begin
|
843
|
-
engine.parse defn(
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
837
|
+
engine.parse defn('A:',
|
838
|
+
' a = 1',
|
839
|
+
' b = [a+1',
|
840
|
+
' for a in [1,2,3]',
|
841
|
+
'B:',
|
842
|
+
)
|
843
|
+
raise
|
850
844
|
rescue Delorean::ParseError => exc
|
851
845
|
exc.line.should == 3
|
852
846
|
end
|
853
847
|
end
|
854
848
|
|
855
|
-
it
|
849
|
+
it 'should error on multiline not properly spaced' do
|
856
850
|
begin
|
857
|
-
engine.parse defn(
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
851
|
+
engine.parse defn('A:',
|
852
|
+
' a = [1,',
|
853
|
+
' 2]',
|
854
|
+
' b = 456',
|
855
|
+
)
|
856
|
+
raise
|
863
857
|
rescue Delorean::ParseError => exc
|
864
858
|
exc.line.should == 2
|
865
859
|
end
|
866
860
|
end
|
867
861
|
|
868
862
|
# this is a parsing limitation which should go away
|
869
|
-
it
|
863
|
+
it 'should not parse interpolated strings' do
|
870
864
|
begin
|
871
|
-
engine.parse defn(
|
865
|
+
engine.parse defn('A:',
|
872
866
|
' d = "#{this is a test}"',
|
873
|
-
|
874
|
-
|
867
|
+
)
|
868
|
+
raise
|
875
869
|
rescue Delorean::ParseError => exc
|
876
870
|
exc.line.should == 2
|
877
871
|
end
|
878
872
|
end
|
879
873
|
|
880
|
-
it
|
881
|
-
engine.parse defn(
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
874
|
+
it 'should parse imports' do
|
875
|
+
engine.parse defn('import AAA',
|
876
|
+
'A:',
|
877
|
+
' b = 456',
|
878
|
+
'B: AAA::X',
|
879
|
+
)
|
886
880
|
end
|
887
881
|
|
888
|
-
xit
|
882
|
+
xit 'should parse ERR()' do
|
889
883
|
# pending ... wrapping with parens -- (ERR()) works
|
890
|
-
engine.parse defn(
|
891
|
-
|
892
|
-
|
884
|
+
engine.parse defn('A:',
|
885
|
+
' b = ERR() && 123',
|
886
|
+
)
|
893
887
|
end
|
894
888
|
|
895
|
-
it
|
889
|
+
it 'should disallow import loops' do
|
896
890
|
skip 'not implemented yet'
|
897
891
|
sset.merge(
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
sset.get_engine(
|
892
|
+
'BBB' =>
|
893
|
+
defn('import AAA',
|
894
|
+
'import CCC',
|
895
|
+
),
|
896
|
+
'CCC' =>
|
897
|
+
defn('import BBB',
|
898
|
+
),
|
899
|
+
)
|
900
|
+
sset.get_engine('CCC')
|
907
901
|
end
|
908
902
|
end
|