delorean_lang 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitlab-ci.yml +1 -2
- data/.rubocop.yml +45 -5
- data/.rubocop_todo.yml +1 -634
- data/Gemfile +3 -1
- data/README.md +22 -0
- data/Rakefile +3 -1
- data/delorean.gemspec +18 -17
- data/lib/delorean/abstract_container.rb +4 -2
- data/lib/delorean/base.rb +30 -27
- data/lib/delorean/cache.rb +2 -0
- data/lib/delorean/cache/adapters.rb +2 -0
- data/lib/delorean/cache/adapters/base.rb +2 -0
- data/lib/delorean/cache/adapters/ruby_cache.rb +5 -0
- data/lib/delorean/const.rb +5 -3
- data/lib/delorean/debug.rb +6 -5
- data/lib/delorean/delorean.rb +466 -147
- data/lib/delorean/delorean.treetop +13 -1
- data/lib/delorean/engine.rb +61 -50
- data/lib/delorean/error.rb +2 -1
- data/lib/delorean/model.rb +12 -9
- data/lib/delorean/nodes.rb +130 -67
- data/lib/delorean/ruby.rb +2 -0
- data/lib/delorean/ruby/whitelists.rb +2 -0
- data/lib/delorean/ruby/whitelists/base.rb +7 -3
- data/lib/delorean/ruby/whitelists/default.rb +6 -6
- data/lib/delorean/ruby/whitelists/empty.rb +3 -2
- data/lib/delorean/ruby/whitelists/matchers.rb +2 -0
- data/lib/delorean/ruby/whitelists/matchers/arguments.rb +2 -0
- data/lib/delorean/ruby/whitelists/matchers/method.rb +5 -2
- data/lib/delorean/ruby/whitelists/whitelist_error.rb +2 -0
- data/lib/delorean/version.rb +3 -1
- data/lib/delorean_lang.rb +3 -1
- data/spec/cache_spec.rb +4 -2
- data/spec/dev_spec.rb +68 -69
- data/spec/eval_spec.rb +824 -729
- data/spec/func_spec.rb +172 -176
- data/spec/parse_spec.rb +516 -522
- data/spec/ruby/whitelist_spec.rb +6 -3
- data/spec/spec_helper.rb +26 -23
- metadata +27 -27
data/lib/delorean/ruby.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'delorean/ruby/whitelists/whitelist_error'
|
2
4
|
require 'delorean/ruby/whitelists/matchers'
|
3
5
|
|
@@ -11,9 +13,11 @@ module Delorean
|
|
11
13
|
return method_name_error unless method_name.is_a?(Symbol)
|
12
14
|
return block_and_match_error if !match_to.nil? && block_given?
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
unless match_to.nil?
|
17
|
+
return add_matched_method(
|
18
|
+
method_name: method_name, match_to: match_to
|
19
|
+
)
|
20
|
+
end
|
17
21
|
|
18
22
|
matchers[method_name.to_sym] = method_matcher_class.new(
|
19
23
|
method_name: method_name, &block
|
@@ -1,13 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'delorean/ruby/whitelists/base'
|
2
4
|
|
3
5
|
module Delorean
|
4
6
|
module Ruby
|
5
7
|
module Whitelists
|
6
8
|
class Default < ::Delorean::Ruby::Whitelists::Base
|
7
|
-
TI_TYPES = [Time, ActiveSupport::TimeWithZone]
|
9
|
+
TI_TYPES = [Time, ActiveSupport::TimeWithZone].freeze
|
8
10
|
DT_TYPES = [Date] + TI_TYPES
|
9
|
-
NUM_OR_STR = [Numeric, String]
|
10
|
-
NUM_OR_NIL = [nil, Integer]
|
11
|
+
NUM_OR_STR = [Numeric, String].freeze
|
12
|
+
NUM_OR_NIL = [nil, Integer].freeze
|
11
13
|
|
12
14
|
def initialize_hook
|
13
15
|
_add_default_methods
|
@@ -63,7 +65,7 @@ module Delorean
|
|
63
65
|
end
|
64
66
|
|
65
67
|
add_method :except do |method|
|
66
|
-
method.called_on Hash, with: [String] + [[nil, String]]*9
|
68
|
+
method.called_on Hash, with: [String] + [[nil, String]] * 9
|
67
69
|
end
|
68
70
|
|
69
71
|
add_method :reverse do |method|
|
@@ -132,8 +134,6 @@ module Delorean
|
|
132
134
|
method.called_on Set, with: [Enumerable]
|
133
135
|
end
|
134
136
|
|
135
|
-
|
136
|
-
|
137
137
|
add_method :keys do |method|
|
138
138
|
method.called_on Hash
|
139
139
|
end
|
@@ -1,11 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'delorean/ruby/whitelists/base'
|
2
4
|
|
3
5
|
module Delorean
|
4
6
|
module Ruby
|
5
7
|
module Whitelists
|
6
8
|
class Empty < ::Delorean::Ruby::Whitelists::Base
|
7
|
-
def initialize_hook
|
8
|
-
end
|
9
|
+
def initialize_hook; end
|
9
10
|
end
|
10
11
|
end
|
11
12
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'delorean/ruby/whitelists/matchers/arguments'
|
2
4
|
|
3
5
|
module Delorean
|
@@ -24,11 +26,12 @@ module Delorean
|
|
24
26
|
end
|
25
27
|
|
26
28
|
def matcher(klass:)
|
27
|
-
matcher = arguments_matchers.find do
|
28
|
-
|
29
|
+
matcher = arguments_matchers.find do |matcher_object|
|
30
|
+
klass <= matcher_object.called_on
|
29
31
|
end
|
30
32
|
|
31
33
|
raise "no such method #{method_name} for #{klass}" if matcher.nil?
|
34
|
+
|
32
35
|
matcher
|
33
36
|
end
|
34
37
|
|
data/lib/delorean/version.rb
CHANGED
data/lib/delorean_lang.rb
CHANGED
data/spec/cache_spec.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
4
|
|
3
|
-
describe
|
5
|
+
describe 'Delorean cache' do
|
4
6
|
before do
|
5
7
|
Dummy.clear_lookup_cache!
|
6
8
|
end
|
@@ -60,6 +62,6 @@ describe "Delorean cache" do
|
|
60
62
|
)
|
61
63
|
|
62
64
|
expect(item_10).to be_a(OpenStruct)
|
63
|
-
expect(item_10[
|
65
|
+
expect(item_10['10']).to eq(10)
|
64
66
|
end
|
65
67
|
end
|
data/spec/dev_spec.rb
CHANGED
@@ -1,90 +1,89 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
describe 'Delorean' do
|
6
|
+
let(:engine) do
|
7
|
+
Delorean::Engine.new('YYY')
|
8
|
+
end
|
8
9
|
|
9
|
-
it
|
10
|
-
engine.parse defn(
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
engine.enumerate_nodes.should == SortedSet.new([
|
10
|
+
it 'can enumerate nodes' do
|
11
|
+
engine.parse defn('X:',
|
12
|
+
' a = 123',
|
13
|
+
' b = a',
|
14
|
+
'Y: X',
|
15
|
+
'A:',
|
16
|
+
'XX: Y',
|
17
|
+
' a = 11',
|
18
|
+
' c =?',
|
19
|
+
' d = 456',
|
20
|
+
)
|
21
|
+
engine.enumerate_nodes.should == SortedSet.new(['A', 'X', 'XX', 'Y'])
|
21
22
|
end
|
22
23
|
|
23
|
-
it
|
24
|
-
engine.parse defn(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
24
|
+
it 'can enumerate attrs by node' do
|
25
|
+
engine.parse defn('X:',
|
26
|
+
' a = 123',
|
27
|
+
' b = a',
|
28
|
+
'Y: X',
|
29
|
+
'Z:',
|
30
|
+
'XX: Y',
|
31
|
+
' a = 11',
|
32
|
+
' c =?',
|
33
|
+
' d = 456',
|
34
|
+
)
|
34
35
|
|
35
36
|
exp = {
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
'X' => ['a', 'b'],
|
38
|
+
'Y' => ['a', 'b'],
|
39
|
+
'Z' => [],
|
40
|
+
'XX' => ['a', 'b', 'c', 'd'],
|
40
41
|
}
|
41
42
|
res = engine.enumerate_attrs
|
42
43
|
|
43
44
|
res.keys.sort.should == exp.keys.sort
|
44
45
|
|
45
|
-
exp.each
|
46
|
-
|k, v|
|
47
|
-
|
46
|
+
exp.each do |k, v|
|
48
47
|
engine.enumerate_attrs_by_node(k).sort.should == v
|
49
48
|
res[k].sort.should == v
|
50
|
-
|
49
|
+
end
|
51
50
|
end
|
52
51
|
|
53
|
-
it
|
54
|
-
engine.parse defn(
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
52
|
+
it 'can enumerate params' do
|
53
|
+
engine.parse defn('X:',
|
54
|
+
' a =? 123',
|
55
|
+
' b = a',
|
56
|
+
'Y: X',
|
57
|
+
'Z:',
|
58
|
+
'XX: Y',
|
59
|
+
' a = 11',
|
60
|
+
' c =?',
|
61
|
+
' d = 123',
|
62
|
+
'YY: XX',
|
63
|
+
' c =? 22',
|
64
|
+
' e =? 11',
|
65
|
+
)
|
67
66
|
|
68
|
-
engine.enumerate_params.should == Set.new([
|
67
|
+
engine.enumerate_params.should == Set.new(['a', 'c', 'e'])
|
69
68
|
end
|
70
69
|
|
71
|
-
it
|
72
|
-
engine.parse defn(
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
engine.enumerate_params_by_node(
|
86
|
-
engine.enumerate_params_by_node(
|
87
|
-
engine.enumerate_params_by_node(
|
88
|
-
engine.enumerate_params_by_node(
|
70
|
+
it 'can enumerate params by node' do
|
71
|
+
engine.parse defn('X:',
|
72
|
+
' a =? 123',
|
73
|
+
' b = a',
|
74
|
+
'Y: X',
|
75
|
+
'Z:',
|
76
|
+
'XX: Y',
|
77
|
+
' a = 11',
|
78
|
+
' c =?',
|
79
|
+
' d = 123',
|
80
|
+
'YY: XX',
|
81
|
+
' c =? 22',
|
82
|
+
' e =? 11',
|
83
|
+
)
|
84
|
+
engine.enumerate_params_by_node('X').should == Set.new(['a'])
|
85
|
+
engine.enumerate_params_by_node('XX').should == Set.new(['a', 'c'])
|
86
|
+
engine.enumerate_params_by_node('YY').should == Set.new(['a', 'c', 'e'])
|
87
|
+
engine.enumerate_params_by_node('Z').should == Set.new([])
|
89
88
|
end
|
90
89
|
end
|
data/spec/eval_spec.rb
CHANGED
@@ -1,316 +1,317 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
-
describe
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
r = engine.evaluate("A", ["x", "b"])
|
4
|
+
describe 'Delorean' do
|
5
|
+
let(:sset) do
|
6
|
+
TestContainer.new(
|
7
|
+
'AAA' =>
|
8
|
+
defn('X:',
|
9
|
+
' a =? 123',
|
10
|
+
' b = a*2',
|
11
|
+
)
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:engine) do
|
16
|
+
Delorean::Engine.new 'XXX', sset
|
17
|
+
end
|
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
|
+
' d = a ** 3 - 10*0.2',
|
26
|
+
)
|
27
|
+
|
28
|
+
engine.evaluate('A', ['a']).should == [123]
|
29
|
+
|
30
|
+
r = engine.evaluate('A', ['x', 'b'])
|
30
31
|
r.should == [-246, -124]
|
31
32
|
|
32
|
-
expect(engine.evaluate(
|
33
|
+
expect(engine.evaluate('A', 'd')).to eq 1_860_865.0
|
33
34
|
end
|
34
35
|
|
35
|
-
it
|
36
|
-
engine.parse defn(
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
it 'proper unary expression evaluation' do
|
37
|
+
engine.parse defn('A:',
|
38
|
+
' a = 123',
|
39
|
+
' c = -a + 1',
|
40
|
+
)
|
40
41
|
|
41
|
-
r = engine.evaluate(
|
42
|
+
r = engine.evaluate('A', 'c')
|
42
43
|
r.should == -122
|
43
44
|
end
|
44
45
|
|
45
|
-
it
|
46
|
-
engine.parse defn(
|
46
|
+
it 'proper string interpolation' do
|
47
|
+
engine.parse defn('A:',
|
47
48
|
' a = "\n123\n"',
|
48
|
-
|
49
|
+
)
|
49
50
|
|
50
|
-
r = engine.evaluate(
|
51
|
+
r = engine.evaluate('A', 'a')
|
51
52
|
r.should == "\n123\n"
|
52
53
|
end
|
53
54
|
|
54
|
-
it
|
55
|
-
engine.parse defn(
|
55
|
+
it 'should handle getattr in expressions' do
|
56
|
+
engine.parse defn('A:',
|
56
57
|
" a = {'x':123, 'y':456, 'z':789}",
|
57
|
-
|
58
|
-
|
59
|
-
engine.evaluate(
|
58
|
+
' b = A.a.x * A.a.y - A.a.z',
|
59
|
+
)
|
60
|
+
engine.evaluate('A', ['b']).should == [123 * 456 - 789]
|
60
61
|
end
|
61
62
|
|
62
|
-
it
|
63
|
-
engine.parse defn(
|
63
|
+
it 'should handle numeric getattr' do
|
64
|
+
engine.parse defn('A:',
|
64
65
|
" a = {1:123, 0:456, 'z':789, 2: {'a':444}}",
|
65
|
-
|
66
|
-
|
67
|
-
engine.evaluate(
|
66
|
+
' b = A.a.1 * A.a.0 - A.a.z - A.a.2.a',
|
67
|
+
)
|
68
|
+
engine.evaluate('A', ['b']).should == [123 * 456 - 789 - 444]
|
68
69
|
end
|
69
70
|
|
70
|
-
it
|
71
|
-
engine.parse defn(
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
71
|
+
it 'should be able to evaluate multiple node attrs' do
|
72
|
+
engine.parse defn('A:',
|
73
|
+
' a =? 123',
|
74
|
+
' b = a % 11',
|
75
|
+
' c = a / 4.0',
|
76
|
+
)
|
76
77
|
|
77
|
-
h = {
|
78
|
-
r = engine.evaluate(
|
78
|
+
h = { 'a' => 16 }
|
79
|
+
r = engine.evaluate('A', ['c', 'b'], h)
|
79
80
|
r.should == [4, 5]
|
80
81
|
end
|
81
82
|
|
82
|
-
it
|
83
|
-
engine.parse defn(
|
84
|
-
|
85
|
-
|
86
|
-
|
83
|
+
it 'should give error when accessing undefined attr' do
|
84
|
+
engine.parse defn('A:',
|
85
|
+
' a = 1',
|
86
|
+
' c = a.to_ss',
|
87
|
+
)
|
87
88
|
|
88
89
|
lambda {
|
89
|
-
|
90
|
+
engine.evaluate('A', 'c')
|
90
91
|
}.should raise_error(Delorean::InvalidGetAttribute)
|
91
92
|
end
|
92
93
|
|
93
|
-
it
|
94
|
-
engine.parse defn(
|
95
|
-
|
96
|
-
|
97
|
-
|
94
|
+
it 'should be able to call 0-ary functions without ()' do
|
95
|
+
engine.parse defn('A:',
|
96
|
+
' a = 1',
|
97
|
+
' d = a.to_s',
|
98
|
+
)
|
98
99
|
|
99
|
-
engine.evaluate(
|
100
|
+
engine.evaluate('A', 'd').should == '1'
|
100
101
|
end
|
101
102
|
|
102
|
-
it
|
103
|
-
engine.parse defn(
|
104
|
-
|
105
|
-
|
106
|
-
|
103
|
+
it 'should handle default param values' do
|
104
|
+
engine.parse defn('A:',
|
105
|
+
' a =? 123',
|
106
|
+
' c = a / 123.0',
|
107
|
+
)
|
107
108
|
|
108
|
-
r = engine.evaluate(
|
109
|
+
r = engine.evaluate('A', 'c')
|
109
110
|
r.should == 1
|
110
111
|
end
|
111
112
|
|
112
|
-
it
|
113
|
-
engine.parse defn(
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
engine.evaluate(
|
120
|
-
engine.evaluate(
|
121
|
-
end
|
122
|
-
|
123
|
-
it
|
124
|
-
engine.parse defn(
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
engine.evaluate(
|
134
|
-
engine.evaluate(
|
135
|
-
engine.evaluate(
|
136
|
-
end
|
137
|
-
|
138
|
-
it
|
139
|
-
engine.parse defn(
|
140
|
-
|
141
|
-
|
142
|
-
|
113
|
+
it 'order of attr evaluation should not matter' do
|
114
|
+
engine.parse defn('A:',
|
115
|
+
' a =? 1',
|
116
|
+
'B:',
|
117
|
+
' a =? 2',
|
118
|
+
' c = A.a',
|
119
|
+
)
|
120
|
+
engine.evaluate('B', %w[c a]).should == [1, 2]
|
121
|
+
engine.evaluate('B', %w[a c]).should == [2, 1]
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'params should behave properly with inheritance' do
|
125
|
+
engine.parse defn('A:',
|
126
|
+
' a =? 1',
|
127
|
+
'B: A',
|
128
|
+
' a =? 2',
|
129
|
+
'C: B',
|
130
|
+
' a =? 3',
|
131
|
+
' b = B.a',
|
132
|
+
' c = A.a',
|
133
|
+
)
|
134
|
+
engine.evaluate('C', %w[a b c]).should == [3, 2, 1]
|
135
|
+
engine.evaluate('C', %w[a b c], 'a' => 4).should == [4, 4, 4]
|
136
|
+
engine.evaluate('C', %w[c b a]).should == [1, 2, 3]
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should give error when param is undefined for eval' do
|
140
|
+
engine.parse defn('A:',
|
141
|
+
' a =?',
|
142
|
+
' c = a / 123.0',
|
143
|
+
)
|
143
144
|
|
144
145
|
lambda {
|
145
|
-
|
146
|
+
engine.evaluate('A', 'c')
|
146
147
|
}.should raise_error(Delorean::UndefinedParamError)
|
147
148
|
end
|
148
149
|
|
149
|
-
it
|
150
|
-
engine.parse defn(
|
151
|
-
|
152
|
-
|
153
|
-
|
150
|
+
it 'should handle simple param computation' do
|
151
|
+
engine.parse defn('A:',
|
152
|
+
' a =?',
|
153
|
+
' c = a / 123.0',
|
154
|
+
)
|
154
155
|
|
155
|
-
r = engine.evaluate(
|
156
|
+
r = engine.evaluate('A', 'c', 'a' => 123)
|
156
157
|
r.should == 1
|
157
158
|
end
|
158
159
|
|
159
|
-
it
|
160
|
-
engine.parse defn(
|
161
|
-
|
162
|
-
|
160
|
+
it 'should give error on unknown node' do
|
161
|
+
engine.parse defn('A:',
|
162
|
+
' a = 1',
|
163
|
+
)
|
163
164
|
|
164
165
|
lambda {
|
165
|
-
|
166
|
+
engine.evaluate('B', 'a')
|
166
167
|
}.should raise_error(Delorean::UndefinedNodeError)
|
167
168
|
end
|
168
169
|
|
169
|
-
it
|
170
|
-
engine.parse defn(
|
171
|
-
|
172
|
-
|
173
|
-
|
170
|
+
it 'should handle runtime errors and report module/line number' do
|
171
|
+
engine.parse defn('A:',
|
172
|
+
' a = 1/0',
|
173
|
+
' b = 10 * a',
|
174
|
+
)
|
174
175
|
|
175
176
|
begin
|
176
|
-
engine.evaluate(
|
177
|
-
rescue => exc
|
177
|
+
engine.evaluate('A', 'b')
|
178
|
+
rescue StandardError => exc
|
178
179
|
res = Delorean::Engine.grok_runtime_exception(exc)
|
179
180
|
end
|
180
181
|
|
181
182
|
res.should == {
|
182
|
-
|
183
|
-
|
183
|
+
'error' => 'divided by 0',
|
184
|
+
'backtrace' => [['XXX', 2, '/'], ['XXX', 2, 'a'], ['XXX', 3, 'b']],
|
184
185
|
}
|
185
186
|
end
|
186
187
|
|
187
|
-
it
|
188
|
-
engine.parse defn(
|
188
|
+
it 'should handle runtime errors 2' do
|
189
|
+
engine.parse defn('A:',
|
189
190
|
" b = Dummy.call_me_maybe('a', 'b')",
|
190
|
-
|
191
|
+
)
|
191
192
|
|
192
193
|
begin
|
193
|
-
engine.evaluate(
|
194
|
-
rescue => exc
|
194
|
+
engine.evaluate('A', 'b')
|
195
|
+
rescue StandardError => exc
|
195
196
|
res = Delorean::Engine.grok_runtime_exception(exc)
|
196
197
|
end
|
197
198
|
|
198
|
-
res[
|
199
|
+
res['backtrace'].should == [['XXX', 2, 'b']]
|
199
200
|
end
|
200
201
|
|
201
|
-
it
|
202
|
-
engine.parse defn(
|
202
|
+
it 'should handle optional args to external fns' do
|
203
|
+
engine.parse defn('A:',
|
203
204
|
" b = Dummy.one_or_two(['a', 'b'])",
|
204
205
|
" c = Dummy.one_or_two([1,2,3], ['a', 'b'])",
|
205
|
-
|
206
|
+
)
|
206
207
|
|
207
|
-
engine.evaluate(
|
208
|
-
engine.evaluate(
|
208
|
+
engine.evaluate('A', 'b').should == [['a', 'b'], nil]
|
209
|
+
engine.evaluate('A', 'c').should == [[1, 2, 3], ['a', 'b']]
|
209
210
|
end
|
210
211
|
|
211
|
-
it
|
212
|
-
engine.parse defn(
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
212
|
+
it 'should handle operator precedence properly' do
|
213
|
+
engine.parse defn('A:',
|
214
|
+
' b = 3+2*4-1',
|
215
|
+
' c = b*3+5',
|
216
|
+
' d = b*2-c*2',
|
217
|
+
' e = if (d < -10) then -123-1 else -456+1',
|
218
|
+
)
|
218
219
|
|
219
|
-
r = engine.evaluate(
|
220
|
+
r = engine.evaluate('A', 'd')
|
220
221
|
r.should == -50
|
221
222
|
|
222
|
-
r = engine.evaluate(
|
223
|
+
r = engine.evaluate('A', 'e')
|
223
224
|
r.should == -124
|
224
225
|
end
|
225
226
|
|
226
|
-
it
|
227
|
-
text = defn(
|
228
|
-
|
227
|
+
it 'should handle if/else' do
|
228
|
+
text = defn('A:',
|
229
|
+
' d =? -10',
|
229
230
|
' e = if d < -10 then "gungam"+"style" else "korea"'
|
230
|
-
|
231
|
+
)
|
231
232
|
|
232
233
|
engine.parse text
|
233
|
-
r = engine.evaluate(
|
234
|
-
r.should ==
|
235
|
-
|
236
|
-
r = engine.evaluate(
|
237
|
-
r.should ==
|
238
|
-
end
|
239
|
-
|
240
|
-
it
|
241
|
-
engine.parse defn(
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
r = engine.evaluate(
|
252
|
-
r.should == 123*123
|
253
|
-
r = engine.evaluate(
|
254
|
-
r.should == 123*123 + 5
|
255
|
-
end
|
256
|
-
|
257
|
-
it
|
258
|
-
engine.parse defn(
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
r = engine.evaluate(
|
266
|
-
r.should == 123*456
|
267
|
-
end
|
268
|
-
|
269
|
-
it
|
270
|
-
engine.parse defn(
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
r = engine.evaluate(
|
234
|
+
r = engine.evaluate('A', 'e', 'd' => -100)
|
235
|
+
r.should == 'gungamstyle'
|
236
|
+
|
237
|
+
r = engine.evaluate('A', 'e')
|
238
|
+
r.should == 'korea'
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'should be able to access specific node attrs ' do
|
242
|
+
engine.parse defn('A:',
|
243
|
+
' b = 123',
|
244
|
+
' c =?',
|
245
|
+
'B: A',
|
246
|
+
' b = 111',
|
247
|
+
' c = A.b * 123',
|
248
|
+
'C:',
|
249
|
+
' c = A.c + B.c',
|
250
|
+
)
|
251
|
+
|
252
|
+
r = engine.evaluate('B', 'c')
|
253
|
+
r.should == 123 * 123
|
254
|
+
r = engine.evaluate('C', 'c', 'c' => 5)
|
255
|
+
r.should == 123 * 123 + 5
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'should be able to access nodes and node attrs dynamically ' do
|
259
|
+
engine.parse defn('A:',
|
260
|
+
' b = 123',
|
261
|
+
'B:',
|
262
|
+
' b = A',
|
263
|
+
' c = b.b * 456',
|
264
|
+
)
|
265
|
+
|
266
|
+
r = engine.evaluate('B', 'c')
|
267
|
+
r.should == 123 * 456
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'should be able to call class methods on ActiveRecord classes' do
|
271
|
+
engine.parse defn('A:',
|
272
|
+
' b = Dummy.call_me_maybe(1, 2, 3, 4)',
|
273
|
+
' c = Dummy.call_me_maybe()',
|
274
|
+
' d = Dummy.call_me_maybe(5) + b + c',
|
275
|
+
)
|
276
|
+
r = engine.evaluate('A', ['b', 'c', 'd'])
|
276
277
|
r.should == [10, 0, 15]
|
277
278
|
end
|
278
279
|
|
279
|
-
it
|
280
|
-
engine.parse defn(
|
280
|
+
it 'should be able to access ActiveRecord whitelisted fns using .x syntax' do
|
281
|
+
engine.parse defn('A:',
|
281
282
|
' b = Dummy.i_just_met_you("CRJ", 1.234).name2',
|
282
|
-
|
283
|
-
r = engine.evaluate(
|
284
|
-
r.should ==
|
283
|
+
)
|
284
|
+
r = engine.evaluate('A', 'b')
|
285
|
+
r.should == 'CRJ-1.234'
|
285
286
|
end
|
286
287
|
|
287
|
-
it
|
288
|
-
engine.parse defn(
|
288
|
+
it 'should be able to get attr on Hash objects using a.b syntax' do
|
289
|
+
engine.parse defn('A:',
|
289
290
|
' b = Dummy.i_threw_a_hash_in_the_well()',
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
engine.evaluate(
|
291
|
+
' c = b.a',
|
292
|
+
' d = b.b',
|
293
|
+
' e = b.this_is_crazy',
|
294
|
+
)
|
295
|
+
engine.evaluate('A', %w[c d e]).should == [456, 789, nil]
|
295
296
|
end
|
296
297
|
|
297
|
-
it
|
298
|
-
engine.parse defn(
|
298
|
+
it 'get attr on nil should return nil' do
|
299
|
+
engine.parse defn('A:',
|
299
300
|
' b = nil',
|
300
301
|
' c = b.gaga',
|
301
302
|
' d = b.gaga || 55',
|
302
|
-
|
303
|
-
r = engine.evaluate(
|
303
|
+
)
|
304
|
+
r = engine.evaluate('A', ['b', 'c', 'd'])
|
304
305
|
r.should == [nil, nil, 55]
|
305
306
|
end
|
306
307
|
|
307
|
-
it
|
308
|
-
engine.parse defn(
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
engine.evaluate(
|
308
|
+
it 'should be able to get attr on node' do
|
309
|
+
engine.parse defn('A:',
|
310
|
+
' a = 123',
|
311
|
+
' b = A',
|
312
|
+
' c = b.a * 2',
|
313
|
+
)
|
314
|
+
engine.evaluate('A', %w[a c]).should == [123, 123 * 2]
|
314
315
|
end
|
315
316
|
|
316
317
|
getattr_code = <<eoc
|
@@ -326,80 +327,87 @@ E:
|
|
326
327
|
xx = [n.x for n in D.xs]
|
327
328
|
eoc
|
328
329
|
|
329
|
-
it
|
330
|
+
it 'should be able to get attr on node 2' do
|
330
331
|
engine.parse getattr_code
|
331
|
-
engine.evaluate(
|
332
|
+
engine.evaluate('E', 'xx').should == [1, 2, 3]
|
332
333
|
end
|
333
334
|
|
334
|
-
it
|
335
|
-
engine.parse defn(
|
336
|
-
|
337
|
-
)
|
338
|
-
|
335
|
+
it 'should be able to call class methods on AR classes in modules' do
|
336
|
+
engine.parse defn('A:',
|
337
|
+
' b = M::LittleDummy.heres_my_number(867, 5309)',
|
338
|
+
' c = M::N::NestedDummy.heres_my_number(867, 5309)',
|
339
|
+
)
|
340
|
+
r = engine.evaluate('A', 'b')
|
341
|
+
r.should == 867 + 5309
|
342
|
+
|
343
|
+
r = engine.evaluate('A', 'c')
|
339
344
|
r.should == 867 + 5309
|
340
345
|
end
|
341
346
|
|
342
|
-
it
|
343
|
-
engine.parse defn(
|
344
|
-
|
345
|
-
|
346
|
-
)
|
347
|
-
|
347
|
+
it 'should be able to use AR classes as values and call their methods' do
|
348
|
+
engine.parse defn('A:',
|
349
|
+
' a = M::LittleDummy',
|
350
|
+
' b = a.heres_my_number(867, 5309)',
|
351
|
+
' c = M::N::NestedDummy.heres_my_number(867, 5309)',
|
352
|
+
)
|
353
|
+
r = engine.evaluate('A', 'b')
|
354
|
+
r.should == 867 + 5309
|
355
|
+
|
356
|
+
r = engine.evaluate('A', 'c')
|
348
357
|
r.should == 867 + 5309
|
349
358
|
end
|
350
359
|
|
351
|
-
it
|
352
|
-
engine.parse defn(
|
353
|
-
|
354
|
-
|
355
|
-
r = engine.evaluate(
|
360
|
+
it 'should ignore undeclared params sent to eval which match attr names' do
|
361
|
+
engine.parse defn('A:',
|
362
|
+
' d = 12',
|
363
|
+
)
|
364
|
+
r = engine.evaluate('A', 'd', 'd' => 5, 'e' => 6)
|
356
365
|
r.should == 12
|
357
366
|
end
|
358
367
|
|
359
|
-
it
|
360
|
-
engine.parse defn(
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
+
it 'should handle different param defaults on nodes' do
|
369
|
+
engine.parse defn('A:',
|
370
|
+
' p =? 1',
|
371
|
+
' c = p * 123',
|
372
|
+
'B: A',
|
373
|
+
' p =? 2',
|
374
|
+
'C: A',
|
375
|
+
' p =? 3',
|
376
|
+
)
|
368
377
|
|
369
|
-
r = engine.evaluate(
|
370
|
-
r.should == 5*123
|
378
|
+
r = engine.evaluate('C', 'c', 'p' => 5)
|
379
|
+
r.should == 5 * 123
|
371
380
|
|
372
|
-
r = engine.evaluate(
|
373
|
-
r.should == 10*123
|
381
|
+
r = engine.evaluate('B', 'c', 'p' => 10)
|
382
|
+
r.should == 10 * 123
|
374
383
|
|
375
|
-
r = engine.evaluate(
|
376
|
-
r.should == 1*123
|
384
|
+
r = engine.evaluate('A', 'c')
|
385
|
+
r.should == 1 * 123
|
377
386
|
|
378
|
-
r = engine.evaluate(
|
379
|
-
r.should == 2*123
|
387
|
+
r = engine.evaluate('B', 'c')
|
388
|
+
r.should == 2 * 123
|
380
389
|
|
381
|
-
r = engine.evaluate(
|
382
|
-
r.should == 3*123
|
390
|
+
r = engine.evaluate('C', 'c')
|
391
|
+
r.should == 3 * 123
|
383
392
|
end
|
384
393
|
|
385
|
-
it
|
386
|
-
engine.parse defn(
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
394
|
+
it 'should allow overriding of attrs as params' do
|
395
|
+
engine.parse defn('A:',
|
396
|
+
' a = 2',
|
397
|
+
' b = a*3',
|
398
|
+
'B: A',
|
399
|
+
' a =?',
|
400
|
+
)
|
392
401
|
|
393
|
-
r = engine.evaluate(
|
394
|
-
r.should == 2*3
|
402
|
+
r = engine.evaluate('A', 'b', 'a' => 10)
|
403
|
+
r.should == 2 * 3
|
395
404
|
|
396
|
-
r = engine.evaluate(
|
397
|
-
r.should == 10*3
|
405
|
+
r = engine.evaluate('B', 'b', 'a' => 10)
|
406
|
+
r.should == 10 * 3
|
398
407
|
|
399
408
|
lambda {
|
400
|
-
r = engine.evaluate(
|
409
|
+
r = engine.evaluate('B', 'b')
|
401
410
|
}.should raise_error(Delorean::UndefinedParamError)
|
402
|
-
|
403
411
|
end
|
404
412
|
|
405
413
|
sample_script = <<eof
|
@@ -416,663 +424,750 @@ B: A
|
|
416
424
|
p =? 5
|
417
425
|
eof
|
418
426
|
|
419
|
-
it
|
427
|
+
it 'should allow overriding of attrs as params' do
|
420
428
|
engine.parse sample_script
|
421
429
|
|
422
|
-
r = engine.evaluate(
|
430
|
+
r = engine.evaluate('C', 'c')
|
423
431
|
r.should == 4
|
424
432
|
|
425
|
-
r = engine.evaluate(
|
433
|
+
r = engine.evaluate('B', 'pc')
|
426
434
|
r.should == 4 + 5
|
427
435
|
|
428
|
-
r = engine.evaluate(
|
436
|
+
r = engine.evaluate('C', 'pc')
|
429
437
|
r.should == 4 + 3
|
430
438
|
|
431
439
|
lambda {
|
432
|
-
r = engine.evaluate(
|
440
|
+
r = engine.evaluate('A', 'pc')
|
433
441
|
}.should raise_error(Delorean::UndefinedParamError)
|
434
442
|
end
|
435
443
|
|
436
|
-
it
|
444
|
+
it 'engines of same name should be independent' do
|
437
445
|
engin2 = Delorean::Engine.new(engine.module_name)
|
438
446
|
|
439
|
-
engine.parse defn(
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
engin2.parse defn(
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
engine.evaluate(
|
456
|
-
engin2.evaluate(
|
457
|
-
|
458
|
-
engine.evaluate(
|
459
|
-
engin2.evaluate(
|
460
|
-
[222.0, 222.0/5, 222.0/5*3]
|
461
|
-
|
462
|
-
engin2.evaluate(
|
447
|
+
engine.parse defn('A:',
|
448
|
+
' a = 123',
|
449
|
+
' b = a*3',
|
450
|
+
'B: A',
|
451
|
+
' c = b*2',
|
452
|
+
)
|
453
|
+
|
454
|
+
engin2.parse defn('A:',
|
455
|
+
' a = 222.0',
|
456
|
+
' b = a/5',
|
457
|
+
'B: A',
|
458
|
+
' c = b*3',
|
459
|
+
'C:',
|
460
|
+
' d = 111',
|
461
|
+
)
|
462
|
+
|
463
|
+
engine.evaluate('A', ['a', 'b']).should == [123, 123 * 3]
|
464
|
+
engin2.evaluate('A', ['a', 'b']).should == [222.0, 222.0 / 5]
|
465
|
+
|
466
|
+
engine.evaluate('B', ['a', 'b', 'c']).should == [123, 123 * 3, 123 * 3 * 2]
|
467
|
+
engin2.evaluate('B', ['a', 'b', 'c']).should ==
|
468
|
+
[222.0, 222.0 / 5, 222.0 / 5 * 3]
|
469
|
+
|
470
|
+
engin2.evaluate('C', 'd').should == 111
|
463
471
|
lambda {
|
464
|
-
engine.evaluate(
|
472
|
+
engine.evaluate('C', 'd')
|
465
473
|
}.should raise_error(Delorean::UndefinedNodeError)
|
466
474
|
end
|
467
475
|
|
468
|
-
it
|
476
|
+
it 'should handle invalid expression evaluation' do
|
469
477
|
# Should handle errors on expression such as -[] or -"xxx" or ("x"
|
470
478
|
# + []) better. Currently, it raises NoMethodError.
|
471
479
|
skip 'handle errors on expressions such as -[] or -"xxx"'
|
472
480
|
end
|
473
481
|
|
474
|
-
it
|
475
|
-
engine.parse defn(
|
476
|
-
|
477
|
-
|
482
|
+
it 'should eval lists' do
|
483
|
+
engine.parse defn('A:',
|
484
|
+
' b = []',
|
485
|
+
' c = [1,2,3]',
|
478
486
|
" d = [b, c, b, c, 1, 2, '123', 1.1, -1.23]",
|
479
|
-
|
480
|
-
|
487
|
+
' e = [1, 1+1, 1+1+1, 1*2*4]',
|
488
|
+
)
|
481
489
|
|
482
|
-
engine.evaluate(
|
490
|
+
engine.evaluate('A', %w[b c d e]).should ==
|
483
491
|
[[],
|
484
492
|
[1, 2, 3],
|
485
|
-
[[], [1, 2, 3], [], [1, 2, 3], 1, 2,
|
493
|
+
[[], [1, 2, 3], [], [1, 2, 3], 1, 2, '123', 1.1, -1.23],
|
486
494
|
[1, 2, 3, 8],
|
487
495
|
]
|
488
496
|
end
|
489
497
|
|
490
|
-
it
|
491
|
-
engine.parse defn(
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
498
|
+
it 'should eval list expressions' do
|
499
|
+
engine.parse defn('A:',
|
500
|
+
' b = []+[]',
|
501
|
+
' c = [1,2,3]+b',
|
502
|
+
' d = c*2',
|
503
|
+
)
|
496
504
|
|
497
|
-
engine.evaluate(
|
505
|
+
engine.evaluate('A', %w[b c d]).should ==
|
498
506
|
[[],
|
499
507
|
[1, 2, 3],
|
500
|
-
[1, 2, 3]*2,
|
508
|
+
[1, 2, 3] * 2,
|
501
509
|
]
|
502
510
|
end
|
503
511
|
|
504
|
-
it
|
505
|
-
engine.parse defn(
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
engine.evaluate(
|
511
|
-
[Set[], Set[5,10,15], Set[1,2,3,4,5]]
|
512
|
+
it 'should eval sets and set comprehension' do
|
513
|
+
engine.parse defn('A:',
|
514
|
+
' a = {-}',
|
515
|
+
' b = {i*5 for i in {1,2,3}}',
|
516
|
+
' c = {1,2,3} | {4,5}',
|
517
|
+
)
|
518
|
+
engine.evaluate('A', ['a', 'b', 'c']).should ==
|
519
|
+
[Set[], Set[5, 10, 15], Set[1, 2, 3, 4, 5]]
|
512
520
|
end
|
513
521
|
|
514
|
-
it
|
515
|
-
engine.parse defn(
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
engine.evaluate(
|
520
|
-
engine.evaluate(
|
522
|
+
it 'should eval list comprehension' do
|
523
|
+
engine.parse defn('A:',
|
524
|
+
' b = [i*5 for i in [1,2,3]]',
|
525
|
+
' c = [a-b for a, b in [[1,2],[4,3]]]'
|
526
|
+
)
|
527
|
+
engine.evaluate('A', 'b').should == [5, 10, 15]
|
528
|
+
engine.evaluate('A', 'c').should == [-1, 1]
|
521
529
|
end
|
522
530
|
|
523
|
-
it
|
524
|
-
engine.parse defn(
|
525
|
-
|
526
|
-
|
527
|
-
engine.evaluate(
|
528
|
-
|
531
|
+
it 'should eval nested list comprehension' do
|
532
|
+
engine.parse defn('A:',
|
533
|
+
' b = [[a+c for c in [4,5]] for a in [1,2,3]]',
|
534
|
+
)
|
535
|
+
engine.evaluate('A', 'b').should == [[5, 6], [6, 7], [7, 8]]
|
529
536
|
end
|
530
537
|
|
531
|
-
it
|
532
|
-
engine.parse defn(
|
533
|
-
|
534
|
-
|
535
|
-
engine.evaluate(
|
538
|
+
it 'should eval list comprehension variable override' do
|
539
|
+
engine.parse defn('A:',
|
540
|
+
' b = [b/2.0 for b in [1,2,3]]',
|
541
|
+
)
|
542
|
+
engine.evaluate('A', 'b').should == [0.5, 1.0, 1.5]
|
536
543
|
end
|
537
544
|
|
538
|
-
it
|
539
|
-
engine.parse defn(
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
engine.evaluate(
|
545
|
+
it 'should eval list comprehension variable override (2)' do
|
546
|
+
engine.parse defn('A:',
|
547
|
+
' a = 1',
|
548
|
+
' b = [a+1 for a in [1,2,3]]',
|
549
|
+
)
|
550
|
+
engine.evaluate('A', 'b').should == [2, 3, 4]
|
544
551
|
end
|
545
552
|
|
546
|
-
it
|
547
|
-
engine.parse defn(
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
engine.evaluate(
|
552
|
-
engine.evaluate(
|
553
|
+
it 'should eval conditional list comprehension' do
|
554
|
+
engine.parse defn('A:',
|
555
|
+
' b = [i*5 for i in [1,2,3,4,5] if i%2 == 1]',
|
556
|
+
' c = [i/10.0 for i in [1,2,3,4,5] if i>4]',
|
557
|
+
)
|
558
|
+
engine.evaluate('A', 'b').should == [5, 15, 25]
|
559
|
+
engine.evaluate('A', 'c').should == [0.5]
|
553
560
|
end
|
554
561
|
|
555
|
-
it
|
556
|
-
engine.parse defn(
|
557
|
-
|
558
|
-
|
559
|
-
engine.evaluate(
|
562
|
+
it 'should handle list comprehension unpacking' do
|
563
|
+
engine.parse defn('A:',
|
564
|
+
' b = [a-b for a, b in [[1,2],[20,10]]]',
|
565
|
+
)
|
566
|
+
engine.evaluate('A', 'b').should == [-1, 10]
|
560
567
|
end
|
561
568
|
|
562
|
-
it
|
563
|
-
skip
|
564
|
-
engine.parse defn(
|
569
|
+
it 'should handle list comprehension with conditions using loop var' do
|
570
|
+
skip 'need to fix'
|
571
|
+
engine.parse defn('A:',
|
565
572
|
" b = [n for n in {'pt' : 1} if n[1]+1]",
|
566
|
-
|
567
|
-
engine.evaluate(
|
573
|
+
)
|
574
|
+
engine.evaluate('A', 'b').should == [['pt', 1]]
|
568
575
|
end
|
569
576
|
|
570
|
-
it
|
571
|
-
engine.parse defn(
|
572
|
-
|
577
|
+
it 'should eval hashes' do
|
578
|
+
engine.parse defn('A:',
|
579
|
+
' b = {}',
|
573
580
|
" c = {'a':1, 'b': 2,'c':3}",
|
574
581
|
" d = {123*2: -123, 'b_b': 1+1}",
|
575
582
|
" e = {'x': 1, 'y': 1+1, 'z': 1+1+1, 'zz': 1*2*4}",
|
576
583
|
" f = {'a': nil, 'b': [1, nil, 2]}",
|
577
|
-
|
578
|
-
|
584
|
+
' g = {b:b, [b]:[1,23], []:345}',
|
585
|
+
)
|
579
586
|
|
580
|
-
engine.evaluate(
|
587
|
+
engine.evaluate('A', %w[b c d e f g]).should ==
|
581
588
|
[{},
|
582
|
-
{
|
583
|
-
{123*2
|
584
|
-
{
|
585
|
-
{
|
586
|
-
{{}=>{}, [{}]=>[1, 23], []=>345},
|
589
|
+
{ 'a' => 1, 'b' => 2, 'c' => 3 },
|
590
|
+
{ 123 * 2 => -123, 'b_b' => 2 },
|
591
|
+
{ 'x' => 1, 'y' => 2, 'z' => 3, 'zz' => 8 },
|
592
|
+
{ 'a' => nil, 'b' => [1, nil, 2] },
|
593
|
+
{ {} => {}, [{}] => [1, 23], [] => 345 },
|
587
594
|
]
|
588
595
|
end
|
589
596
|
|
590
|
-
it
|
591
|
-
engine.parse defn(
|
597
|
+
it 'handles literal hashes with conditionals' do
|
598
|
+
engine.parse defn('A:',
|
592
599
|
" a = {'a':1 if 123, 'b':'x' if nil}",
|
593
600
|
" b = {'a':a if a, 2: a if true, 'c':nil if 2*2}",
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
engine.evaluate(
|
599
|
-
{
|
600
|
-
{
|
601
|
-
{1=>{1=>2}, 2=>{2=>3}},
|
601
|
+
' c = 1>2',
|
602
|
+
' d = {1: {1: 2 if b}, 3: 3 if c, 2: {2: 3 if a}}',
|
603
|
+
)
|
604
|
+
|
605
|
+
engine.evaluate('A', %w[a b d]).should == [
|
606
|
+
{ 'a' => 1 },
|
607
|
+
{ 'a' => { 'a' => 1 }, 2 => { 'a' => 1 }, 'c' => nil },
|
608
|
+
{ 1 => { 1 => 2 }, 2 => { 2 => 3 } },
|
602
609
|
]
|
603
610
|
end
|
604
611
|
|
605
|
-
it
|
606
|
-
engine.parse defn(
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
engine.evaluate(
|
611
|
-
engine.evaluate(
|
612
|
+
it 'should eval hash comprehension' do
|
613
|
+
engine.parse defn('A:',
|
614
|
+
' b = {i*5 :i for i in [1,2,3]}',
|
615
|
+
' c = [kv for kv in {1:11, 2:22}]',
|
616
|
+
)
|
617
|
+
engine.evaluate('A', 'b').should == { 5 => 1, 10 => 2, 15 => 3 }
|
618
|
+
engine.evaluate('A', 'c').should == [[1, 11], [2, 22]]
|
612
619
|
end
|
613
620
|
|
614
|
-
it
|
615
|
-
engine.parse defn(
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
engine.evaluate(
|
623
|
-
engine.evaluate(
|
624
|
-
engine.evaluate(
|
621
|
+
it 'for-in-hash should iterate over key/value pairs' do
|
622
|
+
engine.parse defn('A:',
|
623
|
+
' b = {1: 11, 2: 22}',
|
624
|
+
' c = [kv[0]-kv[1] for kv in b]',
|
625
|
+
' d = {kv[0] : kv[1] for kv in b}',
|
626
|
+
' e = [kv for kv in b if kv[1]]',
|
627
|
+
' f = [k-v for k, v in b if k>1]',
|
628
|
+
)
|
629
|
+
engine.evaluate('A', 'c').should == [-10, -20]
|
630
|
+
engine.evaluate('A', 'd').should == { 1 => 11, 2 => 22 }
|
631
|
+
engine.evaluate('A', 'f').should == [-20]
|
625
632
|
|
626
633
|
# FIXME: this is a known bug in Delorean caused by the strange way
|
627
634
|
# that select iterates over hashes and provides args to the block.
|
628
635
|
# engine.evaluate("A", "e").should == [[1,11], [2,22]]
|
629
636
|
end
|
630
637
|
|
631
|
-
it
|
632
|
-
engine.parse defn(
|
633
|
-
|
634
|
-
|
635
|
-
engine.evaluate(
|
636
|
-
|
638
|
+
it 'should eval nested hash comprehension' do
|
639
|
+
engine.parse defn('A:',
|
640
|
+
' b = { a:{a+c:a-c for c in [4,5]} for a in [1,2,3]}',
|
641
|
+
)
|
642
|
+
engine.evaluate('A', 'b').should == {
|
643
|
+
1 => { 5 => -3, 6 => -4 },
|
644
|
+
2 => { 6 => -2, 7 => -3 },
|
645
|
+
3 => { 7 => -1, 8 => -2 }
|
646
|
+
}
|
637
647
|
end
|
638
648
|
|
639
|
-
it
|
640
|
-
engine.parse defn(
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
engine.evaluate(
|
645
|
-
engine.evaluate(
|
649
|
+
it 'should eval conditional hash comprehension' do
|
650
|
+
engine.parse defn('A:',
|
651
|
+
' b = {i*5:i+5 for i in [1,2,3,4,5] if i%2 == 1}',
|
652
|
+
' c = {i/10.0:i*10 for i in [1,2,3,4,5] if i>4}',
|
653
|
+
)
|
654
|
+
engine.evaluate('A', 'b').should == { 5 => 6, 15 => 8, 25 => 10 }
|
655
|
+
engine.evaluate('A', 'c').should == { 0.5 => 50 }
|
646
656
|
end
|
647
657
|
|
648
|
-
it
|
649
|
-
engine.parse defn(
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
658
|
+
it 'should eval node calls as intermediate results' do
|
659
|
+
engine.parse defn('A:',
|
660
|
+
' a =?',
|
661
|
+
' e = A(a=13)',
|
662
|
+
' d = e.a * 2',
|
663
|
+
' f = e.d / e.a',
|
664
|
+
)
|
655
665
|
|
656
|
-
engine.evaluate(
|
666
|
+
engine.evaluate('A', ['d', 'f']).should == [26, 2]
|
657
667
|
end
|
658
668
|
|
659
|
-
it
|
660
|
-
engine.parse defn(
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
669
|
+
it 'allows node calls from attrs' do
|
670
|
+
engine.parse defn('A:',
|
671
|
+
' a =?',
|
672
|
+
' c =?',
|
673
|
+
' b = a**2',
|
674
|
+
' e = A(a=13)',
|
665
675
|
" d = e(a=4, **{'c': 5})",
|
666
|
-
|
667
|
-
|
676
|
+
' f = d.b + d.c + e().a',
|
677
|
+
)
|
668
678
|
|
669
|
-
engine.evaluate(
|
679
|
+
engine.evaluate('A', ['f']).should == [16 + 5 + 13]
|
670
680
|
end
|
671
681
|
|
672
|
-
it
|
673
|
-
engine.parse defn(
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
engine.evaluate(
|
678
|
-
engine.evaluate(
|
682
|
+
it 'should eval multi-var hash comprehension' do
|
683
|
+
engine.parse defn('A:',
|
684
|
+
' b = {k*5 : v+1 for k, v in {1:2, 7:-30}}',
|
685
|
+
' c = [k-v for k, v in {1:2, 7:-30}]',
|
686
|
+
)
|
687
|
+
engine.evaluate('A', 'b').should == { 5 => 3, 35 => -29 }
|
688
|
+
engine.evaluate('A', 'c').should == [-1, 37]
|
679
689
|
end
|
680
690
|
|
681
|
-
it
|
682
|
-
engine.parse defn(
|
683
|
-
|
684
|
-
|
685
|
-
|
691
|
+
it 'should be able to amend node calls' do
|
692
|
+
engine.parse defn('A:',
|
693
|
+
' a =?',
|
694
|
+
' aa = a*2',
|
695
|
+
' c = A(a=12)',
|
686
696
|
" d = c+{'a':3}",
|
687
697
|
" f = c+{'a':4}",
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
698
|
+
' g = d.aa + f.aa',
|
699
|
+
' h = c(a=5).aa',
|
700
|
+
' j = d(a=6).aa',
|
701
|
+
)
|
692
702
|
|
693
|
-
engine.evaluate(
|
694
|
-
[3*2 + 4*2, 5*2, 6*2]
|
703
|
+
engine.evaluate('A', ['g', 'h', 'j']).should ==
|
704
|
+
[3 * 2 + 4 * 2, 5 * 2, 6 * 2]
|
695
705
|
end
|
696
706
|
|
697
|
-
it
|
698
|
-
engine.parse defn(
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
707
|
+
it 'should be able to amend node calls 2' do
|
708
|
+
engine.parse defn('A:',
|
709
|
+
' a =?',
|
710
|
+
' d = A(a=3)',
|
711
|
+
' e = [d.a, d(a=4).a]',
|
712
|
+
)
|
703
713
|
|
704
|
-
engine.evaluate(
|
714
|
+
engine.evaluate('A', ['e']).should == [[3, 4]]
|
705
715
|
end
|
706
716
|
|
707
|
-
it
|
708
|
-
engine.parse defn(
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
717
|
+
it 'should eval module calls 1' do
|
718
|
+
engine.parse defn('A:',
|
719
|
+
' a = 123',
|
720
|
+
' n = A',
|
721
|
+
' d = n().a',
|
722
|
+
)
|
713
723
|
|
714
|
-
engine.evaluate(
|
724
|
+
engine.evaluate('A', %w[d]).should == [123]
|
715
725
|
end
|
716
726
|
|
717
|
-
it
|
718
|
-
engine.parse defn(
|
719
|
-
|
720
|
-
|
727
|
+
it 'should eval module calls 2' do
|
728
|
+
engine.parse defn('A:',
|
729
|
+
' a = 123',
|
730
|
+
' b = 456 + a',
|
721
731
|
" n = 'A'",
|
722
732
|
" c = nil(x = 123, y = 456) % ['a', 'b']",
|
723
733
|
" d = n(x = 123, y = 456) % ['a', 'b']",
|
724
734
|
" e = nil() % ['b']",
|
725
|
-
|
735
|
+
)
|
726
736
|
|
727
|
-
engine.evaluate(
|
728
|
-
|
737
|
+
engine.evaluate('A', %w[n c d e]).should == [
|
738
|
+
'A',
|
739
|
+
{ 'a' => 123, 'b' => 579 },
|
740
|
+
{ 'a' => 123, 'b' => 579 },
|
741
|
+
{ 'b' => 579 }
|
742
|
+
]
|
729
743
|
end
|
730
744
|
|
731
|
-
it
|
732
|
-
engine.parse defn(
|
733
|
-
|
734
|
-
|
745
|
+
it 'should eval module calls 3' do
|
746
|
+
engine.parse defn('A:',
|
747
|
+
' a = 123',
|
748
|
+
'B:',
|
735
749
|
" n = 'A'",
|
736
|
-
|
737
|
-
|
750
|
+
' d = n().a',
|
751
|
+
)
|
738
752
|
|
739
|
-
engine.evaluate(
|
753
|
+
engine.evaluate('B', %w[d]).should == [123]
|
740
754
|
end
|
741
755
|
|
742
|
-
it
|
743
|
-
engine.parse defn(
|
744
|
-
|
745
|
-
|
746
|
-
|
756
|
+
it 'should be possible to implement recursive calls' do
|
757
|
+
engine.parse defn('A:',
|
758
|
+
' n =?',
|
759
|
+
' fact = if n <= 1 then 1 else n * A(n=n-1).fact',
|
760
|
+
)
|
747
761
|
|
748
|
-
engine.evaluate(
|
762
|
+
engine.evaluate('A', 'fact', 'n' => 10).should == 3_628_800
|
749
763
|
end
|
750
764
|
|
751
|
-
it
|
752
|
-
engine.parse defn(
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
engine.evaluate(
|
765
|
+
it 'should eval module calls by node name' do
|
766
|
+
engine.parse defn('A:',
|
767
|
+
' a = 123',
|
768
|
+
' b = A().a',
|
769
|
+
)
|
770
|
+
engine.evaluate('A', 'b').should == 123
|
757
771
|
end
|
758
772
|
|
759
|
-
it
|
760
|
-
engine.parse defn(
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
engine.evaluate(
|
773
|
+
it 'should eval multiline expressions' do
|
774
|
+
engine.parse defn('A:',
|
775
|
+
' a = 1',
|
776
|
+
' b = [a+1',
|
777
|
+
' for a in [1,2,3]',
|
778
|
+
' ]',
|
779
|
+
)
|
780
|
+
engine.evaluate('A', 'b').should == [2, 3, 4]
|
767
781
|
end
|
768
782
|
|
769
|
-
it
|
770
|
-
engine.parse defn(
|
771
|
-
|
772
|
-
|
773
|
-
|
783
|
+
it 'should eval multiline expressions (2)' do
|
784
|
+
engine.parse defn('A:',
|
785
|
+
' a = 123',
|
786
|
+
' b = 456 + ',
|
787
|
+
' a',
|
774
788
|
" n = 'A'",
|
775
|
-
|
789
|
+
' c = nil(x = 123,',
|
776
790
|
" y = 456) % ['a', 'b']",
|
777
|
-
|
791
|
+
' d = n(',
|
778
792
|
" x = 123, y = 456) % ['a', 'b']",
|
779
|
-
|
793
|
+
' e = nil(',
|
780
794
|
" ) % ['b']",
|
781
|
-
|
795
|
+
)
|
782
796
|
|
783
|
-
engine.evaluate(
|
784
|
-
|
797
|
+
engine.evaluate('A', %w[n c d e]).should == [
|
798
|
+
'A',
|
799
|
+
{ 'a' => 123, 'b' => 579 },
|
800
|
+
{ 'a' => 123, 'b' => 579 },
|
801
|
+
{ 'b' => 579 }
|
802
|
+
]
|
785
803
|
end
|
786
804
|
|
787
|
-
it
|
788
|
-
engine.parse defn(
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
805
|
+
it 'should eval in expressions' do
|
806
|
+
engine.parse defn('A:',
|
807
|
+
' a = [1,2,3,33,44]',
|
808
|
+
' s = {22,33,44}',
|
809
|
+
' b = (1 in a) && (2 in {22,44})',
|
810
|
+
' c = (2 in a) && (22 in s)',
|
811
|
+
' d = [i*2 for i in s if i in a]',
|
812
|
+
)
|
795
813
|
|
796
|
-
engine.evaluate(
|
814
|
+
engine.evaluate('A', %w[b c d]).should ==
|
797
815
|
[false, true, [66, 88]]
|
798
816
|
end
|
799
817
|
|
800
|
-
it
|
801
|
-
engine.parse defn(
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
engine.evaluate(
|
809
|
-
[111, 222, 456*2]
|
810
|
-
end
|
811
|
-
|
812
|
-
it
|
813
|
-
sset.merge(
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
e2 = sset.get_engine(
|
832
|
-
|
833
|
-
e2.evaluate(
|
818
|
+
it 'should eval imports' do
|
819
|
+
engine.parse defn('import AAA',
|
820
|
+
'A:',
|
821
|
+
' b = 456',
|
822
|
+
'B: AAA::X',
|
823
|
+
' a = 111',
|
824
|
+
' c = AAA::X(a=456).b',
|
825
|
+
)
|
826
|
+
engine.evaluate('B', ['a', 'b', 'c'], {}).should ==
|
827
|
+
[111, 222, 456 * 2]
|
828
|
+
end
|
829
|
+
|
830
|
+
it 'should eval imports (2)' do
|
831
|
+
sset.merge(
|
832
|
+
'BBB' =>
|
833
|
+
defn('import AAA',
|
834
|
+
'B: AAA::X',
|
835
|
+
' a = 111',
|
836
|
+
' c = AAA::X(a=-1).b',
|
837
|
+
' d = a * 2',
|
838
|
+
),
|
839
|
+
'CCC' =>
|
840
|
+
defn('import BBB',
|
841
|
+
'import AAA',
|
842
|
+
'B: BBB::B',
|
843
|
+
' e = d * 3',
|
844
|
+
'C: AAA::X',
|
845
|
+
' d = b * 3',
|
846
|
+
),
|
847
|
+
)
|
848
|
+
|
849
|
+
e2 = sset.get_engine('BBB')
|
850
|
+
|
851
|
+
e2.evaluate('B', ['a', 'b', 'c', 'd']).should ==
|
834
852
|
[111, 222, -2, 222]
|
835
853
|
|
836
|
-
engine.parse defn(
|
837
|
-
|
838
|
-
|
839
|
-
|
854
|
+
engine.parse defn('import BBB',
|
855
|
+
'B: BBB::B',
|
856
|
+
' e = d + 3',
|
857
|
+
)
|
840
858
|
|
841
|
-
engine.evaluate(
|
859
|
+
engine.evaluate('B', ['a', 'b', 'c', 'd', 'e']).should ==
|
842
860
|
[111, 222, -2, 222, 225]
|
843
861
|
|
844
|
-
e4 = sset.get_engine(
|
862
|
+
e4 = sset.get_engine('CCC')
|
845
863
|
|
846
|
-
e4.evaluate(
|
864
|
+
e4.evaluate('B', ['a', 'b', 'c', 'd', 'e']).should ==
|
847
865
|
[111, 222, -2, 222, 666]
|
848
866
|
|
849
|
-
e4.evaluate(
|
850
|
-
end
|
851
|
-
|
852
|
-
it
|
853
|
-
sset.merge(
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
e4 = sset.get_engine(
|
864
|
-
e4.evaluate(
|
865
|
-
e4.evaluate(
|
866
|
-
end
|
867
|
-
|
868
|
-
it
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
867
|
+
e4.evaluate('C', ['a', 'b', 'd']).should == [123, 123 * 2, 123 * 3 * 2]
|
868
|
+
end
|
869
|
+
|
870
|
+
it 'should eval imports (3)' do
|
871
|
+
sset.merge(
|
872
|
+
'BBB' => getattr_code,
|
873
|
+
'CCC' =>
|
874
|
+
defn('import BBB',
|
875
|
+
'X:',
|
876
|
+
' xx = [n.x for n in BBB::D().xs]',
|
877
|
+
' yy = [n.x for n in BBB::D.xs]',
|
878
|
+
),
|
879
|
+
)
|
880
|
+
|
881
|
+
e4 = sset.get_engine('CCC')
|
882
|
+
e4.evaluate('X', 'xx').should == [1, 2, 3]
|
883
|
+
e4.evaluate('X', 'yy').should == [1, 2, 3]
|
884
|
+
end
|
885
|
+
|
886
|
+
it 'should eval imports (4) - with ::' do
|
887
|
+
sset.merge(
|
888
|
+
'BBB' => getattr_code,
|
889
|
+
'BBB::A' => defn(
|
890
|
+
'X:',
|
891
|
+
' xx = [1, 2, 3]'
|
892
|
+
),
|
893
|
+
'BBB::A::CC' => defn(
|
894
|
+
'X:',
|
895
|
+
' xx = [1, 2, 3]'
|
896
|
+
),
|
897
|
+
'DDD__Ef__Gh' => defn(
|
898
|
+
'import BBB',
|
899
|
+
'import BBB::A',
|
900
|
+
'import BBB::A::CC',
|
901
|
+
'EfNode:',
|
902
|
+
' g = BBB::D.xs',
|
903
|
+
' gh = BBB::A::X.xx',
|
904
|
+
),
|
905
|
+
'CCC' =>
|
906
|
+
defn('import BBB',
|
907
|
+
'import DDD__Ef__Gh',
|
908
|
+
'X:',
|
909
|
+
' xx = [n.x for n in BBB::D().xs]',
|
910
|
+
' yy = [n.x for n in BBB::D.xs]',
|
911
|
+
' zz = [n * 2 for n in DDD__Ef__Gh::EfNode.gh]',
|
912
|
+
),
|
913
|
+
)
|
914
|
+
|
915
|
+
e4 = sset.get_engine('CCC')
|
916
|
+
e4.evaluate('X', 'xx').should == [1, 2, 3]
|
917
|
+
e4.evaluate('X', 'zz').should == [2, 4, 6]
|
918
|
+
end
|
919
|
+
|
920
|
+
it 'should eval imports (4) - inheritance - with ::' do
|
921
|
+
sset.merge(
|
922
|
+
'BBB' => getattr_code,
|
923
|
+
'BBB::A' => defn(
|
924
|
+
'X:',
|
925
|
+
' xx = [1, 2, 3]'
|
926
|
+
),
|
927
|
+
'BBB::A::CC' => defn(
|
928
|
+
'X:',
|
929
|
+
' xx = [1, 2, 3]'
|
930
|
+
),
|
931
|
+
'DDD__Ef__Gh' => defn(
|
932
|
+
'import BBB',
|
933
|
+
'import BBB::A',
|
934
|
+
'import BBB::A::CC',
|
935
|
+
'EfNode: BBB::A::CC::X',
|
936
|
+
' g = xx',
|
937
|
+
),
|
938
|
+
'CCC' =>
|
939
|
+
defn('import BBB',
|
940
|
+
'import DDD__Ef__Gh',
|
941
|
+
'X:',
|
942
|
+
' zz = [n * 2 for n in DDD__Ef__Gh::EfNode.g]',
|
943
|
+
),
|
944
|
+
)
|
945
|
+
|
946
|
+
e4 = sset.get_engine('CCC')
|
947
|
+
e4.evaluate('X', 'zz').should == [2, 4, 6]
|
948
|
+
end
|
949
|
+
|
950
|
+
it 'can eval indexing' do
|
951
|
+
engine.parse defn('A:',
|
952
|
+
' a = [1,2,3]',
|
953
|
+
' b = a[1]',
|
954
|
+
' c = a[-1]',
|
873
955
|
" d = {'a' : 123, 'b': 456}",
|
874
956
|
" e = d['b']",
|
875
|
-
|
876
|
-
|
877
|
-
r = engine.evaluate(
|
878
|
-
r.should == [2, 3, 456, [2,3]]
|
957
|
+
' f = a[1,2]',
|
958
|
+
)
|
959
|
+
r = engine.evaluate('A', ['b', 'c', 'e', 'f'])
|
960
|
+
r.should == [2, 3, 456, [2, 3]]
|
879
961
|
end
|
880
962
|
|
881
|
-
it
|
882
|
-
engine.parse defn(
|
883
|
-
|
963
|
+
it 'can eval indexing 2' do
|
964
|
+
engine.parse defn('A:',
|
965
|
+
' a = 1',
|
884
966
|
" b = {'x' : 123, 'y': 456}",
|
885
967
|
" c = A() % ['a', 'b']",
|
886
968
|
" d = c['b'].x * c['a'] - c['b'].y",
|
887
|
-
|
888
|
-
r = engine.evaluate(
|
889
|
-
r.should ==
|
890
|
-
|
969
|
+
)
|
970
|
+
r = engine.evaluate('A', ['a', 'b', 'c', 'd'])
|
971
|
+
r.should == [
|
972
|
+
1,
|
973
|
+
{ 'x' => 123, 'y' => 456 },
|
974
|
+
{ 'a' => 1, 'b' => { 'x' => 123, 'y' => 456 } },
|
975
|
+
-333
|
976
|
+
]
|
891
977
|
end
|
892
978
|
|
893
|
-
it
|
894
|
-
engine.parse defn(
|
895
|
-
|
979
|
+
it 'can handle exceptions with / syntax' do
|
980
|
+
engine.parse defn('A:',
|
981
|
+
' a = 1',
|
896
982
|
" b = {'x' : 123, 'y': 456}",
|
897
983
|
" e = ERR('hello')",
|
898
984
|
" c = A() / ['a', 'b']",
|
899
985
|
" d = A() / ['a', 'e']",
|
900
986
|
" f = A() / 'a'",
|
901
|
-
|
902
|
-
r = engine.evaluate(
|
903
|
-
r.should ==
|
904
|
-
|
987
|
+
)
|
988
|
+
r = engine.evaluate('A', ['a', 'b', 'c'])
|
989
|
+
r.should == [
|
990
|
+
1,
|
991
|
+
{ 'x' => 123, 'y' => 456 },
|
992
|
+
{ 'a' => 1, 'b' => { 'x' => 123, 'y' => 456 } }
|
993
|
+
]
|
905
994
|
|
906
|
-
r = engine.evaluate(
|
907
|
-
r.should ==
|
908
|
-
|
995
|
+
r = engine.evaluate('A', ['a', 'd'])
|
996
|
+
r.should == [
|
997
|
+
1,
|
998
|
+
{ 'error' => 'hello', 'backtrace' => [['XXX', 4, 'e'], ['XXX', 6, 'd']] }
|
999
|
+
]
|
909
1000
|
|
910
|
-
r = engine.evaluate(
|
1001
|
+
r = engine.evaluate('A', ['f'])
|
911
1002
|
r.should == [1]
|
912
1003
|
end
|
913
1004
|
|
914
|
-
it
|
915
|
-
engine.parse defn(
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
engine.evaluate(
|
927
|
-
engine.evaluate(
|
928
|
-
engine.evaluate(
|
929
|
-
engine.evaluate(
|
930
|
-
engine.evaluate(
|
931
|
-
engine.evaluate(
|
932
|
-
end
|
933
|
-
|
934
|
-
it
|
935
|
-
engine.parse defn(
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
1005
|
+
it 'should properly eval overridden attrs' do
|
1006
|
+
engine.parse defn('A:',
|
1007
|
+
' a = 5',
|
1008
|
+
' b = a',
|
1009
|
+
'B: A',
|
1010
|
+
' a = 2',
|
1011
|
+
' x = A.b - B.b',
|
1012
|
+
' k = [A.b, B.b]',
|
1013
|
+
' l = [x.b for x in [A, B]]',
|
1014
|
+
' m = [x().b for x in [A, B]]',
|
1015
|
+
)
|
1016
|
+
|
1017
|
+
engine.evaluate('A', 'b').should == 5
|
1018
|
+
engine.evaluate('B', 'b').should == 2
|
1019
|
+
engine.evaluate('B', 'x').should == 3
|
1020
|
+
engine.evaluate('B', 'k').should == [5, 2]
|
1021
|
+
engine.evaluate('B', 'l').should == [5, 2]
|
1022
|
+
engine.evaluate('B', 'm').should == [5, 2]
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
it 'implements simple version of self (_)' do
|
1026
|
+
engine.parse defn('B:',
|
1027
|
+
' a =?',
|
1028
|
+
' b =?',
|
1029
|
+
' x = a - b',
|
1030
|
+
'A:',
|
1031
|
+
' a =?',
|
1032
|
+
' b =?',
|
1033
|
+
' x = _.a * _.b',
|
1034
|
+
' y = a && _',
|
1035
|
+
' z = (B() + _).x',
|
1036
|
+
' w = B(**_).x',
|
946
1037
|
" v = {**_, 'a': 123}",
|
947
|
-
|
948
|
-
|
949
|
-
engine.evaluate(
|
950
|
-
h = {
|
951
|
-
engine.evaluate(
|
952
|
-
engine.evaluate(
|
953
|
-
engine.evaluate(
|
954
|
-
engine.evaluate(
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
1038
|
+
)
|
1039
|
+
|
1040
|
+
engine.evaluate('A', 'x', 'a' => 3, 'b' => 5).should == 15
|
1041
|
+
h = { 'a' => 1, 'b' => 2, 'c' => 3 }
|
1042
|
+
engine.evaluate('A', 'y', 'a' => 1, 'b' => 2, 'c' => 3).should == h
|
1043
|
+
engine.evaluate('A', 'z', 'a' => 1, 'b' => 2, 'c' => 3).should == -1
|
1044
|
+
engine.evaluate('A', 'w', 'a' => 4, 'b' => 5, 'c' => 3).should == -1
|
1045
|
+
engine.evaluate('A', 'v', 'a' => 4, 'b' => 5, 'c' => 3).should == {
|
1046
|
+
'a' => 123, 'b' => 5, 'c' => 3
|
1047
|
+
}
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
it 'implements positional args in node calls' do
|
1051
|
+
engine.parse defn('B:',
|
1052
|
+
' a =?',
|
1053
|
+
' b =?',
|
1054
|
+
' x = (_.0 - _.1) * (a - b)',
|
1055
|
+
' y = [_.0, _.1, _.2]',
|
1056
|
+
'A:',
|
1057
|
+
' a = _.0 - _.1',
|
1058
|
+
' z = B(10, 20, a=3, b=7).x',
|
967
1059
|
" y = B('x', 'y').y",
|
968
|
-
|
969
|
-
engine.evaluate(
|
970
|
-
[123-456, 40, [
|
1060
|
+
)
|
1061
|
+
engine.evaluate('A', ['a', 'z', 'y'], 0 => 123, 1 => 456).should ==
|
1062
|
+
[123 - 456, 40, ['x', 'y', nil]]
|
971
1063
|
end
|
972
1064
|
|
973
|
-
it
|
974
|
-
engine.parse defn(
|
1065
|
+
it 'can call 0-arity functions in list comprehension' do
|
1066
|
+
engine.parse defn('A:',
|
975
1067
|
' b = [x.name for x in Dummy.all_of_me]',
|
976
|
-
|
977
|
-
r = engine.evaluate(
|
978
|
-
expect(r).to eq [
|
1068
|
+
)
|
1069
|
+
r = engine.evaluate('A', 'b')
|
1070
|
+
expect(r).to eq ['hello']
|
979
1071
|
end
|
980
1072
|
|
981
|
-
it
|
982
|
-
engine.parse defn(
|
983
|
-
|
984
|
-
|
985
|
-
|
1073
|
+
it 'node calls are not memoized/cached' do
|
1074
|
+
engine.parse defn('A:',
|
1075
|
+
' x = Dummy.side_effect',
|
1076
|
+
'B: A',
|
1077
|
+
' x = (A() + _).x + (A() + _).x'
|
986
1078
|
)
|
987
|
-
r = engine.evaluate(
|
1079
|
+
r = engine.evaluate('B', 'x')
|
988
1080
|
expect(r).to eq 3
|
989
1081
|
end
|
990
1082
|
|
991
|
-
it
|
992
|
-
engine.parse defn(
|
993
|
-
|
994
|
-
|
995
|
-
|
1083
|
+
it 'node calls with double splats' do
|
1084
|
+
engine.parse defn('A:',
|
1085
|
+
' a =?',
|
1086
|
+
' b =?',
|
1087
|
+
' c = a+b',
|
996
1088
|
" h = {'a': 123}",
|
997
1089
|
" k = {'b': 456}",
|
998
|
-
|
1090
|
+
' x = A(**h, **k).c'
|
999
1091
|
)
|
1000
|
-
r = engine.evaluate(
|
1092
|
+
r = engine.evaluate('A', 'x')
|
1001
1093
|
expect(r).to eq 579
|
1002
1094
|
end
|
1003
1095
|
|
1004
|
-
it
|
1005
|
-
engine.parse defn(
|
1006
|
-
|
1007
|
-
|
1096
|
+
it 'hash literal with double splats' do
|
1097
|
+
engine.parse defn('A:',
|
1098
|
+
' a =?',
|
1099
|
+
' b =?',
|
1008
1100
|
" h = {'a': 123, **a}",
|
1009
1101
|
" k = {'b': 456, **h, **a, **b}",
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1102
|
+
' l = {**k}',
|
1103
|
+
' m = {**k, 1:1, 2:2, 3:33}',
|
1104
|
+
' n = {**k if false, 1:1, 2:2, 3:33}',
|
1013
1105
|
)
|
1014
|
-
r = engine.evaluate(
|
1015
|
-
|
1106
|
+
r = engine.evaluate(
|
1107
|
+
'A',
|
1108
|
+
['h', 'k', 'l', 'm', 'n'],
|
1109
|
+
'a' => { 3 => 3, 4 => 4 }, 'b' => { 5 => 5, 'a' => 'aa' }
|
1110
|
+
)
|
1111
|
+
|
1016
1112
|
expect(r).to eq [
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
end
|
1024
|
-
|
1025
|
-
it "understands openstructs" do
|
1026
|
-
engine.parse defn("A:",
|
1027
|
-
" os = Dummy.returns_openstruct",
|
1028
|
-
" abc = os.abc",
|
1029
|
-
" not_found = os.not_found"
|
1030
|
-
)
|
1031
|
-
r = engine.evaluate("A", ["os", "abc", "not_found"])
|
1032
|
-
expect(r[0].abc).to eq("def")
|
1033
|
-
expect(r[1]).to eq("def")
|
1034
|
-
expect(r[2]).to be_nil
|
1113
|
+
{ 'a' => 123, 3 => 3, 4 => 4 },
|
1114
|
+
{ 'b' => 456, 'a' => 'aa', 3 => 3, 4 => 4, 5 => 5 },
|
1115
|
+
{ 'b' => 456, 'a' => 'aa', 3 => 3, 4 => 4, 5 => 5 },
|
1116
|
+
{ 'b' => 456, 'a' => 'aa', 3 => 33, 4 => 4, 5 => 5, 1 => 1, 2 => 2 },
|
1117
|
+
{ 1 => 1, 2 => 2, 3 => 33 },
|
1118
|
+
]
|
1035
1119
|
end
|
1036
1120
|
|
1037
|
-
it
|
1121
|
+
it 'understands openstructs' do
|
1122
|
+
engine.parse defn('A:',
|
1123
|
+
' os = Dummy.returns_openstruct',
|
1124
|
+
' abc = os.abc',
|
1125
|
+
' not_found = os.not_found'
|
1126
|
+
)
|
1127
|
+
r = engine.evaluate('A', ['os', 'abc', 'not_found'])
|
1128
|
+
expect(r[0].abc).to eq('def')
|
1129
|
+
expect(r[1]).to eq('def')
|
1130
|
+
expect(r[2]).to be_nil
|
1131
|
+
end
|
1038
1132
|
|
1133
|
+
it 'can use nodes as continuations' do
|
1039
1134
|
# FIME: This is actually a trivial exmaple. Ideally we should be
|
1040
1135
|
# able to pass arguments to the nodes when evaluating ys. If the
|
1041
1136
|
# arguments do not change the computation of "x" then "x" should
|
1042
1137
|
# not be recomputed. This would need some flow analysis though.
|
1043
1138
|
|
1044
|
-
engine.parse defn(
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
)
|
1054
|
-
r = engine.evaluate(
|
1139
|
+
engine.parse defn('A:',
|
1140
|
+
' a =?',
|
1141
|
+
' x = Dummy.side_effect',
|
1142
|
+
' y = x*a',
|
1143
|
+
'B:',
|
1144
|
+
' ns = [A(a=a) for a in [1, 1, 1]]',
|
1145
|
+
' xs = [n.x for n in ns]',
|
1146
|
+
' ys = [n.y for n in ns]',
|
1147
|
+
' res = [xs, ys]',
|
1148
|
+
)
|
1149
|
+
r = engine.evaluate('B', 'res')
|
1055
1150
|
expect(r[1]).to eq r[0]
|
1056
1151
|
end
|
1057
1152
|
|
1058
|
-
it
|
1059
|
-
engine.parse defn(
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1153
|
+
it 'can use nodes as continuations -- simple' do
|
1154
|
+
engine.parse defn('A:',
|
1155
|
+
' x = Dummy.side_effect',
|
1156
|
+
' y = x',
|
1157
|
+
'B:',
|
1158
|
+
' ns = A()',
|
1159
|
+
' res = [ns.x, ns.y]',
|
1065
1160
|
" res2 = ns % ['x', 'y']",
|
1066
1161
|
)
|
1067
|
-
r = engine.evaluate(
|
1162
|
+
r = engine.evaluate('B', 'res')
|
1068
1163
|
expect(r[1]).to eq r[0]
|
1069
1164
|
|
1070
1165
|
# this one works as expected
|
1071
|
-
r2 = engine.evaluate(
|
1166
|
+
r2 = engine.evaluate('B', 'res2')
|
1072
1167
|
expect(r2.values.uniq.length).to eq 1
|
1073
1168
|
end
|
1074
1169
|
|
1075
|
-
it
|
1170
|
+
it 'Implements ability to use overridden superclass attrs' do
|
1076
1171
|
code = <<-DELOREAN
|
1077
1172
|
A:
|
1078
1173
|
x = 123
|
@@ -1086,7 +1181,7 @@ eof
|
|
1086
1181
|
|
1087
1182
|
engine.parse code.gsub(/^ /, '')
|
1088
1183
|
|
1089
|
-
r = engine.evaluate(
|
1184
|
+
r = engine.evaluate('B', ['x', 'y', 'xx', 'yy'])
|
1090
1185
|
expect(r).to eq [5, 2460, 128, 1230]
|
1091
1186
|
end
|
1092
1187
|
end
|