delorean_lang 0.5.1 → 0.5.2
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/.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
|