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
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
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
200
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.1k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
390
Measuring & Analyzing Core Web Vitals
bluesmoon
9
860
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.3k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
160
Paper Plane
katiecoart
PRO
1
51k
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
530
Product Roadmaps are Hard
iamctodd
PRO
55
12k
Marketing to machines
jonoalderson
1
5.4k
We Have a Design System, Now What?
morganepeng
55
8.2k
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/