delorean_lang 0.0.33

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module Delorean
2
+ VERSION = "0.0.33"
3
+ end
@@ -0,0 +1,12 @@
1
+ require "delorean/version"
2
+
3
+ require 'treetop'
4
+ require 'delorean/delorean'
5
+ require 'delorean/nodes'
6
+ require 'delorean/engine'
7
+ require 'delorean/functions'
8
+ require 'delorean/base'
9
+ require 'delorean/error'
10
+ require 'delorean/container'
11
+ require 'delorean/model'
12
+
data/spec/dev_spec.rb ADDED
@@ -0,0 +1,98 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Delorean" do
4
+
5
+ let(:engine) {
6
+ Delorean::Engine.new("YYY")
7
+ }
8
+
9
+ it "can enumerate nodes" do
10
+ engine.parse defn("X:",
11
+ " a = 123",
12
+ " b = a",
13
+ "Y: X",
14
+ "A:",
15
+ "XX: Y",
16
+ " a = 11",
17
+ " c =?",
18
+ " d = 456",
19
+ )
20
+ engine.enumerate_nodes.should == SortedSet.new(["A", "X", "XX", "Y"])
21
+ end
22
+
23
+ it "can enumerate all attrs" do
24
+ engine.parse defn("X:",
25
+ " a = 123",
26
+ " b = a",
27
+ "Y: X",
28
+ "Z:",
29
+ "XX: Y",
30
+ " a = 11",
31
+ " c =?",
32
+ " d = 456",
33
+ )
34
+ engine.enumerate_attrs.should == {
35
+ "X"=>["a", "b"],
36
+ "Y"=>["a", "b"],
37
+ "Z"=>[],
38
+ "XX"=>["a", "c", "d", "b"],
39
+ }
40
+ end
41
+
42
+ it "can enumerate attrs by node" do
43
+ engine.parse defn("X:",
44
+ " a = 123",
45
+ " b = a",
46
+ "Y: X",
47
+ "Z:",
48
+ "XX: Y",
49
+ " a = 11",
50
+ " c =?",
51
+ " d = 456",
52
+ )
53
+ engine.enumerate_attrs_by_node("X").should == { "X"=>["a", "b"] }
54
+ engine.enumerate_attrs_by_node("Y").should == { "Y"=>["a", "b"] }
55
+ engine.enumerate_attrs_by_node("Z").should == { "Z"=>[] }
56
+ engine.enumerate_attrs_by_node("XX").should == { "XX"=>["a", "c", "d", "b"] }
57
+ engine.enumerate_attrs_by_node("UNK").should == { }
58
+ end
59
+
60
+ it "can enumerate params" do
61
+ engine.parse defn("X:",
62
+ " a =? 123",
63
+ " b = a",
64
+ "Y: X",
65
+ "Z:",
66
+ "XX: Y",
67
+ " a = 11",
68
+ " c =?",
69
+ " d = 123",
70
+ "YY: XX",
71
+ " c =? 22",
72
+ " e =? 11",
73
+ )
74
+
75
+ engine.enumerate_params.should == Set.new(["a", "c", "e"])
76
+ end
77
+
78
+ it "can enumerate params by node" do
79
+ engine.parse defn("X:",
80
+ " a =? 123",
81
+ " b = a",
82
+ "Y: X",
83
+ "Z:",
84
+ "XX: Y",
85
+ " a = 11",
86
+ " c =?",
87
+ " d = 123",
88
+ "YY: XX",
89
+ " c =? 22",
90
+ " e =? 11",
91
+ )
92
+ engine.enumerate_params_by_node("X").should == Set.new(["a"])
93
+ engine.enumerate_params_by_node("XX").should == Set.new(["a", "c"])
94
+ engine.enumerate_params_by_node("YY").should == Set.new(["a", "c", "e"])
95
+ engine.enumerate_params_by_node("Z").should == Set.new([])
96
+ engine.enumerate_params_by_node("UNK").should == Set.new([])
97
+ end
98
+ end
data/spec/eval_spec.rb ADDED
@@ -0,0 +1,609 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Delorean" do
4
+
5
+ let(:engine) {
6
+ Delorean::Engine.new "XXX"
7
+ }
8
+
9
+ let(:sset) {
10
+ TestContainer.new({
11
+ ["AAA", "0001"] =>
12
+ defn("X:",
13
+ " a =? 123",
14
+ " b = a*2",
15
+ )
16
+ })
17
+ }
18
+
19
+ it "evaluate simple expressions" do
20
+ engine.parse defn("A:",
21
+ " a = 123",
22
+ " x = -(a * 2)",
23
+ " b = -(a + 1)",
24
+ " c = -a + 1",
25
+ )
26
+
27
+ engine.evaluate_attrs("A", ["a"]).should == [123]
28
+
29
+ r = engine.evaluate_attrs("A", ["x", "b"])
30
+ r.should == [-246, -124]
31
+ end
32
+
33
+ it "proper unary expression evaluation" do
34
+ engine.parse defn("A:",
35
+ " a = 123",
36
+ " c = -a + 1",
37
+ )
38
+
39
+ r = engine.evaluate("A", "c")
40
+ r.should == -122
41
+ end
42
+
43
+ it "should be able to evaluate multiple node attrs" do
44
+ engine.parse defn("A:",
45
+ " a =? 123",
46
+ " b = a % 11",
47
+ " c = a / 4.0",
48
+ )
49
+
50
+ h = {"a" => 16}
51
+ r = engine.evaluate_attrs("A", ["c", "b"], h)
52
+ r.should == [4, 5]
53
+ end
54
+
55
+ it "should give error when accessing undefined attr" do
56
+ engine.parse defn("A:",
57
+ " a = 1",
58
+ " c = a.to_s",
59
+ )
60
+
61
+ lambda {
62
+ r = engine.evaluate("A", "c")
63
+ }.should raise_error(Delorean::InvalidGetAttribute)
64
+ end
65
+
66
+ it "should handle default param values" do
67
+ engine.parse defn("A:",
68
+ " a =? 123",
69
+ " c = a / 123.0",
70
+ )
71
+
72
+ r = engine.evaluate("A", "c")
73
+ r.should == 1
74
+ end
75
+
76
+ it "order of attr evaluation should not matter" do
77
+ engine.parse defn("A:",
78
+ " a =? 1",
79
+ "B:",
80
+ " a =? 2",
81
+ " c = A.a",
82
+ )
83
+ engine.evaluate_attrs("B", %w{c a}).should == [1, 2]
84
+ engine.evaluate_attrs("B", %w{a c}).should == [2, 1]
85
+ end
86
+
87
+ it "params should behave properly with inheritance" do
88
+ engine.parse defn("A:",
89
+ " a =? 1",
90
+ "B: A",
91
+ " a =? 2",
92
+ "C: B",
93
+ " a =? 3",
94
+ " b = B.a",
95
+ " c = A.a",
96
+ )
97
+ engine.evaluate_attrs("C", %w{a b c}).should == [3, 2, 1]
98
+ engine.evaluate_attrs("C", %w{a b c}, {"a" => 4}).should == [4, 4, 4]
99
+ engine.evaluate_attrs("C", %w{c b a}).should == [1, 2, 3]
100
+ end
101
+
102
+ it "should give error when param is undefined for eval" do
103
+ engine.parse defn("A:",
104
+ " a =?",
105
+ " c = a / 123.0",
106
+ )
107
+
108
+ lambda {
109
+ r = engine.evaluate("A", "c")
110
+ }.should raise_error(Delorean::UndefinedParamError)
111
+ end
112
+
113
+ it "should handle simple param computation" do
114
+ engine.parse defn("A:",
115
+ " a =?",
116
+ " c = a / 123.0",
117
+ )
118
+
119
+ r = engine.evaluate("A", "c", {"a" => 123})
120
+ r.should == 1
121
+ end
122
+
123
+ it "should give error on unknown node" do
124
+ engine.parse defn("A:",
125
+ " a = 1",
126
+ )
127
+
128
+ lambda {
129
+ r = engine.evaluate("B", "a")
130
+ }.should raise_error(Delorean::UndefinedNodeError)
131
+ end
132
+
133
+ it "should handle runtime errors and report module/line number" do
134
+ engine.parse defn("A:",
135
+ " a = 1/0",
136
+ " b = 10 * a",
137
+ )
138
+
139
+ begin
140
+ engine.evaluate("A", "b")
141
+ rescue => exc
142
+ res = engine.parse_runtime_exception(exc)
143
+ end
144
+
145
+ res.should == ["divided by 0", [["XXX", 2, "/"], ["XXX", 2, "a"], ["XXX", 3, "b"]]]
146
+ end
147
+
148
+ it "should handle runtime errors 2" do
149
+ engine.parse defn("A:",
150
+ " b = Dummy.call_me_maybe('a', 'b')",
151
+ )
152
+
153
+ begin
154
+ engine.evaluate("A", "b")
155
+ rescue => exc
156
+ res = engine.parse_runtime_exception(exc)
157
+ end
158
+
159
+ res[1].should == [["XXX", 2, "b"]]
160
+ end
161
+
162
+ it "should handle operator precedence properly" do
163
+ engine.parse defn("A:",
164
+ " b = 3+2*4-1",
165
+ " c = b*3+5",
166
+ " d = b*2-c*2",
167
+ " e = if (d < -10) then -123-1 else -456+1",
168
+ )
169
+
170
+ r = engine.evaluate("A", "d")
171
+ r.should == -50
172
+
173
+ r = engine.evaluate("A", "e")
174
+ r.should == -124
175
+ end
176
+
177
+ it "should handle if/else" do
178
+ text = defn("A:",
179
+ " d =? -10",
180
+ ' e = if d < -10 then "gungam"+"style" else "korea"'
181
+ )
182
+
183
+ engine.parse text
184
+ r = engine.evaluate("A", "e", {"d" => -100})
185
+ r.should == "gungam"+"style"
186
+
187
+ r = engine.evaluate("A", "e")
188
+ r.should == "korea"
189
+ end
190
+
191
+ it "should be able to access specific node attrs " do
192
+ engine.parse defn("A:",
193
+ " b = 123",
194
+ " c =?",
195
+ "B: A",
196
+ " b = 111",
197
+ " c = A.b * 123",
198
+ "C:",
199
+ " c = A.c + B.c",
200
+ )
201
+
202
+ r = engine.evaluate("B", "c")
203
+ r.should == 123*123
204
+ r = engine.evaluate("C", "c", {"c" => 5})
205
+ r.should == 123*123 + 5
206
+ end
207
+
208
+ it "should be able to call class methods on ActiveRecord classes" do
209
+ engine.parse defn("A:",
210
+ " b = Dummy.call_me_maybe(1, 2, 3, 4)",
211
+ " c = Dummy.call_me_maybe()",
212
+ " d = Dummy.call_me_maybe(5) + b + c",
213
+ )
214
+ r = engine.evaluate_attrs("A", ["b", "c", "d"])
215
+ r.should == [10, 0, 15]
216
+ end
217
+
218
+ it "should be able to get attr on ActiveRecord objects using a.b syntax" do
219
+ engine.parse defn("A:",
220
+ ' b = Dummy.i_just_met_you("this is crazy", 0.404)',
221
+ " c = b.number",
222
+ " d = b.name",
223
+ " e = b.foo",
224
+ )
225
+ r = engine.evaluate("A", "c")
226
+ r.should == 0.404
227
+
228
+ r = engine.evaluate("A", "d")
229
+ r.should == "this is crazy"
230
+
231
+ lambda {
232
+ r = engine.evaluate("A", "e")
233
+ }.should raise_error(Delorean::InvalidGetAttribute)
234
+ end
235
+
236
+ it "should be able to get attr on ActiveRecord objects using Class.method().attr syntax" do
237
+ engine.parse defn("A:",
238
+ ' b = Dummy.i_just_met_you("CRJ", 1.234).name',
239
+ )
240
+ r = engine.evaluate("A", "b")
241
+ r.should == "CRJ"
242
+ end
243
+
244
+ it "should be able to get attr on Hash objects using a.b syntax" do
245
+ engine.parse defn("A:",
246
+ ' b = Dummy.i_threw_a_hash_in_the_well()',
247
+ " c = b.a",
248
+ " d = b.b",
249
+ " e = b.this_is_crazy",
250
+ )
251
+ engine.evaluate_attrs("A", %w{c d e}).should == [456, 789, nil]
252
+ end
253
+
254
+ it "get attr on nil should return nil" do
255
+ engine.parse defn("A:",
256
+ ' b = Dummy.i_just_met_you("CRJ", 1.234).dummy',
257
+ ' c = b.gaga',
258
+ ' d = b.gaga || 55',
259
+ )
260
+ r = engine.evaluate_attrs("A", ["b", "c", "d"])
261
+ r.should == [nil, nil, 55]
262
+ end
263
+
264
+ it "should be able to get assoc attr on ActiveRecord objects" do
265
+ engine.parse defn("A:",
266
+ ' b = Dummy.miss_you_so_bad()',
267
+ ' c = b.dummy',
268
+ )
269
+ r = engine.evaluate("A", "c")
270
+ r.name.should == "hello"
271
+ end
272
+
273
+ it "should be able to call class methods on ActiveRecord classes in modules" do
274
+ engine.parse defn("A:",
275
+ " b = M::LittleDummy.heres_my_number(867, 5309)",
276
+ )
277
+ r = engine.evaluate("A", "b")
278
+ r.should == 867 + 5309
279
+ end
280
+
281
+ it "should not eval inside strings" do
282
+ engine.parse defn("A:",
283
+ ' d = "#{this is a test}"',
284
+ )
285
+
286
+ r = engine.evaluate("A", "d")
287
+ r.should == '#{this is a test}'
288
+ end
289
+
290
+ it "should ignore undeclared params sent to eval which match attr names" do
291
+ engine.parse defn("A:",
292
+ " d = 12",
293
+ )
294
+ r = engine.evaluate("A", "d", {"d" => 5, "e" => 6})
295
+ r.should == 12
296
+ end
297
+
298
+ it "should handle different param defaults on nodes" do
299
+ engine.parse defn("A:",
300
+ " p =? 1",
301
+ " c = p * 123",
302
+ "B: A",
303
+ " p =? 2",
304
+ "C: A",
305
+ " p =? 3",
306
+ )
307
+
308
+ r = engine.evaluate("C", "c", {"p" => 5})
309
+ r.should == 5*123
310
+
311
+ r = engine.evaluate("B", "c", {"p" => 10})
312
+ r.should == 10*123
313
+
314
+ r = engine.evaluate("A", "c")
315
+ r.should == 1*123
316
+
317
+ r = engine.evaluate("B", "c")
318
+ r.should == 2*123
319
+
320
+ r = engine.evaluate("C", "c")
321
+ r.should == 3*123
322
+ end
323
+
324
+ it "should allow overriding of attrs as params" do
325
+ engine.parse defn("A:",
326
+ " a = 2",
327
+ " b = a*3",
328
+ "B: A",
329
+ " a =?",
330
+ )
331
+
332
+ r = engine.evaluate("A", "b", {"a" => 10})
333
+ r.should == 2*3
334
+
335
+ r = engine.evaluate("B", "b", {"a" => 10})
336
+ r.should == 10*3
337
+
338
+ lambda {
339
+ r = engine.evaluate("B", "b")
340
+ }.should raise_error(Delorean::UndefinedParamError)
341
+
342
+ end
343
+
344
+ sample_script = <<eof
345
+ A:
346
+ a = 2
347
+ p =?
348
+ c = a * 2
349
+ pc = p + c
350
+
351
+ C: A
352
+ p =? 3
353
+
354
+ B: A
355
+ p =? 5
356
+ eof
357
+
358
+ it "should allow overriding of attrs as params" do
359
+ engine.parse sample_script
360
+
361
+ r = engine.evaluate("C", "c")
362
+ r.should == 4
363
+
364
+ r = engine.evaluate("B", "pc")
365
+ r.should == 4 + 5
366
+
367
+ r = engine.evaluate("C", "pc")
368
+ r.should == 4 + 3
369
+
370
+ lambda {
371
+ r = engine.evaluate("A", "pc")
372
+ }.should raise_error(Delorean::UndefinedParamError)
373
+ end
374
+
375
+ it "engines of same name should be independent" do
376
+ engin2 = Delorean::Engine.new(engine.module_name)
377
+
378
+ engine.parse defn("A:",
379
+ " a = 123",
380
+ " b = a*3",
381
+ "B: A",
382
+ " c = b*2",
383
+ )
384
+
385
+ engin2.parse defn("A:",
386
+ " a = 222.0",
387
+ " b = a/5",
388
+ "B: A",
389
+ " c = b*3",
390
+ "C:",
391
+ " d = 111",
392
+ )
393
+
394
+ engine.evaluate_attrs("A", ["a", "b"]).should == [123, 123*3]
395
+ engin2.evaluate_attrs("A", ["a", "b"]).should == [222.0, 222.0/5]
396
+
397
+ engine.evaluate_attrs("B", ["a", "b", "c"]).should == [123, 123*3, 123*3*2]
398
+ engin2.evaluate_attrs("B", ["a", "b", "c"]).should == [222.0, 222.0/5, 222.0/5*3]
399
+
400
+ engin2.evaluate("C", "d").should == 111
401
+ lambda {
402
+ engine.evaluate("C", "d")
403
+ }.should raise_error(Delorean::UndefinedNodeError)
404
+ end
405
+
406
+ it "should handle invalid expression evaluation" do
407
+ # Should handle errors on expression such as -[] or -"xxx" or ("x"
408
+ # + []) better. Currently, it raised NoMethodError.
409
+ pending
410
+ end
411
+
412
+ it "should eval lists" do
413
+ engine.parse defn("A:",
414
+ " b = []",
415
+ " c = [1,2,3]",
416
+ " d = [b, c, b, c, 1, 2, '123', 1.1, -1.23]",
417
+ " e = [1, 1+1, 1+1+1, 1*2*4]",
418
+ )
419
+
420
+ engine.evaluate_attrs("A", %w{b c d e}).should ==
421
+ [[],
422
+ [1, 2, 3],
423
+ [[], [1, 2, 3], [], [1, 2, 3], 1, 2, "123", 1.1, -1.23],
424
+ [1, 2, 3, 8],
425
+ ]
426
+ end
427
+
428
+ it "should eval list expressions" do
429
+ engine.parse defn("A:",
430
+ " b = []+[]",
431
+ " c = [1,2,3]+b",
432
+ " d = c*2",
433
+ )
434
+
435
+ engine.evaluate_attrs("A", %w{b c d}).should ==
436
+ [[],
437
+ [1, 2, 3],
438
+ [1, 2, 3]*2,
439
+ ]
440
+ end
441
+
442
+ it "should eval list comprehension" do
443
+ engine.parse defn("A:",
444
+ " b = [i*5 for i in [1,2,3]]",
445
+ )
446
+ engine.evaluate("A", "b").should == [5, 10, 15]
447
+ end
448
+
449
+ it "should eval nested list comprehension" do
450
+ engine.parse defn("A:",
451
+ " b = [[a+c for c in [4,5]] for a in [1,2,3]]",
452
+ )
453
+ engine.evaluate("A", "b").should == [[5, 6], [6, 7], [7, 8]]
454
+
455
+ end
456
+
457
+ it "should eval list comprehension variable override" do
458
+ engine.parse defn("A:",
459
+ " b = [b/2.0 for b in [1,2,3]]",
460
+ )
461
+ engine.evaluate("A", "b").should == [0.5, 1.0, 1.5]
462
+ end
463
+
464
+ it "should eval list comprehension variable override (2)" do
465
+ engine.parse defn("A:",
466
+ " a = 1",
467
+ " b = [a+1 for a in [1,2,3]]",
468
+ )
469
+ engine.evaluate("A", "b").should == [2, 3, 4]
470
+ end
471
+
472
+ it "should eval conditional list comprehension" do
473
+ engine.parse defn("A:",
474
+ " b = [i*5 for i in [1,2,3,4,5] if i%2 == 1]",
475
+ )
476
+ engine.evaluate("A", "b").should == [5, 15, 25]
477
+ end
478
+
479
+ it "should eval hashes" do
480
+ engine.parse defn("A:",
481
+ " b = {}",
482
+ " c = {'a':1, 'b': 2,'c':3}",
483
+ " d = {123*2: -123, 'b_b': 1+1}",
484
+ " e = {'x': 1, 'y': 1+1, 'z': 1+1+1, 'zz': 1*2*4}",
485
+ " f = {'a': nil, 'b': [1, nil, 2]}",
486
+ " g = {b:b, [b]:[1,23], []:345}",
487
+ )
488
+
489
+ engine.evaluate_attrs("A", %w{b c d e f g}).should ==
490
+ [{},
491
+ {"a"=>1, "b"=>2, "c"=>3},
492
+ {123*2=>-123, "b_b"=>2},
493
+ {"x"=>1, "y"=>2, "z"=>3, "zz"=>8},
494
+ {"a"=>nil, "b"=>[1, nil, 2]},
495
+ {{}=>{}, [{}]=>[1, 23], []=>345},
496
+ ]
497
+ end
498
+
499
+ it "should eval module calls" do
500
+ engine.parse defn("A:",
501
+ " a = 123",
502
+ " b = 456 + a",
503
+ " n = 'A'",
504
+ " c = @('a', 'b', x: 123, y: 456)",
505
+ " d = @n('a', 'b', x: 123, y: 456)",
506
+ " e = @('b')",
507
+ )
508
+
509
+ engine.evaluate_attrs("A", %w{n c d e}).should ==
510
+ ["A", {"a"=>123, "b"=>579}, {"a"=>123, "b"=>579}, 579]
511
+ end
512
+
513
+ it "should be possible to implement recursive calls" do
514
+ engine.parse defn("A:",
515
+ " n =?",
516
+ " factorial = if n <= 1 then 1 else n * @('factorial', n: n-1)",
517
+ )
518
+
519
+ engine.evaluate("A", "factorial", "n" => 10).should == 3628800
520
+ end
521
+
522
+ it "should eval module calls by node name" do
523
+ engine.parse defn("A:",
524
+ " a = 123",
525
+ " b = @A('a')",
526
+ )
527
+ engine.evaluate("A", "b").should == 123
528
+ end
529
+
530
+ it "should eval multiline expressions" do
531
+ engine.parse defn("A:",
532
+ " a = 1",
533
+ " b = [a+1",
534
+ " for a in [1,2,3]",
535
+ " ]",
536
+ )
537
+ engine.evaluate("A", "b").should == [2, 3, 4]
538
+ end
539
+
540
+ it "should eval multiline expressions" do
541
+ engine.parse defn("A:",
542
+ " a = 123",
543
+ " b = 456 + ",
544
+ " a",
545
+ " n = 'A'",
546
+ " c = @('a', ",
547
+ " 'b', ",
548
+ " x: 123, ",
549
+ " y: 456)",
550
+ " d = @n('a', ",
551
+ " 'b', ",
552
+ " x: 123, y: 456)",
553
+ " e = @(",
554
+ " 'b'",
555
+ " )",
556
+ )
557
+
558
+ engine.evaluate_attrs("A", %w{n c d e}).should ==
559
+ ["A", {"a"=>123, "b"=>579}, {"a"=>123, "b"=>579}, 579]
560
+ end
561
+
562
+ it "should eval imports" do
563
+ engine.parse defn("import AAA 0001",
564
+ "A:",
565
+ " b = 456",
566
+ "B: AAA::X",
567
+ " a = 111",
568
+ " c = @AAA::X('b', a: 456)",
569
+ ), sset
570
+ engine.evaluate_attrs("B", ["a", "b", "c"], {}).should == [111, 222, 456*2]
571
+ end
572
+
573
+ it "should eval imports (2)" do
574
+ sset.merge({
575
+ ["BBB", "0002"] =>
576
+ defn("import AAA 0001",
577
+ "B: AAA::X",
578
+ " a = 111",
579
+ " c = @AAA::X('b', a: -1)",
580
+ " d = a * 2",
581
+ ),
582
+ ["CCC", "0003"] =>
583
+ defn("import BBB 0002",
584
+ "import AAA 0001",
585
+ "B: BBB::B",
586
+ " e = d * 3",
587
+ "C: AAA::X",
588
+ " d = b * 3",
589
+ ),
590
+ })
591
+
592
+ e2 = sset.get_engine("BBB", "0002")
593
+
594
+ e2.evaluate_attrs("B", ["a", "b", "c", "d"]).should == [111, 222, -2, 222]
595
+
596
+ engine.parse defn("import BBB 0002",
597
+ "B: BBB::B",
598
+ " e = d + 3",
599
+ ), sset
600
+
601
+ engine.evaluate_attrs("B", ["a", "b", "c", "d", "e"]).should == [111, 222, -2, 222, 225]
602
+
603
+ e4 = sset.get_engine("CCC", "0003")
604
+
605
+ e4.evaluate_attrs("B", ["a", "b", "c", "d", "e"]).should == [111, 222, -2, 222, 666]
606
+ e4.evaluate_attrs("C", ["a", "b", "d"]).should == [123, 123*2, 123*3*2]
607
+ end
608
+
609
+ end