LoginSignup
2
0

More than 3 years have passed since last update.

pryでRubyVM::AbstractSyntaxTree.ofを使って雑にASTを取る

Last updated at Posted at 2019-05-04

TL;DR

~/.pryrcに以下を足すだけでOK

unless defined?(SCRIPT_LINES__)
  SCRIPT_LINES__ = {}
end

Pry.config.hooks.add_hook(:before_eval, :"ast<3") do |code, _pry|
  SCRIPT_LINES__["(pry)"] = code.lines
  code
end

% pry
[1] pry(main)> RubyVM::AbstractSyntaxTree.of(-> { puts :hi })
=> (SCOPE@1:32-1:45
 tbl: []
 args:
   (ARGS@1:32-1:32
    pre_num: 0
    pre_init: nil
    opt: nil
    first_post: nil
    post_num: 0
    post_init: nil
    rest: nil
    kw: nil
    kwrest: nil
    block: nil)
 body: (FCALL@1:35-1:43 :puts (ARRAY@1:40-1:43 (LIT@1:40-1:43 :hi) nil)))
[2] pry(main)>

eval上書きではだめなのはなぜ

結論から言うとrubyには数々のeval属があってpryが使っているのはBinding#eval。
なのでKernel#evalを上書きしても動かない。

神速さんのエントリを読んだ。

byebugやpryもevalを使っているだろうし、evalを上書きすれば他も簡単に対応できる....と思っていた。 世の中はそんなに甘くなかった。
https://sinsoku.hatenablog.com/entry/2019/05/04/032007

これはすごい! evalだけ書き換えればよいとかなりべんりじゃん、マーベラス!と思って一応eval上書きも試したがうまく動かなかった。

      def pry_eval(*eval_strs)
        b =
          if eval_strs.first.is_a?(String)
            Pry.toplevel_binding
          else
            Pry.binding_for(eval_strs.shift)
          end
        pry_tester(b).eval(*eval_strs)
      end

一応動かなかった実装例をあげておきます。
こうしてCで実装されているメソッドにパッチを当てておくとc_callじゃなくcallイベントで取れるようになる。
targetで指定できるようになるしbindingからローカル変数取れるようになってべんり。
戻り値を書き換えるときにも使えてべんりで詳しくは過去記事参照されたし。
https://qiita.com/hanachin_/items/d5e243048900344b2146

unless defined?(SCRIPT_LINES__)
  SCRIPT_LINES__ = {}
end

eval_patch = Module.new do
  def eval(string, binding = nil, filename = nil, lineno = nil)
    super
  end
end

Kernel.prepend(eval_patch)

ast_happier = TracePoint.new(:call) do |tp|
  filename = tp.binding.local_variable_get(:filename)

  next unless filename
  next if File.exist?(filename)

  SCRIPT_LINES__[filename] = tp.binding.local_variable_get(:string)
end
ast_happier.enable(target: eval_patch.instance_method(:eval))

ならばBinding#eval上書きだ!

byebugとかだとbyebug -r ast.rb foo.rbみたいな感じで-rオプションで読み込ませておいてあげるとうまく動く

ast.rb
unless defined?(SCRIPT_LINES__)
  SCRIPT_LINES__ = {}
end

eval_patch = Module.new do
  def eval(string, filename = nil, lineno = nil)
    SCRIPT_LINES__[filename] = string.lines
    super
  end
end

Binding.prepend(eval_patch)

pryの場合

ところでpryにはモンキーパチらなくていいbefore_eval hookがあるのだった

参考

2
0
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
2
0