delorean_lang 0.6.3 → 1.0.0
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/.rubocop.yml +4 -0
- data/README.md +7 -3
- data/delorean.gemspec +4 -4
- data/lib/delorean/base.rb +29 -9
- data/lib/delorean/engine.rb +1 -1
- data/lib/delorean/functions.rb +21 -15
- data/lib/delorean/version.rb +1 -1
- data/spec/dev_spec.rb +9 -9
- data/spec/eval_spec.rb +259 -192
- data/spec/func_spec.rb +34 -37
- data/spec/parse_spec.rb +107 -107
- data/spec/spec_helper.rb +5 -5
- metadata +17 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3da8ff66652a79d2c5e7b38c57f35ba6821e3140
|
4
|
+
data.tar.gz: 6c6095ab7aeca95707b88f1966210085ac5a4a5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ca7618ab74138cf05b64caaaef1e726acbcfa60d98fdb0bd85a4ff5c3033571acca3314568fcb906f4708c877c65b97bd4ade67b6a3a507943df2bc3a03cab7
|
7
|
+
data.tar.gz: ec8046cb9e85260ab4d6c44a09e853556e906d30e70ca86a48c2f522786bfe776f1176f12c829f7407f8da6c1bcf7d1e6acbb8b3c614ff1242da734f8aca32d8
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -188,7 +188,7 @@ By default Delorean has some methods whitelisted, such as `length`, `min`, `max`
|
|
188
188
|
|
189
189
|
```
|
190
190
|
|
191
|
-
Another way is to define methods using `delorean_fn` and `
|
191
|
+
Another way is to define methods using `delorean_fn` with optional `private` and `cache` flags.
|
192
192
|
Use `extend Delorean::Functions` or `include Delorean::Model` in your module or class.
|
193
193
|
|
194
194
|
```ruby
|
@@ -198,6 +198,10 @@ class Dummy < ActiveRecord::Base
|
|
198
198
|
delorean_fn(:heres_my_number, sig: [0, Float::INFINITY]) do |*a|
|
199
199
|
a.inject(0, :+)
|
200
200
|
end
|
201
|
+
|
202
|
+
delorean_fn :private_cached_number, cache: true, private: true do |*a|
|
203
|
+
a.inject(0, :+)
|
204
|
+
end
|
201
205
|
end
|
202
206
|
|
203
207
|
module DummyModule
|
@@ -219,10 +223,10 @@ ExampleScript:
|
|
219
223
|
|
220
224
|
### Caching
|
221
225
|
|
222
|
-
Delorean provides `
|
226
|
+
Delorean provides `cache` flag for `delorean_fn` method that will cache result based on arguments.
|
223
227
|
|
224
228
|
```ruby
|
225
|
-
|
229
|
+
delorean_fn :returns_cached_openstruct, cache: true do |timestamp|
|
226
230
|
User.all
|
227
231
|
end
|
228
232
|
|
data/delorean.gemspec
CHANGED
@@ -17,12 +17,12 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.version = Delorean::VERSION
|
18
18
|
gem.licenses = ['MIT']
|
19
19
|
|
20
|
-
gem.add_dependency 'activerecord'
|
21
|
-
gem.add_dependency 'treetop'
|
20
|
+
gem.add_dependency 'activerecord'
|
21
|
+
gem.add_dependency 'treetop'
|
22
22
|
gem.add_development_dependency 'pry'
|
23
|
-
gem.add_development_dependency 'rspec'
|
23
|
+
gem.add_development_dependency 'rspec'
|
24
24
|
gem.add_development_dependency 'rspec-instafail'
|
25
25
|
gem.add_development_dependency 'rubocop'
|
26
26
|
gem.add_development_dependency 'rubocop-performance'
|
27
|
-
gem.add_development_dependency 'sqlite3'
|
27
|
+
gem.add_development_dependency 'sqlite3'
|
28
28
|
end
|
data/lib/delorean/base.rb
CHANGED
@@ -66,14 +66,7 @@ module Delorean
|
|
66
66
|
# return true when we called obj.instance_of?(Hash) and do not
|
67
67
|
# work with the "case/when" matcher!!! For now, this is a
|
68
68
|
# hacky workaround. This is likely some sort of Ruby bug.
|
69
|
-
if obj.instance_of?(Hash)
|
70
|
-
# FIXME: this implementation doesn't handle something like
|
71
|
-
# {}.length. i.e. length is a whitelisted function, but not
|
72
|
-
# an attr. This implementation returns nil instead of 0.
|
73
|
-
return obj[attr] if obj.member?(attr)
|
74
|
-
|
75
|
-
return attr.is_a?(String) ? obj[attr.to_sym] : nil
|
76
|
-
end
|
69
|
+
return _get_hash_attr(obj, attr, _e) if obj.instance_of?(Hash)
|
77
70
|
|
78
71
|
# NOTE: should keep this function consistent with _index
|
79
72
|
case obj
|
@@ -99,6 +92,29 @@ module Delorean
|
|
99
92
|
end
|
100
93
|
end
|
101
94
|
|
95
|
+
def self._get_hash_attr(obj, attr, _e, index_call = false)
|
96
|
+
return obj[attr] if obj.key?(attr)
|
97
|
+
|
98
|
+
return obj[attr.to_sym] if attr.is_a?(String) && obj.key?(attr.to_sym)
|
99
|
+
|
100
|
+
# Shouldn't try to call the method if hash['length'] was called.
|
101
|
+
return nil if index_call
|
102
|
+
|
103
|
+
# Return nil when it's obviously not a method
|
104
|
+
return nil unless attr.is_a?(String) || attr.is_a?(Symbol)
|
105
|
+
|
106
|
+
# hash.length might be either hash['length'] or hash.length call.
|
107
|
+
# If key is not found, check if object responds to method and call it.
|
108
|
+
# If not succeeded, return nil, assuming that it was an attribute call.
|
109
|
+
return nil unless obj.respond_to?(attr)
|
110
|
+
|
111
|
+
begin
|
112
|
+
return _instance_call(obj, attr, [], _e)
|
113
|
+
rescue StandardError
|
114
|
+
return nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
102
118
|
######################################################################
|
103
119
|
|
104
120
|
def self._index(obj, args, _e)
|
@@ -108,7 +124,11 @@ module Delorean
|
|
108
124
|
# FIXME: even Javascript which is superpermissive raises an
|
109
125
|
# exception on null getattr.
|
110
126
|
nil
|
111
|
-
when Hash
|
127
|
+
when Hash
|
128
|
+
raise InvalidIndex unless args.length == 1
|
129
|
+
|
130
|
+
_get_hash_attr(obj, args[0], _e, true)
|
131
|
+
when NodeCall, Class, OpenStruct
|
112
132
|
raise InvalidIndex unless args.length == 1
|
113
133
|
|
114
134
|
_get_attr(obj, args[0], _e)
|
data/lib/delorean/engine.rb
CHANGED
data/lib/delorean/functions.rb
CHANGED
@@ -2,29 +2,39 @@
|
|
2
2
|
|
3
3
|
module Delorean
|
4
4
|
module Functions
|
5
|
-
def delorean_fn(name,
|
5
|
+
def delorean_fn(name, options = {}, &block)
|
6
|
+
if options[:cache] == true
|
7
|
+
new_options = options.reject { |key, _| key == :cache }
|
8
|
+
return _cached_delorean_fn(name, new_options, &block)
|
9
|
+
end
|
10
|
+
|
6
11
|
any_args = Delorean::Ruby::Whitelists::Matchers::Arguments::ANYTHING
|
7
12
|
|
8
13
|
define_singleton_method(name, block)
|
9
14
|
|
10
|
-
|
11
|
-
|
15
|
+
if options[:private] == true
|
16
|
+
singleton_class.class_eval { private name }
|
17
|
+
else
|
18
|
+
::Delorean::Ruby.whitelist.add_class_method name do |method|
|
19
|
+
method.called_on self, with: any_args
|
20
|
+
end
|
12
21
|
end
|
13
22
|
|
14
23
|
name.to_sym
|
15
24
|
end
|
16
25
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
26
|
+
def clear_lookup_cache!
|
27
|
+
::Delorean::Cache.adapter.clear!(klass: self)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
21
31
|
|
22
32
|
# By default implements a VERY HACKY class-based (per process) caching
|
23
|
-
# mechanism for database lookup results.
|
24
|
-
# values are ActiveRecord objects.
|
25
|
-
# large lists which we count as one item in the cache.
|
33
|
+
# mechanism for database lookup results. Issues include: cached
|
34
|
+
# values are ActiveRecord objects. Query results can be very
|
35
|
+
# large lists which we count as one item in the cache. Caching
|
26
36
|
# mechanism will result in large processes.
|
27
|
-
def
|
37
|
+
def _cached_delorean_fn(name, options = {})
|
28
38
|
delorean_fn(name, options) do |*args|
|
29
39
|
delorean_cache_adapter = ::Delorean::Cache.adapter
|
30
40
|
# Check if caching should be performed
|
@@ -55,9 +65,5 @@ module Delorean
|
|
55
65
|
res
|
56
66
|
end
|
57
67
|
end
|
58
|
-
|
59
|
-
def clear_lookup_cache!
|
60
|
-
::Delorean::Cache.adapter.clear!(klass: self)
|
61
|
-
end
|
62
68
|
end
|
63
69
|
end
|
data/lib/delorean/version.rb
CHANGED
data/spec/dev_spec.rb
CHANGED
@@ -18,7 +18,7 @@ describe 'Delorean' do
|
|
18
18
|
' c =?',
|
19
19
|
' d = 456',
|
20
20
|
)
|
21
|
-
engine.enumerate_nodes.
|
21
|
+
expect(engine.enumerate_nodes).to eq(SortedSet.new(['A', 'X', 'XX', 'Y']))
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'can enumerate attrs by node' do
|
@@ -41,11 +41,11 @@ describe 'Delorean' do
|
|
41
41
|
}
|
42
42
|
res = engine.enumerate_attrs
|
43
43
|
|
44
|
-
res.keys.sort.
|
44
|
+
expect(res.keys.sort).to eq(exp.keys.sort)
|
45
45
|
|
46
46
|
exp.each do |k, v|
|
47
|
-
engine.enumerate_attrs_by_node(k).sort.
|
48
|
-
res[k].sort.
|
47
|
+
expect(engine.enumerate_attrs_by_node(k).sort).to eq(v)
|
48
|
+
expect(res[k].sort).to eq(v)
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
@@ -64,7 +64,7 @@ describe 'Delorean' do
|
|
64
64
|
' e =? 11',
|
65
65
|
)
|
66
66
|
|
67
|
-
engine.enumerate_params.
|
67
|
+
expect(engine.enumerate_params).to eq(Set.new(['a', 'c', 'e']))
|
68
68
|
end
|
69
69
|
|
70
70
|
it 'can enumerate params by node' do
|
@@ -81,9 +81,9 @@ describe 'Delorean' do
|
|
81
81
|
' c =? 22',
|
82
82
|
' e =? 11',
|
83
83
|
)
|
84
|
-
engine.enumerate_params_by_node('X').
|
85
|
-
engine.enumerate_params_by_node('XX').
|
86
|
-
engine.enumerate_params_by_node('YY').
|
87
|
-
engine.enumerate_params_by_node('Z').
|
84
|
+
expect(engine.enumerate_params_by_node('X')).to eq(Set.new(['a']))
|
85
|
+
expect(engine.enumerate_params_by_node('XX')).to eq(Set.new(['a', 'c']))
|
86
|
+
expect(engine.enumerate_params_by_node('YY')).to eq(Set.new(['a', 'c', 'e']))
|
87
|
+
expect(engine.enumerate_params_by_node('Z')).to eq(Set.new([]))
|
88
88
|
end
|
89
89
|
end
|
data/spec/eval_spec.rb
CHANGED
@@ -25,10 +25,10 @@ describe 'Delorean' do
|
|
25
25
|
' d = a ** 3 - 10*0.2',
|
26
26
|
)
|
27
27
|
|
28
|
-
engine.evaluate('A', ['a']).
|
28
|
+
expect(engine.evaluate('A', ['a'])).to eq([123])
|
29
29
|
|
30
30
|
r = engine.evaluate('A', ['x', 'b'])
|
31
|
-
r.
|
31
|
+
expect(r).to eq([-246, -124])
|
32
32
|
|
33
33
|
expect(engine.evaluate('A', 'd')).to eq 1_860_865.0
|
34
34
|
end
|
@@ -40,7 +40,7 @@ describe 'Delorean' do
|
|
40
40
|
)
|
41
41
|
|
42
42
|
r = engine.evaluate('A', 'c')
|
43
|
-
r.
|
43
|
+
expect(r).to eq(-122)
|
44
44
|
end
|
45
45
|
|
46
46
|
it 'proper string interpolation' do
|
@@ -49,7 +49,7 @@ describe 'Delorean' do
|
|
49
49
|
)
|
50
50
|
|
51
51
|
r = engine.evaluate('A', 'a')
|
52
|
-
r.
|
52
|
+
expect(r).to eq("\n123\n")
|
53
53
|
end
|
54
54
|
|
55
55
|
it 'should handle getattr in expressions' do
|
@@ -57,7 +57,7 @@ describe 'Delorean' do
|
|
57
57
|
" a = {'x':123, 'y':456, 'z':789}",
|
58
58
|
' b = A.a.x * A.a.y - A.a.z',
|
59
59
|
)
|
60
|
-
engine.evaluate('A', ['b']).
|
60
|
+
expect(engine.evaluate('A', ['b'])).to eq([123 * 456 - 789])
|
61
61
|
end
|
62
62
|
|
63
63
|
it 'should handle numeric getattr' do
|
@@ -65,7 +65,7 @@ describe 'Delorean' do
|
|
65
65
|
" a = {1:123, 0:456, 'z':789, 2: {'a':444}}",
|
66
66
|
' b = A.a.1 * A.a.0 - A.a.z - A.a.2.a',
|
67
67
|
)
|
68
|
-
engine.evaluate('A', ['b']).
|
68
|
+
expect(engine.evaluate('A', ['b'])).to eq([123 * 456 - 789 - 444])
|
69
69
|
end
|
70
70
|
|
71
71
|
it 'should be able to evaluate multiple node attrs' do
|
@@ -77,7 +77,7 @@ describe 'Delorean' do
|
|
77
77
|
|
78
78
|
h = { 'a' => 16 }
|
79
79
|
r = engine.evaluate('A', ['c', 'b'], h)
|
80
|
-
r.
|
80
|
+
expect(r).to eq([4, 5])
|
81
81
|
end
|
82
82
|
|
83
83
|
it 'should give error when accessing undefined attr' do
|
@@ -86,9 +86,9 @@ describe 'Delorean' do
|
|
86
86
|
' c = a.to_ss',
|
87
87
|
)
|
88
88
|
|
89
|
-
|
90
|
-
|
91
|
-
|
89
|
+
expect { engine.evaluate('A', 'c') }.to raise_error(
|
90
|
+
Delorean::InvalidGetAttribute
|
91
|
+
)
|
92
92
|
end
|
93
93
|
|
94
94
|
it 'should be able to call 0-ary functions without ()' do
|
@@ -96,8 +96,7 @@ describe 'Delorean' do
|
|
96
96
|
' a = 1',
|
97
97
|
' d = a.to_s',
|
98
98
|
)
|
99
|
-
|
100
|
-
engine.evaluate('A', 'd').should == '1'
|
99
|
+
expect(engine.evaluate('A', 'd')).to eq('1')
|
101
100
|
end
|
102
101
|
|
103
102
|
it 'should handle default param values' do
|
@@ -107,7 +106,7 @@ describe 'Delorean' do
|
|
107
106
|
)
|
108
107
|
|
109
108
|
r = engine.evaluate('A', 'c')
|
110
|
-
r.
|
109
|
+
expect(r).to eq(1)
|
111
110
|
end
|
112
111
|
|
113
112
|
it 'order of attr evaluation should not matter' do
|
@@ -117,8 +116,8 @@ describe 'Delorean' do
|
|
117
116
|
' a =? 2',
|
118
117
|
' c = A.a',
|
119
118
|
)
|
120
|
-
engine.evaluate('B', %w[c a]).
|
121
|
-
engine.evaluate('B', %w[a c]).
|
119
|
+
expect(engine.evaluate('B', %w[c a])).to eq([1, 2])
|
120
|
+
expect(engine.evaluate('B', %w[a c])).to eq([2, 1])
|
122
121
|
end
|
123
122
|
|
124
123
|
it 'params should behave properly with inheritance' do
|
@@ -131,9 +130,9 @@ describe 'Delorean' do
|
|
131
130
|
' b = B.a',
|
132
131
|
' c = A.a',
|
133
132
|
)
|
134
|
-
engine.evaluate('C', %w[a b c]).
|
135
|
-
engine.evaluate('C', %w[a b c], 'a' => 4).
|
136
|
-
engine.evaluate('C', %w[c b a]).
|
133
|
+
expect(engine.evaluate('C', %w[a b c])).to eq([3, 2, 1])
|
134
|
+
expect(engine.evaluate('C', %w[a b c], 'a' => 4)).to eq([4, 4, 4])
|
135
|
+
expect(engine.evaluate('C', %w[c b a])).to eq([1, 2, 3])
|
137
136
|
end
|
138
137
|
|
139
138
|
it 'should give error when param is undefined for eval' do
|
@@ -142,9 +141,9 @@ describe 'Delorean' do
|
|
142
141
|
' c = a / 123.0',
|
143
142
|
)
|
144
143
|
|
145
|
-
|
146
|
-
|
147
|
-
|
144
|
+
expect { engine.evaluate('A', 'c') }.to raise_error(
|
145
|
+
Delorean::UndefinedParamError
|
146
|
+
)
|
148
147
|
end
|
149
148
|
|
150
149
|
it 'should handle simple param computation' do
|
@@ -154,7 +153,7 @@ describe 'Delorean' do
|
|
154
153
|
)
|
155
154
|
|
156
155
|
r = engine.evaluate('A', 'c', 'a' => 123)
|
157
|
-
r.
|
156
|
+
expect(r).to eq(1)
|
158
157
|
end
|
159
158
|
|
160
159
|
it 'should give error on unknown node' do
|
@@ -162,9 +161,9 @@ describe 'Delorean' do
|
|
162
161
|
' a = 1',
|
163
162
|
)
|
164
163
|
|
165
|
-
|
166
|
-
|
167
|
-
|
164
|
+
expect { engine.evaluate('B', 'a') }.to raise_error(
|
165
|
+
Delorean::UndefinedNodeError
|
166
|
+
)
|
168
167
|
end
|
169
168
|
|
170
169
|
it 'should handle runtime errors and report module/line number' do
|
@@ -179,10 +178,10 @@ describe 'Delorean' do
|
|
179
178
|
res = Delorean::Engine.grok_runtime_exception(exc)
|
180
179
|
end
|
181
180
|
|
182
|
-
res.
|
181
|
+
expect(res).to eq(
|
183
182
|
'error' => 'divided by 0',
|
184
183
|
'backtrace' => [['XXX', 2, '/'], ['XXX', 2, 'a'], ['XXX', 3, 'b']],
|
185
|
-
|
184
|
+
)
|
186
185
|
end
|
187
186
|
|
188
187
|
it 'should handle runtime errors 2' do
|
@@ -196,7 +195,7 @@ describe 'Delorean' do
|
|
196
195
|
res = Delorean::Engine.grok_runtime_exception(exc)
|
197
196
|
end
|
198
197
|
|
199
|
-
res['backtrace'].
|
198
|
+
expect(res['backtrace']).to eq([['XXX', 2, 'b']])
|
200
199
|
end
|
201
200
|
|
202
201
|
it 'should handle optional args to external fns' do
|
@@ -205,8 +204,8 @@ describe 'Delorean' do
|
|
205
204
|
" c = Dummy.one_or_two([1,2,3], ['a', 'b'])",
|
206
205
|
)
|
207
206
|
|
208
|
-
engine.evaluate('A', 'b').
|
209
|
-
engine.evaluate('A', 'c').
|
207
|
+
expect(engine.evaluate('A', 'b')).to eq([['a', 'b'], nil])
|
208
|
+
expect(engine.evaluate('A', 'c')).to eq([[1, 2, 3], ['a', 'b']])
|
210
209
|
end
|
211
210
|
|
212
211
|
it 'should handle operator precedence properly' do
|
@@ -218,10 +217,10 @@ describe 'Delorean' do
|
|
218
217
|
)
|
219
218
|
|
220
219
|
r = engine.evaluate('A', 'd')
|
221
|
-
r.
|
220
|
+
expect(r).to eq(-50)
|
222
221
|
|
223
222
|
r = engine.evaluate('A', 'e')
|
224
|
-
r.
|
223
|
+
expect(r).to eq(-124)
|
225
224
|
end
|
226
225
|
|
227
226
|
it 'should handle if/else' do
|
@@ -232,10 +231,10 @@ describe 'Delorean' do
|
|
232
231
|
|
233
232
|
engine.parse text
|
234
233
|
r = engine.evaluate('A', 'e', 'd' => -100)
|
235
|
-
r.
|
234
|
+
expect(r).to eq('gungamstyle')
|
236
235
|
|
237
236
|
r = engine.evaluate('A', 'e')
|
238
|
-
r.
|
237
|
+
expect(r).to eq('korea')
|
239
238
|
end
|
240
239
|
|
241
240
|
it 'should be able to access specific node attrs ' do
|
@@ -250,9 +249,9 @@ describe 'Delorean' do
|
|
250
249
|
)
|
251
250
|
|
252
251
|
r = engine.evaluate('B', 'c')
|
253
|
-
r.
|
252
|
+
expect(r).to eq(123 * 123)
|
254
253
|
r = engine.evaluate('C', 'c', 'c' => 5)
|
255
|
-
r.
|
254
|
+
expect(r).to eq(123 * 123 + 5)
|
256
255
|
end
|
257
256
|
|
258
257
|
it 'should be able to access nodes and node attrs dynamically ' do
|
@@ -264,7 +263,7 @@ describe 'Delorean' do
|
|
264
263
|
)
|
265
264
|
|
266
265
|
r = engine.evaluate('B', 'c')
|
267
|
-
r.
|
266
|
+
expect(r).to eq(123 * 456)
|
268
267
|
end
|
269
268
|
|
270
269
|
it 'should be able to call class methods on ActiveRecord classes' do
|
@@ -274,7 +273,7 @@ describe 'Delorean' do
|
|
274
273
|
' d = Dummy.call_me_maybe(5) + b + c',
|
275
274
|
)
|
276
275
|
r = engine.evaluate('A', ['b', 'c', 'd'])
|
277
|
-
r.
|
276
|
+
expect(r).to eq([10, 0, 15])
|
278
277
|
end
|
279
278
|
|
280
279
|
it 'should be able to access ActiveRecord whitelisted fns using .x syntax' do
|
@@ -282,7 +281,7 @@ describe 'Delorean' do
|
|
282
281
|
' b = Dummy.i_just_met_you("CRJ", 1.234).name2',
|
283
282
|
)
|
284
283
|
r = engine.evaluate('A', 'b')
|
285
|
-
r.
|
284
|
+
expect(r).to eq('CRJ-1.234')
|
286
285
|
end
|
287
286
|
|
288
287
|
it 'should be able to get attr on Hash objects using a.b syntax' do
|
@@ -292,7 +291,7 @@ describe 'Delorean' do
|
|
292
291
|
' d = b.b',
|
293
292
|
' e = b.this_is_crazy',
|
294
293
|
)
|
295
|
-
engine.evaluate('A', %w[c d e]).
|
294
|
+
expect(engine.evaluate('A', %w[c d e])).to eq([456, 789, nil])
|
296
295
|
end
|
297
296
|
|
298
297
|
it 'get attr on nil should return nil' do
|
@@ -302,7 +301,7 @@ describe 'Delorean' do
|
|
302
301
|
' d = b.gaga || 55',
|
303
302
|
)
|
304
303
|
r = engine.evaluate('A', ['b', 'c', 'd'])
|
305
|
-
r.
|
304
|
+
expect(r).to eq([nil, nil, 55])
|
306
305
|
end
|
307
306
|
|
308
307
|
it 'should be able to get attr on node' do
|
@@ -311,7 +310,7 @@ describe 'Delorean' do
|
|
311
310
|
' b = A',
|
312
311
|
' c = b.a * 2',
|
313
312
|
)
|
314
|
-
engine.evaluate('A', %w[a c]).
|
313
|
+
expect(engine.evaluate('A', %w[a c])).to eq([123, 123 * 2])
|
315
314
|
end
|
316
315
|
|
317
316
|
getattr_code = <<eoc
|
@@ -329,7 +328,7 @@ eoc
|
|
329
328
|
|
330
329
|
it 'should be able to get attr on node 2' do
|
331
330
|
engine.parse getattr_code
|
332
|
-
engine.evaluate('E', 'xx').
|
331
|
+
expect(engine.evaluate('E', 'xx')).to eq([1, 2, 3])
|
333
332
|
end
|
334
333
|
|
335
334
|
it 'should be able to call class methods on AR classes in modules' do
|
@@ -338,10 +337,10 @@ eoc
|
|
338
337
|
' c = M::N::NestedDummy.heres_my_number(867, 5309)',
|
339
338
|
)
|
340
339
|
r = engine.evaluate('A', 'b')
|
341
|
-
r.
|
340
|
+
expect(r).to eq(867 + 5309)
|
342
341
|
|
343
342
|
r = engine.evaluate('A', 'c')
|
344
|
-
r.
|
343
|
+
expect(r).to eq(867 + 5309)
|
345
344
|
end
|
346
345
|
|
347
346
|
it 'should be able to use AR classes as values and call their methods' do
|
@@ -351,10 +350,10 @@ eoc
|
|
351
350
|
' c = M::N::NestedDummy.heres_my_number(867, 5309)',
|
352
351
|
)
|
353
352
|
r = engine.evaluate('A', 'b')
|
354
|
-
r.
|
353
|
+
expect(r).to eq(867 + 5309)
|
355
354
|
|
356
355
|
r = engine.evaluate('A', 'c')
|
357
|
-
r.
|
356
|
+
expect(r).to eq(867 + 5309)
|
358
357
|
end
|
359
358
|
|
360
359
|
it 'should be able to use ruby modules as values and call their methods' do
|
@@ -365,10 +364,10 @@ eoc
|
|
365
364
|
)
|
366
365
|
# binding.pry
|
367
366
|
r = engine.evaluate('A', 'b')
|
368
|
-
r.
|
367
|
+
expect(r).to eq(867 + 5309)
|
369
368
|
|
370
369
|
r = engine.evaluate('A', 'c')
|
371
|
-
r.
|
370
|
+
expect(r).to eq(867 + 5309)
|
372
371
|
end
|
373
372
|
|
374
373
|
it 'should be able call method defined in a parent or matched to' do
|
@@ -381,16 +380,16 @@ eoc
|
|
381
380
|
)
|
382
381
|
|
383
382
|
r = engine.evaluate('A', 'b')
|
384
|
-
r.
|
383
|
+
expect(r).to eq(:test_fn_result)
|
385
384
|
|
386
385
|
r = engine.evaluate('A', 'c')
|
387
|
-
r.
|
386
|
+
expect(r).to eq(:test_fn2_result)
|
388
387
|
|
389
388
|
r = engine.evaluate('A', 'd')
|
390
|
-
r.
|
389
|
+
expect(r).to eq(:test_fn2_result_different)
|
391
390
|
|
392
391
|
r = engine.evaluate('A', 'e')
|
393
|
-
r.
|
392
|
+
expect(r).to eq(:test_fn2_result_different)
|
394
393
|
end
|
395
394
|
|
396
395
|
it 'should raise exception if method is not whitelisted' do
|
@@ -401,20 +400,38 @@ eoc
|
|
401
400
|
' c = Dummy.this_is_crazy()',
|
402
401
|
)
|
403
402
|
|
404
|
-
|
405
|
-
engine.evaluate('A', 'a')
|
406
|
-
}.should raise_error(
|
403
|
+
expect { engine.evaluate('A', 'a') }.to raise_error(
|
407
404
|
Delorean::InvalidGetAttribute,
|
408
405
|
"attr lookup failed: 'test_fn4' on <Class> DeloreanFunctionsChildClass - no such method test_fn4"
|
409
406
|
)
|
410
407
|
|
411
|
-
|
412
|
-
|
413
|
-
|
408
|
+
expect { engine.evaluate('A', 'b') }.to raise_error(
|
409
|
+
RuntimeError, 'no such method test_fn4'
|
410
|
+
)
|
411
|
+
|
412
|
+
expect { engine.evaluate('A', 'c') }.to raise_error(
|
413
|
+
RuntimeError, 'no such method this_is_crazy'
|
414
|
+
)
|
415
|
+
end
|
416
|
+
|
417
|
+
it 'should be able to call cached_delorean_fn' do
|
418
|
+
engine.parse defn(
|
419
|
+
'A:',
|
420
|
+
' b = Dummy.returns_cached_openstruct(1, 2)',
|
421
|
+
' c = Dummy.returns_cached_openstruct(1, 2)',
|
422
|
+
' d = Dummy.returns_cached_openstruct(1, 3)',
|
423
|
+
)
|
424
|
+
|
425
|
+
expect(OpenStruct).to receive(:new).twice.and_call_original
|
414
426
|
|
415
|
-
|
416
|
-
|
417
|
-
|
427
|
+
r = engine.evaluate('A', 'b')
|
428
|
+
expect(r['1']).to eq(2)
|
429
|
+
|
430
|
+
r = engine.evaluate('A', 'c')
|
431
|
+
expect(r['1']).to eq(2)
|
432
|
+
|
433
|
+
r = engine.evaluate('A', 'd')
|
434
|
+
expect(r['1']).to eq(3)
|
418
435
|
end
|
419
436
|
|
420
437
|
it 'should raise exception if required arguments are missing' do
|
@@ -428,32 +445,56 @@ eoc
|
|
428
445
|
)
|
429
446
|
|
430
447
|
r = engine.evaluate('A', 'a')
|
431
|
-
r.
|
448
|
+
expect(r).to eq(a: 1, b: 2, c: 3, d: 4, e: 5, rest: [6, 7, 8, 9, 10])
|
432
449
|
|
433
450
|
r = engine.evaluate('A', 'b')
|
434
|
-
r.
|
451
|
+
expect(r).to eq(a: 1, b: 2, c: 3, d: 4, e: nil, rest: [])
|
435
452
|
|
436
|
-
|
437
|
-
r = engine.evaluate('A', 'c')
|
438
|
-
}.should raise_error(
|
453
|
+
expect { r = engine.evaluate('A', 'c') }.to raise_error(
|
439
454
|
ArgumentError,
|
440
455
|
'wrong number of arguments (given 2, expected 3+)'
|
441
456
|
)
|
442
457
|
|
443
|
-
|
444
|
-
r = engine.evaluate('A', 'd')
|
445
|
-
}.should raise_error(
|
458
|
+
expect { r = engine.evaluate('A', 'd') }.to raise_error(
|
446
459
|
ArgumentError,
|
447
460
|
'wrong number of arguments (given 0, expected 3+)'
|
448
461
|
)
|
449
462
|
end
|
450
463
|
|
464
|
+
it 'should raise exception if private method is called' do
|
465
|
+
engine.parse defn(
|
466
|
+
'A:',
|
467
|
+
' a = DeloreanFunctionsClass.test_private_fn',
|
468
|
+
' b = DeloreanFunctionsChildClass.test_private_fn'
|
469
|
+
)
|
470
|
+
|
471
|
+
expect do
|
472
|
+
engine.evaluate('A', 'a')
|
473
|
+
end.to raise_error(
|
474
|
+
Delorean::InvalidGetAttribute,
|
475
|
+
"attr lookup failed: 'test_private_fn' on <Class> DeloreanFunctionsClass - no such method test_private_fn"
|
476
|
+
)
|
477
|
+
|
478
|
+
expect do
|
479
|
+
engine.evaluate('A', 'b')
|
480
|
+
end.to raise_error(
|
481
|
+
"attr lookup failed: 'test_private_fn' on <Class> DeloreanFunctionsChildClass - no such method test_private_fn"
|
482
|
+
)
|
483
|
+
|
484
|
+
expect do
|
485
|
+
DeloreanFunctionsClass.test_private_fn
|
486
|
+
end.to raise_error(
|
487
|
+
NoMethodError,
|
488
|
+
"private method `test_private_fn' called for DeloreanFunctionsClass:Class"
|
489
|
+
)
|
490
|
+
end
|
491
|
+
|
451
492
|
it 'should ignore undeclared params sent to eval which match attr names' do
|
452
493
|
engine.parse defn('A:',
|
453
494
|
' d = 12',
|
454
495
|
)
|
455
496
|
r = engine.evaluate('A', 'd', 'd' => 5, 'e' => 6)
|
456
|
-
r.
|
497
|
+
expect(r).to eq(12)
|
457
498
|
end
|
458
499
|
|
459
500
|
it 'should handle different param defaults on nodes' do
|
@@ -467,19 +508,19 @@ eoc
|
|
467
508
|
)
|
468
509
|
|
469
510
|
r = engine.evaluate('C', 'c', 'p' => 5)
|
470
|
-
r.
|
511
|
+
expect(r).to eq(5 * 123)
|
471
512
|
|
472
513
|
r = engine.evaluate('B', 'c', 'p' => 10)
|
473
|
-
r.
|
514
|
+
expect(r).to eq(10 * 123)
|
474
515
|
|
475
516
|
r = engine.evaluate('A', 'c')
|
476
|
-
r.
|
517
|
+
expect(r).to eq(1 * 123)
|
477
518
|
|
478
519
|
r = engine.evaluate('B', 'c')
|
479
|
-
r.
|
520
|
+
expect(r).to eq(2 * 123)
|
480
521
|
|
481
522
|
r = engine.evaluate('C', 'c')
|
482
|
-
r.
|
523
|
+
expect(r).to eq(3 * 123)
|
483
524
|
end
|
484
525
|
|
485
526
|
it 'should allow overriding of attrs as params' do
|
@@ -491,14 +532,14 @@ eoc
|
|
491
532
|
)
|
492
533
|
|
493
534
|
r = engine.evaluate('A', 'b', 'a' => 10)
|
494
|
-
r.
|
535
|
+
expect(r).to eq(2 * 3)
|
495
536
|
|
496
537
|
r = engine.evaluate('B', 'b', 'a' => 10)
|
497
|
-
r.
|
538
|
+
expect(r).to eq(10 * 3)
|
498
539
|
|
499
|
-
|
500
|
-
|
501
|
-
|
540
|
+
expect { r = engine.evaluate('B', 'b') }.to raise_error(
|
541
|
+
Delorean::UndefinedParamError
|
542
|
+
)
|
502
543
|
end
|
503
544
|
|
504
545
|
sample_script = <<eof
|
@@ -519,17 +560,17 @@ eof
|
|
519
560
|
engine.parse sample_script
|
520
561
|
|
521
562
|
r = engine.evaluate('C', 'c')
|
522
|
-
r.
|
563
|
+
expect(r).to eq(4)
|
523
564
|
|
524
565
|
r = engine.evaluate('B', 'pc')
|
525
|
-
r.
|
566
|
+
expect(r).to eq(4 + 5)
|
526
567
|
|
527
568
|
r = engine.evaluate('C', 'pc')
|
528
|
-
r.
|
569
|
+
expect(r).to eq(4 + 3)
|
529
570
|
|
530
|
-
|
531
|
-
|
532
|
-
|
571
|
+
expect { r = engine.evaluate('A', 'pc') }.to raise_error(
|
572
|
+
Delorean::UndefinedParamError
|
573
|
+
)
|
533
574
|
end
|
534
575
|
|
535
576
|
it 'engines of same name should be independent' do
|
@@ -551,17 +592,24 @@ eof
|
|
551
592
|
' d = 111',
|
552
593
|
)
|
553
594
|
|
554
|
-
engine.evaluate('A', ['a', 'b']).
|
555
|
-
|
595
|
+
expect(engine.evaluate('A', ['a', 'b'])).to eq(
|
596
|
+
[123, 123 * 3]
|
597
|
+
)
|
598
|
+
expect(engin2.evaluate('A', ['a', 'b'])).to eq(
|
599
|
+
[222.0, 222.0 / 5]
|
600
|
+
)
|
556
601
|
|
557
|
-
engine.evaluate('B', ['a', 'b', 'c']).
|
558
|
-
|
602
|
+
expect(engine.evaluate('B', ['a', 'b', 'c'])).to eq(
|
603
|
+
[123, 123 * 3, 123 * 3 * 2]
|
604
|
+
)
|
605
|
+
expect(engin2.evaluate('B', ['a', 'b', 'c'])).to eq(
|
559
606
|
[222.0, 222.0 / 5, 222.0 / 5 * 3]
|
607
|
+
)
|
560
608
|
|
561
|
-
engin2.evaluate('C', 'd').
|
562
|
-
|
563
|
-
|
564
|
-
|
609
|
+
expect(engin2.evaluate('C', 'd')).to eq(111)
|
610
|
+
expect { engine.evaluate('C', 'd') }.to raise_error(
|
611
|
+
Delorean::UndefinedNodeError
|
612
|
+
)
|
565
613
|
end
|
566
614
|
|
567
615
|
it 'should handle invalid expression evaluation' do
|
@@ -578,12 +626,12 @@ eof
|
|
578
626
|
' e = [1, 1+1, 1+1+1, 1*2*4]',
|
579
627
|
)
|
580
628
|
|
581
|
-
engine.evaluate('A', %w[b c d e]).
|
629
|
+
expect(engine.evaluate('A', %w[b c d e])).to eq(
|
582
630
|
[[],
|
583
631
|
[1, 2, 3],
|
584
632
|
[[], [1, 2, 3], [], [1, 2, 3], 1, 2, '123', 1.1, -1.23],
|
585
633
|
[1, 2, 3, 8],
|
586
|
-
]
|
634
|
+
])
|
587
635
|
end
|
588
636
|
|
589
637
|
it 'should eval list expressions' do
|
@@ -593,11 +641,11 @@ eof
|
|
593
641
|
' d = c*2',
|
594
642
|
)
|
595
643
|
|
596
|
-
engine.evaluate('A', %w[b c d]).
|
644
|
+
expect(engine.evaluate('A', %w[b c d])).to eq(
|
597
645
|
[[],
|
598
646
|
[1, 2, 3],
|
599
647
|
[1, 2, 3] * 2,
|
600
|
-
]
|
648
|
+
])
|
601
649
|
end
|
602
650
|
|
603
651
|
it 'should eval sets and set comprehension' do
|
@@ -606,8 +654,9 @@ eof
|
|
606
654
|
' b = {i*5 for i in {1,2,3}}',
|
607
655
|
' c = {1,2,3} | {4,5}',
|
608
656
|
)
|
609
|
-
engine.evaluate('A', ['a', 'b', 'c']).
|
657
|
+
expect(engine.evaluate('A', ['a', 'b', 'c'])).to eq(
|
610
658
|
[Set[], Set[5, 10, 15], Set[1, 2, 3, 4, 5]]
|
659
|
+
)
|
611
660
|
end
|
612
661
|
|
613
662
|
it 'should eval list comprehension' do
|
@@ -615,22 +664,22 @@ eof
|
|
615
664
|
' b = [i*5 for i in [1,2,3]]',
|
616
665
|
' c = [a-b for a, b in [[1,2],[4,3]]]'
|
617
666
|
)
|
618
|
-
engine.evaluate('A', 'b').
|
619
|
-
engine.evaluate('A', 'c').
|
667
|
+
expect(engine.evaluate('A', 'b')).to eq([5, 10, 15])
|
668
|
+
expect(engine.evaluate('A', 'c')).to eq([-1, 1])
|
620
669
|
end
|
621
670
|
|
622
671
|
it 'should eval nested list comprehension' do
|
623
672
|
engine.parse defn('A:',
|
624
673
|
' b = [[a+c for c in [4,5]] for a in [1,2,3]]',
|
625
674
|
)
|
626
|
-
engine.evaluate('A', 'b').
|
675
|
+
expect(engine.evaluate('A', 'b')).to eq([[5, 6], [6, 7], [7, 8]])
|
627
676
|
end
|
628
677
|
|
629
678
|
it 'should eval list comprehension variable override' do
|
630
679
|
engine.parse defn('A:',
|
631
680
|
' b = [b/2.0 for b in [1,2,3]]',
|
632
681
|
)
|
633
|
-
engine.evaluate('A', 'b').
|
682
|
+
expect(engine.evaluate('A', 'b')).to eq([0.5, 1.0, 1.5])
|
634
683
|
end
|
635
684
|
|
636
685
|
it 'should eval list comprehension variable override (2)' do
|
@@ -638,7 +687,7 @@ eof
|
|
638
687
|
' a = 1',
|
639
688
|
' b = [a+1 for a in [1,2,3]]',
|
640
689
|
)
|
641
|
-
engine.evaluate('A', 'b').
|
690
|
+
expect(engine.evaluate('A', 'b')).to eq([2, 3, 4])
|
642
691
|
end
|
643
692
|
|
644
693
|
it 'should eval conditional list comprehension' do
|
@@ -646,15 +695,15 @@ eof
|
|
646
695
|
' b = [i*5 for i in [1,2,3,4,5] if i%2 == 1]',
|
647
696
|
' c = [i/10.0 for i in [1,2,3,4,5] if i>4]',
|
648
697
|
)
|
649
|
-
engine.evaluate('A', 'b').
|
650
|
-
engine.evaluate('A', 'c').
|
698
|
+
expect(engine.evaluate('A', 'b')).to eq([5, 15, 25])
|
699
|
+
expect(engine.evaluate('A', 'c')).to eq([0.5])
|
651
700
|
end
|
652
701
|
|
653
702
|
it 'should handle list comprehension unpacking' do
|
654
703
|
engine.parse defn('A:',
|
655
704
|
' b = [a-b for a, b in [[1,2],[20,10]]]',
|
656
705
|
)
|
657
|
-
engine.evaluate('A', 'b').
|
706
|
+
expect(engine.evaluate('A', 'b')).to eq([-1, 10])
|
658
707
|
end
|
659
708
|
|
660
709
|
it 'should handle list comprehension with conditions using loop var' do
|
@@ -662,7 +711,7 @@ eof
|
|
662
711
|
engine.parse defn('A:',
|
663
712
|
" b = [n for n in {'pt' : 1} if n[1]+1]",
|
664
713
|
)
|
665
|
-
engine.evaluate('A', 'b').
|
714
|
+
expect(engine.evaluate('A', 'b')).to eq([['pt', 1]])
|
666
715
|
end
|
667
716
|
|
668
717
|
it 'should eval hashes' do
|
@@ -675,14 +724,14 @@ eof
|
|
675
724
|
' g = {b:b, [b]:[1,23], []:345}',
|
676
725
|
)
|
677
726
|
|
678
|
-
engine.evaluate('A', %w[b c d e f g]).
|
727
|
+
expect(engine.evaluate('A', %w[b c d e f g])).to eq(
|
679
728
|
[{},
|
680
729
|
{ 'a' => 1, 'b' => 2, 'c' => 3 },
|
681
730
|
{ 123 * 2 => -123, 'b_b' => 2 },
|
682
731
|
{ 'x' => 1, 'y' => 2, 'z' => 3, 'zz' => 8 },
|
683
732
|
{ 'a' => nil, 'b' => [1, nil, 2] },
|
684
733
|
{ {} => {}, [{}] => [1, 23], [] => 345 },
|
685
|
-
]
|
734
|
+
])
|
686
735
|
end
|
687
736
|
|
688
737
|
it 'handles literal hashes with conditionals' do
|
@@ -693,11 +742,11 @@ eof
|
|
693
742
|
' d = {1: {1: 2 if b}, 3: 3 if c, 2: {2: 3 if a}}',
|
694
743
|
)
|
695
744
|
|
696
|
-
engine.evaluate('A', %w[a b d]).
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
745
|
+
expect(engine.evaluate('A', %w[a b d])).to eq([
|
746
|
+
{ 'a' => 1 },
|
747
|
+
{ 'a' => { 'a' => 1 }, 2 => { 'a' => 1 }, 'c' => nil },
|
748
|
+
{ 1 => { 1 => 2 }, 2 => { 2 => 3 } },
|
749
|
+
])
|
701
750
|
end
|
702
751
|
|
703
752
|
it 'should eval hash comprehension' do
|
@@ -705,8 +754,8 @@ eof
|
|
705
754
|
' b = {i*5 :i for i in [1,2,3]}',
|
706
755
|
' c = [kv for kv in {1:11, 2:22}]',
|
707
756
|
)
|
708
|
-
engine.evaluate('A', 'b').
|
709
|
-
engine.evaluate('A', 'c').
|
757
|
+
expect(engine.evaluate('A', 'b')).to eq(5 => 1, 10 => 2, 15 => 3)
|
758
|
+
expect(engine.evaluate('A', 'c')).to eq([[1, 11], [2, 22]])
|
710
759
|
end
|
711
760
|
|
712
761
|
it 'for-in-hash should iterate over key/value pairs' do
|
@@ -717,9 +766,9 @@ eof
|
|
717
766
|
' e = [kv for kv in b if kv[1]]',
|
718
767
|
' f = [k-v for k, v in b if k>1]',
|
719
768
|
)
|
720
|
-
engine.evaluate('A', 'c').
|
721
|
-
engine.evaluate('A', 'd').
|
722
|
-
engine.evaluate('A', 'f').
|
769
|
+
expect(engine.evaluate('A', 'c')).to eq([-10, -20])
|
770
|
+
expect(engine.evaluate('A', 'd')).to eq(1 => 11, 2 => 22)
|
771
|
+
expect(engine.evaluate('A', 'f')).to eq([-20])
|
723
772
|
|
724
773
|
# FIXME: this is a known bug in Delorean caused by the strange way
|
725
774
|
# that select iterates over hashes and provides args to the block.
|
@@ -730,11 +779,11 @@ eof
|
|
730
779
|
engine.parse defn('A:',
|
731
780
|
' b = { a:{a+c:a-c for c in [4,5]} for a in [1,2,3]}',
|
732
781
|
)
|
733
|
-
engine.evaluate('A', 'b').
|
782
|
+
expect(engine.evaluate('A', 'b')).to eq(
|
734
783
|
1 => { 5 => -3, 6 => -4 },
|
735
784
|
2 => { 6 => -2, 7 => -3 },
|
736
785
|
3 => { 7 => -1, 8 => -2 }
|
737
|
-
|
786
|
+
)
|
738
787
|
end
|
739
788
|
|
740
789
|
it 'should eval conditional hash comprehension' do
|
@@ -742,8 +791,20 @@ eof
|
|
742
791
|
' b = {i*5:i+5 for i in [1,2,3,4,5] if i%2 == 1}',
|
743
792
|
' c = {i/10.0:i*10 for i in [1,2,3,4,5] if i>4}',
|
744
793
|
)
|
745
|
-
engine.evaluate('A', 'b').
|
746
|
-
engine.evaluate('A', 'c').
|
794
|
+
expect(engine.evaluate('A', 'b')).to eq(5 => 6, 15 => 8, 25 => 10)
|
795
|
+
expect(engine.evaluate('A', 'c')).to eq(0.5 => 50)
|
796
|
+
end
|
797
|
+
|
798
|
+
it 'should eval hash methods such as length' do
|
799
|
+
engine.parse defn('A:',
|
800
|
+
' b = {}',
|
801
|
+
" c = {'a':1, 'b': 2,'c':3}",
|
802
|
+
' length1 = b.length',
|
803
|
+
' length2 = c.length',
|
804
|
+
)
|
805
|
+
|
806
|
+
expect(engine.evaluate('A', 'length1')).to eq(0)
|
807
|
+
expect(engine.evaluate('A', 'length2')).to eq(3)
|
747
808
|
end
|
748
809
|
|
749
810
|
it 'should eval node calls as intermediate results' do
|
@@ -754,7 +815,7 @@ eof
|
|
754
815
|
' f = e.d / e.a',
|
755
816
|
)
|
756
817
|
|
757
|
-
engine.evaluate('A', ['d', 'f']).
|
818
|
+
expect(engine.evaluate('A', ['d', 'f'])).to eq([26, 2])
|
758
819
|
end
|
759
820
|
|
760
821
|
it 'allows node calls from attrs' do
|
@@ -767,7 +828,7 @@ eof
|
|
767
828
|
' f = d.b + d.c + e().a',
|
768
829
|
)
|
769
830
|
|
770
|
-
engine.evaluate('A', ['f']).
|
831
|
+
expect(engine.evaluate('A', ['f'])).to eq([16 + 5 + 13])
|
771
832
|
end
|
772
833
|
|
773
834
|
it 'should eval multi-var hash comprehension' do
|
@@ -775,8 +836,8 @@ eof
|
|
775
836
|
' b = {k*5 : v+1 for k, v in {1:2, 7:-30}}',
|
776
837
|
' c = [k-v for k, v in {1:2, 7:-30}]',
|
777
838
|
)
|
778
|
-
engine.evaluate('A', 'b').
|
779
|
-
engine.evaluate('A', 'c').
|
839
|
+
expect(engine.evaluate('A', 'b')).to eq(5 => 3, 35 => -29)
|
840
|
+
expect(engine.evaluate('A', 'c')).to eq([-1, 37])
|
780
841
|
end
|
781
842
|
|
782
843
|
it 'should be able to amend node calls' do
|
@@ -791,8 +852,9 @@ eof
|
|
791
852
|
' j = d(a=6).aa',
|
792
853
|
)
|
793
854
|
|
794
|
-
engine.evaluate('A', ['g', 'h', 'j']).
|
855
|
+
expect(engine.evaluate('A', ['g', 'h', 'j'])).to eq(
|
795
856
|
[3 * 2 + 4 * 2, 5 * 2, 6 * 2]
|
857
|
+
)
|
796
858
|
end
|
797
859
|
|
798
860
|
it 'should be able to amend node calls 2' do
|
@@ -802,7 +864,7 @@ eof
|
|
802
864
|
' e = [d.a, d(a=4).a]',
|
803
865
|
)
|
804
866
|
|
805
|
-
engine.evaluate('A', ['e']).
|
867
|
+
expect(engine.evaluate('A', ['e'])).to eq([[3, 4]])
|
806
868
|
end
|
807
869
|
|
808
870
|
it 'should eval module calls 1' do
|
@@ -812,7 +874,7 @@ eof
|
|
812
874
|
' d = n().a',
|
813
875
|
)
|
814
876
|
|
815
|
-
engine.evaluate('A', %w[d]).
|
877
|
+
expect(engine.evaluate('A', %w[d])).to eq([123])
|
816
878
|
end
|
817
879
|
|
818
880
|
it 'should eval module calls 2' do
|
@@ -825,12 +887,13 @@ eof
|
|
825
887
|
" e = nil() % ['b']",
|
826
888
|
)
|
827
889
|
|
828
|
-
engine.evaluate('A', %w[n c d e]).
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
890
|
+
expect(engine.evaluate('A', %w[n c d e])).to eq(
|
891
|
+
[
|
892
|
+
'A',
|
893
|
+
{ 'a' => 123, 'b' => 579 },
|
894
|
+
{ 'a' => 123, 'b' => 579 },
|
895
|
+
{ 'b' => 579 }
|
896
|
+
])
|
834
897
|
end
|
835
898
|
|
836
899
|
it 'should eval module calls 3' do
|
@@ -841,7 +904,7 @@ eof
|
|
841
904
|
' d = n().a',
|
842
905
|
)
|
843
906
|
|
844
|
-
engine.evaluate('B', %w[d]).
|
907
|
+
expect(engine.evaluate('B', %w[d])).to eq([123])
|
845
908
|
end
|
846
909
|
|
847
910
|
it 'should be possible to implement recursive calls' do
|
@@ -850,7 +913,7 @@ eof
|
|
850
913
|
' fact = if n <= 1 then 1 else n * A(n=n-1).fact',
|
851
914
|
)
|
852
915
|
|
853
|
-
engine.evaluate('A', 'fact', 'n' => 10).
|
916
|
+
expect(engine.evaluate('A', 'fact', 'n' => 10)).to eq(3_628_800)
|
854
917
|
end
|
855
918
|
|
856
919
|
it 'should eval module calls by node name' do
|
@@ -858,7 +921,7 @@ eof
|
|
858
921
|
' a = 123',
|
859
922
|
' b = A().a',
|
860
923
|
)
|
861
|
-
engine.evaluate('A', 'b').
|
924
|
+
expect(engine.evaluate('A', 'b')).to eq(123)
|
862
925
|
end
|
863
926
|
|
864
927
|
it 'should eval multiline expressions' do
|
@@ -868,7 +931,7 @@ eof
|
|
868
931
|
' for a in [1,2,3]',
|
869
932
|
' ]',
|
870
933
|
)
|
871
|
-
engine.evaluate('A', 'b').
|
934
|
+
expect(engine.evaluate('A', 'b')).to eq([2, 3, 4])
|
872
935
|
end
|
873
936
|
|
874
937
|
it 'should eval multiline expressions (2)' do
|
@@ -885,12 +948,13 @@ eof
|
|
885
948
|
" ) % ['b']",
|
886
949
|
)
|
887
950
|
|
888
|
-
engine.evaluate('A', %w[n c d e]).
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
951
|
+
expect(engine.evaluate('A', %w[n c d e])).to eq(
|
952
|
+
[
|
953
|
+
'A',
|
954
|
+
{ 'a' => 123, 'b' => 579 },
|
955
|
+
{ 'a' => 123, 'b' => 579 },
|
956
|
+
{ 'b' => 579 }
|
957
|
+
])
|
894
958
|
end
|
895
959
|
|
896
960
|
it 'should eval in expressions' do
|
@@ -902,8 +966,7 @@ eof
|
|
902
966
|
' d = [i*2 for i in s if i in a]',
|
903
967
|
)
|
904
968
|
|
905
|
-
engine.evaluate('A', %w[b c d]).
|
906
|
-
[false, true, [66, 88]]
|
969
|
+
expect(engine.evaluate('A', %w[b c d])).to eq([false, true, [66, 88]])
|
907
970
|
end
|
908
971
|
|
909
972
|
it 'should eval imports' do
|
@@ -914,8 +977,7 @@ eof
|
|
914
977
|
' a = 111',
|
915
978
|
' c = AAA::X(a=456).b',
|
916
979
|
)
|
917
|
-
engine.evaluate('B', ['a', 'b', 'c'], {}).
|
918
|
-
[111, 222, 456 * 2]
|
980
|
+
expect(engine.evaluate('B', ['a', 'b', 'c'], {})).to eq([111, 222, 456 * 2])
|
919
981
|
end
|
920
982
|
|
921
983
|
it 'should eval imports (2)' do
|
@@ -939,23 +1001,24 @@ eof
|
|
939
1001
|
|
940
1002
|
e2 = sset.get_engine('BBB')
|
941
1003
|
|
942
|
-
e2.evaluate('B', ['a', 'b', 'c', 'd']).
|
943
|
-
[111, 222, -2, 222]
|
1004
|
+
expect(e2.evaluate('B', ['a', 'b', 'c', 'd'])).to eq([111, 222, -2, 222])
|
944
1005
|
|
945
1006
|
engine.parse defn('import BBB',
|
946
1007
|
'B: BBB::B',
|
947
1008
|
' e = d + 3',
|
948
1009
|
)
|
949
1010
|
|
950
|
-
engine.evaluate('B', ['a', 'b', 'c', 'd', 'e']).
|
1011
|
+
expect(engine.evaluate('B', ['a', 'b', 'c', 'd', 'e'])).to eq(
|
951
1012
|
[111, 222, -2, 222, 225]
|
1013
|
+
)
|
952
1014
|
|
953
1015
|
e4 = sset.get_engine('CCC')
|
954
1016
|
|
955
|
-
e4.evaluate('B', ['a', 'b', 'c', 'd', 'e']).
|
1017
|
+
expect(e4.evaluate('B', ['a', 'b', 'c', 'd', 'e'])).to eq(
|
956
1018
|
[111, 222, -2, 222, 666]
|
1019
|
+
)
|
957
1020
|
|
958
|
-
e4.evaluate('C', ['a', 'b', 'd']).
|
1021
|
+
expect(e4.evaluate('C', ['a', 'b', 'd'])).to eq([123, 123 * 2, 123 * 3 * 2])
|
959
1022
|
end
|
960
1023
|
|
961
1024
|
it 'should eval imports (3)' do
|
@@ -970,8 +1033,8 @@ eof
|
|
970
1033
|
)
|
971
1034
|
|
972
1035
|
e4 = sset.get_engine('CCC')
|
973
|
-
e4.evaluate('X', 'xx').
|
974
|
-
e4.evaluate('X', 'yy').
|
1036
|
+
expect(e4.evaluate('X', 'xx')).to eq([1, 2, 3])
|
1037
|
+
expect(e4.evaluate('X', 'yy')).to eq([1, 2, 3])
|
975
1038
|
end
|
976
1039
|
|
977
1040
|
it 'should eval imports (4) - with ::' do
|
@@ -1004,8 +1067,8 @@ eof
|
|
1004
1067
|
)
|
1005
1068
|
|
1006
1069
|
e4 = sset.get_engine('CCC')
|
1007
|
-
e4.evaluate('X', 'xx').
|
1008
|
-
e4.evaluate('X', 'zz').
|
1070
|
+
expect(e4.evaluate('X', 'xx')).to eq([1, 2, 3])
|
1071
|
+
expect(e4.evaluate('X', 'zz')).to eq([2, 4, 6])
|
1009
1072
|
end
|
1010
1073
|
|
1011
1074
|
it 'should eval imports (4) - inheritance - with ::' do
|
@@ -1035,7 +1098,7 @@ eof
|
|
1035
1098
|
)
|
1036
1099
|
|
1037
1100
|
e4 = sset.get_engine('CCC')
|
1038
|
-
e4.evaluate('X', 'zz').
|
1101
|
+
expect(e4.evaluate('X', 'zz')).to eq([2, 4, 6])
|
1039
1102
|
end
|
1040
1103
|
|
1041
1104
|
it 'can eval indexing' do
|
@@ -1048,7 +1111,7 @@ eof
|
|
1048
1111
|
' f = a[1,2]',
|
1049
1112
|
)
|
1050
1113
|
r = engine.evaluate('A', ['b', 'c', 'e', 'f'])
|
1051
|
-
r.
|
1114
|
+
expect(r).to eq([2, 3, 456, [2, 3]])
|
1052
1115
|
end
|
1053
1116
|
|
1054
1117
|
it 'can eval indexing 2' do
|
@@ -1059,12 +1122,13 @@ eof
|
|
1059
1122
|
" d = c['b'].x * c['a'] - c['b'].y",
|
1060
1123
|
)
|
1061
1124
|
r = engine.evaluate('A', ['a', 'b', 'c', 'd'])
|
1062
|
-
r.
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1125
|
+
expect(r).to eq(
|
1126
|
+
[
|
1127
|
+
1,
|
1128
|
+
{ 'x' => 123, 'y' => 456 },
|
1129
|
+
{ 'a' => 1, 'b' => { 'x' => 123, 'y' => 456 } },
|
1130
|
+
-333
|
1131
|
+
])
|
1068
1132
|
end
|
1069
1133
|
|
1070
1134
|
it 'can handle exceptions with / syntax' do
|
@@ -1077,20 +1141,22 @@ eof
|
|
1077
1141
|
" f = A() / 'a'",
|
1078
1142
|
)
|
1079
1143
|
r = engine.evaluate('A', ['a', 'b', 'c'])
|
1080
|
-
r.
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1144
|
+
expect(r).to eq(
|
1145
|
+
[
|
1146
|
+
1,
|
1147
|
+
{ 'x' => 123, 'y' => 456 },
|
1148
|
+
{ 'a' => 1, 'b' => { 'x' => 123, 'y' => 456 } }
|
1149
|
+
])
|
1085
1150
|
|
1086
1151
|
r = engine.evaluate('A', ['a', 'd'])
|
1087
|
-
r.
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1152
|
+
expect(r).to eq(
|
1153
|
+
[
|
1154
|
+
1,
|
1155
|
+
{ 'error' => 'hello', 'backtrace' => [['XXX', 4, 'e'], ['XXX', 6, 'd']] }
|
1156
|
+
])
|
1091
1157
|
|
1092
1158
|
r = engine.evaluate('A', ['f'])
|
1093
|
-
r.
|
1159
|
+
expect(r).to eq([1])
|
1094
1160
|
end
|
1095
1161
|
|
1096
1162
|
it 'should properly eval overridden attrs' do
|
@@ -1105,12 +1171,12 @@ eof
|
|
1105
1171
|
' m = [x().b for x in [A, B]]',
|
1106
1172
|
)
|
1107
1173
|
|
1108
|
-
engine.evaluate('A', 'b').
|
1109
|
-
engine.evaluate('B', 'b').
|
1110
|
-
engine.evaluate('B', 'x').
|
1111
|
-
engine.evaluate('B', 'k').
|
1112
|
-
engine.evaluate('B', 'l').
|
1113
|
-
engine.evaluate('B', 'm').
|
1174
|
+
expect(engine.evaluate('A', 'b')).to eq(5)
|
1175
|
+
expect(engine.evaluate('B', 'b')).to eq(2)
|
1176
|
+
expect(engine.evaluate('B', 'x')).to eq(3)
|
1177
|
+
expect(engine.evaluate('B', 'k')).to eq([5, 2])
|
1178
|
+
expect(engine.evaluate('B', 'l')).to eq([5, 2])
|
1179
|
+
expect(engine.evaluate('B', 'm')).to eq([5, 2])
|
1114
1180
|
end
|
1115
1181
|
|
1116
1182
|
it 'implements simple version of self (_)' do
|
@@ -1128,14 +1194,14 @@ eof
|
|
1128
1194
|
" v = {**_, 'a': 123}",
|
1129
1195
|
)
|
1130
1196
|
|
1131
|
-
engine.evaluate('A', 'x', 'a' => 3, 'b' => 5).
|
1197
|
+
expect(engine.evaluate('A', 'x', 'a' => 3, 'b' => 5)).to eq(15)
|
1132
1198
|
h = { 'a' => 1, 'b' => 2, 'c' => 3 }
|
1133
|
-
engine.evaluate('A', 'y', 'a' => 1, 'b' => 2, 'c' => 3).
|
1134
|
-
engine.evaluate('A', 'z', 'a' => 1, 'b' => 2, 'c' => 3).
|
1135
|
-
engine.evaluate('A', 'w', 'a' => 4, 'b' => 5, 'c' => 3).
|
1136
|
-
engine.evaluate('A', 'v', 'a' => 4, 'b' => 5, 'c' => 3).
|
1199
|
+
expect(engine.evaluate('A', 'y', 'a' => 1, 'b' => 2, 'c' => 3)).to eq(h)
|
1200
|
+
expect(engine.evaluate('A', 'z', 'a' => 1, 'b' => 2, 'c' => 3)).to eq(-1)
|
1201
|
+
expect(engine.evaluate('A', 'w', 'a' => 4, 'b' => 5, 'c' => 3)).to eq(-1)
|
1202
|
+
expect(engine.evaluate('A', 'v', 'a' => 4, 'b' => 5, 'c' => 3)).to eq(
|
1137
1203
|
'a' => 123, 'b' => 5, 'c' => 3
|
1138
|
-
|
1204
|
+
)
|
1139
1205
|
end
|
1140
1206
|
|
1141
1207
|
it 'implements positional args in node calls' do
|
@@ -1149,8 +1215,9 @@ eof
|
|
1149
1215
|
' z = B(10, 20, a=3, b=7).x',
|
1150
1216
|
" y = B('x', 'y').y",
|
1151
1217
|
)
|
1152
|
-
engine.evaluate('A', ['a', 'z', 'y'], 0 => 123, 1 => 456).
|
1218
|
+
expect(engine.evaluate('A', ['a', 'z', 'y'], 0 => 123, 1 => 456)).to eq(
|
1153
1219
|
[123 - 456, 40, ['x', 'y', nil]]
|
1220
|
+
)
|
1154
1221
|
end
|
1155
1222
|
|
1156
1223
|
it 'can call 0-arity functions in list comprehension' do
|