subaltern 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/CHANGELOG.txt
ADDED
data/LICENSE.txt
ADDED
@@ -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
|
+
|
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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
|
+
|
data/TODO.txt
ADDED
@@ -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
|
+
|
data/lib/subaltern.rb
ADDED
@@ -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
|
+
|