u16suzuの blog

日々学んだことのメモブログです。

「Rubyのしくみ」(Ruby Under a Microscope)を読んだ

Rubyのしくみ」(Ruby Under a Microscope)を読みました。

Rubyのしくみ -Ruby Under a Microscope-

Rubyのしくみ -Ruby Under a Microscope-

Rubyの内部における実行の流れを調べる

字句解析、構文解析コンパイルが実際にどう行われているかを Rubyに添付されている、Rubyプログラムのパーサである Ripperクラス で調査することができます。その方法を説明します。

Ruby実行の流れは 1.8.xと1.9.xで大きく異なります。 1.9.xからYARV(Yet Another Ruby Virtual Machine)が導入されました。

  • v1.8.X

[Rubyコード] 字句解析 => 構文解析 => 実行

  • v1.9.Xより上のバージョン

[Rubyコード] =字句解析=> [トークン列] =構文解析=> [AST] =コンパイル=> [YARV命令文] => YARVで実行される

ASTは構文解析木(Abstract syntax tree)の略です。 まずは字句解析からです。 Rubyソースコードの読み込み処理です。

字句解析

require "ripper"
require "pp"

code = <<STR
10.times do |n|
  puts n
end
STR

puts code
pp Ripper.lex(code)
  • 実行結果
[[[1, 0], :on_int, "10"],
 [[1, 2], :on_period, "."],
 [[1, 3], :on_ident, "times"],
 [[1, 7], :on_sp, " "],
 [[1, 8], :on_kw, "do"],
 [[1, 10], :on_sp, " "],
 [[1, 11], :on_op, "|"],
 [[1, 12], :on_ident, "n"],
 [[1, 13], :on_op, "|"],
 [[1, 14], :on_ignored_nl, "\n"],
 [[2, 0], :on_sp, "  "],
 [[2, 2], :on_ident, "puts"],
 [[2, 6], :on_sp, " "],
 [[2, 7], :on_ident, "n"],
 [[2, 8], :on_nl, "\n"],
 [[3, 0], :on_kw, "end"],
 [[3, 3], :on_nl, "\n"]]

続いて、構文解析です。

構文解析

require "ripper"
require "pp"

code = <<STR
10.times do |n|
  puts n
end
STR

puts code
pp Ripper.sexp(code)
  • 実行結果
[:program,
 [[:method_add_block,
   [:call, [:@int, "10", [1, 0]], :".", [:@ident, "times", [1, 3]]],
   [:do_block,
    [:block_var,
     [:params, [[:@ident, "n", [1, 12]]], nil, nil, nil, nil, nil, nil],
     false],
    [[:command,
      [:@ident, "puts", [2, 2]],
      [:args_add_block, [[:var_ref, [:@ident, "n", [2, 7]]]], false]]]]]]]

最後にYARV実行命令です。

コンパイル(YARV命令を表示)

code = <<END
puts 2+2
END

puts RubyVM::InstructionSequence.compile(code).disasm
  • 実行結果
10.times do |n|
  puts n
end
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
0000 trace            1                                               (   1)
0002 putself
0003 putobject        2
0005 putobject        2
0007 opt_mult         <callinfo!mid:*, argc:1, ARGS_SKIP>
0009 opt_send_simple  <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>
0011 leave