LoginSignup
4
4

More than 5 years have passed since last update.

Ruby の Proc オブジェクトのソースを表示するコード

Last updated at Posted at 2017-10-06

Proc オブジェクトのソースを表示するコードを書いてみました。

proc_source.rb
require "ripper"

class Proc
  def proc_source
    return @source if @source
    filepath, start_pos = source_location

    code = File.readlines(filepath)[(start_pos-1)..-1].join
    tokens = Ripper.lex(code).drop_while do |t|
      !proc_token?(t)
    end

    @source = " " * tokens[0][0][1].to_i

    while token = tokens.shift
      @source += token[2]
      break if valid_proc_source?(@source)
    end

    @source
  end

  private

  def proc_token?(token)
    _pos, event, ident = token
    return true if event == :on_const && ident == "Proc"
    return true if event == :on_ident && ident == "lambda"
    return true if event == :on_ident && ident == "proc"
    return true if event == :on_kw && ident == "do"
    return true if event == :on_lbrace
    return true if event == :on_tlambda
    false
  end

  def valid_proc_source?(source)
    source = "Proc.new " + source if source =~ /\A\s*(do|{)/
    eval(source).instance_of?(Proc)
  rescue SyntaxError, ArgumentError
    false
  end
end

こんな感じのコードを書くと

a = -> { 'hi' }
puts a.proc_source

こんな感じで表示されます。

    -> { 'hi' }

Gem にしてみました

せっかくなので gem にしました。

うまくいかない例

いくつかうまくソースが取得出来ないケースを見つけているのですが、対応するのがつらいのでやる気があるときに頑張りたいと思います。

eval で作られる

lambda1 = eval('lambda { "hi" }')
puts lambda1.proc_source  #=> No such file or directory @ rb_sysopen - (eval) (Errno::ENOENT)

多重代入される

proc1, proc2 = Proc.new { "hi" }, Proc.new { "hey" }
puts proc1.proc_source #=>                Proc.new { "hi" }
puts proc2.proc_source #=>                Proc.new { "hi" }

etc...

#source_location が列番号も返してくれると問題も解決しやすくなりそうです。

追記 (2019/01/30)

Ruby 2.6 から RubyVM::AbstractSyntaxTree.of が使えるようになり、より簡潔にソースを取得出来るようになりました。

参考サイト

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4