subaltern 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.txt +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +32 -0
- data/Rakefile +85 -0
- data/TODO.txt +32 -0
- data/lib/subaltern.rb +39 -0
- data/lib/subaltern/context.rb +95 -0
- data/lib/subaltern/errors.rb +78 -0
- data/lib/subaltern/evaluator.rb +597 -0
- data/lib/subaltern/kernel.rb +64 -0
- data/lib/subaltern/version.rb +28 -0
- data/spec/array_spec.rb +74 -0
- data/spec/basics_spec.rb +125 -0
- data/spec/boolean_spec.rb +129 -0
- data/spec/context_spec.rb +23 -0
- data/spec/control_flow_spec.rb +251 -0
- data/spec/evil_spec.rb +106 -0
- data/spec/fixnum_spec.rb +20 -0
- data/spec/functions_spec.rb +123 -0
- data/spec/hash_spec.rb +88 -0
- data/spec/loops_spec.rb +108 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/string_spec.rb +26 -0
- data/spec/support/subaltern_helper.rb +9 -0
- data/subaltern.gemspec +34 -0
- metadata +122 -0
@@ -0,0 +1,251 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
describe Subaltern do
|
6
|
+
|
7
|
+
describe 'if' do
|
8
|
+
|
9
|
+
it 'works (then)' do
|
10
|
+
|
11
|
+
Subaltern.eval(%{
|
12
|
+
if true
|
13
|
+
:ok
|
14
|
+
end
|
15
|
+
}).should == :ok
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'works (complex expression)' do
|
19
|
+
|
20
|
+
Subaltern.eval(%{
|
21
|
+
if false or true
|
22
|
+
:ok
|
23
|
+
end
|
24
|
+
}).should == :ok
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'works (else)' do
|
28
|
+
|
29
|
+
Subaltern.eval(%{
|
30
|
+
if false
|
31
|
+
:not_ok
|
32
|
+
else
|
33
|
+
:ok
|
34
|
+
end
|
35
|
+
}).should == :ok
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'works postfix' do
|
39
|
+
|
40
|
+
Subaltern.eval(%{
|
41
|
+
'ok' if true
|
42
|
+
}).should == 'ok'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'elsif' do
|
47
|
+
|
48
|
+
it 'works' do
|
49
|
+
|
50
|
+
Subaltern.eval(%{
|
51
|
+
if false
|
52
|
+
:not_ok
|
53
|
+
elsif true
|
54
|
+
:ok
|
55
|
+
else
|
56
|
+
:really_not_ok
|
57
|
+
end
|
58
|
+
}).should == :ok
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'unless' do
|
63
|
+
|
64
|
+
it 'works' do
|
65
|
+
|
66
|
+
Subaltern.eval(%{
|
67
|
+
unless false
|
68
|
+
:ok
|
69
|
+
else
|
70
|
+
:not_ok
|
71
|
+
end
|
72
|
+
}).should == :ok
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'case' do
|
77
|
+
|
78
|
+
it 'works' do
|
79
|
+
|
80
|
+
Subaltern.eval(%{
|
81
|
+
case true
|
82
|
+
when true then :ok
|
83
|
+
when false then :not_ok
|
84
|
+
else :really_not_ok
|
85
|
+
end
|
86
|
+
}).should == :ok
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'works (else)' do
|
90
|
+
|
91
|
+
Subaltern.eval(%{
|
92
|
+
case false
|
93
|
+
when true then :not_ok
|
94
|
+
else :ok
|
95
|
+
end
|
96
|
+
}).should == :ok
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'works (multiline)' do
|
100
|
+
|
101
|
+
Subaltern.eval(%{
|
102
|
+
case true
|
103
|
+
when true
|
104
|
+
:not_ok
|
105
|
+
:ok
|
106
|
+
else
|
107
|
+
:really_not_ok
|
108
|
+
end
|
109
|
+
}).should == :ok
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'works (classes)' do
|
113
|
+
|
114
|
+
Subaltern.eval(%{
|
115
|
+
case true
|
116
|
+
when FalseClass
|
117
|
+
:not_ok
|
118
|
+
when TrueClass
|
119
|
+
:ok
|
120
|
+
else
|
121
|
+
:really_not_ok
|
122
|
+
end
|
123
|
+
}).should == :ok
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'works (regexes)' do
|
127
|
+
|
128
|
+
Subaltern.eval(%{
|
129
|
+
case 'the little bird'
|
130
|
+
when /fox/
|
131
|
+
:not_ok
|
132
|
+
when /bird/
|
133
|
+
:ok
|
134
|
+
else
|
135
|
+
:really_not_ok
|
136
|
+
end
|
137
|
+
}).should == :ok
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'works (regexes 2)' do
|
141
|
+
|
142
|
+
Subaltern.eval(%{
|
143
|
+
case 'the little bird'
|
144
|
+
when /squirrel/
|
145
|
+
:not_ok
|
146
|
+
when /chipmunk/
|
147
|
+
:really_not_ok
|
148
|
+
else
|
149
|
+
:ok
|
150
|
+
end
|
151
|
+
}).should == :ok
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context '#each' do
|
156
|
+
|
157
|
+
describe 'break' do
|
158
|
+
|
159
|
+
it 'works' do
|
160
|
+
|
161
|
+
Subaltern.eval(%{
|
162
|
+
[ 1, 2, 3 ].each do |i|
|
163
|
+
break
|
164
|
+
end
|
165
|
+
}).should == nil
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe 'break y' do
|
170
|
+
|
171
|
+
it 'works' do
|
172
|
+
|
173
|
+
Subaltern.eval(%{
|
174
|
+
[ 1, 2, 3 ].each do |i|
|
175
|
+
break i
|
176
|
+
end
|
177
|
+
}).should == 1
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe 'next' do
|
182
|
+
|
183
|
+
it 'works' do
|
184
|
+
|
185
|
+
Subaltern.eval(%{
|
186
|
+
a = []
|
187
|
+
[ 1, 2, 3 ].each do |i|
|
188
|
+
next
|
189
|
+
a << i
|
190
|
+
end
|
191
|
+
a
|
192
|
+
}).should == []
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context '#collect' do
|
198
|
+
|
199
|
+
describe 'next x' do
|
200
|
+
|
201
|
+
it 'works' do
|
202
|
+
|
203
|
+
Subaltern.eval(%{
|
204
|
+
[ 1, 2, 3 ].collect do |i|
|
205
|
+
next i * 3
|
206
|
+
end
|
207
|
+
}).should == [ 3, 6, 9 ]
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'works with multiple values' do
|
211
|
+
|
212
|
+
Subaltern.eval(%{
|
213
|
+
[ 1, 2, 3 ].collect do |i|
|
214
|
+
next i, i * 3
|
215
|
+
end
|
216
|
+
}).should == [ [ 1, 3 ], [ 2, 6 ], [ 3, 9 ] ]
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe '#?' do
|
222
|
+
|
223
|
+
it 'works' do
|
224
|
+
|
225
|
+
Subaltern.eval(%{
|
226
|
+
true ? 1 : 0
|
227
|
+
}).should == 1
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'works (then)' do
|
231
|
+
|
232
|
+
Subaltern.eval(%{
|
233
|
+
false ? 0 : 1
|
234
|
+
}).should == 1
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
describe 'rescue' do
|
239
|
+
|
240
|
+
describe 'rescue :x' do
|
241
|
+
|
242
|
+
it 'works' do
|
243
|
+
|
244
|
+
Subaltern.eval(%{
|
245
|
+
1 / 0 rescue :limit
|
246
|
+
}).should == :limit
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
data/spec/evil_spec.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
class String
|
6
|
+
def constantize
|
7
|
+
eval(self)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe Subaltern do
|
12
|
+
|
13
|
+
context 'evil' do
|
14
|
+
|
15
|
+
describe "''.eval('1 + 1')" do
|
16
|
+
|
17
|
+
it 'raises' do
|
18
|
+
|
19
|
+
lambda {
|
20
|
+
Subaltern.eval("''.eval('1 + 1')")
|
21
|
+
}.should raise_error(Subaltern::NonWhitelistedMethodError)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'calls on non-whitelisted classes' do
|
26
|
+
|
27
|
+
it 'raises' do
|
28
|
+
|
29
|
+
lambda {
|
30
|
+
Subaltern.eval("'File'.constantize.read('#{__FILE__}')")
|
31
|
+
#}.should raise_error(Subaltern::NonWhitelistedClassError)
|
32
|
+
}.should raise_error(Subaltern::NonWhitelistedMethodError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'a constant lookup' do
|
37
|
+
|
38
|
+
it 'raises (ENV)' do
|
39
|
+
|
40
|
+
lambda {
|
41
|
+
Subaltern.eval('ENV')
|
42
|
+
}.should raise_error(Subaltern::AccessError)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'raises (File::Utils)' do
|
46
|
+
|
47
|
+
lambda {
|
48
|
+
Subaltern.eval('File::Utils')
|
49
|
+
}.should raise_error(Subaltern::AccessError)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'a call on a constant' do
|
54
|
+
|
55
|
+
it 'raises (ENV["HOME"])' do
|
56
|
+
|
57
|
+
lambda {
|
58
|
+
Subaltern.eval('ENV["HOME"]')
|
59
|
+
}.should raise_error(Subaltern::AccessError)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'a global variable lookup' do
|
64
|
+
|
65
|
+
it 'raises ($nada)' do
|
66
|
+
|
67
|
+
lambda {
|
68
|
+
Subaltern.eval('$nada')
|
69
|
+
}.should raise_error(Subaltern::AccessError)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'backquoting' do
|
74
|
+
|
75
|
+
it 'raises (simple case)' do
|
76
|
+
|
77
|
+
lambda {
|
78
|
+
Subaltern.eval('`ls -al`')
|
79
|
+
}.should raise_error(Subaltern::AccessError)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'raises (string extrapolation case)' do
|
83
|
+
|
84
|
+
lambda {
|
85
|
+
Subaltern.eval('`ls -al #{nada}`')
|
86
|
+
}.should raise_error(Subaltern::AccessError)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe 'method with a block' do
|
91
|
+
|
92
|
+
it 'is not automatically whitelisted' do
|
93
|
+
|
94
|
+
class Array
|
95
|
+
def whatever
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
lambda {
|
100
|
+
Subaltern.eval('[].whatever { |e| false }')
|
101
|
+
}.should raise_error(Subaltern::NonWhitelistedMethodError)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
data/spec/fixnum_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
describe Subaltern do
|
6
|
+
|
7
|
+
describe 'Fixnum' do
|
8
|
+
|
9
|
+
it 'works' do
|
10
|
+
|
11
|
+
Subaltern.eval('1.0').should == 1.0
|
12
|
+
Subaltern.eval('1 + 1').should == 2
|
13
|
+
Subaltern.eval('1 * 2').should == 2
|
14
|
+
Subaltern.eval('3 - 1').should == 2
|
15
|
+
Subaltern.eval('1.even?').should == false
|
16
|
+
Subaltern.eval('1.odd?').should == true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,123 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
describe Subaltern do
|
6
|
+
|
7
|
+
context 'functions' do
|
8
|
+
|
9
|
+
it 'accepts function definitions' do
|
10
|
+
|
11
|
+
Subaltern.eval(%{
|
12
|
+
def addition(a, b)
|
13
|
+
a + b
|
14
|
+
end
|
15
|
+
}).should == nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'accepts calls to defined functions' do
|
19
|
+
|
20
|
+
Subaltern.eval(%{
|
21
|
+
def addition(a, b)
|
22
|
+
a + b
|
23
|
+
end
|
24
|
+
addition(1, 2)
|
25
|
+
}).should == 3
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'accepts functions with optional parameters' do
|
29
|
+
|
30
|
+
Subaltern.eval(%{
|
31
|
+
def add(a, b=1, c=2)
|
32
|
+
a + b + c
|
33
|
+
end
|
34
|
+
[ add(1, 2), add(4) ]
|
35
|
+
}).should == [ 5, 7 ]
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'accepts functions with starred parameters' do
|
39
|
+
|
40
|
+
Subaltern.eval(%{
|
41
|
+
def concat(a, *args)
|
42
|
+
[ a ] + args
|
43
|
+
end
|
44
|
+
concat(1, 2, 3, 4)
|
45
|
+
}).should == [ 1, 2, 3, 4 ]
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'accepts a final hash argument' do
|
49
|
+
|
50
|
+
Subaltern.eval(%{
|
51
|
+
def pretty(a, b)
|
52
|
+
[ a, b ]
|
53
|
+
end
|
54
|
+
pretty('a', 'b' => 'c')
|
55
|
+
}).should == [ 'a', { 'b' => 'c' } ]
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'accepts "return" in the middle of functions' do
|
59
|
+
|
60
|
+
Subaltern.eval(%{
|
61
|
+
def hello
|
62
|
+
1
|
63
|
+
return 'nada'
|
64
|
+
2
|
65
|
+
end
|
66
|
+
hello
|
67
|
+
}).should == 'nada'
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'accepts functions with blocks' do
|
71
|
+
|
72
|
+
Subaltern.eval(%{
|
73
|
+
def hello
|
74
|
+
1 + yield
|
75
|
+
end
|
76
|
+
hello { 3 }
|
77
|
+
}).should == 4
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'accepts functions with explicit blocks' do
|
81
|
+
|
82
|
+
Subaltern.eval(%{
|
83
|
+
def hello(start, &block)
|
84
|
+
start + block.call
|
85
|
+
end
|
86
|
+
hello(1) { 3 }
|
87
|
+
}).should == 4
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'accepts functions that call blocks with arguments' do
|
91
|
+
|
92
|
+
Subaltern.eval(%{
|
93
|
+
def hello(start, &block)
|
94
|
+
start + block.call(start)
|
95
|
+
end
|
96
|
+
hello(3) { |s| s * 3 }
|
97
|
+
}).should == 12
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'accepts functions that yield arguments to blocks' do
|
101
|
+
|
102
|
+
Subaltern.eval(%{
|
103
|
+
def hello(start)
|
104
|
+
start + yield(start)
|
105
|
+
end
|
106
|
+
hello(3) { |s| s * 3 }
|
107
|
+
}).should == 12
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'raises when a block is called without its arg' do
|
111
|
+
|
112
|
+
lambda {
|
113
|
+
Subaltern.eval(%{
|
114
|
+
def hello(start, &block)
|
115
|
+
start + block.call
|
116
|
+
end
|
117
|
+
hello(3) { |start| start * 3 }
|
118
|
+
})
|
119
|
+
}.should raise_error(Subaltern::NonWhitelistedMethodError)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|