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.
@@ -0,0 +1,8 @@
1
+
2
+ = subaltern CHANGELOG.txt
3
+
4
+
5
+ == subaltern - 1.0.0 not yet released
6
+
7
+ - initial release
8
+
@@ -0,0 +1,21 @@
1
+
2
+ Copyright (c) 2011-2012, John Mettraux, jmettraux@gmail.com
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+
@@ -0,0 +1,32 @@
1
+
2
+ # subaltern
3
+
4
+ Subaltern is a Ruby self-interpreter. It's sub-functional, it's a subaltern.
5
+
6
+
7
+ ## usage
8
+
9
+ require 'subaltern'
10
+
11
+ Subaltern.eval('1 + 1')
12
+ # => 2
13
+
14
+ c = Subaltern::Context.new('a' => 7)
15
+ c.eval('a + 2')
16
+ # => 9
17
+
18
+
19
+ ## issues
20
+
21
+ https://github.com/jmettraux/subaltern/issues
22
+
23
+
24
+ ## contact
25
+
26
+ IRC freenode.net #ruote
27
+
28
+
29
+ ## license
30
+
31
+ MIT
32
+
@@ -0,0 +1,85 @@
1
+
2
+ $:.unshift('.') # 1.9.2
3
+
4
+ require 'rubygems'
5
+ require 'rubygems/user_interaction' if Gem::RubyGemsVersion == '1.5.0'
6
+
7
+ require 'rake'
8
+ require 'rake/clean'
9
+ require 'rspec/core/rake_task'
10
+
11
+
12
+ #
13
+ # clean
14
+
15
+ CLEAN.include('pkg', 'rdoc')
16
+
17
+
18
+ #
19
+ # test / spec
20
+
21
+ RSpec::Core::RakeTask.new
22
+
23
+ task :test => :spec
24
+ task :default => :spec
25
+
26
+
27
+ #
28
+ # gem
29
+
30
+ GEMSPEC_FILE = Dir['*.gemspec'].first
31
+ GEMSPEC = eval(File.read(GEMSPEC_FILE))
32
+ GEMSPEC.validate
33
+
34
+
35
+ desc %{
36
+ builds the gem and places it in pkg/
37
+ }
38
+ task :build do
39
+
40
+ sh "gem build #{GEMSPEC_FILE}"
41
+ sh "mkdir pkg" rescue nil
42
+ sh "mv #{GEMSPEC.name}-#{GEMSPEC.version}.gem pkg/"
43
+ end
44
+
45
+ desc %{
46
+ builds the gem and pushes it to rubygems.org
47
+ }
48
+ task :push => :build do
49
+
50
+ sh "gem push pkg/#{GEMSPEC.name}-#{GEMSPEC.version}.gem"
51
+ end
52
+
53
+
54
+ ##
55
+ ## rdoc
56
+ ##
57
+ ## make sure to have rdoc 2.5.x to run that
58
+ #
59
+ #Rake::RDocTask.new do |rd|
60
+ #
61
+ # rd.main = 'README.txt'
62
+ # rd.rdoc_dir = "rdoc/#{GEMSPEC.name}"
63
+ #
64
+ # rd.rdoc_files.include('README.mdown', 'CHANGELOG.txt', 'lib/**/*.rb')
65
+ #
66
+ # rd.title = "#{GEMSPEC.name} #{GEMSPEC.version}"
67
+ #end
68
+ #
69
+ #
70
+ ##
71
+ ## upload_rdoc
72
+ #
73
+ #desc %{
74
+ # upload the rdoc to rubyforge
75
+ #}
76
+ #task :upload_rdoc => [ :clean, :rdoc ] do
77
+ #
78
+ # account = 'jmettraux@rubyforge.org'
79
+ # webdir = '/var/www/gforge-projects/rufus'
80
+ #
81
+ # sh "rsync -azv -e ssh rdoc/#{GEMSPEC.name} #{account}:#{webdir}/"
82
+ #end
83
+ #
84
+ # keep that in the fridge for now
85
+
@@ -0,0 +1,32 @@
1
+
2
+ [o] yield with arguments
3
+ [o] not / !
4
+ [o] if
5
+ [o] unless
6
+ [o] if postfix
7
+ [o] unless postfix
8
+ [o] case
9
+ [o] < <= > >=
10
+ [o] ===
11
+ [o] elsif
12
+ [o] ?:
13
+
14
+ [o] break / next
15
+ [ ] break / next with function (method is already OK)
16
+
17
+ [o] while / until
18
+ [o] for
19
+ [o] Kernel.loop
20
+
21
+ [o] rescue :x
22
+ [x] begin/rescue
23
+
24
+ [ ] redo/retry (low priority)
25
+ verify if retry is still in Ruby
26
+
27
+ [o] unary - / +
28
+ [o] ranges
29
+ [ ] flip-flops
30
+
31
+ [ ] (spec) brackets ()
32
+
@@ -0,0 +1,39 @@
1
+ #--
2
+ # Copyright (c) 2011-2012, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #++
22
+
23
+ require 'ruby_parser'
24
+
25
+ require 'subaltern/version'
26
+ require 'subaltern/errors'
27
+ require 'subaltern/context'
28
+ require 'subaltern/evaluator'
29
+ require 'subaltern/kernel'
30
+
31
+
32
+ module Subaltern
33
+
34
+ def self.eval(source, vars={})
35
+
36
+ Context.new(nil, vars).eval(source)
37
+ end
38
+ end
39
+
@@ -0,0 +1,95 @@
1
+ #--
2
+ # Copyright (c) 2011-2012, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #++
22
+
23
+
24
+ module Subaltern
25
+
26
+ #
27
+ # Variable scope.
28
+ #
29
+ class Context
30
+
31
+ attr_reader :parent
32
+
33
+ # Create a new context
34
+ #
35
+ # c = Subaltern.new
36
+ # # or
37
+ # c = Subaltern.new('var0' => 'value0', 'var1' => [ 1, 2, 3 ])
38
+ #
39
+ def initialize(parent={}, vars=nil)
40
+
41
+ vars.merge!(Subaltern.kernel) if parent.nil?
42
+
43
+ @parent, @variables = [ parent, vars ]
44
+ @parent, @variables = [ nil, parent ] if vars.nil?
45
+ end
46
+
47
+ def [](key)
48
+
49
+ return @variables[key] if @variables.has_key?(key)
50
+ return @parent[key] if @parent
51
+
52
+ raise UndefinedVariableError.new(key)
53
+ end
54
+
55
+ def []=(key, value)
56
+
57
+ @variables[key] = value unless set(key, value)
58
+
59
+ value
60
+ end
61
+
62
+ # Warning : shallow (doesn't lookup in parent context)
63
+ #
64
+ def has_key?(key)
65
+
66
+ @variables.has_key?(key)
67
+ end
68
+
69
+ # Eval a piece of Ruby code within this context.
70
+ #
71
+ def eval(source)
72
+
73
+ begin
74
+ Subaltern.eval_tree(self, RubyParser.new.parse(source).to_a)
75
+ rescue Return => r
76
+ r.value
77
+ end
78
+ end
79
+
80
+ protected
81
+
82
+ def set(key, value)
83
+
84
+ if has_key?(key)
85
+ @variables[key] = value
86
+ true
87
+ elsif @parent == nil
88
+ false
89
+ else
90
+ @parent.set(key, value)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
@@ -0,0 +1,78 @@
1
+ #--
2
+ # Copyright (c) 2011-2012, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #++
22
+
23
+
24
+ module Subaltern
25
+
26
+ class SubalternError < RuntimeError
27
+ end
28
+
29
+ class BlacklistedMethodError < SubalternError
30
+
31
+ attr_reader :klass, :method
32
+
33
+ def initialize(klass, meth)
34
+
35
+ @klass = klass
36
+ @method = meth
37
+ super("blacklisted : #meth (in this case : #{klass.inspect})")
38
+ end
39
+ end
40
+
41
+ class NonWhitelistedClassError < SubalternError
42
+
43
+ attr_reader :klass
44
+
45
+ def initialize(klass)
46
+
47
+ @klass = klass
48
+ super("not whitelisted : #{klass.inspect}")
49
+ end
50
+ end
51
+
52
+ class NonWhitelistedMethodError < SubalternError
53
+
54
+ attr_reader :klass, :method
55
+
56
+ def initialize(klass, meth)
57
+
58
+ @klass = klass
59
+ @method = meth
60
+ super("not whitelisted : #{klass.inspect}##{meth}")
61
+ end
62
+ end
63
+
64
+ class UndefinedVariableError < SubalternError
65
+
66
+ attr_reader :variable_name
67
+
68
+ def initialize(varname)
69
+
70
+ @variable_name = varname
71
+ super("undefined variable #{varname.inspect}")
72
+ end
73
+ end
74
+
75
+ class AccessError < SubalternError
76
+ end
77
+ end
78
+
@@ -0,0 +1,597 @@
1
+ #--
2
+ # Copyright (c) 2011-2012, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #++
22
+
23
+
24
+ module Subaltern
25
+
26
+ #--
27
+ # whitelisting, and some blacklisting, out of pure distrust
28
+ #++
29
+
30
+ BLACKLIST = %w[
31
+ extend instance_eval instance_exec method methods include
32
+ private_methods public_methods protected_methods singleton_methods
33
+ send __send__
34
+ taint tainted? untaint
35
+ instance_variables instance_variable_get instance_variable_set
36
+ instance_variable_defined?
37
+ free frozen?
38
+ ]
39
+
40
+ # TODO :
41
+ #
42
+ # eventually generate those whitelists by doing
43
+ #
44
+ # class.public_instance_methods - BLACKLIST
45
+ #
46
+ # though it would accept any new methods added by the 'user'.
47
+ # Well, since the 'user' can't overwrite methods...
48
+
49
+ WHITELIST = {
50
+ Array => %w[
51
+ & * + - << <=> == === =~ [] []=
52
+ all? any? assoc at choice class clear clone collect collect! combination
53
+ compact compact! concat count cycle delete delete_at delete_if detect display
54
+ drop drop_while dup each each_cons each_index each_slice each_with_index
55
+ empty? entries enum_cons enum_for enum_slice enum_with_index eql? equal?
56
+ fetch fill find find_all find_index first flatten flatten!
57
+ grep group_by hash id include? index indexes indices inject insert inspect
58
+ instance_of? is_a? join kind_of? last length map map! max max_by member?
59
+ min min_by minmax minmax_by nil? nitems none? object_id one? pack partition
60
+ permutation pop product push rassoc reduce reject reject! replace respond_to?
61
+ reverse reverse! reverse_each rindex select shift shuffle shuffle! size
62
+ slice slice! sort sort! sort_by take take_while tap to_a to_ary to_enum to_s
63
+ transpose type uniq uniq! unshift untaint values_at zip |
64
+ ],
65
+ Fixnum => %w[
66
+ % & * ** + +@ - -@ / < << <= <=> == === =~ > >= >> [] ^ __id__
67
+ abs between? ceil chr class clone coerce display div divmod downto dup enum_for
68
+ eql? equal? even? fdiv floor hash id id2name inspect
69
+ instance_of? integer? is_a? kind_of? modulo next nil? nonzero? object_id odd?
70
+ ord prec prec_f prec_i pred quo remainder respond_to? round size step succ tap
71
+ times to_a to_enum to_f to_i to_int to_s to_sym truncate type upto zero? | ~
72
+ ],
73
+ Hash => %w[
74
+ == === =~ [] []= __id__
75
+ all? any? class clear clone collect count cycle default default= default_proc
76
+ delete delete_if detect display drop drop_while dup each each_cons each_key
77
+ each_pair each_slice each_value each_with_index empty? entries enum_cons
78
+ enum_for enum_slice enum_with_index eql? equal? fetch find find_all find_index
79
+ first grep group_by has_key? has_value? hash id include? index indexes indices
80
+ inject inspect instance_of? invert is_a? key? keys kind_of? length map max
81
+ max_by member? merge merge! min min_by minmax minmax_by nil? none? object_id
82
+ one? partition reduce rehash reject reject! replace respond_to? reverse_each
83
+ select shift size sort sort_by store take take_while tap to_a to_enum to_hash
84
+ to_s type update value? values values_at zip
85
+ ],
86
+ MatchData => %[
87
+ []
88
+ ],
89
+ NilClass => %[
90
+ & == === =~ ^ __id__ class clone display dup enum_for eql? equal? hash
91
+ id inspect instance_of? is_a? kind_of? nil? object_id respond_to?
92
+ tap to_a to_enum to_f to_i to_s type |
93
+ ],
94
+ Regexp => %w[
95
+ match
96
+ ],
97
+ String => %w[
98
+ % * + < << <= <=> == === =~ > >= [] []= __id__
99
+ all? any? between? bytes bytesize capitalize capitalize! casecmp center chars
100
+ chomp chomp! chop chop! class clone collect concat count crypt cycle delete
101
+ delete! detect display downcase downcase! drop drop_while dump dup
102
+ each each_byte each_char each_cons each_line each_slice each_with_index empty?
103
+ end_with? entries enum_cons enum_for enum_slice enum_with_index eql? equal?
104
+ find find_all find_index first grep group_by gsub gsub! hash hex id include?
105
+ index inject insert inspect instance_of? intern is_a? kind_of? length lines
106
+ ljust lstrip lstrip! map match max max_by member? min min_by minmax minmax_by
107
+ next next! nil? none? object_id oct one? partition reduce reject replace
108
+ respond_to? reverse reverse! reverse_each rindex rjust rpartition rstrip
109
+ rstrip! scan select size slice slice! sort sort_by split squeeze squeeze!
110
+ start_with? strip strip! sub sub! succ succ! sum swapcase swapcase! take
111
+ take_while tap to_a to_enum to_f to_i to_s to_str to_sym tr tr! tr_s tr_s!
112
+ type unpack upcase upcase! upto zip
113
+ ],
114
+ Class => %w[
115
+ ===
116
+ ],
117
+ TrueClass => %w[
118
+ ==
119
+ ],
120
+ FalseClass => %w[
121
+ ==
122
+ ],
123
+ }
124
+
125
+ WHITELISTED_CONSTANTS =
126
+ WHITELIST.keys.collect { |k| k.name.to_sym } +
127
+ [ :FalseClass, :TrueClass ]
128
+
129
+ #--
130
+ # helper classes
131
+ #++
132
+
133
+ #
134
+ # I wish I could use throw/catch, but only Ruby 1.9.x allows for throwing
135
+ # something else than a Symbol.
136
+ # Going with a standard error for now.
137
+ #
138
+ class Return < StandardError
139
+
140
+ attr_reader :value
141
+
142
+ def initialize(value)
143
+
144
+ @value = value
145
+ super('')
146
+ end
147
+ end
148
+
149
+ #
150
+ # A command like 'break' or 'next'.
151
+ #
152
+ class Command < StandardError
153
+
154
+ attr_reader :name, :args
155
+
156
+ def initialize(name, args)
157
+ @name = name
158
+ @args = args
159
+ end
160
+
161
+ # Used by Command.narrow
162
+ #
163
+ def result
164
+
165
+ return nil if args.empty?
166
+ return @args.first if args.size == 1
167
+ @args
168
+ end
169
+
170
+ # Used to circumvent a difference between ruby 1.8 and 1.9...
171
+ #
172
+ def self.narrow(o)
173
+
174
+ return o.result if o.is_a?(Command)
175
+ return o.collect { |e| e.is_a?(Command) ? e.result : e } if o.is_a?(Array)
176
+ o
177
+ end
178
+ end
179
+
180
+ #
181
+ # Wrapper for function trees when stored in Context instances.
182
+ #
183
+ class Function
184
+
185
+ def initialize(tree)
186
+
187
+ @tree = tree
188
+
189
+ # TODO : eventually, follow Block's example and split the tree here
190
+ end
191
+
192
+ def call(context, tree)
193
+
194
+ meth_args = @tree[2][1..-1]
195
+ l = meth_args.last
196
+ meth_arg_defaults = l.is_a?(Array) && l.first == :block ? l[1..-1] : []
197
+ call_args = tree[3][1..-1].collect { |t| Subaltern.eval_tree(context, t) }
198
+
199
+ con = Context.new(context, {})
200
+
201
+ # bind arguments
202
+
203
+ call_args.each_with_index do |arg, i|
204
+ name = meth_args[i].to_s
205
+ if m = name.match(/^\*(.+)$/)
206
+ con[m[1]] = call_args[i..-1]
207
+ break
208
+ end
209
+ con[name] = arg
210
+ end
211
+
212
+ # bind default arguments (when necessary)
213
+
214
+ meth_arg_defaults.each do |d|
215
+ name = d[1].to_s
216
+ con[name] = Subaltern.eval_tree(context, d[2]) unless con.has_key?(name)
217
+ end
218
+
219
+ # is there a block ?
220
+
221
+ if m = (meth_args.last || '').to_s.match(/^&(.+)/)
222
+ con[m[1]] = context['__block']
223
+ end
224
+
225
+ # now do it
226
+
227
+ begin
228
+ Subaltern.eval_tree(con, @tree[3])
229
+ rescue Return => r
230
+ r.value
231
+ end
232
+ end
233
+ end
234
+
235
+ #
236
+ # Wrapper for Ruby blocks.
237
+ #
238
+ class Block
239
+
240
+ def initialize(tree)
241
+
242
+ @arglist = Array(refine(tree[0] || [])).flatten
243
+ @tree = tree[1]
244
+ end
245
+
246
+ def call(context, arguments, new_context=true)
247
+
248
+ arguments = arguments.flatten
249
+
250
+ con = new_context ? Context.new(context, {}) : context
251
+
252
+ @arglist.each_with_index { |a, i| con[a] = arguments[i] }
253
+
254
+ Subaltern.eval_tree(con, @tree)
255
+ end
256
+
257
+ protected
258
+
259
+ def refine(tree)
260
+
261
+ if tree[0] == :masgn
262
+ tree[1][1..-1].collect { |t| refine(t) }
263
+ else # :lasgn
264
+ tree[1].to_s
265
+ end
266
+ end
267
+ end
268
+
269
+ # Will raise an exception if calling that method on this target
270
+ # is not whitelisted (or is blacklisted).
271
+ #
272
+ def self.bounce(target, method)
273
+
274
+ if BLACKLIST.include?(method.to_s)
275
+ raise BlacklistedMethodError.new(target.class)
276
+ end
277
+ unless WHITELIST.keys.include?(target.class)
278
+ raise NonWhitelistedClassError.new(target.class)
279
+ end
280
+ unless WHITELIST[target.class].include?(method.to_s)
281
+ raise NonWhitelistedMethodError.new(target.class, method)
282
+ end
283
+ end
284
+
285
+ #--
286
+ # the eval_ methods
287
+ #++
288
+
289
+ # The entry point.
290
+ #
291
+ def self.eval_tree(context, tree)
292
+
293
+ send("eval_#{tree.first}", context, tree)
294
+
295
+ rescue NoMethodError => nme
296
+ puts '-' * 80
297
+ p nme
298
+ p tree
299
+ puts nme.backtrace.join("\n")
300
+ puts '-' * 80
301
+ raise nme
302
+ end
303
+
304
+ %w[ false true nil ].each do |key|
305
+ instance_eval %{ def eval_#{key}(context, tree); #{key}; end }
306
+ end
307
+
308
+ def self.eval_lit(context, tree)
309
+
310
+ tree[1]
311
+ end
312
+
313
+ def self.eval_str(context, tree)
314
+
315
+ tree[1]
316
+ end
317
+
318
+ def self.eval_dstr(context, tree)
319
+
320
+ tree[1..-1].collect { |t|
321
+ t.is_a?(String) ? t : eval_tree(context, t)
322
+ }.join
323
+ end
324
+
325
+ def self.eval_evstr(context, tree)
326
+
327
+ eval_tree(context, tree[1])
328
+ end
329
+
330
+ def self.eval_xstr(context, tree)
331
+
332
+ raise AccessError.new("no backquoting allowed (#{tree[1]})")
333
+ end
334
+
335
+ def self.eval_dxstr(context, tree)
336
+
337
+ raise AccessError.new("no backquoting allowed (#{tree[1]})")
338
+ end
339
+
340
+ def self.eval_array(context, tree)
341
+
342
+ tree[1..-1].collect { |t| eval_tree(context, t) }
343
+ end
344
+
345
+ def self.eval_hash(context, tree)
346
+
347
+ Hash[*eval_array(context, tree)]
348
+ end
349
+
350
+ def self.is_javascripty_hash_lookup?(target, method, args)
351
+
352
+ target.is_a?(Hash) &&
353
+ args.empty? &&
354
+ ( ! WHITELIST[Hash].include?(method)) &&
355
+ target.has_key?(method)
356
+ end
357
+
358
+ def self.eval_call(context, tree)
359
+
360
+ if tree[1] == nil
361
+ # variable lookup or function application...
362
+
363
+ value = eval_lvar(context, tree[1, 2])
364
+
365
+ return value.call(context, tree) if value.is_a?(Subaltern::Function)
366
+ return value
367
+ end
368
+
369
+ target = eval_tree(context, tree[1])
370
+ method = tree[2].to_s
371
+ args = tree[3][1..-1]
372
+
373
+ if is_javascripty_hash_lookup?(target, method, args)
374
+ return target[method]
375
+ end
376
+
377
+ args = args.collect { |t| eval_tree(context, t) }
378
+
379
+ return target.call(context, args) if target.is_a?(Subaltern::Block)
380
+
381
+ bounce(target, method)
382
+
383
+ target.send(method, *args)
384
+ end
385
+
386
+ def self.eval_block(context, tree)
387
+
388
+ tree[1..-1].collect { |t| eval_tree(context, t) }.last
389
+ end
390
+
391
+ def self.eval_lasgn(context, tree)
392
+
393
+ context[tree[1].to_s] = eval_tree(context, tree[2])
394
+ end
395
+
396
+ def self.eval_lvar(context, tree)
397
+
398
+ context[tree[1].to_s]
399
+ end
400
+
401
+ def self.eval_gvar(context, tree)
402
+
403
+ raise AccessError.new("no global variables (#{tree[1]})")
404
+ end
405
+
406
+ def self.eval_colon2(context, tree)
407
+
408
+ raise AccessError.new("no access to constants (#{tree[1]})")
409
+ end
410
+
411
+ def self.eval_const(context, tree)
412
+
413
+ if WHITELISTED_CONSTANTS.include?(tree[1])
414
+ return Kernel.const_get(tree[1].to_s)
415
+ end
416
+
417
+ raise AccessError.new("no access to constants (#{tree[1]})")
418
+ end
419
+
420
+ def self.eval_defn(context, tree)
421
+
422
+ name = tree[1].to_s
423
+ context[name] = Function.new(tree)
424
+
425
+ nil
426
+ end
427
+
428
+ def self.eval_scope(context, tree)
429
+
430
+ eval_tree(context, tree[1])
431
+ end
432
+
433
+ def self.eval_return(context, tree)
434
+
435
+ raise(Return.new(eval_tree(context, tree[1])))
436
+ end
437
+
438
+ def self.eval_iter(context, tree)
439
+
440
+ if tree[1][1] == nil
441
+ #
442
+ # function with a block
443
+
444
+ con = Context.new(context, '__block' => Block.new(tree[2..-1]))
445
+
446
+ eval_call(con, tree[1])
447
+
448
+ else
449
+ #
450
+ # method with a block
451
+
452
+ target = eval_tree(context, tree[1][1])
453
+ method = tree[1][2]
454
+
455
+ bounce(target, method)
456
+
457
+ method_args = tree[1][3][1..-1].collect { |t| eval_tree(context, t) }
458
+ block = Block.new(tree[2..-1])
459
+
460
+ command = nil
461
+
462
+ result = target.send(method, *method_args) { |*args|
463
+ begin
464
+ block.call(context, args)
465
+ rescue Command => c
466
+ case c.name
467
+ when 'break' then break c
468
+ when 'next' then next c
469
+ else raise c
470
+ end
471
+ end
472
+ }
473
+
474
+ Command.narrow(result)
475
+ end
476
+ end
477
+
478
+ def self.eval_yield(context, tree)
479
+
480
+ args = tree[1..-1].collect { |t| eval_tree(context, t) }
481
+
482
+ context['__block'].call(context, args)
483
+ end
484
+
485
+ def self.eval_break(context, tree)
486
+
487
+ raise(
488
+ Command.new('break', tree[1..-1].collect { |t| eval_tree(context, t) }))
489
+ end
490
+
491
+ def self.eval_next(context, tree)
492
+
493
+ raise(
494
+ Command.new('next', tree[1..-1].collect { |t| eval_tree(context, t) }))
495
+ end
496
+
497
+ def self.eval_if(context, tree)
498
+
499
+ if eval_tree(context, tree[1])
500
+ eval_tree(context, tree[2])
501
+ elsif tree[3]
502
+ eval_tree(context, tree[3])
503
+ end
504
+ end
505
+
506
+ def self.eval_or(context, tree)
507
+
508
+ tree[1..-1].each { |t|
509
+ result = eval_tree(context, t)
510
+ return result if result
511
+ }
512
+ end
513
+
514
+ def self.eval_and(context, tree)
515
+
516
+ tree[1..-1].inject(nil) { |_, t|
517
+
518
+ current = eval_tree(context, t)
519
+
520
+ return current unless current
521
+
522
+ current
523
+ }
524
+ end
525
+
526
+ def self.eval_not(context, tree)
527
+
528
+ not eval_tree(context, tree[1])
529
+ end
530
+
531
+ def self.eval_case(context, tree)
532
+
533
+ value = eval_tree(context, tree[1])
534
+ whens = tree[2..-1].select { |t| t[0] == :when }
535
+ the_else = (tree[2..-1] - whens).first
536
+
537
+ whens.each do |w|
538
+
539
+ eval_tree(context, w[1]).each do |v|
540
+
541
+ return eval_tree(context, w[2]) if v === value
542
+ end
543
+ end
544
+
545
+ the_else ? eval_tree(context, the_else) : nil
546
+ end
547
+
548
+ def self.eval_while(context, tree)
549
+
550
+ result = while eval_tree(context, tree[1])
551
+
552
+ begin
553
+ eval_tree(context, tree[2])
554
+ rescue Command => c
555
+ case c.name
556
+ when 'break' then break c
557
+ when 'next' then next c
558
+ else raise c
559
+ end
560
+ end
561
+ end
562
+
563
+ Command.narrow(result)
564
+ end
565
+
566
+ def self.eval_until(context, tree)
567
+
568
+ result = until eval_tree(context, tree[1])
569
+
570
+ begin
571
+ eval_tree(context, tree[2])
572
+ rescue Command => c
573
+ case c.name
574
+ when 'break' then break *c.args
575
+ when 'next' then next *c.args
576
+ else raise c
577
+ end
578
+ end
579
+ end
580
+
581
+ Command.narrow(result)
582
+ end
583
+
584
+ def self.eval_for(context, tree)
585
+
586
+ values = eval_tree(context, tree[1])
587
+ block = Block.new(tree[2..-1])
588
+
589
+ values.each { |v| block.call(context, [ v ], false) }
590
+ end
591
+
592
+ def self.eval_rescue(context, tree)
593
+
594
+ Subaltern.eval_tree(context, tree[2][2])
595
+ end
596
+ end
597
+