serializable_proc 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|