RubyKaigi 2024参加記事を書いていたら長くなりそうだったので、ひとまず聞いたセッションとそのメモだけを記事にした。
技術以外の話は下記記事でどうぞ。
はじめに
ちょうど『インタプリタの作り方』を読んでいたこともあり、「これなら少しくらい理解できそうかな?」というものを中心に聞いていった。
手元でのメモを中心に見聞きしたことを数行で書いていく。正直、少ししか理解できなかったので、ググりワードを並べるくらいの気持ちで書いていく。
この記事の読者様も、どうかそのおつもりで……。。。
Day 1: The grand strategy of Ruby Parser
スライド: The grand strategy of Ruby Parser - Speaker Deck
事前準備の記事でも言及させていただいたパーサジェネレータのLramaを開発したkanekoさんによる発表。一言で言うとparser keynoteという感じだった。まぁThe grand strategyだしそりゃそうか。
- なぜ◯◯か
- パーサジェネレータ: Rubyはすごく変化する言語なのでBNFで文法を定義出来るパーサジェネレータ便利
- LR Parser: 人間にとって理解しやすいのはLRに近いのでは的な
- Lrama: Bisonのツラミ脱出(機能不足・version問題)
- 今思ったこと→ **env for Bisonみたいなのないのかな。Terraformにおけるtfenv的な。そうすればBisonのversion管理いろんな環境でうまく出来そうだけど。
- チャレンジ大戦略
- LSPと周辺ツールを提供すること
- LSP / TypeProf / Rubocop いろんなツールがASTを使う。これらから使いやすいASTを。
- CRubyはASTレベルでの最適化が多くそれでASTと元のコードに相違出てるとか、共用体を多用しているのでキャストしにくいとか
- 入力中に正しくない文法があってもパースしてほしいとか
- CPCT+っていうアルゴリズムがいいらしい
- parse.y大変
- lexerは通常state持たないがRubyのは持ってる
- PSLR(1)で解決できるが、前段階でIELRが必要
- Day3: From LALR to IELR: A Lrama's Next Stepにつながる
- 素朴なBNFより表現力豊かにしたい
- Parameterizing rulesってやつでなんとかなりました
- Day 2: Does Ruby Parser dream of highly expressive grammar?につながる
- lexerは通常state持たないがRubyのは持ってる
- LSP / TypeProf / Rubocop いろんなツールがASTを使う。これらから使いやすいASTを。
- ユニバーサルパーサを提供すること
- CRubyのパーサはCRubyの他の機能に依存していて、他のRuby実装(mrubyとか)から使えない
- 依存を解いて使えるように
- Day2: Unlock The Universal Parsers: A New PicoRuby Compiler
- Day2 LT: Contributing to the Ruby Parser
- 依存を解いて使えるように
- CRubyのパーサはCRubyの他の機能に依存していて、他のRuby実装(mrubyとか)から使えない
- LSPと周辺ツールを提供すること
- 真のユニバーサルパーサとは?
- 現状のパーサはC言語で実装されているため、他の言語で素直に扱えない
- なんらかの中間言語があって↑をSKIP出来ればそれは真のユニバーサルパーサだよね的な?
Day 2: Does Ruby Parser dream of highly expressive grammar?
スライド: Does Ruby Parser dream of highly expressive grammar? - Speaker Deck
kanekoさんの発表で紹介されていたもの。
- 「Bisonがもっとリッチな文法をサポートしていればなー」→「Lramaになったので出来るように。」
- その一例としてのParameterizing Rules
- BNFのパターンに名前をつけてルールを明確化し、再利用可能に。
- OCamlのパーサジェネレータであるMenhirから拝借したアイデアとのこと
- ↑によって、抽象化・構造化が出来るように。これはすごいこと。
- Parameterizing Rules with Tag
- 諸々聞き逃してしまった。。後ほどキャッチアップ。
- Standard Library
- 「一般的な構造をいちいち定義することなく使いたいよね」的なRubyの哲学はParser generatorにも適用されるはず。
- Standard Libraryとして一般的なParameterizing Rulesを提供。オプションでOFFにもできる。
- リッチな文法定義はまさにlexer / parserが密結合している故のもの。
- Parameterizing Rules with Conditional Statements
- コンフリクトの解消
- (自分調べ: パーサにおけるコンフリクトとは)
- あいまいな文法を食わせたときにおこるエラー?
- 参考: プログラミング言語処理の「あいまいな文法とshift/reduce conflict, reduce/reduce conflict」の章
- あいまいな文法を食わせたときにおこるエラー?
- 解決策としてのInlining
- これもOCamlから拝借してきたアイデア
- (自分調べ: パーサにおけるコンフリクトとは)
-
文法ファイルのメンテナビリティを向上し、それがRuby自体の発展に寄与すると信じて、やっていきたい的な話
Day 2: Unlock The Universal Parsers: A New PicoRuby Compiler
スライド: Unlock The Universal Parsers: A New PicoRuby Compiler - HASUMI Hitoshi - Rabbit Slide Show
こちらもkanekoさんの発表で紹介されていたもの。kanekoさんは、CRuby以外からも使えるユニバーサルパーサを提供したいって話をしていたけど、これはそれを使う側の視点からの話。(ってkanekoさんがおっしゃっていたように理解した)
- Ruby実装ごとにどのパーサを採用しているか違う
- PicoRubyはRPi Picoで動かすのでメモリフットプリント小さくあってほしい
- PicoRubyでも使えるくらい小さなメモリフットプリントじゃないとuniversalじゃないよね
- PrismもLramaもめっちゃいい
- 令和5年、parse.yはno longer a labyrinth
- Lrama-genなおかげ
- (自分メモ: Lramaによって生成されたパーサには名前はついていないが、この方以外も結構みんなLrama-gen parserと呼んでいた。)
- Lrama-genなおかげ
- PicoRuby compilerは両方使います(?)
- 令和5年、parse.yはno longer a labyrinth
- mrbc_lrama: A mruby compiler using Lrama-generated parser
- ver. 0.0.0.0.1: libruby-static.a をリンクしてやる→9.985MB
- ↑から始まり、脱GC・脱VALUE・脱IMEMOなどを経て、39.91KBになった。(96%減)
- 一方、ROMサイズは24MBある。
- arm-none-eabi向けのクロスコンパイル試したがエラーになる
- none-eabiはstdio.hのような標準ライブラリをサポートしてない
- 今後、bare-metalに対してconfigure出来るようにCRubyのbuild-process見直したい
- arm-none-eabi向けのクロスコンパイル試したがエラーになる
Day 2: Lightning Talks
LT一覧: LT - RubyKaigi 2024
あまりメモは取らなかったのだが、Enjoy Creative Coding with Ruby / The Journey of rubocop-daemon into RuboCop / Drive Your Code: Building an RC Car by Writing Only Rubyの3つが印象に残っている。
特にrubocop-daemonの話は良かった。rubocop-daemonというRuboCopをdaemonizeして速くする3rd-party gemがいかにしてRuboCop本体に取り込まれたかみたいな話。著名なOSSの本体にContributeするのはハードル高くても、3rd-party gemという選択肢もあるんやでというような話だった気がするが、まさにという気持ちで聞いていた。自分もやってみたい、となったとき今パッと思いつくものが無いのだが、常に脳裏にそういう思想を置いておくことで、なにか不便を感じたときに放置するのではなく自分が新たに3rd-party gemを開発するという発想になれると良いなと思うし、実際にそれをやられたという意味で非常に勇気づけられるLTだった。
Day 3: How to implement a RubyVM with PHP?
スライド: How to implement a RubyVM with PHP? - Speaker Deck
ちょうど『インタプリタの作り方』で、VMを作ってたので親近感が湧いたので聞いてみることに。Rubyコードの字句解析・構文解析・意味解析、そしてYARVバイトコードへのコンパイルまではRubyにやってもらって、そのバイトコードを実行するVMを自作する、という話。
- 新しい言語を普通に学ぶのがシンドいな、おもしろくないなと思い始めて「そうだ、VMをつくろう」となったらしい。
- RubyVMがどう動くか
- RubyVMとは?→ YARVのこと
- なので今回はYAYARVつくった
- YARV命令列の構造について
- RubyVMをPHPで実装するにはどうするか
- JVMは企業主導の仕様書がある。Rubyは企業が作っているわけではないので無い。OSSなのでコード読めばOK。
- RubyVM::InstructionSequence.compile.to_binary でYARV命令列が手に這入る。
- YARV header -> iseq offsets -> global objec offsets -> byte-code -> エントリポイントの実行という順番で処理していく。
- PHPでどうバイナリを読んでいくか
- ファイル処理関数とunpack。unpackはPHPでうまくバイナリを扱うために必要。
- YARV命令列の構造の解説
- ヘッダ部を読む話
- 先頭nバイトは◯◯って決まってるので愚直に
- ヘッダ部を読む話
- YARV命令列を上から読んでスタックマシンのスタックに積んでいく話
- CallInfoEntryとは
- 引数の数とかみたいなメタデータ入ってるやつ
- RubyVMはどれくらいの命令セット数があるのか?
- 200個くらいある。実際によく使うものは100個くらい?Ruby3.3から6個増えたらしい。
- Hello WorldやFizzBuzz、クイックソートくらいならそんなにいらない。
- RubyコアコードからどうRubyVM理解する?
- compile.cやifb_read_*を読むと良い
- 200個くらいある。実際によく使うものは100個くらい?Ruby3.3から6個増えたらしい。
- デモでHello World出来てめでたい
Day 3: Using "modern" Ruby to build a better, faster Homebrew
スライド: Using "modern" Ruby to build a better, faster Homebrew - Speaker Deck
Homebrewということで聴講。英語全然聞き取れませんでした……。HomebrewがmacOS標準添付のRubyではなく新しいRubyを使うみたいな話という予習はした。発表内容はほぼ聞き取れずなので間違い多めかも……)
- Homebrew and Ruby
- FormulaはDSL。Formulaを継承したRubyクラス。#installやtest blockのoverride。
- brew caskは cask "1password" do ~ end みたいなDSL
- 各種コマンドもRuby実装。AbstractCommandを継承したクラス。
- Homebrew Ruby tools
- Homebrew Rubyバージョンの変遷。
- macOS添付のRubyバージョン(/usr/bin/ruby)を使っていた。macOS添付のRubyバージョンは古いので2016年に1.8.7だったり。
- 今はportable-rubyを使っている
- Building Better brew
- brewのいろいろなコマンドの紹介
- brew listをはやくした
Day 3: From LALR to IELR: A Lrama's Next Step
スライド: From LALR to IELR: A Lrama's Next Step - Speaker Deck
kanekoさんの発表で紹介されていたもの。LramaがPSLR(1)をやるために、まずはIELRをやるという話。昨年のTokyuRuby会議14でたまたま隣の席に座っていて繋がらせていただいた@junk0612さんの発表。(びっくりした)
- Lramaはbuilt with ruby
- junkさんの実績
- LramaにNamed referencesを実装
-
Lramaの内部パーサーをraccで生成
- CRubyのlexerの状態管理が大変だからなんとかしたい
- 教科書的にはparser / lexerは分離可能だが、現実問題としてlexerの状態はparserの状態に影響を受ける。
- || がきたのでORと思ったら実はブロック引数のやつでしたとか
- <<- がHEREDOCかと思ったら配列に-1をpushしてましたとか []<<-1
- ↑をどうにかするためのイチアイデアとしてのScannerless Parser
- ↑のイチ実装としてのPSLR(1)
- PSLR(1)のためには、IELRが必要。
- 教科書的にはparser / lexerは分離可能だが、現実問題としてlexerの状態はparserの状態に影響を受ける。
- LALR(1)とCanonical LALRの比較
- LALR(1)は実用的・省メモリだが、Canonical LALRだとパース出来るものが出来ない
-
IELR Overview
- Canonical LALRとLALR(1)のいいとこ取り
- 今後
- IELRのPRをマージに持っていく
- PSLRパーサの実装
- parse.yのリファクタとlex_satteawayの廃止
全体的な感想
たまたまインタプリタ本で手書きlexer / parserを実装したのでパーサまわりの発表を聞くことが多かった。Rubyに特化していない一般論、発表中に出てきた言葉を借りれば教科書的な話は「わかるわかる」という感じだったが、Ruby特化の話やちょっとアカデミック的な話が出てきたときに「ウッなんもわからん」となった。
印象的だったのは、「Lrama(というかLR Parser)によって、既存のCS知識・資産を活かせる」みたいな話。実際、ML系言語のparserから拝借してきたアイデアがRubyに取り込まれたりという話があった。これまで触れた言語はどれも仕事都合で学ぶ必要のあったもので、だいたい似たパラダイムのものである。が、より良いRubyのコードを書く*1ためには、他の言語のことも知る必要があるわけで、「あぁそういうのも触らんとなー」と。ひとまず、『7つの言語 7つの世界』でも読もう。ML系出てこないけどまぁいろんなパラダイムの言語を触る訓練として。
「授業でやったくらいで」「本1冊読んだ程度で」と言われてしまえばそれまでだが、高専の授業でlex/yaccを、本で手書きスキャナ・パーサをそれぞれ実装したので、パーサジェネレータと手書きパーサの比較の話や、字句解析がパーサの状態に依存するみたいな話は結構頷けるところがあった。これまでアプリ開発にしか目が向いてこなかった自分にとって、ちょっとでもそういう話に共感を覚えるというのはなにか新鮮な体験であった。
おわりに
登壇者のみなさま、とても刺激的なお話をありがとうございました。
*1:二重の意味で