Go to list of users who liked
Share on X(Twitter)
Share on Facebook
More than 1 year has passed since last update.
この記事は2013年時点の状況をまとめたものです。現在では内容が古くなっています。
今後はプロと読み解くRuby 3.3 NEWS - STORES Product Blogの「(参考)Ruby パーサの比較」などを参照することをお勧めします。
ripper
Ruby1.9からMRIの標準ライブラリとなっており、今後とも安定してメンテナンスされることが期待できる。MRIのパーサーを拡張ライブラリ化しているため、文法の互換性の点では他の追随を許
さない。
require'ripper'pRipper.sexp('puts "hello, world."')# [:program,# [[:command,# [:@ident, "puts", [1, 0]],# [:args_add_block,# [[:string_literal,# [:string_content, [:@tstring_content, "hello, world.", [1, 6]]]]],# false]]]]パース結果はS式で出力したり、SAX風のイベントドリブン型インターフェイスで取得したりできる。
イベントには、字句解析によって発生するscanner eventと、構文解析によって発生するparser eventの二種類がある。それぞれのイベントの種類についてはRipper::SCANNER_EVENTSとRipper::PARSER_EVENTSで確認できる。
ソースコードのエラー処理
compile_error,on_parse_error,warn,warningというメソッドを定義することで、文法エラーや警告が取得できる。
require'ripper'classRippy<Ripper%w(compile_error on_parse_error warn warning).eachdo|name|define_methodnamedo|*args|puts"#{name}:"pargsp({lineno:lineno,column:column})endendend['[,]','(','p /','if(a = 1);end'].eachdo|code|puts"input:"putscodeputsRippy.new(code).parseputsendこのコードを実行すると次の出力が得られる。
input:[,]on_parse_error:["syntax error, unexpected ',', expecting ']'"]{:lineno=>1, :column=>2}input:(on_parse_error:["syntax error, unexpected $end"]{:lineno=>1, :column=>1}input:p /compile_error:["unterminated regexp meets end of file"]{:lineno=>0, :column=>3}input:if(a = 1);endp /ではlinenoが1ではなく0になる。if(a = 1);endではwarning: found = in conditional, should be ==という警告が出るはずだが、取得できない。
ruby 1.9.3p448, ruby 2.0.0p353, ruby 2.1.0p0, ruby 2.1.1p76, ruby 2.1.2p95でこれらの問題が起きることを確認した。
メソッド呼び出しに対応するparser eventは複数ある
次のように書き方によってノード名が変化する。
Ripper.sexp('a')#=> [:program, [[:vcall, [:@ident, "a", [1, 0]]]]]Ripper.sexp('a()')#=> [:program,# [[:method_add_arg, [:fcall, [:@ident, "a", [1, 0]]], [:arg_paren, nil]]]]Ripper.sexp('self.a')#=> [:program,# [[:call, [:var_ref, [:@kw, "self", [1, 0]]], :".", [:@ident, "a", [1, 5]]]]]# Parsing Ruby - whitespace# http://whitequark.org/blog/2012/10/02/parsing-ruby/# から引用scanner eventは不正確なことがある
# 通常のシンボルリテラルでは本体が`ident`になるが…Ripper.lex(":abc")[[[1,0],:on_symbeg,":"],[[1,1],:on_ident,"abc"]]# 演算子やキーワードになる文字列は`ident`にならない。Ripper.lex(":+")[[[1,0],:on_symbeg,":"],[[1,1],:on_op,"+"]]Ripper.lex(":end")[[[1,0],:on_symbeg,":"],[[1,1],:on_kw,"end"]]# `end`というメソッドを定義した場合Ripper.lex("def end;end")[[[1,0],:on_kw,"def"],[[1,3],:on_sp," "],[[1,4],:on_kw,"end"],# 通常ならメソッド名はon_identになる[[1,7],:on_semicolon,";"],[[1,8],:on_kw,"end"]]# ブロックの仮引数リストの`|`がon_opになるRipper.lex("map{|i| i}")[[[1,0],:on_ident,"map"],[[1,3],:on_lbrace,"{"],[[1,4],:on_op,"|"],[[1,5],:on_ident,"i"],[[1,6],:on_op,"|"],[[1,7],:on_sp," "],[[1,8],:on_ident,"i"],[[1,9],:on_rbrace,"}"]]後方互換性は保証されない
Ripper is still early-alpha version.
I never assure any kind of backward compatibility.
ruby/ext/ripper/README at trunk · ruby/rubyより
バージョンによってパース結果が変化することがある。
require'ripper'Ripper.lex('"#{}"')# 2.0.0-p353, 2.1.0# [[[1, 0], :on_tstring_beg, "\""], [[1, 1], :on_embexpr_beg, "\#{"], [[1, 3], :on_embexpr_end, "}"], [[1, 4], :on_tstring_end, "\""]]# 1.9.3-p448# [[[1, 0], :on_tstring_beg, "\""], [[1, 1], :on_embexpr_beg, "\#{"], [[1, 3], :on_rbrace, "}"], [[1, 4], :on_tstring_end, "\""]]# 文字列内の式展開を閉じる`}`が、1.9.3では`on_rbrace`とされているのに対して、2.0以降では`on_embexpr_end`とされている。関連ライブラリ
- jimweirich/sorcerer · GitHub - RipperのS式をRubyのコードに変換する
関連リンク
- LoveRubyNet Wiki: Ripper (リンク切れ -Internet Archive)- 作者による解説など。
- Rubyリファレンスマニュアル: class Ripper
- Index of Classes & Methods in ripper: Ruby Standard Library Documentation (Ruby 2.5.0)
- [ruby-list:49823] Re: ripperのcompile_error呼び出しの挙動について
使用例
- todesking/ruby_hl_lvar.vim - ローカル変数をハイライトするVimプラグイン
- zenspider/enhanced-ruby-mode - EmacsのRuby用メジャーモード
- k-tsj/power_assert
- ruby-formatter/rufo - コード整形ツール
parser
pure ruby。lexerはRagelで生成しており、parserはMRIのparse.yをベースにした文法ファイルからraccを利用して生成している。旧来のパーサー以上の機能や性能を謳う。
コードを変換するためのツールが付属している(=の位置を揃えるフィルターと、whileのdo・ifのthenを削除するフィルターの作例あり)。
ややこしいコードを渡すとパースが微妙に狂うことがある。
require'parser/ruby25'pParser::Ruby25.parse(<<'CODE')puts(<<HERE)#{<<THERE}THEREHERECODE# s(:send, nil, :puts,# s(:dstr,# s(:begin,# s(:dstr)),# s(:str, "\n"),# s(:str, "THERE\n"))) ヒアドキュメントを閉じる部分が正しく認識されていない関連リンク
- Parsing Ruby - whitespace - 開発に至った経緯。既存のRubyをパースするライブラリの簡単な紹介と問題点の説明など。
関連ライブラリ
- mbj/unparser · GitHub - parserによって出力されたASTをソースコードに戻す。出力されるコードはASTに変換される前のコードと等価(equivalent)だが、同一(identical)であることは保証されない。たとえば
%w(foo bar)をパースしてコードに戻すと["foo", "bar"]となる。
ruby_parser
raccを利用してpure rubyで書かれたパーサー。パースの精度は高い(READMEにrubygemsを対象にしたテストの結果が記載されている)。
たまにトークンの行番号がずれることがある。
jruby-parser
JRubyのパーサーをIDEなどで使えるようにしたもの。NetBeansやEclipseで採用されている。コードはJavaで書かれており、JRubyから使えるラッパーもgemとして提供されている。
そのほか
- seattlerb/parsetree - Ruby 1.8系までをサポートしている。2014年に開発終了。
- coatl/redparse - pure RubyなRubyパーサー。2012年にコミットが途絶えている。
パーサーに関連するライブラリ
ripper_ruby_parser
ripperを利用してruby_parser互換のS式を出力する。
ripper2ruby
Ripperを使用。ソースコードをパースして加工したあとソースに戻すのが主な用途。
forkされたkristianmandrup/ripper2rubyでは、クラスの定義部分やメソッドの呼び出し部分を取得するといった高水準の操作もできる。
しかし、
require'ripper/ruby_builder'require'ruby_api'require'ripper/event_log'src=%q{gets}code=Ripper::RubyBuilder.build(src)pblock_node=code.find_call('gets')このように引数のないメソッド呼び出しが含まれたコードを呼ぶと例外を出して落ちてしまう。
開発は止まっている。
参考
- Rubyのコードを解析する…? - Qiita
- How many s-expression formats are there for Ruby? | Toxic Elephant - RubyのソースコードのS式表現のバリアントが多すぎるという話。
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme