{{ ty's engineering log }}
  ~ non-optimized bits & pieces of my techincal side

>> Getting a 1.9.*-ish Proc#source_location

Ruby 1.9.* comes with Proc#source_location, which according to the documentation, returns the ruby source filename and line number containing this proc, or nil if this proc was not defined in ruby (i.e. native):

  1. prc.source_location
  2. # >> [String, Fixnum]

I’ve never wrote proc in native code, but from my experience while writing sourcify, the procs derived from Method#to_proc & Symbol#to_proc returns Proc#source_location as nil:

  1. thing = Class.new {
  2. def test1(&block); block ; end
  3. def test2; end
  4. def test3; lambda{}; end
  5. }.new
  6. thing.method(:test1).to_proc.source_location
  7. # >> nil
  8. thing.test1(&:test2).source_location
  9. # >> nil
  10. thing.test3.source_location
  11. # >> ["/tmp/test.rb", 4]

If we wanna enhance 1.8.* & JRuby to support Proc#source_location (a bonus provided by sourcify in supporting Proc#to_source), the fundamental approach is to use Proc#inspect:

  1. proc = lambda{}
  2. inspect_str = proc.inspect
  3. # >> #<Proc:0x00007f53743f24a0@/tmp/test.rb:2>
  4. %r{^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$}.match(inspect_str)[1..2]
  5. # >> ["/tmp/test.rb", "2"]

Thanks to ruby’s open class support, we open up the Proc class to add the method:

  1. class Proc
  2. # Honour the already implemented Proc#source_location
  3. unless lambda{}.respond_to?(:source_location)
  4. def source_location
  5. file, line = %r{^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$}.match(inspect)[1..2]
  6. [file, line.to_i]
  7. end
  8. end
  9. end

Reusing the above derivation of thing, let’s see if the home-baked Proc#source_location behaves as expected:

  1. # Nope, it doesn't :(
  2. thing.method(:test1).to_proc.source_location
  3. # Ruby-1.8.7 >> ["/tmp/test.rb", 2]
  4. # JRuby-1.5.2 >> ["/tmp/test.rb", 2]
  5. # Nope, it doesn't :(
  6. thing.test1(&:test2).source_location
  7. # Ruby-1.8.7 >> ["/tmp/test.rb", 7]
  8. # JRuby-1.5.2 >> ["/home/ty.archlinux/.rvm/rubies/jruby-1.5.2/lib/ruby/site_ruby/shared/builtin/core_ext/symbol.rb", 3]
  9. # Yup, it does :)
  10. thing.test3.source_location
  11. # Ruby-1.8.7 >> ["/tmp/test.rb", 12]
  12. # JRuby-1.5.2 >> ["/tmp/test.rb", 12]

For JRuby, Symbol#to_proc yields a proc with that originates from somewhere in JRuby’s core installation. For the case of JRuby, we can use this unique locator to achieve what we have 1.9.*. But that ONLY solves half of the problem for JRuby, and non for Ruby-1.8.7.

In order to capture created-on-the-fly-ness of the proc, i introduced Proc#created_on_the_fly to store the flag, here’s the solution i’ve used:

  1. class Proc
  2. unless lambda{}.respond_to?(:source_location)
  3. def source_location
  4. # Only if proc is not created on the fly then we return meaningful source location
  5. unless created_on_the_fly
  6. file, line = %r{^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$}.match(inspect)[1..2]
  7. [file, line.to_i]
  8. end
  9. end
  10. # Flag to capture if proc is created on the fly
  11. attr_accessor :created_on_the_fly
  12. [Method, Symbol].each do |klass|
  13. klass.class_eval do
  14. # Backup the original klass#to_proc
  15. alias_method :__pre_patched_to_proc, :to_proc
  16. def to_proc
  17. _proc = __pre_patched_to_proc # get the original proc
  18. _proc.created_on_the_fly = true # manipulate the flag
  19. _proc
  20. end
  21. end
  22. end
  23. end
  24. end

Seeing is believing:

  1. # Yup, it does :)
  2. thing.method(:test1).to_proc.source_location
  3. # Ruby-1.8.7 >> nil
  4. # JRuby-1.5.2 >> nil
  5. # Yup, it does :)
  6. thing.test1(&:test2).source_location
  7. # Ruby-1.8.7 >> nil
  8. # JRuby-1.5.2 >> nil
  9. # Yup, it does :)
  10. thing.test3.source_location
  11. # Ruby-1.8.7 >> ["/tmp/test.rb", 12]
  12. # JRuby-1.5.2 >> ["/tmp/test.rb", 12]

Does the above work for Ruby-1.8.6 ? Yup, but a little bit of tweaking of the above solution is required. Anyway, the above is already implemented in sourcify, do it check it out :]

~ September 20, 2010

Posted in category ruby. Tagged with tips, ruby.

blog comments powered by Disqus

Archive