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.
Files changed (41) hide show
  1. data/.gitignore +1 -0
  2. data/HISTORY.txt +9 -0
  3. data/README.rdoc +28 -10
  4. data/VERSION +1 -1
  5. data/lib/serializable_proc/binding.rb +27 -32
  6. data/lib/serializable_proc/fixes.rb +1 -1
  7. data/lib/serializable_proc/isolatable.rb +59 -0
  8. data/lib/serializable_proc/parsers/pt.rb +2 -5
  9. data/lib/serializable_proc/parsers/rp.rb +29 -29
  10. data/lib/serializable_proc/parsers.rb +22 -0
  11. data/lib/serializable_proc.rb +76 -10
  12. data/serializable_proc.gemspec +51 -21
  13. data/spec/bounded_vars/class_vars_spec.rb +60 -0
  14. data/spec/bounded_vars/class_vars_within_block_scope_spec.rb +31 -0
  15. data/spec/bounded_vars/errors_spec.rb +27 -0
  16. data/spec/bounded_vars/global_vars_spec.rb +60 -0
  17. data/spec/bounded_vars/global_vars_within_block_scope_spec.rb +31 -0
  18. data/spec/bounded_vars/instance_vars_spec.rb +60 -0
  19. data/spec/bounded_vars/instance_vars_within_block_scope_spec.rb +31 -0
  20. data/spec/bounded_vars/local_vars_spec.rb +60 -0
  21. data/spec/bounded_vars/local_vars_within_block_scope_spec.rb +31 -0
  22. data/spec/code_block/errors_spec.rb +65 -0
  23. data/spec/{multiple_arities_serializable_proc_spec.rb → code_block/multiple_arities_spec.rb} +2 -2
  24. data/spec/{optional_arity_serializable_proc_spec.rb → code_block/optional_arity_spec.rb} +2 -2
  25. data/spec/code_block/renaming_vars_spec.rb +160 -0
  26. data/spec/{one_arity_serializable_proc_spec.rb → code_block/single_arity_spec.rb} +2 -2
  27. data/spec/{zero_arity_serializable_proc_spec.rb → code_block/zero_arity_spec.rb} +2 -2
  28. data/spec/proc_like/extras_spec.rb +82 -0
  29. data/spec/proc_like/invoking_with_args_spec.rb +58 -0
  30. data/spec/proc_like/invoking_with_class_vars_spec.rb +96 -0
  31. data/spec/proc_like/invoking_with_global_vars_spec.rb +79 -0
  32. data/spec/proc_like/invoking_with_instance_vars_spec.rb +79 -0
  33. data/spec/proc_like/invoking_with_local_vars_spec.rb +79 -0
  34. data/spec/{marshalling_spec.rb → proc_like/marshalling_spec.rb} +1 -1
  35. data/spec/proc_like/others_spec.rb +106 -0
  36. data/spec/spec_helper.rb +10 -0
  37. metadata +52 -22
  38. data/lib/serializable_proc/sandboxer.rb +0 -24
  39. data/spec/extracting_bound_variables_spec.rb +0 -99
  40. data/spec/initializing_errors_spec.rb +0 -95
  41. data/spec/proc_like_spec.rb +0 -191
data/.gitignore CHANGED
@@ -19,3 +19,4 @@ rdoc
19
19
  pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
+ tmp
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
- Upon initializing, all variables (local, instance, class & global) within its context
16
- are extracted from the proc's binding, and are isolated from changes outside the proc's
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. Provide flexibility for user to specify whether global variables should be isolated,
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
- 3. Implementing alternative means of extracting the code block without requiring help
137
+ 2. Implementing alternative means of extracting the code block without requiring help
120
138
  of ParseTree or RubyParser
121
- 4. Implement workaround to tackle line-numbering bug in JRuby, which causes the
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.1
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, sexp_str = {}, sexp.inspect
12
- while m = sexp_str.match(/^(.*?s\(:(?:l|g|c|i)var, :([^\)]+)\))/)
13
- ignore, var = m[1..2]
14
- sexp_str.sub!(ignore,'')
15
- begin
16
- val = eval(var, binding) rescue nil
17
- @vars.update(Sandboxer.fvar(var) => mclone(val))
18
- rescue TypeError
19
- raise CannotSerializeVariableError.new("Variable #{var} cannot be serialized !!")
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
- @binding ||= (
26
- set_vars = @vars.map{|(k,v)| "#{k} = Marshal.load(%|#{mdump(v)}|)" } * '; '
27
- eval(set_vars, binding = Kernel.binding)
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
- # binding.extend(Extensions)
30
- )
39
+ end
31
40
  end
32
41
 
33
- module Extensions
34
- def self.extended(base)
35
- class << base
42
+ private
36
43
 
37
- alias_method :orig_eval, :eval
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
@@ -1,7 +1,7 @@
1
1
  # NOTE: This file contains hackeries to ensure compatibility with different rubies
2
2
 
3
3
  unless 1.respond_to?(:pred)
4
- class Integer
4
+ class Integer #:nodoc:
5
5
  def pred
6
6
  self - 1
7
7
  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
- module PT
3
+ class PT < Base
4
4
  class << self
5
5
  def process(block)
6
6
  if Object.const_defined?(:ParseTree)
7
- sexp = block.to_sexp
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
- module RP
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, type, marker = extract_sexp_args
18
+ sexp_str, remaining, @marker = extract_sexp_args
19
19
  while frag = remaining[/^([^\)]*\))/,1]
20
20
  begin
21
- sexp = eval(sexp_str += frag) # this throws SyntaxError if sexp is invalid
22
- runnable_code, extracted_code = [
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 extract_sexp_args
36
- raw, type, marker = raw_sexp_and_marker
37
- rq = lambda{|s| Regexp.quote(s) }
38
- regexp = Regexp.new([
39
- '^(.*(', (
40
- case type
41
- when /(#{@klass}|Proc)/ then rq["s(:iter, s(:call, s(:const, :#{$1}), :new, s(:arglist)),"]
42
- else rq['s(:iter, s(:call, nil, :'] + '(?:proc|lambda)' + rq[', s(:arglist']
43
- end
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*(\|([^\|]*)\|\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
- match, type = lines2[0].match(regexp)[1..2] rescue next
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
- lines = lines1.join + escape_magic_vars(lines2[0].sub(match, match+marker+';') + lines2[1..-1].join)
67
- return [RUBY_PARSER.parse(lines).inspect, type, marker]
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
 
@@ -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
- # Upon initializing, all variables (local, instance, class & global) within its context
23
- # are extracted from the proc's binding, and are isolated from changes outside the
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
- @proc ||= eval(@code[:runnable], @binding.eval!, @file, @line)
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
- to_proc.call(*params)
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
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{serializable_proc}
8
- s.version = "0.1.1"
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-16}
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/extracting_bound_variables_spec.rb",
40
- "spec/initializing_errors_spec.rb",
41
- "spec/marshalling_spec.rb",
42
- "spec/multiple_arities_serializable_proc_spec.rb",
43
- "spec/one_arity_serializable_proc_spec.rb",
44
- "spec/optional_arity_serializable_proc_spec.rb",
45
- "spec/proc_like_spec.rb",
46
- "spec/spec_helper.rb",
47
- "spec/zero_arity_serializable_proc_spec.rb"
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/initializing_errors_spec.rb",
71
- "spec/extracting_bound_variables_spec.rb",
72
- "spec/one_arity_serializable_proc_spec.rb",
73
- "spec/zero_arity_serializable_proc_spec.rb",
74
- "spec/multiple_arities_serializable_proc_spec.rb",
75
- "spec/marshalling_spec.rb",
76
- "spec/optional_arity_serializable_proc_spec.rb",
77
- "spec/spec_helper.rb",
78
- "spec/proc_like_spec.rb"
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