Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
RubyでLALR(1)パーサージェネレータを作ってみた
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Kota Kato
June 28, 2025
150
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
RubyでLALR(1)パーサージェネレータを作ってみた
Kota Kato
June 28, 2025
Featured
See All Featured
The Cult of Friendly URLs
andyhume
79
6.9k
Ethics towards AI in product and experience design
skipperchong
2
310
What does AI have to do with Human Rights?
axbom
PRO
1
2.2k
Typedesign – Prime Four
hannesfritz
42
3.1k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
How STYLIGHT went responsive
nonsquared
100
6.2k
My Coaching Mixtape
mlcsv
0
140
Leo the Paperboy
mayatellez
7
1.8k
A Tale of Four Properties
chriscoyier
163
24k
The SEO identity crisis: Don't let AI make you average
varn
0
490
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.2k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
610
Transcript
RubyでLALR(1)パーサージェ ネレータを作ってみた kat0h
関西RubyKaigiたのしかったですね 松山RubyKaigiもたのしかったですね
RubyKaigiで(うっかり)パーサージェネレータに はまったので
Rubyでパーサージェネレータを作ってみました
成果物 https://github.com/kat0h/lalr1
???「パーサージェネレータってきょうび人間 が描くものじゃないよね」
パーサージェネレータ 🟰 パーサーをジェネレートするプログラム
例: yacc / bison / lrama
パーサーは対象とする言語クラスごとに種類が ある LR / LL etc…
lramaはLALR(1) なので、これを作る
教科書的なLALR(1)パー サージェネレータ造りの ロードマップ これを一つ一つ実装してい く
Grammer
文法を記述する 例:足し算・掛け算・かっこが記述できる文法 E = E + T | T T
= T * F | F F = ( E ) | number 終端記号: ( ) * + number 非終端記号: E T F
で、こうなる vn: 非終端記号 vt: 終端記号 s: 開始記号 p: 文法規則(と、アクション) precedence:
優先度の定義
First関数を実装する
First関数: a → {a1, a2, … } a∈Vt 入力記号の最初に出うる終端記号の集合を求 める
例えば。。。 First(number) はnumber First(F)は{ (, number } First(T)も{ (, number
}
実装する def first g,a,visited=Set[] return Set[] if visited.include?(a); visited<<a g.vt.include?(a)?Set[a]:g.p.find_all{_1.l==a}.map{first
g,_1.r[0],visited}.reduce(&:|) end
Closure関数を実装する
Closure LR(1)項集合の閉包を求める
LR(1)項? A. [ A -> a_1 ・ a_2 , x
] 左 辺 右 辺 注 目 点 先 読 み 記 号
LRパーサーのオートマトンのそれぞれの状態は LR(n)項集合のClosure
dotは現時点でどこまでパースが終わっているか を示す xは先読み記号、dotが最後にあるかつxが入力 されたとき、この生成規則で還元できる。。という 意味
LRパーサーのオートマトンのそれぞれの状態は LR(0)項集合のClosure
Closure({ E → E + ・ T })を求めることを考える ・の直後はT(非終端記号) T
→ ・T * F と T → ・F
T → ・T * F と T → ・F の直後はF
F → ・( E ) と F → ・i
これを全部をまとめて Closure({ E → E + ・ T }) =
{ E → E + ・ T , T → ・T * F , T → ・F, F → ・( E ) , F → ・i } メモ: 開始規則と注目点が左端にない項をカー ネル項という
Goto関数を実装する
Goto ≒ パーサーの オートマトンの遷 移関数 注目点を一つ進 めて、その Closureを取る 湯浅太一 コンパイラ p104より引用
例 I = { E → E + ・ T
, T → ・T * F , T → ・F, F → ・( E ) , F → ・i } Goto( I, ( )を考える Iの中で注目点の次に(がある規則の集合... { F → ・( E ) } 注目点を一つ動かす { F → ( ・ E ) } Closureを取るClosure({ F → ( ・ E ) })
canonical setの計算
初期状態からGotoを使って遷移できる項集合を 全て計算する 湯浅太一 コンパイラ p104より引用
action tableの計算
Action パーサーの遷移関数 Action : I x a → 動作
LRパーサーはスタック付きのオートマトン 記号が入力されたとき次にする動作は shift/reduce/acceptのどれかになる
次に遷移する先があるなら... shfit 現在の状態に完全項が含まれるかつ、その項の先読み記号が 入力された記号...reduce 現在の状態に受理できるルールがある、、かつ入力が EOF...accept
次に取れる動作が複数ある→コンフリクト💥 ...ざんねんながらコンフリクトした文法はLRパー サーで扱えません (しかたないね)
LR(1) Parsing Tableの計 算
構文解析表のデータ構造 ↓これだけ LR1ParsingTable=Struct.new(:rule,:vn,:vt,:s,: action,:goto,keyword_init:true) 構文規則 非終端記号 終端記号 開始記号 Action表 Goto表
Action表 - 今の状態と入力(終端記号)に対する次の動作 Goto表 - reduceするときに、次にどの状態に遷移すればいいかを記述した表 - I x a
→ I ( a \in Vn ) これだけ
LALR(1) Parsing Tableの 計算
アイデア LR(1)の構文解析表はデカい → こまるね → それぞれの状態をみると、先読み記号のバリエーションだけ違うのがあるじゃん → まとめちゃえ☆
まとめる まとめた (とてもきたないコード)
LRLR(1)のデメリット - LR(1)ではパースできるけど、LALR(1)ではパースできない文法が存在する (cf. mysterious conflict) - エラーを報告するまでに何回かreduceしてしまうことがある でも、、解析表がコンパクトになってうれしい 31状態から16状態に!
LR(1)の解析表を経由しないで、いきなり LALR(1)の解析表をつくる方法はある (きのう川でjunkさんに教えてもらった)
構文解析表を実行するパーサーの実装
junk0612さんのLTを参照! これだけ
曖昧な文法への対応
遠い記憶
コンフリクトした文法もちょっと頑張ればパースで きるようにすることができる
たとえば、、、 四則演算 expr = expr + expr | expr *
expr | num これはコンフリクトする、、、が、演算子の 優先度が分かれば解決できる 1 + 1 + 1 は (1 + 1) + 1なの?1 + (1 + 1)なの? 1 + 2 * 3 は(1 + 2) * 3なの?1 + (2 * 3)なの?
つまり、、、 +と*は左結合の演算子 +より*の方が優先度が高い これを指示できるように...した
↓こんなかんじ precedence: [ [:left, ["+", "-"]], [:left, ["/", "*"]], [:right,
["^"]], ]
この情報を使って、コンフリクトを解消する
↓コンフリクト解消のログ 状態14で+が入力の時コンフリクトが検出されました [expr → expr * expr ・, $/+///*/-/^] [expr
→ expr ・+ expr, $/+///*/-/^] [expr → expr ・/ expr, $/+///*/-/^] [expr → expr ・* expr, $/+///*/-/^] [expr → expr ・- expr, $/+///*/-/^] [expr → expr ・^ expr, $/+///*/-/^] shiftの優先度: 0 reduceの優先度: 1 > reduceを選択 *と+のどっちを優先すればいいか分からないので → 優先度表を見ると*の方が高い → ので、さきにreduceする *のあとに... +を入力する
こんなかんじで、ごちゃごちゃ書いた結果...
このような構文定 義で、、、
REPLを書いて..
こんな感じの入力を与えると... calc> 1+2*4^2+4/2 35 四則演算がパースできる!!
多分、yaccの機能と同等のパースができるよう になった
ありがとうございました
参考文献 湯淺 太一, コンパイラ, 情報系教科書シリーズ , オーム社, 2014年9月, ISBN: 978-4-274-21620-6.
中田育男, コンパイラ[第2版] サイエンス社, 2009年, ISBN: 978-4-7819-1229-5. A.V. エイホ, M.S. ラム, R. セシィ, J.D. ウルマン 共著, 原田 賢一 訳, コンパイラ[第2 版], サイエンス社, 2009年5月25日, ISBN: 978-4-7819-1229-5 Free Software Foundation, GNU Bison Manual, Version 3.8.1, Section 5.7: Mysterious Conflicts, 10 September 2021, https://www.gnu.org/software/bison/manual/html_node/Mysterious-Conflicts.html 小林 純一, LRでJSONパーサーを作る / Coding LR JSON Parser 大阪Ruby会議04 スポンサー LT, 2024年8月24日, https://speakerdeck.com/junk0612/coding-json-lr-parser yui-knk-blog yui-knk, yui-knk's blog, https://yui-knk.hatenablog.com/