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
data/spec/func_spec.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Delorean" do
|
4
|
+
|
5
|
+
let(:engine) {
|
6
|
+
Delorean::Engine.new("ZZZ")
|
7
|
+
}
|
8
|
+
|
9
|
+
it "should handle MAX as a node name" do
|
10
|
+
engine.parse defn("MAX:",
|
11
|
+
" a = MAX(1, 2, 3, 0, -10)",
|
12
|
+
)
|
13
|
+
|
14
|
+
r = engine.evaluate("MAX", "a")
|
15
|
+
r.should == 3
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should handle MAX" do
|
19
|
+
engine.parse defn("A:",
|
20
|
+
" a = MAX(1, 2, 3)",
|
21
|
+
)
|
22
|
+
|
23
|
+
r = engine.evaluate("A", "a")
|
24
|
+
r.should == 3
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should handle insufficient args" do
|
28
|
+
lambda {
|
29
|
+
engine.parse defn("A:",
|
30
|
+
" a = MAX(1)",
|
31
|
+
)
|
32
|
+
}.should raise_error(Delorean::BadCallError)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should handle MIN" do
|
36
|
+
engine.parse defn("A:",
|
37
|
+
" a = MIN(1, 2, -3, 4)",
|
38
|
+
)
|
39
|
+
|
40
|
+
r = engine.evaluate("A", "a")
|
41
|
+
r.should == -3
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should handle MAXLIST" do
|
45
|
+
engine.parse defn("A:",
|
46
|
+
" a = MAXLIST([1, 2, 3])",
|
47
|
+
)
|
48
|
+
|
49
|
+
engine.evaluate("A", "a").should == 3
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should handle MINLIST" do
|
53
|
+
engine.parse defn("A:",
|
54
|
+
" a = MINLIST([1, 10, -3])",
|
55
|
+
)
|
56
|
+
|
57
|
+
engine.evaluate("A", "a").should == -3
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should handle ROUND" do
|
61
|
+
engine.parse defn("A:",
|
62
|
+
" a = ROUND(12.3456, 2)",
|
63
|
+
" b = ROUND(12.3456, 1)",
|
64
|
+
" c = ROUND(12.3456)",
|
65
|
+
)
|
66
|
+
|
67
|
+
r = engine.evaluate_attrs("A", ["a", "b", "c"])
|
68
|
+
r.should == [12.35, 12.3, 12]
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should handle TIMEPART" do
|
72
|
+
engine.parse defn("A:",
|
73
|
+
" p =?",
|
74
|
+
" p2 =?",
|
75
|
+
" h = TIMEPART(p, 'h')",
|
76
|
+
" m = TIMEPART(p, 'm')",
|
77
|
+
" s = TIMEPART(p, 's')",
|
78
|
+
" d = TIMEPART(p, 'd')",
|
79
|
+
" d2 = TIMEPART(p2, 'd')",
|
80
|
+
" h2 = TIMEPART(p2, 'h')",
|
81
|
+
)
|
82
|
+
|
83
|
+
p = Time.now
|
84
|
+
params = {"p" => p, "p2" => Float::INFINITY}
|
85
|
+
r = engine.evaluate_attrs("A", %w{h m s d d2}, params)
|
86
|
+
r.should == [p.hour, p.min, p.sec, p.to_date, Float::INFINITY]
|
87
|
+
|
88
|
+
expect { engine.evaluate_attrs("A", ["h2"], params) }.to raise_error
|
89
|
+
|
90
|
+
# Non time argument should raise an error
|
91
|
+
expect { engine.evaluate_attrs("A", ["m"], {"p" => 123}) }.to raise_error
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should handle DATEPART" do
|
96
|
+
engine.parse defn("A:",
|
97
|
+
" p =?",
|
98
|
+
" y = DATEPART(p, 'y')",
|
99
|
+
" d = DATEPART(p, 'd')",
|
100
|
+
" m = DATEPART(p, 'm')",
|
101
|
+
)
|
102
|
+
|
103
|
+
p = Date.today
|
104
|
+
r = engine.evaluate_attrs("A", ["y", "d", "m"], {"p" => p})
|
105
|
+
r.should == [p.year, p.day, p.month]
|
106
|
+
|
107
|
+
# Non date argument should raise an error
|
108
|
+
expect { engine.evaluate_attrs("A", ["y", "d", "m"], {"p" => 123}) }.to raise_error
|
109
|
+
# Invalid part argument should raise an error
|
110
|
+
engine.reset
|
111
|
+
engine.parse defn("A:",
|
112
|
+
" p =?",
|
113
|
+
" x = DATEPART(p, 'x')",
|
114
|
+
)
|
115
|
+
expect { engine.evaluate_attrs("A", ["x"], {"p" => p}) }.to raise_error
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should handle DATEADD" do
|
119
|
+
engine.parse defn("A:",
|
120
|
+
" p =?",
|
121
|
+
" y = DATEADD(p, 1, 'y')",
|
122
|
+
" d = DATEADD(p, 30, 'd')",
|
123
|
+
" m = DATEADD(p, 2, 'm')",
|
124
|
+
)
|
125
|
+
|
126
|
+
p = Date.today
|
127
|
+
r = engine.evaluate_attrs("A", ["y", "d", "m"], {"p" => p})
|
128
|
+
r.should == [p + 1.years, p + 30.days, p + 2.months]
|
129
|
+
|
130
|
+
# Non date argument should raise an error
|
131
|
+
expect { engine.evaluate_attrs("A", ["y", "d", "m"], {"p" => 123}) }.to raise_error
|
132
|
+
|
133
|
+
# Invalid interval argument should raise an error
|
134
|
+
engine.reset
|
135
|
+
engine.parse defn("A:",
|
136
|
+
" p =?",
|
137
|
+
" m = DATEADD(p, 1.3, 'm')",
|
138
|
+
)
|
139
|
+
expect { engine.evaluate_attrs("A", ["m"], {"p" => p}) }.to raise_error
|
140
|
+
|
141
|
+
# Invalid part argument should raise an error
|
142
|
+
engine.reset
|
143
|
+
engine.parse defn("A:",
|
144
|
+
" p =?",
|
145
|
+
" x = DATEADD(p, 1, 'x')",
|
146
|
+
)
|
147
|
+
expect { engine.evaluate_attrs("A", ["x"], {"p" => p}) }.to raise_error
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should handle INDEX" do
|
151
|
+
engine.parse defn("A:",
|
152
|
+
" a = [INDEX([0, 11, 22, 33], i) for i in [1,2]]",
|
153
|
+
)
|
154
|
+
|
155
|
+
engine.evaluate("A", "a").should == [11,22]
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should handle FLATTEN" do
|
159
|
+
x = [[1,2,[3]], 4, 5, [6]]
|
160
|
+
|
161
|
+
engine.parse defn("A:",
|
162
|
+
" a = #{x}",
|
163
|
+
" b = FLATTEN(a) + FLATTEN(a, 1)"
|
164
|
+
)
|
165
|
+
|
166
|
+
engine.evaluate("A", "b").should == x.flatten + x.flatten(1)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should handle ERR" do
|
170
|
+
engine.parse defn("A:",
|
171
|
+
" a = ERR('hello')",
|
172
|
+
" b = ERR('xx', 1, 2, 3)",
|
173
|
+
)
|
174
|
+
|
175
|
+
expect { engine.evaluate("A", "a") }.to raise_error('hello')
|
176
|
+
|
177
|
+
lambda {
|
178
|
+
r = engine.evaluate("A", "b")
|
179
|
+
}.should raise_error("xx, 1, 2, 3")
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should handle SYM" do
|
184
|
+
engine.parse defn("S:",
|
185
|
+
' a = TOSYM("hello")',
|
186
|
+
)
|
187
|
+
|
188
|
+
r = engine.evaluate("S", "a")
|
189
|
+
r.should == :hello
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
data/spec/parse_spec.rb
ADDED
@@ -0,0 +1,688 @@
|
|
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
|
+
let(:sset) {
|
10
|
+
TestContainer.new({
|
11
|
+
["AAA", "0001"] =>
|
12
|
+
defn("X:",
|
13
|
+
" a = 123",
|
14
|
+
" b = a",
|
15
|
+
)
|
16
|
+
})
|
17
|
+
}
|
18
|
+
|
19
|
+
it "can parse very simple calls" do
|
20
|
+
engine.parse defn("X:",
|
21
|
+
" a = 123",
|
22
|
+
" b = a",
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "can parse simple expressions - 1" do
|
27
|
+
engine.parse defn("A:",
|
28
|
+
" a = 123",
|
29
|
+
" x = -(a*2)",
|
30
|
+
" b = -(a + 1)",
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can parse simple expressions - 2" do
|
35
|
+
engine.parse defn("A:",
|
36
|
+
" a = 1 + 2 * -3 - -4",
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can parse params" do
|
41
|
+
engine.parse defn("A:",
|
42
|
+
" a =?",
|
43
|
+
" b =? a*2",
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should accept default param definitions" do
|
48
|
+
lambda {
|
49
|
+
engine.parse defn("A:",
|
50
|
+
" b =? 1",
|
51
|
+
" c =? -1.1",
|
52
|
+
" d = b + c",
|
53
|
+
)
|
54
|
+
}.should_not raise_error
|
55
|
+
end
|
56
|
+
|
57
|
+
it "gives errors with attrs not in node" do
|
58
|
+
lambda {
|
59
|
+
engine.parse defn("a = 123",
|
60
|
+
"b = a * 2",
|
61
|
+
)
|
62
|
+
}.should raise_error(Delorean::ParseError)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should disallow .<digits> literals" do
|
66
|
+
lambda {
|
67
|
+
engine.parse defn("A:",
|
68
|
+
" a = .123",
|
69
|
+
)
|
70
|
+
}.should raise_error(Delorean::ParseError)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should disallow bad attr names" do
|
74
|
+
lambda {
|
75
|
+
engine.parse defn("A:",
|
76
|
+
" B = 1",
|
77
|
+
)
|
78
|
+
}.should raise_error(Delorean::ParseError)
|
79
|
+
|
80
|
+
engine.reset
|
81
|
+
|
82
|
+
lambda {
|
83
|
+
engine.parse defn("A:",
|
84
|
+
" _b = 1",
|
85
|
+
)
|
86
|
+
}.should raise_error(Delorean::ParseError)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should disallow bad node names" do
|
90
|
+
lambda {
|
91
|
+
engine.parse defn("a:",
|
92
|
+
)
|
93
|
+
}.should raise_error(Delorean::ParseError)
|
94
|
+
|
95
|
+
engine.reset
|
96
|
+
|
97
|
+
lambda {
|
98
|
+
engine.parse defn("_A:",
|
99
|
+
)
|
100
|
+
}.should raise_error(Delorean::ParseError)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should disallow recursion" do
|
104
|
+
lambda {
|
105
|
+
engine.parse defn("A:",
|
106
|
+
" a = 1",
|
107
|
+
"B: A",
|
108
|
+
" a = a + 1",
|
109
|
+
)
|
110
|
+
}.should raise_error(Delorean::RecursionError)
|
111
|
+
|
112
|
+
engine.reset
|
113
|
+
|
114
|
+
lambda {
|
115
|
+
engine.parse defn("A:",
|
116
|
+
" a = 1",
|
117
|
+
"B: A",
|
118
|
+
" b = a",
|
119
|
+
" a = b",
|
120
|
+
)
|
121
|
+
}.should raise_error(Delorean::RecursionError)
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should allow non-recursive code 1" do
|
126
|
+
# this is not a recursion error
|
127
|
+
engine.parse defn("A:",
|
128
|
+
" a = 1",
|
129
|
+
" b = 2",
|
130
|
+
"B: A",
|
131
|
+
" a = A.b",
|
132
|
+
" b = a",
|
133
|
+
)
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should allow non-recursive code 2" do
|
138
|
+
engine.parse defn("A:",
|
139
|
+
" a = 1",
|
140
|
+
" b = 2",
|
141
|
+
"B: A",
|
142
|
+
" a = A.b",
|
143
|
+
" b = A.b + B.a",
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should allow non-recursive code 3" do
|
148
|
+
engine.parse defn("A:",
|
149
|
+
" b = 2",
|
150
|
+
" a = A.b + A.b",
|
151
|
+
" c = a + b + a + b",
|
152
|
+
)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should check for recursion with default params 1" do
|
156
|
+
lambda {
|
157
|
+
engine.parse defn("A:",
|
158
|
+
" a =? a",
|
159
|
+
)
|
160
|
+
}.should raise_error(Delorean::UndefinedError)
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should check for recursion with default params 2" do
|
164
|
+
lambda {
|
165
|
+
engine.parse defn("A:",
|
166
|
+
" a = 1",
|
167
|
+
"B: A",
|
168
|
+
" b =? a",
|
169
|
+
" a =? b",
|
170
|
+
)
|
171
|
+
}.should raise_error(Delorean::RecursionError)
|
172
|
+
end
|
173
|
+
|
174
|
+
it "gives errors for attrs defined more than once in a node" do
|
175
|
+
lambda {
|
176
|
+
engine.parse defn("B:",
|
177
|
+
" b = 1 + 1",
|
178
|
+
" b = 123",
|
179
|
+
)
|
180
|
+
}.should raise_error(Delorean::RedefinedError)
|
181
|
+
|
182
|
+
engine.reset
|
183
|
+
|
184
|
+
lambda {
|
185
|
+
engine.parse defn("B:",
|
186
|
+
" b =?",
|
187
|
+
" b = 123",
|
188
|
+
)
|
189
|
+
}.should raise_error(Delorean::RedefinedError)
|
190
|
+
|
191
|
+
engine.reset
|
192
|
+
|
193
|
+
lambda {
|
194
|
+
engine.parse defn("B:",
|
195
|
+
" b =? 22",
|
196
|
+
" b = 123",
|
197
|
+
)
|
198
|
+
}.should raise_error(Delorean::RedefinedError)
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should raise error for nodes defined more than once" do
|
202
|
+
lambda {
|
203
|
+
engine.parse defn("B:",
|
204
|
+
" b =?",
|
205
|
+
"B:",
|
206
|
+
)
|
207
|
+
}.should raise_error(Delorean::RedefinedError)
|
208
|
+
|
209
|
+
engine.reset
|
210
|
+
|
211
|
+
lambda {
|
212
|
+
engine.parse defn("B:",
|
213
|
+
"A:",
|
214
|
+
"B:",
|
215
|
+
)
|
216
|
+
}.should raise_error(Delorean::RedefinedError)
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should not be valid to derive from undefined nodes" do
|
220
|
+
lambda {
|
221
|
+
engine.parse defn("A: B",
|
222
|
+
" a = 456 * 123",
|
223
|
+
)
|
224
|
+
}.should raise_error(Delorean::UndefinedError)
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should not be valid to use an undefined attr" do
|
228
|
+
lambda {
|
229
|
+
engine.parse defn("A:",
|
230
|
+
" a = 456 * 123",
|
231
|
+
"B: A",
|
232
|
+
" b = a",
|
233
|
+
" c = d",
|
234
|
+
)
|
235
|
+
}.should raise_error(Delorean::UndefinedError)
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should be able to use ruby keywords as identifier" do
|
239
|
+
lambda {
|
240
|
+
engine.parse defn("A:",
|
241
|
+
" in = 123",
|
242
|
+
)
|
243
|
+
}.should_not raise_error
|
244
|
+
|
245
|
+
engine.reset
|
246
|
+
|
247
|
+
lambda {
|
248
|
+
engine.parse defn("B:",
|
249
|
+
" in1 = 123",
|
250
|
+
)
|
251
|
+
}.should_not raise_error
|
252
|
+
|
253
|
+
engine.reset
|
254
|
+
|
255
|
+
lambda {
|
256
|
+
engine.parse defn("C:",
|
257
|
+
" ifx = 123",
|
258
|
+
" elsey = ifx + 1",
|
259
|
+
)
|
260
|
+
}.should_not raise_error
|
261
|
+
|
262
|
+
engine.reset
|
263
|
+
|
264
|
+
lambda {
|
265
|
+
engine.parse defn("D:",
|
266
|
+
" true = false",
|
267
|
+
)
|
268
|
+
}.should_not raise_error
|
269
|
+
|
270
|
+
engine.reset
|
271
|
+
|
272
|
+
lambda {
|
273
|
+
engine.parse defn("E:",
|
274
|
+
" a = 1",
|
275
|
+
" return=a",
|
276
|
+
)
|
277
|
+
}.should_not raise_error
|
278
|
+
end
|
279
|
+
|
280
|
+
it "should be able to chain method calls on model functions" do
|
281
|
+
lambda {
|
282
|
+
engine.parse defn("A:",
|
283
|
+
" b = Dummy.i_just_met_you('CRJ', 123).name"
|
284
|
+
)
|
285
|
+
}.should_not raise_error
|
286
|
+
end
|
287
|
+
|
288
|
+
it "should be able to call class methods on ActiveRecord classes" do
|
289
|
+
engine.parse defn("A:",
|
290
|
+
" b = Dummy.call_me_maybe()",
|
291
|
+
)
|
292
|
+
end
|
293
|
+
|
294
|
+
it "shouldn't be able to call ActiveRecord methods without signature" do
|
295
|
+
lambda {
|
296
|
+
engine.parse defn("A:",
|
297
|
+
" b = Dummy.this_is_crazy()",
|
298
|
+
)
|
299
|
+
}.should raise_error(Delorean::UndefinedFunctionError)
|
300
|
+
end
|
301
|
+
|
302
|
+
it "should be able to call class methods on ActiveRecord classes in modules" do
|
303
|
+
engine.parse defn("A:",
|
304
|
+
" b = M::LittleDummy.heres_my_number(867, 5309)",
|
305
|
+
)
|
306
|
+
end
|
307
|
+
|
308
|
+
it "should be able to override parameters with attribute definitions" do
|
309
|
+
engine.parse defn("A:",
|
310
|
+
" b =? 22",
|
311
|
+
"B: A",
|
312
|
+
" b = 123",
|
313
|
+
"C: B",
|
314
|
+
" b =? 11",
|
315
|
+
)
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should raise error on node attr access without all needed params" do
|
319
|
+
pending
|
320
|
+
end
|
321
|
+
|
322
|
+
it "should be able to access derived attrs" do
|
323
|
+
engine.parse defn("A:",
|
324
|
+
" b =? 22",
|
325
|
+
"B: A",
|
326
|
+
" c = b * 123",
|
327
|
+
"C: B",
|
328
|
+
" d =? c * b + 11",
|
329
|
+
)
|
330
|
+
end
|
331
|
+
|
332
|
+
it "should not be able to access attrs not defined in ancestors" do
|
333
|
+
lambda {
|
334
|
+
engine.parse defn("A:",
|
335
|
+
" b =? 22",
|
336
|
+
"B: A",
|
337
|
+
" c = b * 123",
|
338
|
+
"C: A",
|
339
|
+
" d =? c * b + 11",
|
340
|
+
)
|
341
|
+
}.should raise_error(Delorean::UndefinedError)
|
342
|
+
end
|
343
|
+
|
344
|
+
it "should be able to access specific node attrs " do
|
345
|
+
engine.parse defn("A:",
|
346
|
+
" b = 123",
|
347
|
+
" c = A.b",
|
348
|
+
)
|
349
|
+
|
350
|
+
engine.reset
|
351
|
+
|
352
|
+
engine.parse defn("A:",
|
353
|
+
" b = 123",
|
354
|
+
"B: A",
|
355
|
+
" b = 111",
|
356
|
+
" c = A.b * 123",
|
357
|
+
" d = B.b",
|
358
|
+
)
|
359
|
+
end
|
360
|
+
|
361
|
+
it "should be able to perform arbitrary getattr" do
|
362
|
+
engine.parse defn("A:",
|
363
|
+
" b = 22",
|
364
|
+
" c = b.x.y.z",
|
365
|
+
)
|
366
|
+
|
367
|
+
engine.reset
|
368
|
+
|
369
|
+
lambda {
|
370
|
+
engine.parse defn("B:",
|
371
|
+
" c = b.x.y.z",
|
372
|
+
)
|
373
|
+
}.should raise_error(Delorean::UndefinedError)
|
374
|
+
|
375
|
+
end
|
376
|
+
|
377
|
+
it "should handle lines with comments" do
|
378
|
+
engine.parse defn("A: # kaka",
|
379
|
+
" b = 22 # testing #",
|
380
|
+
" c = b",
|
381
|
+
)
|
382
|
+
end
|
383
|
+
|
384
|
+
it "should be able to report error line during parse" do
|
385
|
+
begin
|
386
|
+
engine.parse defn("A:",
|
387
|
+
" b = 123",
|
388
|
+
"B: .A",
|
389
|
+
)
|
390
|
+
rescue => exc
|
391
|
+
end
|
392
|
+
|
393
|
+
exc.module_name.should == "YYY"
|
394
|
+
exc.line.should == 3
|
395
|
+
|
396
|
+
engine.reset
|
397
|
+
|
398
|
+
begin
|
399
|
+
engine.parse defn("A:",
|
400
|
+
" b = 3 % b",
|
401
|
+
)
|
402
|
+
rescue => exc
|
403
|
+
end
|
404
|
+
|
405
|
+
exc.module_name.should == "YYY"
|
406
|
+
exc.line.should == 2
|
407
|
+
end
|
408
|
+
|
409
|
+
it "should raise error on malformed string" do
|
410
|
+
lambda {
|
411
|
+
engine.parse defn("A:",
|
412
|
+
' d = "testing"" ',
|
413
|
+
)
|
414
|
+
}.should raise_error(Delorean::ParseError)
|
415
|
+
end
|
416
|
+
|
417
|
+
it "should not allow inherited ruby methods as attrs" do
|
418
|
+
lambda {
|
419
|
+
engine.parse defn("A:",
|
420
|
+
" a = name",
|
421
|
+
)
|
422
|
+
}.should raise_error(Delorean::UndefinedError)
|
423
|
+
|
424
|
+
engine.reset
|
425
|
+
|
426
|
+
lambda {
|
427
|
+
engine.parse defn("A:",
|
428
|
+
" a = new",
|
429
|
+
)
|
430
|
+
}.should raise_error(Delorean::UndefinedError)
|
431
|
+
end
|
432
|
+
|
433
|
+
it "should be able to parse lists" do
|
434
|
+
engine.parse defn("A:",
|
435
|
+
" b = []",
|
436
|
+
" c = [1,2,3]",
|
437
|
+
" d = [b, c, b, c, 1, 2, '123', 1.1]",
|
438
|
+
" e = [1, 1+1, 1+1+1]",
|
439
|
+
)
|
440
|
+
|
441
|
+
engine.reset
|
442
|
+
|
443
|
+
lambda {
|
444
|
+
engine.parse defn("A:",
|
445
|
+
" a = [",
|
446
|
+
)
|
447
|
+
}.should raise_error(Delorean::ParseError)
|
448
|
+
|
449
|
+
engine.reset
|
450
|
+
|
451
|
+
lambda {
|
452
|
+
engine.parse defn("A:",
|
453
|
+
" a = []-",
|
454
|
+
)
|
455
|
+
}.should raise_error(Delorean::ParseError)
|
456
|
+
|
457
|
+
end
|
458
|
+
|
459
|
+
it "should handle trailing ',' with lists" do
|
460
|
+
engine.parse defn("A:",
|
461
|
+
" b = [1,2,3,]",
|
462
|
+
)
|
463
|
+
|
464
|
+
engine.reset
|
465
|
+
|
466
|
+
lambda {
|
467
|
+
engine.parse defn("A:",
|
468
|
+
" a = [,]",
|
469
|
+
)
|
470
|
+
}.should raise_error(Delorean::ParseError)
|
471
|
+
|
472
|
+
engine.reset
|
473
|
+
|
474
|
+
lambda {
|
475
|
+
engine.parse defn("A:",
|
476
|
+
" a = [1,2,,]",
|
477
|
+
)
|
478
|
+
}.should raise_error(Delorean::ParseError)
|
479
|
+
end
|
480
|
+
|
481
|
+
it "should be able to parse hashes" do
|
482
|
+
engine.parse defn("A:",
|
483
|
+
" b = {}",
|
484
|
+
" c = {'a':1, 'b': 2, 'c':-3}",
|
485
|
+
" d = [{1:11}, {2: 22}]",
|
486
|
+
)
|
487
|
+
|
488
|
+
engine.reset
|
489
|
+
|
490
|
+
lambda {
|
491
|
+
engine.parse defn("A:",
|
492
|
+
" a = {",
|
493
|
+
)
|
494
|
+
}.should raise_error(Delorean::ParseError)
|
495
|
+
|
496
|
+
engine.reset
|
497
|
+
|
498
|
+
lambda {
|
499
|
+
engine.parse defn("A:",
|
500
|
+
" a = {}+",
|
501
|
+
)
|
502
|
+
}.should raise_error(Delorean::ParseError)
|
503
|
+
end
|
504
|
+
|
505
|
+
it "should handle trailing ',' with hashes" do
|
506
|
+
engine.parse defn("A:",
|
507
|
+
" b = {-1:1,}",
|
508
|
+
)
|
509
|
+
|
510
|
+
engine.reset
|
511
|
+
|
512
|
+
lambda {
|
513
|
+
engine.parse defn("A:",
|
514
|
+
" a = {,}",
|
515
|
+
)
|
516
|
+
}.should raise_error(Delorean::ParseError)
|
517
|
+
|
518
|
+
engine.reset
|
519
|
+
|
520
|
+
lambda {
|
521
|
+
engine.parse defn("A:",
|
522
|
+
" a = {-1:1,,}",
|
523
|
+
)
|
524
|
+
}.should raise_error(Delorean::ParseError)
|
525
|
+
end
|
526
|
+
|
527
|
+
it "should be able to parse list operations " do
|
528
|
+
engine.parse defn("A:",
|
529
|
+
" b = [] + []",
|
530
|
+
)
|
531
|
+
end
|
532
|
+
|
533
|
+
it "should parse list comprehension" do
|
534
|
+
engine.parse defn("A:",
|
535
|
+
" b = [123 for i in 123]",
|
536
|
+
)
|
537
|
+
|
538
|
+
end
|
539
|
+
|
540
|
+
it "should parse list comprehension (2)" do
|
541
|
+
engine.parse defn("A:",
|
542
|
+
" b = [i+1 for i in [1,2,3]]",
|
543
|
+
)
|
544
|
+
|
545
|
+
end
|
546
|
+
|
547
|
+
it "should parse nested list comprehension" do
|
548
|
+
engine.parse defn("A:",
|
549
|
+
" b = [[a+c for c in [4,5]] for a in [1,2,3]]",
|
550
|
+
)
|
551
|
+
|
552
|
+
end
|
553
|
+
|
554
|
+
it "should accept list comprehension variable override" do
|
555
|
+
engine.parse defn("A:",
|
556
|
+
" b = [b+1 for b in [1,2,3]]",
|
557
|
+
)
|
558
|
+
end
|
559
|
+
|
560
|
+
it "should accept list comprehension variable override (2)" do
|
561
|
+
engine.parse defn("A:",
|
562
|
+
" a = 1",
|
563
|
+
" b = [a+1 for a in [1,2,3]]",
|
564
|
+
)
|
565
|
+
end
|
566
|
+
|
567
|
+
it "errors out on bad list comprehension" do
|
568
|
+
lambda {
|
569
|
+
engine.parse defn("A:",
|
570
|
+
" b = [i+1 for x in [1,2,3]]",
|
571
|
+
)
|
572
|
+
}.should raise_error(Delorean::UndefinedError)
|
573
|
+
engine.reset
|
574
|
+
|
575
|
+
lambda {
|
576
|
+
engine.parse defn("A:",
|
577
|
+
" a = [123 for b in b]",
|
578
|
+
)
|
579
|
+
}.should raise_error(Delorean::UndefinedError)
|
580
|
+
engine.reset
|
581
|
+
|
582
|
+
# disallow nested comprehension var reuse
|
583
|
+
lambda {
|
584
|
+
engine.parse defn("A:",
|
585
|
+
" b = [[a+1 for a in [4,5]] for a in [1,2,3]]",
|
586
|
+
)
|
587
|
+
}.should raise_error(Delorean::RedefinedError)
|
588
|
+
engine.reset
|
589
|
+
end
|
590
|
+
|
591
|
+
it "should parse module calls" do
|
592
|
+
engine.parse defn("A:",
|
593
|
+
" a = 123",
|
594
|
+
" b = 456 + a",
|
595
|
+
" n = 'A'",
|
596
|
+
" c = @('a', 'b', x: 123, y: 456)",
|
597
|
+
" d = @n('a', 'b', x: 123, y: 456)",
|
598
|
+
)
|
599
|
+
end
|
600
|
+
|
601
|
+
it "should parse module calls by node name" do
|
602
|
+
engine.parse defn("A:",
|
603
|
+
" a = 123",
|
604
|
+
" d = @A('a')",
|
605
|
+
)
|
606
|
+
end
|
607
|
+
|
608
|
+
it "should parse multiline attr defs" do
|
609
|
+
engine.parse defn("A:",
|
610
|
+
" a = [1,",
|
611
|
+
" 2,",
|
612
|
+
" 3]",
|
613
|
+
" b = 456",
|
614
|
+
)
|
615
|
+
end
|
616
|
+
|
617
|
+
it "should give proper errors on parse multiline attr defs" do
|
618
|
+
begin
|
619
|
+
engine.parse defn("A:",
|
620
|
+
" a = [1,",
|
621
|
+
" 2,",
|
622
|
+
" 3];",
|
623
|
+
" b = 456",
|
624
|
+
)
|
625
|
+
raise "fail"
|
626
|
+
rescue Delorean::ParseError => exc
|
627
|
+
exc.line.should == 2
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
it "should give proper errors when multiline error falls off the end" do
|
632
|
+
begin
|
633
|
+
engine.parse defn("A:",
|
634
|
+
" x = 123",
|
635
|
+
" a = 1 +",
|
636
|
+
" 2 +",
|
637
|
+
)
|
638
|
+
raise "fail"
|
639
|
+
rescue Delorean::ParseError => exc
|
640
|
+
exc.line.should == 3
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
it "should parse imports" do
|
645
|
+
engine.parse defn("import AAA 0001",
|
646
|
+
"A:",
|
647
|
+
" b = 456",
|
648
|
+
"B: AAA::X",
|
649
|
+
), sset
|
650
|
+
|
651
|
+
end
|
652
|
+
|
653
|
+
it "should disallow import loops" do
|
654
|
+
pending
|
655
|
+
sset.merge({
|
656
|
+
["BBB", "0001"] =>
|
657
|
+
defn("import AAA 0001",
|
658
|
+
"import CCC 0001",
|
659
|
+
),
|
660
|
+
["CCC", "0001"] =>
|
661
|
+
defn("import BBB 0001",
|
662
|
+
),
|
663
|
+
})
|
664
|
+
sset.get_engine("CCC", "0001")
|
665
|
+
end
|
666
|
+
|
667
|
+
it "should disallow multiple versions of same script" do
|
668
|
+
sset.merge({
|
669
|
+
["AAA", "0002"] =>
|
670
|
+
defn("A:",
|
671
|
+
),
|
672
|
+
["BBB", "0001"] =>
|
673
|
+
defn("import AAA 0001",
|
674
|
+
),
|
675
|
+
["CCC", "0001"] =>
|
676
|
+
defn("import BBB 0001",
|
677
|
+
"import AAA 0002",
|
678
|
+
),
|
679
|
+
})
|
680
|
+
begin
|
681
|
+
sset.get_engine("CCC", "0001")
|
682
|
+
rescue Delorean::ImportError => exc
|
683
|
+
exc.line.should == 2
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
end
|
688
|
+
|