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.
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