delorean_lang 0.0.33
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.
- data/.gitignore +20 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +145 -0
- data/Rakefile +2 -0
- data/delorean.gemspec +21 -0
- data/lib/delorean/base.rb +54 -0
- data/lib/delorean/container.rb +40 -0
- data/lib/delorean/delorean.rb +3157 -0
- data/lib/delorean/delorean.treetop +172 -0
- data/lib/delorean/engine.rb +359 -0
- data/lib/delorean/error.rb +45 -0
- data/lib/delorean/functions.rb +134 -0
- data/lib/delorean/model.rb +25 -0
- data/lib/delorean/nodes.rb +401 -0
- data/lib/delorean/version.rb +3 -0
- data/lib/delorean_lang.rb +12 -0
- data/spec/dev_spec.rb +98 -0
- data/spec/eval_spec.rb +609 -0
- data/spec/func_spec.rb +192 -0
- data/spec/parse_spec.rb +688 -0
- data/spec/spec_helper.rb +100 -0
- metadata +138 -0
@@ -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
|