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/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
|