subaltern 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+