虚無庵

徒然なるままに

個人的に Ruby parser についてまとめてみた

youtu.be

RubyKaigi Takeout 2021 の Ruby Committers vs the World で議題になった「A maintainable, flexible, and usable Ruby parser」部分だけ個人的に気になったのでまとめてみた*1

A maintainable, flexible, and usable Ruby parser(保守性、柔軟性、使用性に優れた Ruby パーサー)

  • Objective
    • Make the parser easy to maintain
    • Make the parser useful for LSP (VS Code/IDE)
  • Problems
    • The current parse.y is a hell
    • LSP often requires more detailed syntax tree (concrete syntax tree?)
      • Need the location of comments, punctuations, etc.
      • The current AST node keeps only a range (the beginning and the end)
    • LSP requires an error-tolerant parse
      • IDE needs to handle incomplete source code
  • Ideas
    • Rewrite it with PEG (like Python)?
    • Use the parser gem? Use Ripper?

ちなみにこれらの項目は、動画で mame さんが言及している通り、整理された項目ではない。”何かもやもや不満があるもの”をスライドに書きあげてみただけで、スライドに書いてあることをやりたいわけではない(と mame さんは言っていた)。つまり、実際に実現できるようなことをスライドに書いているわけではない。

俺なりの訳。

  • 目的
    • パーサーを保守しやすくする
    • LSP(VS Code/IDE)で使えるパーサーにする
  • 問題点
    • 現在の parse.y は地獄*2である
    • LSP では、より詳細なシンタックスツリーが必要である(具体的なシンタックスツリー?)
      • コメントや句読点などの位置情報を必要とする
      • 現在の AST ノードは、範囲(最初と最後)だけを保持している
    • LSP はエラーに強いパースを必要とする
  • 代替案

”何かもやもや不満があるもの”をもう少し具体的に

  • やりたいこと
    • パーサーを誰でもメンテナンスできるようにしたい
    • LSP(VS Code/IDE)を活用できる柔軟で便利なパーサーにしたい
  • どうにかしたいこと
    • パーサーのメンテナンスが nobu さん以外できない
      • どこに何があって何が何やら分からない
    • LSP だとコメント文にドキュメントを含んでいるのでコメント文を残すようなパーサーにしたい
      • インタプリタであればコメント文はパースの時点で捨ててしまっていい
    • LSP では壊れかけた中途半端な書き換え中のソースコードもそこそこいい感じにパースして何とかしたい

パーシングの速度について

  • soutaro: Ruby 実行時のパーシングはは速くないといけないのか?Ruby のパーシングが現バージョン*3から 10 倍遅くなっても無視できるか?
    • mame: 10 倍遅くなると何かのアプリで問題が見えてくると思うが、そこまでボトルネックではないと思う
      • ko1: bootsnap を使っている人が多いくらいにはパースは時間がかかるものではないか?
        • mame: そうだった
  • naruse: パーサーの部分と ISEQ のコンパイルの部分を混同して話をしていないか?
    • naruse: パーサーを並列に動かすというのもありだと思う
    • naruse: そこまではボトルネックではないと思うが、一方で 10 倍遅くなることが許容できるかどうかは計測してみないと分からない

parser gem について

  • mame: Steep は parser gem を使っている
    • mame: カンマの位置がどこにあるかはできないようだが、Ruby パーサーよりは大分マシ
    • soutaro: Steep はあまり速度を求めていないので parser gem で十分である
  • ???: parser gem をインタプリタのパーサーにするのはどうか?
    • mame: その発想はなかった。bundle してくるイメージ*4だった
      • soutaro: 2 個ほぼ同じものがほぼ同時に bundle してくるのはきつくないか?
        • mame: ぶっちゃけきつい
    • mame: parser gem をインタプリタのパーサーにするというのは本気なのか?
      • nobu: さすがに難しいのではないか
      • nobu: parser gem を bundle するとは言えない

ブートストラップ問題

  • nobu: 古いバージョンのシンタックスまでサポートする必要はないが、パーサーを動かすための Ruby はどうするのか?
    • nobu: 5 倍遅くなるで収まるのか?
      • mame: parser gem はもっと細かい情報をとるのでもっと遅くなると思う
      • nobu: 10 倍遅くなるのは許容できない
        • nobu: bundle?(聞き取れなかった…)が全部遅くなると思う
  • nobu: Ruby で書いたパーサーでやるならば、AOT*5にしないと困るのではないか?
    • mame: やるとするならば ISEQ にして ISEQ をインタプリタに埋め込む形になると思う
      • ko1 & mame: 理論上できなくはない。やる意味があるのかはよく分からないけど
    • nobu: ISEQ は同じリビジョンで作らないといけないのではないか?
      • ko1: そんなことはない。parser gem と古いリビジョンで作ればいいのではないか
        • nobu: いまのインストラクションを埋め込むのは、そこまでポータブルなものではない
  • mrkn: 新しいパーサーは parse.y で解析したらいいのではないか?
    • mrkn: parse.y は残しておいてブートストラップ用に使えばいいのではないか
      • nobu: 新しいパーサーと prase.y を二重にメンテナンスすることにならないか?
        • mrkn: parse.y はメンテナンスしなくていいと思う。パーサーは古い Ruby で書けばいい
    • shyouhei: まずは「parse.y を捨てたい」という前提があるのではないか?
      • nobu: 捨てるとまではいかなくても、整理しなくてはいけない

LSP について

  • naruse: LSP は parser gem 同様古いバージョンでも動かなければならない
    • naruse: 実は ruby 本体の実行用のパーサーではなく、parser gem のような複数の古いバージョンでも動くものが適切ととらえれば「parser gem と古いリビジョンで作ればいい」のかもしれない
  • nobu: 現時点*6では LSP はそんなに厳密なパーサーではなくてもいいのではないか
    • nobu: 逆に細かくエラーチェックが入ると困るし、細かいエラーチェックは不要なのではないか
      • soutaro: そんなことはない
      • naruse: ???(聞き取れなかった…)を通らないものに関しては何かエラーを出力して欲しいかも

車輪の再発明

  • ko1: 「とにかく動かすためにパースすれば良かった」というものから、だんだん他の要件が増えてきたのでパーサーそのものを考え直す時期が来たのだと思う
  • ko1: matz は趣味的に作り直したい気持ちはあったりする?
    • matz: ない
  • nobu: mruby でパーサーを作った人がいましたよね?
    • matz: hasumikin さんが mruby のパーサーを Lemon で書き換えた*7
      • naruse: Lemon はそこまで表現力が高くない
      • naruse: 「どうせ書き換えるなら、もっと表現力の高いパーサーを導入して、Ruby の文法をもっと複雑にしてやろうぜ」ということを以前 matz が言っていた気がする
      • naruse: 「一度はシンプルに文法を変えないようにしようかと思った」が、数年考えた結果「文法が複雑なのは Rubyアイデンティティ*8だ」という趣旨のことを matz が言っていた

結局いまの Ruby パーサーの問題点って何?

  • akr: parse.y が大変なのは何故なのか?
    • matz: 見通しが悪いだけなのではないか
    • akr: 個人的には以下のように妄想している
      • akr: yacc で書いてあるから BNF を全部手書きしなければならない(シンタックスを手書きしなければならない)
      • akr: Ruby の場合、引数の箇所みたいに組み合わせ論的に BNF を書かなければならない箇所が存在する。つまり、???(聞き取れなかった…)ということになる
        • akr: そこ問題なのであれば、そういうものを生成してくれるような機能*9が欲しくなるのではないか
    • matz: トップダウン*10で書いていると「ルールにパラメーターを渡す」ことができない*11。それができればいいなと思うことは度々ある
      • akr: そういうのも含めて問題を把握する必要があるのではないか

エラーハンドリング/エラーリカバリ

  • ???: さっき話題に出ていたのはエラーハンドリングではないか?
    • mame: 新しい要件が出てきた、という方が分かりやすい
    • akr: エラーハンドリングであれば yacc にある
    • mame: 必ずしも「今の bison を捨てろ」とは言っていない。Ripper を使っても良いと言っている
    • nobu: 「エラーが起きても最後までパースして欲しい」というのがさっきの話題だった
    • naruse: yui-knk さんが昔やっていた*12
    • nobu: 「エラーが起きても最後までパースしてトークンを返す」というのを使っているのが今の irb ではないか?
    • mame: lexer の方ができるという話ではないか?lexer はエラーハンドリングできるという話?
    • soutaro: 「ここに構文エラーがあって、残りはこうなっている」というパーサーが欲しいんですよね*13
      • mame: 可能であれば欲しい
      • mame: キーノートで話した例だと「キーワード引数の ':' を書いた状態までだとパースエラーとなるが、できれば(エラーにならず最後まで)パースしたい」
        • mame: 引数の情報を補完をするためには 'keyword:' まで書いた状態のコアな構文木が欲しい
        • akr: シンタックスを具体的に変形させることは可能な気がする。完全なシンタックスから途中でエラーで止まるようなシンタックスを作ることは可能な気がする
        • mame: スライドが整理されていないのは事実。何かもやもや不満があるものを、スライドに書きあげてみただけ。スライドに書いてあることをやりたいわけではないし、実際に実現できるようなことはスライドには書かれていない
  • mame: yui-knk さんはエラーリカバリをやっていた?エラーハンドリングをやっていた?
    • yui-knk: エラーリカバリとはトークンが途中で壊れる話を指しているか?
    • yui-knk: エラーリカバリは教科書*14とかを見ると、よく隅っこの方に載ってますよね?
      • mame: 載ってる
      • yui-knk: ちゃんと具体的な説明や細かい説明を読んだことないけど、とりあえず当てはまりそうなトークンを突っ込んで回復するというのが多い印象
        • akr: 実際にトークンを生成するのではなく、適当に”いま問題になっている non-terminal が生成されたことにして”進めるのではないか?
        • yui-knk: 「補完して進める」というのであっている
        • akr: 実際のトークンとは関係なく「この non-terminal が必要」として、実際にエラーとして作ってしまう
    • yui-knk: Ruby みたいにコンフリクトしない言語であれば、補完するべきものは一意に定まるものなのか?
      • matz: 一意には定まらない

まとめ

この Ruby パーサーに関して、俺は真に驚くべき代替案を見つけたが、この余白はそれを書くには狭すぎる*15

*1:動画の 11:54 〜 28:06 あたり

*2:もしくは魔境

*3:2021/11/11 時点では 3.0.2

*4:このイメージがどういうイメージなのか、俺は理解できていない

*5:Ahead-Of-Time コンパイラ

*6:2021/09/10 時点

*7:RubyKaigi Takeout 2020で発表した「mmruby」について

*8:???「困ったアイデンティティだなぁw」

*9:パーサージェネレータージェネレーター

*10:yaccボトムアップ

*11:ルールから値を引き渡すことはできる

*12:この議題の時 yui-knk さんは不在だった

*13:なぜならプログラムを書いている途中で入力補完を出したいから

*14:そんな教科書があるのか…。俺は知らない…

*15:結局何も分かってない