serializable_proc 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/HISTORY.txt +9 -0
- data/README.rdoc +28 -10
- data/VERSION +1 -1
- data/lib/serializable_proc/binding.rb +27 -32
- data/lib/serializable_proc/fixes.rb +1 -1
- data/lib/serializable_proc/isolatable.rb +59 -0
- data/lib/serializable_proc/parsers/pt.rb +2 -5
- data/lib/serializable_proc/parsers/rp.rb +29 -29
- data/lib/serializable_proc/parsers.rb +22 -0
- data/lib/serializable_proc.rb +76 -10
- data/serializable_proc.gemspec +51 -21
- data/spec/bounded_vars/class_vars_spec.rb +60 -0
- data/spec/bounded_vars/class_vars_within_block_scope_spec.rb +31 -0
- data/spec/bounded_vars/errors_spec.rb +27 -0
- data/spec/bounded_vars/global_vars_spec.rb +60 -0
- data/spec/bounded_vars/global_vars_within_block_scope_spec.rb +31 -0
- data/spec/bounded_vars/instance_vars_spec.rb +60 -0
- data/spec/bounded_vars/instance_vars_within_block_scope_spec.rb +31 -0
- data/spec/bounded_vars/local_vars_spec.rb +60 -0
- data/spec/bounded_vars/local_vars_within_block_scope_spec.rb +31 -0
- data/spec/code_block/errors_spec.rb +65 -0
- data/spec/{multiple_arities_serializable_proc_spec.rb → code_block/multiple_arities_spec.rb} +2 -2
- data/spec/{optional_arity_serializable_proc_spec.rb → code_block/optional_arity_spec.rb} +2 -2
- data/spec/code_block/renaming_vars_spec.rb +160 -0
- data/spec/{one_arity_serializable_proc_spec.rb → code_block/single_arity_spec.rb} +2 -2
- data/spec/{zero_arity_serializable_proc_spec.rb → code_block/zero_arity_spec.rb} +2 -2
- data/spec/proc_like/extras_spec.rb +82 -0
- data/spec/proc_like/invoking_with_args_spec.rb +58 -0
- data/spec/proc_like/invoking_with_class_vars_spec.rb +96 -0
- data/spec/proc_like/invoking_with_global_vars_spec.rb +79 -0
- data/spec/proc_like/invoking_with_instance_vars_spec.rb +79 -0
- data/spec/proc_like/invoking_with_local_vars_spec.rb +79 -0
- data/spec/{marshalling_spec.rb → proc_like/marshalling_spec.rb} +1 -1
- data/spec/proc_like/others_spec.rb +106 -0
- data/spec/spec_helper.rb +10 -0
- metadata +52 -22
- data/lib/serializable_proc/sandboxer.rb +0 -24
- data/spec/extracting_bound_variables_spec.rb +0 -99
- data/spec/initializing_errors_spec.rb +0 -95
- data/spec/proc_like_spec.rb +0 -191
data/.gitignore
CHANGED
data/HISTORY.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
=== 0.2.0 (Aug 18, 2010)
|
2
|
+
|
3
|
+
* added SerializableProc#to_sexp to facilitate retrieving of sexp representation of the
|
4
|
+
code block [#ngty]
|
5
|
+
* added support @@_not_isolated_vars, which allows fine-tuning of whether global, class,
|
6
|
+
instance or local variables should be isolated [#ngty]
|
7
|
+
* fixed bug of block-scoped (eg. within 'def' block) variables being isolated [#ngty]
|
8
|
+
* house-keeping, lots of reorganizing for specs [#ngty]
|
9
|
+
|
1
10
|
=== 0.1.1 (Aug 16, 2010)
|
2
11
|
|
3
12
|
* acheived compatibility with MRI-1.8.6 [#ngty]
|
data/README.rdoc
CHANGED
@@ -12,9 +12,9 @@ A SerializableProc differs from the vanilla Proc in the following 2 ways:
|
|
12
12
|
|
13
13
|
=== 1. Isolated variables
|
14
14
|
|
15
|
-
|
16
|
-
are extracted from the proc's binding, and are isolated from changes outside the
|
17
|
-
scope, thus, achieving a snapshot effect.
|
15
|
+
By default, upon initializing, all variables (local, instance, class & global) within its
|
16
|
+
context are extracted from the proc's binding, and are isolated from changes outside the
|
17
|
+
proc's scope, thus, achieving a snapshot effect.
|
18
18
|
|
19
19
|
require 'rubygems'
|
20
20
|
require 'serializable_proc'
|
@@ -29,6 +29,28 @@ scope, thus, achieving a snapshot effect.
|
|
29
29
|
s_proc.call # >> "lx, ix, cx, gx"
|
30
30
|
v_proc.call # >> "ly, iy, cy, gy"
|
31
31
|
|
32
|
+
Sometimes, we may want global variables to behave as truely global, meaning we don't want
|
33
|
+
to isolate globals at all, this can be done by declaring @@_not_isolated_vars within the
|
34
|
+
code block:
|
35
|
+
|
36
|
+
s_proc = SerializableProc.new do
|
37
|
+
@@_not_isolated_vars = :global # globals won't be isolated
|
38
|
+
$stdout << "WakeUp !!" # $stdout is the $stdout in the execution context
|
39
|
+
end
|
40
|
+
|
41
|
+
The following declares all variables as not isolatable:
|
42
|
+
|
43
|
+
s_proc = SerializableProc.new do
|
44
|
+
@@_not_isolated_vars = :global, :class, :instance, :local
|
45
|
+
# (blah blah)
|
46
|
+
end
|
47
|
+
|
48
|
+
When invoking, Kernel.binding should be passed in to avoid unpleasant surprises:
|
49
|
+
|
50
|
+
s_proc.call(binding)
|
51
|
+
|
52
|
+
(take a look at SerializableProc's rdoc for more details)
|
53
|
+
|
32
54
|
=== 2. Marshallable
|
33
55
|
|
34
56
|
No throwing of TypeError when marshalling a SerializableProc:
|
@@ -110,15 +132,11 @@ SerializableProc has been tested to work on the following rubies:
|
|
110
132
|
|
111
133
|
== TODO (just brain-dumping)
|
112
134
|
|
113
|
-
1.
|
114
|
-
because globals are globals, it may sometimes be useful to use/manipulate the globals
|
115
|
-
within the context that the SerializableProc is called, instead of when it is
|
116
|
-
initialized
|
117
|
-
2. The RubyParser-based implementation probably need alot more optimization to catch up
|
135
|
+
1. The RubyParser-based implementation probably need alot more optimization to catch up
|
118
136
|
on ParseTree-based one
|
119
|
-
|
137
|
+
2. Implementing alternative means of extracting the code block without requiring help
|
120
138
|
of ParseTree or RubyParser
|
121
|
-
|
139
|
+
3. Implement workaround to tackle line-numbering bug in JRuby, which causes the
|
122
140
|
RubyParser-based implementation to fail, for more info abt JRuby's line-numbering
|
123
141
|
bug, see http://stackoverflow.com/questions/3454838/jruby-line-numbering-problem &
|
124
142
|
http://jira.codehaus.org/browse/JRUBY-5014
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -4,51 +4,46 @@ class SerializableProc
|
|
4
4
|
|
5
5
|
class Binding
|
6
6
|
|
7
|
+
include Isolatable
|
7
8
|
include Marshalable
|
8
9
|
marshal_attr :vars
|
9
10
|
|
10
11
|
def initialize(binding, sexp)
|
11
|
-
@vars,
|
12
|
-
|
13
|
-
|
14
|
-
sexp_str.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
sexp, @vars = sexp.gsub(s(:scope, s(:block, SexpAny.new)), nil), {}
|
13
|
+
types = isolated_types(sexp)
|
14
|
+
unless types.empty?
|
15
|
+
sexp_str = sexp.inspect
|
16
|
+
while m = sexp_str.match(/^(.*?s\(:(?:#{types.join('|')})var, :([^\)]+)\))/)
|
17
|
+
ignore, var = m[1..2]
|
18
|
+
sexp_str.sub!(ignore,'')
|
19
|
+
next unless isolatable?(var)
|
20
|
+
begin
|
21
|
+
val = eval(var, binding) rescue nil
|
22
|
+
@vars.update(isolated_var(var) => mclone(val))
|
23
|
+
rescue TypeError
|
24
|
+
raise CannotSerializeVariableError.new("Variable #{var} cannot be serialized !!")
|
25
|
+
end
|
20
26
|
end
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
24
|
-
def eval!
|
25
|
-
|
26
|
-
|
27
|
-
|
30
|
+
def eval!(binding = nil)
|
31
|
+
unless binding
|
32
|
+
@binding ||= (
|
33
|
+
eval(declare_vars, binding = Kernel.binding)
|
34
|
+
binding
|
35
|
+
)
|
36
|
+
else
|
37
|
+
eval(declare_vars, binding)
|
28
38
|
binding
|
29
|
-
|
30
|
-
)
|
39
|
+
end
|
31
40
|
end
|
32
41
|
|
33
|
-
|
34
|
-
def self.extended(base)
|
35
|
-
class << base
|
42
|
+
private
|
36
43
|
|
37
|
-
|
38
|
-
|
39
|
-
def eval(str)
|
40
|
-
begin
|
41
|
-
@fvar = Sandboxer.fvar(str).to_s
|
42
|
-
orig_eval(@fvar)
|
43
|
-
rescue NameError => e
|
44
|
-
msg = e.message.sub(@fvar, str)
|
45
|
-
raise NameError.new(msg)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
44
|
+
def declare_vars
|
45
|
+
@declare_vars ||= @vars.map{|(k,v)| "#{k} = Marshal.load(%|#{mdump(v)}|)" } * '; '
|
50
46
|
end
|
51
|
-
end
|
52
47
|
|
53
48
|
end
|
54
49
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class SerializableProc
|
2
|
+
module Isolatable
|
3
|
+
|
4
|
+
protected
|
5
|
+
|
6
|
+
def isolated_sexp(sexp)
|
7
|
+
# TODO: This nasty mess needs some loving touch !!
|
8
|
+
block_replace = :_serializable_proc_block_scope_marker_
|
9
|
+
t_sexp = sexp.gsub(s(:scope, s(:block, SexpAny.new)), block_replace)
|
10
|
+
blocks_pattern = %r{^#{Regexp.quote(t_sexp.inspect).gsub(block_replace.inspect,'(.*?)')}$}
|
11
|
+
blocks = sexp.inspect.match(blocks_pattern)[1..-1] rescue []
|
12
|
+
n_sexp_str = nil
|
13
|
+
|
14
|
+
unless (types = isolated_types(sexp)).empty?
|
15
|
+
var_pattern = /^(.*?s\(:)((#{types.join('|')})(asgn|var|vdecl))(,\ :)((|@|@@|\$)([\w]+))(\)|,)/
|
16
|
+
t_sexp_str = t_sexp.inspect
|
17
|
+
|
18
|
+
while m = t_sexp_str.match(var_pattern)
|
19
|
+
orig, prepend, _, type, declare, join, var, _, name, append = m[0..-1]
|
20
|
+
n_sexp_str = isolatable?(var) ?
|
21
|
+
"#{n_sexp_str}#{prepend}l#{declare.sub('vdecl','asgn')}#{join}#{type}var_#{name}#{append}" :
|
22
|
+
"#{n_sexp_str}#{orig}"
|
23
|
+
t_sexp_str.sub!(orig,'')
|
24
|
+
end
|
25
|
+
|
26
|
+
n_sexp_str = n_sexp_str.nil? ? nil :
|
27
|
+
blocks.inject("#{n_sexp_str}#{t_sexp_str}") do |sexp_str, block_str|
|
28
|
+
sexp_str.sub(block_replace.inspect, block_str)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
eval(n_sexp_str ? n_sexp_str : sexp.inspect)
|
33
|
+
end
|
34
|
+
|
35
|
+
def isolated_var(var)
|
36
|
+
@translate_var_maps ||= {'@' => 'ivar_', '@@' => 'cvar_', '$' => 'gvar_', '' => 'lvar_'}
|
37
|
+
m = var.to_s.match(/^(|@|@@|\$)(\w+)$/)
|
38
|
+
var.to_s.sub(m[1], @translate_var_maps[m[1]]).to_sym
|
39
|
+
end
|
40
|
+
|
41
|
+
def isolated_types(sexp)
|
42
|
+
o_sexp_arry = sexp.to_a
|
43
|
+
n_sexp = sexp.gsub(s(:cvdecl, :@@_not_isolated_vars, SexpAny.new), nil)
|
44
|
+
types = %w{global instance local class}
|
45
|
+
|
46
|
+
if (diff = o_sexp_arry - n_sexp.to_a).empty?
|
47
|
+
types.map{|t| t[0].chr }
|
48
|
+
else
|
49
|
+
sexp_str = Sexp.from_array(diff).inspect
|
50
|
+
types.map{|t| t[0].chr unless sexp_str.include?("s(:lit, :#{t})") }.compact
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def isolatable?(var)
|
55
|
+
var != '@@_not_isolated_vars'
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -1,13 +1,10 @@
|
|
1
1
|
class SerializableProc
|
2
2
|
module Parsers
|
3
|
-
|
3
|
+
class PT < Base
|
4
4
|
class << self
|
5
5
|
def process(block)
|
6
6
|
if Object.const_defined?(:ParseTree)
|
7
|
-
|
8
|
-
runnable_code = RUBY_2_RUBY.process(Sandboxer.fsexp(sexp))
|
9
|
-
extracted_code = RUBY_2_RUBY.process(eval(sexp.inspect))
|
10
|
-
[{:runnable => runnable_code, :extracted => extracted_code}, sexp]
|
7
|
+
sexp_derivatives(block.to_sexp)
|
11
8
|
end
|
12
9
|
end
|
13
10
|
end
|
@@ -3,7 +3,7 @@ class SerializableProc
|
|
3
3
|
class CannotAnalyseCodeError < Exception ; end
|
4
4
|
|
5
5
|
module Parsers
|
6
|
-
|
6
|
+
class RP < Base
|
7
7
|
class << self
|
8
8
|
|
9
9
|
def process(klass, file, line)
|
@@ -15,47 +15,46 @@ class SerializableProc
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def extract_code_and_sexp
|
18
|
-
sexp_str, remaining,
|
18
|
+
sexp_str, remaining, @marker = extract_sexp_args
|
19
19
|
while frag = remaining[/^([^\)]*\))/,1]
|
20
20
|
begin
|
21
|
-
sexp =
|
22
|
-
|
23
|
-
RUBY_2_RUBY.process(Sandboxer.fsexp(sexp)),
|
24
|
-
RUBY_2_RUBY.process(eval(sexp.inspect))
|
25
|
-
].map do |code|
|
26
|
-
unescape_magic_vars(code).sub(/#{marker}\s*;?/m,'').sub(type,'lambda')
|
27
|
-
end
|
28
|
-
return [{:runnable => runnable_code, :extracted => extracted_code}, sexp]
|
21
|
+
sexp = normalized_eval(sexp_str += frag)
|
22
|
+
return sexp_derivatives(sexp){|code| unescape_magic_vars(code) }
|
29
23
|
rescue SyntaxError
|
30
24
|
remaining.sub!(frag,'')
|
31
25
|
end
|
32
26
|
end
|
33
27
|
end
|
34
28
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
),
|
45
|
-
'.*?',
|
46
|
-
rq["s(:call, nil, :#{marker}, s(:arglist))"],
|
47
|
-
'))(.*)$'
|
48
|
-
].join, Regexp::MULTILINE)
|
49
|
-
[raw.match(regexp)[2..3], type, marker].flatten
|
29
|
+
def normalized_eval(sexp_str)
|
30
|
+
sexp = eval(sexp_str) # this will fail unless the sexp is valid
|
31
|
+
sexp.delete(marker_sexp = s(:call, nil, :"#{@marker}", s(:arglist)))
|
32
|
+
sexp.find_node(:block).delete(marker_sexp) rescue nil
|
33
|
+
if (block = sexp.find_node(:block)) && block.to_a.size == 2
|
34
|
+
sexp.gsub(block, Sexp.from_array(block.to_a[1]))
|
35
|
+
else
|
36
|
+
sexp
|
37
|
+
end
|
50
38
|
end
|
51
39
|
|
40
|
+
def extract_sexp_args
|
41
|
+
raw, marker = raw_sexp_and_marker
|
42
|
+
regexp = Regexp.new(
|
43
|
+
'^(.*(' + Regexp.quote('s(:iter, s(:call, nil, :') +
|
44
|
+
'(?:proc|lambda)' + Regexp.quote(', s(:arglist') +
|
45
|
+
'.*?' + Regexp.quote("s(:call, nil, :#{marker}, s(:arglist))") +
|
46
|
+
'))(.*)$', Regexp::MULTILINE
|
47
|
+
)
|
48
|
+
[raw.match(regexp)[2..3], marker].flatten
|
49
|
+
end
|
52
50
|
|
53
51
|
def raw_sexp_and_marker
|
52
|
+
# TODO: Ugly chunk, need some lovely cleanup !!
|
54
53
|
%W{#{@klass}\.new lambda|proc|Proc\.new}.each do |declarative|
|
55
|
-
regexp = /^(.*?(#{declarative})\s*(do|\{)\s*(
|
54
|
+
regexp = /^((.*?)(#{declarative})(\s*(?:do|\{)\s*(?:\|(?:[^\|]*)\|\s*)?)(.*)?)$/m
|
56
55
|
raw = raw_code
|
57
56
|
lines1, lines2 = [(0 .. (@line - 2)), (@line.pred .. -1)].map{|r| raw[r] }
|
58
|
-
|
57
|
+
prepend, type, block_start, append = lines2[0].match(regexp)[2..5] rescue next
|
59
58
|
|
60
59
|
if lines2[0] =~ /^(.*?\W)?(#{declarative})(\W.*?\W(#{declarative}))+(\W.*)?$/
|
61
60
|
msg = "Static code analysis can only handle single occurrence of '%s' per line !!" %
|
@@ -63,8 +62,9 @@ class SerializableProc
|
|
63
62
|
raise CannotAnalyseCodeError.new(msg)
|
64
63
|
elsif lines2[0] =~ /^(.*?\W)?(#{declarative})(\W.*)?$/
|
65
64
|
marker = "__serializable_proc_marker_#{@line}__"
|
66
|
-
|
67
|
-
|
65
|
+
line = "#{prepend}proc#{block_start} #{marker}; #{append}"
|
66
|
+
lines = lines1.join + escape_magic_vars(line + lines2[1..-1].join)
|
67
|
+
return [RUBY_PARSER.parse(lines).inspect, marker]
|
68
68
|
end
|
69
69
|
end
|
70
70
|
raise CannotAnalyseCodeError.new('Cannot find specified initializer !!')
|
@@ -1,6 +1,28 @@
|
|
1
1
|
class SerializableProc
|
2
2
|
module Parsers
|
3
|
+
|
3
4
|
RUBY_2_RUBY = Ruby2Ruby.new
|
5
|
+
|
6
|
+
class Base
|
7
|
+
class << self
|
8
|
+
|
9
|
+
include Isolatable
|
10
|
+
|
11
|
+
def sexp_derivatives(sexp, &fix_code)
|
12
|
+
isexp = isolated_sexp(sexp)
|
13
|
+
icode, code = [isexp, sexp].map do |_sexp|
|
14
|
+
code = RUBY_2_RUBY.process(Sexp.from_array(_sexp.to_a))
|
15
|
+
block_given? ? fix_code.call(code) : code
|
16
|
+
end
|
17
|
+
[
|
18
|
+
{:runnable => icode, :extracted => code},
|
19
|
+
{:runnable => isexp, :extracted => sexp}
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
4
26
|
end
|
5
27
|
end
|
6
28
|
|
data/lib/serializable_proc.rb
CHANGED
@@ -2,9 +2,9 @@ require 'rubygems'
|
|
2
2
|
require 'forwardable'
|
3
3
|
require 'ruby2ruby'
|
4
4
|
require 'serializable_proc/marshalable'
|
5
|
+
require 'serializable_proc/isolatable'
|
5
6
|
require 'serializable_proc/parsers'
|
6
7
|
require 'serializable_proc/binding'
|
7
|
-
require 'serializable_proc/sandboxer'
|
8
8
|
require 'serializable_proc/fixes'
|
9
9
|
|
10
10
|
begin
|
@@ -19,9 +19,9 @@ end
|
|
19
19
|
#
|
20
20
|
# #1. Isolated variables
|
21
21
|
#
|
22
|
-
#
|
23
|
-
# are extracted from the proc's binding, and are isolated from changes
|
24
|
-
# proc's scope, thus, achieving a snapshot effect.
|
22
|
+
# By default, upon initializing, all variables (local, instance, class & global) within
|
23
|
+
# its context are extracted from the proc's binding, and are isolated from changes
|
24
|
+
# outside the proc's scope, thus, achieving a snapshot effect.
|
25
25
|
#
|
26
26
|
# require 'rubygems'
|
27
27
|
# require 'serializable_proc'
|
@@ -36,6 +36,24 @@ end
|
|
36
36
|
# s_proc.call # >> "lx, ix, cx, gx"
|
37
37
|
# v_proc.call # >> "ly, iy, cy, gy"
|
38
38
|
#
|
39
|
+
# It is possible to fine-tune how variables isolation is being applied by declaring
|
40
|
+
# @@_not_isolated_vars within the code block:
|
41
|
+
#
|
42
|
+
# x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
|
43
|
+
#
|
44
|
+
# s_proc = SerializableProc.new do
|
45
|
+
# @@_not_isolated_vars = :global, :class, :instance, :local
|
46
|
+
# [x, @x, @@x, $x].join(', ')
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# x, @x, @@x, $x = 'ly', 'iy', 'cy', 'gy'
|
50
|
+
#
|
51
|
+
# # Passing Kernel.binding is required to avoid nasty surprises
|
52
|
+
# s_proc.call(binding) # >> "ly, iy, cy, gy"
|
53
|
+
#
|
54
|
+
# Note that it is strongly-advised to append Kernel.binding as the last parameter when
|
55
|
+
# invoking the proc to avoid unnecessary nasty surprises.
|
56
|
+
#
|
39
57
|
# #2. Marshallable
|
40
58
|
#
|
41
59
|
# No throwing of TypeError when marshalling a SerializableProc:
|
@@ -46,7 +64,7 @@ end
|
|
46
64
|
class SerializableProc
|
47
65
|
|
48
66
|
include Marshalable
|
49
|
-
marshal_attrs :file, :line, :code, :arity, :binding
|
67
|
+
marshal_attrs :file, :line, :code, :arity, :binding, :sexp
|
50
68
|
|
51
69
|
##
|
52
70
|
# Creates a new instance of SerializableProc by passing in a code block, in the process,
|
@@ -64,11 +82,21 @@ class SerializableProc
|
|
64
82
|
# def action(&block) ; SerializableProc.new(&block) ; end
|
65
83
|
# action { ... }
|
66
84
|
#
|
85
|
+
# Fine-tuning of variables isolation can be done by declaring @@_not_isolated_vars
|
86
|
+
# within the code block:
|
87
|
+
#
|
88
|
+
# SerializableProc.new do
|
89
|
+
# @@_not_isolated_vars = :global # don't isolate globals
|
90
|
+
# $stdout << 'WAKE UP !!' # $stdout won't be isolated (avoid marshal error)
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# (see #call for invoking)
|
94
|
+
#
|
67
95
|
def initialize(&block)
|
68
96
|
file, line = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$/.match(block.inspect)[1..2]
|
69
97
|
@file, @line, @arity = File.expand_path(file), line.to_i, block.arity
|
70
|
-
@code, sexp = Parsers::PT.process(block) || Parsers::RP.process(self.class, @file, @line)
|
71
|
-
@binding = Binding.new(block.binding, sexp)
|
98
|
+
@code, @sexp = Parsers::PT.process(block) || Parsers::RP.process(self.class, @file, @line)
|
99
|
+
@binding = Binding.new(block.binding, @sexp[:extracted])
|
72
100
|
end
|
73
101
|
|
74
102
|
##
|
@@ -103,8 +131,12 @@ class SerializableProc
|
|
103
131
|
# def action(&block) ; yield ; end
|
104
132
|
# action(&s_proc) # >> 'lx, ix, cx, gx'
|
105
133
|
#
|
106
|
-
def to_proc
|
107
|
-
|
134
|
+
def to_proc(binding = nil)
|
135
|
+
if binding
|
136
|
+
eval(@code[:runnable], @binding.eval!(binding), @file, @line)
|
137
|
+
else
|
138
|
+
@proc ||= eval(@code[:runnable], @binding.eval!, @file, @line)
|
139
|
+
end
|
108
140
|
end
|
109
141
|
|
110
142
|
##
|
@@ -131,6 +163,16 @@ class SerializableProc
|
|
131
163
|
@code[debug ? :runnable : :extracted]
|
132
164
|
end
|
133
165
|
|
166
|
+
##
|
167
|
+
# Returns the sexp representation of this instance. By default, the sexp represents the
|
168
|
+
# extracted code, if +debug+ specified as true, the runnable code version is returned.
|
169
|
+
#
|
170
|
+
# SerializableProc.new { [x, @x, @@x, $x].join(', ') }.to_sexp
|
171
|
+
#
|
172
|
+
def to_sexp(debug = false)
|
173
|
+
@sexp[debug ? :runnable : :extracted]
|
174
|
+
end
|
175
|
+
|
134
176
|
##
|
135
177
|
# Returns the number of arguments accepted when running #call. This is extracted directly
|
136
178
|
# from the initializing code block, & is only as accurate as Proc#arity.
|
@@ -157,8 +199,32 @@ class SerializableProc
|
|
157
199
|
# SerializableProc.new{|i| (['hello'] * i).join(' ') }.call(2)
|
158
200
|
# # >> 'hello hello'
|
159
201
|
#
|
202
|
+
# In the case where variables have been declared not-isolated with @@_not_isolated_vars,
|
203
|
+
# invoking requires passing in +Kernel.binding+ as the last parameter avoid unexpected
|
204
|
+
# surprises:
|
205
|
+
#
|
206
|
+
# x, @x, @@x, $x = 'lx', 'ix', 'cx', 'gx'
|
207
|
+
# s_proc = SerializableProc.new do
|
208
|
+
# @@_not_isolated_vars = :global, :class, :instance, :local
|
209
|
+
# [x, @x, @@x, $x].join(', ')
|
210
|
+
# end
|
211
|
+
#
|
212
|
+
# s_proc.call
|
213
|
+
# # >> raises NameError for x
|
214
|
+
# # >> @x is assumed nil (undefined)
|
215
|
+
# # >> raises NameError for @@x (actually this depends on if u are using 1.9.* or 1.8.*)
|
216
|
+
# # >> no issue with $x (since global is, after all, a global)
|
217
|
+
#
|
218
|
+
# To ensure expected results:
|
219
|
+
#
|
220
|
+
# s_proc.call(binding) # >> 'lx, ix, cx, gx'
|
221
|
+
#
|
160
222
|
def call(*params)
|
161
|
-
|
223
|
+
if (binding = params[-1]).is_a?(::Binding)
|
224
|
+
to_proc(binding).call(*params[0..-2])
|
225
|
+
else
|
226
|
+
to_proc.call(*params)
|
227
|
+
end
|
162
228
|
end
|
163
229
|
|
164
230
|
alias_method :[], :call
|
data/serializable_proc.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{serializable_proc}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["NgTzeYang"]
|
12
|
-
s.date = %q{2010-08-
|
12
|
+
s.date = %q{2010-08-18}
|
13
13
|
s.description = %q{
|
14
14
|
Give & take, serializing a ruby proc is possible, though not a perfect one.
|
15
15
|
Requires either ParseTree (faster) or RubyParser (& Ruby2Ruby).
|
@@ -30,21 +30,36 @@ Gem::Specification.new do |s|
|
|
30
30
|
"lib/serializable_proc.rb",
|
31
31
|
"lib/serializable_proc/binding.rb",
|
32
32
|
"lib/serializable_proc/fixes.rb",
|
33
|
+
"lib/serializable_proc/isolatable.rb",
|
33
34
|
"lib/serializable_proc/marshalable.rb",
|
34
35
|
"lib/serializable_proc/parsers.rb",
|
35
36
|
"lib/serializable_proc/parsers/pt.rb",
|
36
37
|
"lib/serializable_proc/parsers/rp.rb",
|
37
|
-
"lib/serializable_proc/sandboxer.rb",
|
38
38
|
"serializable_proc.gemspec",
|
39
|
-
"spec/
|
40
|
-
"spec/
|
41
|
-
"spec/
|
42
|
-
"spec/
|
43
|
-
"spec/
|
44
|
-
"spec/
|
45
|
-
"spec/
|
46
|
-
"spec/
|
47
|
-
"spec/
|
39
|
+
"spec/bounded_vars/class_vars_spec.rb",
|
40
|
+
"spec/bounded_vars/class_vars_within_block_scope_spec.rb",
|
41
|
+
"spec/bounded_vars/errors_spec.rb",
|
42
|
+
"spec/bounded_vars/global_vars_spec.rb",
|
43
|
+
"spec/bounded_vars/global_vars_within_block_scope_spec.rb",
|
44
|
+
"spec/bounded_vars/instance_vars_spec.rb",
|
45
|
+
"spec/bounded_vars/instance_vars_within_block_scope_spec.rb",
|
46
|
+
"spec/bounded_vars/local_vars_spec.rb",
|
47
|
+
"spec/bounded_vars/local_vars_within_block_scope_spec.rb",
|
48
|
+
"spec/code_block/errors_spec.rb",
|
49
|
+
"spec/code_block/multiple_arities_spec.rb",
|
50
|
+
"spec/code_block/optional_arity_spec.rb",
|
51
|
+
"spec/code_block/renaming_vars_spec.rb",
|
52
|
+
"spec/code_block/single_arity_spec.rb",
|
53
|
+
"spec/code_block/zero_arity_spec.rb",
|
54
|
+
"spec/proc_like/extras_spec.rb",
|
55
|
+
"spec/proc_like/invoking_with_args_spec.rb",
|
56
|
+
"spec/proc_like/invoking_with_class_vars_spec.rb",
|
57
|
+
"spec/proc_like/invoking_with_global_vars_spec.rb",
|
58
|
+
"spec/proc_like/invoking_with_instance_vars_spec.rb",
|
59
|
+
"spec/proc_like/invoking_with_local_vars_spec.rb",
|
60
|
+
"spec/proc_like/marshalling_spec.rb",
|
61
|
+
"spec/proc_like/others_spec.rb",
|
62
|
+
"spec/spec_helper.rb"
|
48
63
|
]
|
49
64
|
s.homepage = %q{http://github.com/ngty/serializable_proc}
|
50
65
|
s.post_install_message = %q{
|
@@ -67,15 +82,30 @@ Gem::Specification.new do |s|
|
|
67
82
|
s.rubygems_version = %q{1.3.7}
|
68
83
|
s.summary = %q{Proc that can be serialized (as the name suggests)}
|
69
84
|
s.test_files = [
|
70
|
-
"spec/
|
71
|
-
"spec/
|
72
|
-
"spec/
|
73
|
-
"spec/
|
74
|
-
"spec/
|
75
|
-
"spec/
|
76
|
-
"spec/
|
77
|
-
"spec/
|
78
|
-
"spec/
|
85
|
+
"spec/proc_like/extras_spec.rb",
|
86
|
+
"spec/proc_like/invoking_with_local_vars_spec.rb",
|
87
|
+
"spec/proc_like/invoking_with_instance_vars_spec.rb",
|
88
|
+
"spec/proc_like/invoking_with_class_vars_spec.rb",
|
89
|
+
"spec/proc_like/invoking_with_args_spec.rb",
|
90
|
+
"spec/proc_like/others_spec.rb",
|
91
|
+
"spec/proc_like/invoking_with_global_vars_spec.rb",
|
92
|
+
"spec/proc_like/marshalling_spec.rb",
|
93
|
+
"spec/code_block/multiple_arities_spec.rb",
|
94
|
+
"spec/code_block/zero_arity_spec.rb",
|
95
|
+
"spec/code_block/errors_spec.rb",
|
96
|
+
"spec/code_block/renaming_vars_spec.rb",
|
97
|
+
"spec/code_block/single_arity_spec.rb",
|
98
|
+
"spec/code_block/optional_arity_spec.rb",
|
99
|
+
"spec/bounded_vars/global_vars_within_block_scope_spec.rb",
|
100
|
+
"spec/bounded_vars/instance_vars_within_block_scope_spec.rb",
|
101
|
+
"spec/bounded_vars/errors_spec.rb",
|
102
|
+
"spec/bounded_vars/local_vars_within_block_scope_spec.rb",
|
103
|
+
"spec/bounded_vars/class_vars_spec.rb",
|
104
|
+
"spec/bounded_vars/local_vars_spec.rb",
|
105
|
+
"spec/bounded_vars/global_vars_spec.rb",
|
106
|
+
"spec/bounded_vars/instance_vars_spec.rb",
|
107
|
+
"spec/bounded_vars/class_vars_within_block_scope_spec.rb",
|
108
|
+
"spec/spec_helper.rb"
|
79
109
|
]
|
80
110
|
|
81
111
|
if s.respond_to? :specification_version then
|