Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Lrama へのコントリビューションを通して学ぶ Ruby のパーサジェネレータ事情
Search
Junichi Kobayashi
September 09, 2023
Programming
7.4k
4
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Lrama へのコントリビューションを通して学ぶ Ruby のパーサジェネレータ事情
Junichi Kobayashi
September 09, 2023
More Decks by Junichi Kobayashi
See All by Junichi Kobayashi
XP をバイブルにする事業部で10年働いた新卒が語りたい『Tidy First?』/ Put your understanding into the code
junk0612
0
100
rage against annotate_predecessor
junk0612
0
280
The Implementations of Advanced LR Parser Algorithm
junk0612
3
2.8k
「今のプロジェクトいろいろ大変なんですよ、app/services とかもあって……」/After Kaigi on Rails 2024 LT Night
junk0612
6
2.9k
LR で JSON パーサーを作る / Coding LR JSON Parser
junk0612
2
2k
「ナントカLR」を整理する / Clarifying LR Algorithms
junk0612
1
730
From LALR to IELR: A Lrama's Next Step
junk0612
2
5.3k
RubyConf Taiwan / Understanding Parser Generators surrounding Ruby with Contributing Lrama
junk0612
2
7.2k
LL法とLR法の違いは?調べてみた!-完全版-/Comparing LL and LR parse algorithm -EX Edition-
junk0612
0
2.6k
Other Decks in Programming
See All in Programming
App Storeの外へ──日本のiOSサイドローディング入門 for iOSDC Japan 2026
yuukiw00w
0
250
iOS 27でニュースアプリはどう変わる!? 〜日経電子版の新機能対応と、開発事例から〜
lynnswap
6
13k
スマートフォンでモールス信号を送受信する 〜スマートフォンのLEDとカメラで作る光通信の設計と実装〜
atsuki_seo
0
190
【高い買い物LT会】初任給で話題の国産フィジカルAIを買った話
akagami
PRO
0
170
個人開発基盤をまるごとCloudflareに引っ越して爆速で総合的体験を向上させた話
tinykitten
0
210
すこし踏み込む CancellationToken
htkym
2
1.1k
速く作れる。その次は、速く確かめられる開発へ 〜AIネイティブ開発を支える、Shift Down〜 / Can build fast. Next, moving to development where we can verify fast.
rkaga
6
4.8k
手動確認はもう限界 〜XCUITestでCustom URL Schemeの遷移を起動種別ごとに自動テストする〜 / Testing Custom URL Schemes with XCUITest
otouto
0
340
UnityでSystem.Net.WebSocketsなWebSocketサーバが動かないのでUnity Monoのコードを覗いてみた / about implementing websocket server with unity mono
drumath2237
1
390
仕様駆動開発による爆速プロダクト開発 / Bakusoku Spec Driven Development
kobakei
0
130
フロントエンドUIフレームワークのこれまでとこれから
ssssota
5
2.9k
そのリトライ、死んだコネクションを使い回していませんか ── GoのHTTPクライアントとHTTP/2を実プロダクト障害から学び直す
myus4a
0
270
Featured
See All Featured
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
490
Mobile First: as difficult as doing things right
swwweet
225
10k
Future Trends and Review - Lecture 12 - Web Technologies (1019888BNR)
signer
PRO
0
3.8k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
390
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
1
610
Rebuilding a faster, lazier Slack
samanthasiow
85
9.7k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
250
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Product Roadmaps are Hard
iamctodd
55
13k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.8k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.8k
Transcript
Lrama へのコントリビューションを通して学ぶ Ruby のパーサージェネレーター事情 株式会社永和システムマネジメント アジャイル事業部 小林 純一 (@junk0612) 大阪
Ruby 会議 03 ハートンホテル心斎橋別館 松風ホール 2023/09/09(Sat.)
小林 純一 (@junk0612)
自己紹介 • 小林純一 • X / GitHub: @junk0612 • 株式会社永和システムマネジメント
アジャイル事業部 ◦ RubyxAgile グループ ◦ 構文解析器研究部員 • 音ゲーマー・ボードゲーマー
None
本日のおしながき • パーサー周りの基礎知識 • Lrama にコントリビューションした話 • 実装を通して知った Lrama の内部構造
• 今後やってみたいこと
パーサー周りの基礎知識
パーサー周りの基礎知識 • 構文解析器の構成要素 ◦ レキサー ◦ パーサー ◦ パーサージェネレーター •
言語処理系の用語 ◦ 形式言語 ◦ 文脈自由言語 ◦ BNF
構文解析器の構成要素 • レキサー ◦ テキストを読み込んで、トークンに分割するプログラム ◦ トークンに分割することをトークナイズという • パーサー ◦
トークン列を読み込んで、内部構造を取り出すプログラム ◦ コンパイラ系では「ソースコード → AST」 ▪ AST: Abstract Syntax Tree (抽象構文木) ◦ JSON や CSV のパーサーでは「テキスト → データ構造」 • パーサージェネレーター ◦ 文法ファイルを読み込んで、パーサーを生成するプログラム
CRuby 実行環境 .rb ファイル Ruby VM 構文解析器の構成要素
CRuby 実行環境 .rb ファイル Ruby VM レキサー トークン列 AST バイトコード
パーサー 生成器 構文解析器の構成要素
CRuby 実行環境 .rb ファイル Ruby VM レキサー トークン列 AST バイトコード
パーサー 生成器 パーサージェネレーター 文法ファイル パーサー 構文解析器の構成要素
CRuby 実行環境 .rb ファイル Ruby VM レキサー トークン列 AST バイトコード
パーサー 生成器 パーサージェネレーター 文法ファイル レキサー トークン列 AST パーサー 生成器 パーサー 構文解析器の構成要素
言語処理系の用語 • 形式言語 ◦ 「言語」を数学的・集合論的に取り扱う言語学の分野 ◦ 言語がどのようなテキストとして表されるかを考える ▪ 人間に意味のある言葉として認識されるかは気にしない ▪
英語なら「アルファベットの羅列で、たまにスペースや 記号が入る」など ◦ 言語を構成する記号と、それらを結びつける文法からなる
言語処理系の用語 • 文脈自由言語 (Context Free Grammar: CFG) ◦ 形式言語の1つで、以下の形式で表されるもの ▪
rule: A B C ... | D E F ... ▪ このような記法を BNF(Backus-Naur Form) という ◦ 世の中のほとんどのプログラミング言語が属する ◦ パーサージェネレーターの入力となる文法ファイルに使われる ◦ 他の文法によって展開できる記号を非終端記号という ◦ それ以上展開できない記号を終端記号という
文脈自由言語と BNF number: digit digit digit: '0' | '1' |
'2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
文脈自由言語と BNF number: digit digit digit: '0' | '1' |
'2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 00 ~ 99 の2桁の数字全体を表す言語
文脈自由言語と BNF expression: digit '+' digit | digit '-' digit
| digit '*' digit | digit '/' digit digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
文脈自由言語と BNF expression: digit '+' digit | digit '-' digit
| digit '*' digit | digit '/' digit digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 1桁の数の2項四則演算式を表す言語
文脈自由言語と BNF expression: digit | expression '+' digit | expression
'-' digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
文脈自由言語と BNF expression: digit | expression '+' digit | expression
'-' digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: expression
文脈自由言語と BNF expression: digit | expression '+' digit | expression
'-' digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: expression / digit
文脈自由言語と BNF expression: digit | expression '+' digit | expression
'-' digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( expression ) / digit
文脈自由言語と BNF expression: digit | expression '+' digit | expression
'-' digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( expression + digit ) / digit
文脈自由言語と BNF expression: digit | expression '+' digit | expression
'-' digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( digit + digit ) / digit
文脈自由言語と BNF expression: digit | expression '+' digit | expression
'-' digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( 2 + digit ) / digit
文脈自由言語と BNF expression: digit | expression '+' digit | expression
'-' digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( 2 + 7 ) / digit
文脈自由言語と BNF expression: digit | expression '+' digit | expression
'-' digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( 2 + 7 ) / 3
文脈自由言語と BNF expression: digit | expression '+' digit | expression
'-' digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 1桁のかっこ付き四則演算式を表す言語
実際のパーサーの処理 expression: digit | expression '+' digit | expression '-'
digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( 2 + 7 ) / 3
実際のパーサーの処理 expression: digit | expression '+' digit | expression '-'
digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( digit + 7 ) / 3
実際のパーサーの処理 expression: digit | expression '+' digit | expression '-'
digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( digit + digit ) / 3
実際のパーサーの処理 expression: digit | expression '+' digit | expression '-'
digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( digit + digit ) / digit
実際のパーサーの処理 expression: digit | expression '+' digit | expression '-'
digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( expression + digit ) / digit
実際のパーサーの処理 expression: digit | expression '+' digit | expression '-'
digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: ( expression ) / digit
実際のパーサーの処理 expression: digit | expression '+' digit | expression '-'
digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: expression / digit
実際のパーサーの処理 expression: digit | expression '+' digit | expression '-'
digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 例: expression
実際のパーサーの処理 expression: digit | expression '+' digit | expression '-'
digit | expression '*' digit | expression '/' digit | '(' expression ')' digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' Accepted!
まとめ • ソースコードが実行・コンパイルされるとき、 レキサーとパーサーがプログラムを解析してデータ構造を作る • パーサーを文法ファイルから自動生成するプログラムを パーサージェネレーターという • パーサージェネレーターの入力に使われる文法ファイルは 文脈自由文法で書かれる
• 文脈自由文法の記法として BNF がある
Lrama に コントリビューションした話
やったこと • Lrama に Named References を実装した
やったこと • Lrama に Named References を実装した 🤔
Lrama とは • Bison を代替するために作られた Ruby 製パーサージェネレーター • RubyKaigi 2023
で kaneko.y さんが発表した ◦ 詳しくは https://youtu.be/IhfDsLx784g?si=kO1q6mLpTa1bIRYL • CRuby 3.3 から導入される ◦ preview1 には入っているので、今この場でお試しいただけます ◦ ※ Ruby のふるまいは変わりません
Lrama を導入するメリット • Bison のバージョンに引きずられずに済む ◦ Bison はユーザーごとにバージョンが異なるので、古いものがインス トールされていることを想定しなければいけない ◦
新しい機能が導入されても使えない • Ruby 独自の実装ができる ◦ LSP 向けに書きかけのコードをいい感じにパースする ◦ 複雑になっている文法のパースをうまくやる
Lrama が目指すところ • Usability • Maintainability • Universal Parser •
詳しくは RubyKaigi 2023 での kaneko.y さんの発表をどうぞ ◦ https://youtu.be/IhfDsLx784g?si=kO1q6mLpTa1bIRYL
Usability • Error-tolerant なパーサーがほしい ◦ 旧来のパーサーは、入力されたコードが文法的に正しいか どうかを判断すればよかった ◦ LSP が存在している現代においては、書きかけの半端なコード
についてもなるべくパースしたいという要求がある ▪ Bison の panic モードでは文法エラーの場所を 読み飛ばしてしまうので困る
Maintainability • 歴史的経緯により、parse.y は非常に複雑になっている ◦ 特に、ここから生成される CRuby のパーサーとレキサーが 密結合しているのがメンテナンス性に悪影響を与えている •
パーサーの力をうまく使ってやるともっとメンテナンス しやすくなるので、それを使うための機能を入れたい
Universal Parser • 世の中にはさまざまな Ruby 実装があり、 それぞれ独自のパーサーを利用している ◦ mruby や
JRuby といった別実装 ◦ sorbet や typeprof のようなツール • 現在の CRuby のパーサーは CRuby 独自の機能に依存しており 移植が難しくなっている • 文法ファイルを整理することで、他実装にも利用できるパーサーを 作りたい
Lrama 周辺のパーサー事情 • YARP ◦ Bison が作ったパーサーを置き換えるべく作られている CRuby 向けの手書き (!)
パーサー ◦ JRuby / TruffleRuby での実績もあるらしい • Bison ◦ GNU が開発した Yacc の後継のパーサージェネレーター ◦ CRuby においては、parse.y を読み込んで Ruby のパーサーを 出力するために使われていた • Racc ◦ 青木峰郎さんの開発したパーサージェネレーター ◦ parser gem (RuboCop の依存 gem) などで使われている
Q: Racc で Bison の置き換えってできないの? 生成アルゴリズムは同じだが、下記の部分が異なっているために共通で利用 できる部分が少なく、イチから作ったほうが手間が少ない • 入力する文法ファイルそのものの文法 ◦
Bison は Yacc 由来、Racc は独自記法 • 出力するパーサーの言語 ◦ Bison は C、Racc は Ruby
Named References とは • Bison の機能の 1 つ • Action
内の References として非終端記号名を利用できる
Named References とは • Bison の機能の 1 つ • Action
内の References として非終端記号名を利用できる 🤔
%{ Prologue (約 1500 行) %} Bison declarations (約 200
行) %% Grammar rules (約 4500 行) %% Epilogue (約 8300 行) Bison 文法ファイルの構造 かっこ内は CRuby の parse.y における行数
%{ Prologue (約 1500 行) %} Bison declarations (約 200
行) %% Grammar rules (約 4500 行) ← 今回はここの話 %% Epilogue (約 8300 行) Bison 文法ファイルの構造
Grammar rules の構造 rule_name: rule rule .. rule { action
} | rule rule .. rule { action } expression: NUMBER '+' expression { $$ = $1 + $3 } | NUMBER '-' expression { $$ = $1 - $3 } | '(' expression ')' { $$ = $2 }
Grammar rules の構造 rule_name: rule rule .. rule { action
} | rule rule .. rule { action } expression: NUMBER '+' expression { $$ = $1 + $3 } ← | NUMBER '-' expression { $$ = $1 - $3 } ← 今回はここの話 | '(' expression ')' { $$ = $2 } ←
Action とは • Bison が生成するパーサーは、なにもしなければ 文法に則った入力になっているかどうかしか教えてくれない ◦ AST を作ったり、後の処理に必要な情報を保存したりはしない •
各文法の後ろに {} でくくってプログラムを書くことができる • $n や @n を使うと、文法中の記号の値を使って処理が行える ◦ この機能を (Numbered) References という
Action の具体例 expression: NUMBER '+' expression { $$ = $1
+ $3 } この文法を受理したときの返り値は 1つ目の要素と3つ目の要素の足し算
Action の具体例 expression: NUMBER '+' expression { $$ = $1
+ $3 } この文法を受理したときの返り値は 1つ目の要素と3つ目の要素の足し算
Action の具体例 expression: NUMBER '+' expression { $$ = $1
+ $3 } この文法を受理したときの返り値は 1つ目の要素と3つ目の要素の足し算
Action の具体例 expression: NUMBER '+' expression { $$ = $1
+ $3 } この文法を受理したときの返り値は 1つ目の要素と3つ目の要素の足し算
Named References とは • Numbered References の問題点 ◦ 記述的でなくわかりにくい ◦
場所の番号で指定するため、 文法が変更になると書き直さなければならない • 上記の問題を解消するため、非終端記号名を参照して 値を使えるようにした機能が Named References
Named References とは expression: NUMBER '+' expression { $$ =
$1 + $3 } | NUMBER '-' expression { $$ = $1 - $3 } | '(' expression ')' { $$ = $2 } expression[result]: NUMBER '+' expression[rest] { $result = $NUMBER + $rest } | NUMBER '-' expression[rest] { $result = $NUMBER - $rest } | '(' expression[inside-exp] ')' { $result = $[inside-exp] }
Named References とは expression: NUMBER '+' expression { $$ =
$1 + $3 } | NUMBER '-' expression { $$ = $1 - $3 } | '(' expression ')' { $$ = $2 } expression[result]: NUMBER '+' expression[rest] { $result = $NUMBER + $rest } | NUMBER '-' expression[rest] { $result = $NUMBER - $rest } | '(' expression[inside-exp] ')' { $result = $[inside-exp] } 前に $ をつけてルール名で値を参照できる
Named References とは expression: NUMBER '+' expression { $$ =
$1 + $3 } | NUMBER '-' expression { $$ = $1 - $3 } | '(' expression ')' { $$ = $2 } expression[result]: NUMBER '+' expression[rest] { $result = $NUMBER + $rest } | NUMBER '-' expression[rest] { $result = $NUMBER - $rest } | '(' expression[inside-exp] ')' { $result = $[inside-exp] } ルール記述側で [] でくくると別名をつけられる
Named References とは expression: NUMBER '+' expression { $$ =
$1 + $3 } | NUMBER '-' expression { $$ = $1 - $3 } | '(' expression ')' { $$ = $2 } expression[result]: NUMBER '+' expression[rest] { $result = $NUMBER + $rest } | NUMBER '-' expression[rest] { $result = $NUMBER - $rest } | '(' expression[inside-exp] ')' { $result = $[inside-exp] } ルール名や別名に記号を含む場合は 呼び出し側も [] でくくれば OK
Named References とは expression: NUMBER '+' expression { $$ =
$1 + $3 } | NUMBER '-' expression { $$ = $1 - $3 } | '(' expression ')' { $$ = $2 } expression[result]: NUMBER '+' expression[rest] { $result = $NUMBER + $rest } | NUMBER '-' expression[rest] { $result = $NUMBER - $rest } | '(' expression[inside-exp] ')' { $result = $[inside-exp] }
ここまでのまとめ • やったことの説明 ◦ Lrama に Named References を実装した ▪
Lrama は Bison を置き換えるために作られた パーサージェネレーター ▪ Named References は Bison が持つ機能で、Action 内の References として非終端記号名を利用できる
実装を通して知った Lrama の内部構造
パーサージェネレーターの内部構造 パーサージェネレーター 文法ファイル レキサー トークン列 AST パーサー 生成器 パーサー
Lrama の内部構造 Lrama parse.y Lexer Token Grammar Parser Output パーサー
まずはテストを書く
どこに手を入れるか? • Numbered References はすでに実装されている ◦ 非終端記号名と Action 内の呼び出しを関連づけられれば 関連からコードを生成する部分はすでにある
◦ Numbered References の実装を参考にして 非終端記号名と呼び出しを関連づけることにする
どこに手を入れるか? Lrama parse.y Lexer Token Grammar Parser Output パーサー
どこに手を入れるか? Lrama parse.y Lexer Token Grammar Parser Output パーサー
どこに手を入れるか? Lrama parse.y Lexer Token Grammar Parser Output パーサー
どこに手を入れるか? Lrama parse.y Lexer Token Grammar Parser Output パーサー
なぜレキサーに手を入れるのか • Lrama ではレキサーが文脈を持ってトークナイズしている ◦ 「いま何をパースしているか」という情報 • Action を読み込むとき、直前に読んだルールの場所を確認して 「どのルールを参照しているか」という関連づけまでやっている
• これをまるごとパーサーに持っていくのは非常に大変だし、 もともとの設計にも合わない ◦ トークナイズ → パースというフェーズ分けをしたかった
文脈をどこに持つか? • レキサーは文脈を持っていないと適切なトークナイズができない ◦ コメントは読み飛ばす ◦ HereDoc の途中で改行が含まれてもパースエラーにしない ◦ etc.
• これをパーサーに持つかレキサーに持つか
文脈をどこに持つか? • レキサーに持つと ◦ 入力から自身の状態を変えながら一気にトークナイズができ、 トークナイズ → パースというフェーズ分けができる • パーサーに持つと
◦ パーサーが状態を元に次のトークンをレキサーから受け取る ようになるので、レキサーはトークナイズに集中できる
https://github.com/ruby/lrama/pull/41 Pull Request
今後やりたいこと • 内部パーサーの Racc による自動生成化 ◦ WIP の PR →
https://github.com/ruby/lrama/pull/62 • Bison の機能の取り込み • kaneko.y さんによるやりたいことリスト → https://docs.google.com/document/d/1EAZzYMXBOdzK-6mMIj 2YNJxZZRVcpJxE7-4zXbHn8JA/edit?usp=sharing
世はまさに 大パーサー時代 人々はパーサーの 海へと漕ぎ出す ――RubyKaigi 2023 LT kaneko.y