Slide 1

Slide 1 text

LR で JSON パーサーを作る 小林 純一 (@junk0612) 株式会社永和システムマネジメント アジャイル事業部 大阪 Ruby 会議 04 中之島フェスティバルタワー・ウエスト 2024/08/24 (土) Sponsor LT

Slide 2

Slide 2 text

小林 純一 ● X / GitHub: @junk0612 ● 永和システムマネジメント ○ Rails エンジニア ○ 構文解析器研究部員 ● Lrama のコミッター ● 趣味 ○ パーサー ○ 音楽ゲーム ○ ボードゲーム ○ 俳句

Slide 3

Slide 3 text

パーサー 3分クッキング

Slide 4

Slide 4 text

ジェーソンの 手作りエルアールパーサー

Slide 5

Slide 5 text

こんなこと思いました? ● LRパーサーってなんだか難しそう…… ● パーサージェネレーター使わないの? ● 手書きパーサー? 再帰下降で十分でしょ……

Slide 6

Slide 6 text

でも大丈夫! ● LRパーサーってなんだか難しそう…… → 基本を押さえればカンタン、便利! ● パーサージェネレーター使わないの? → 手書きなら、細かなチューニングも自由自在! ● 手書きパーサー? 再帰下降で十分でしょ…… → LR の強力な解析能力を使わないのはもったいない!

Slide 7

Slide 7 text

提 供 情報化技術を通じて社会と共生する

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

材料 ● JSON の BNF ● https://www.json.org/json-ja.html

Slide 11

Slide 11 text

👈

Slide 12

Slide 12 text

👉

Slide 13

Slide 13 text

下ごしらえ: LR パーサーの雛形を作る

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

👈 スタック 👉 オートマトン

Slide 16

Slide 16 text

LR パーサーの雛形を作る class JSONParser class State def initialize(actions, gotos) @actions = actions @gotos = gotos end end def initialize @states = [] @stack = [0] # 最初は状態0からスタート end end

Slide 17

Slide 17 text

LR パーサーの雛形を作る class JSONParser ...(snip)... private def lex(string) s = StringScanner.new(string) input = [] until s.eos? if s.match?(/true|false|null/) input << s.matched else input << s.getch end end input << :eof input end end

Slide 18

Slide 18 text

LR パーサーの雛形を作る class JSONParser ...(snip)... def parse(string) lex(string).each do |c| action = @states[@stack.last].actions(c) unless action.nil? send(*action) redo if action[0] == :reduce else error end end end ...(snip)... end

Slide 19

Slide 19 text

LR パーサーの雛形を作る class JSONParser ...(snip)... def shift(to_state) @stack.push to_state end def reduce(pop_count, goto_sym) @stack.pop pop_count @stack.push @states[@stack.last].gotos[goto_sym] end def accept puts 'accept!' end def error puts 'error!' raise end end

Slide 20

Slide 20 text

手順1: 各状態を計算する

Slide 21

Slide 21 text

各状態を計算する 1. カーネル項を見つける 2. 非カーネル項を計算する 3. 遷移先状態の カーネル項を計算する

Slide 22

Slide 22 text

各状態を計算する 1. カーネル項を見つける 2. 非カーネル項を計算する 3. 遷移先状態の カーネル項を計算する 状態0 ● start: . json, EOF

Slide 23

Slide 23 text

各状態を計算する 状態0 ● start: . json, EOF ● json: . element, EOF ● element: . ws value ws, EOF ● ws: ., true/false/null/{/[/"/0/ONENINE/- ● ws: . ' ' ws, true/false/null/{/[/"/0/ONENINE/- ● ws: . '\n' ws, true/false/null/{/[/"/0/ONENINE/- ● ws: . '\r' ws, true/false/null/{/[/"/0/ONENINE/- ● ws: . '\t' ws, true/false/null/{/[/"/0/ONENINE/- 1. カーネル項を見つける 2. 非カーネル項を計算する 3. 遷移先状態の カーネル項を計算する

Slide 24

Slide 24 text

各状態を計算する 1. カーネル項を見つける 2. 非カーネル項を計算する 3. 遷移先状態の カーネル項を計算する 状態0 ● start: . json, EOF ● json: . element, EOF ● element: . ws value ws, EOF 状態1 ● start: json ., EOF 状態2 ● json: element ., EOF 状態3 ● element: ws . value ws, EOF

Slide 25

Slide 25 text

各状態を計算する 1. カーネル項を見つける 2. 非カーネル項を計算する 3. 遷移先状態の カーネル項を計算する 状態0 ● ws: . ' ' ws, true/false/null/{/[/"/0/ONENINE/- ● ws: . '\n' ws, true/false/null/{/[/"/0/ONENINE/- ● ws: . '\r' ws, true/false/null/{/[/"/0/ONENINE/- ● ws: . '\t' ws, true/false/null/{/[/"/0/ONENINE/- 状態4 ● ws: ' ' . ws, true/false/null/{/[/"/0/ONENINE/- 状態5 ● ws: '\n' . ws, true/false/null/{/[/"/0/ONENINE/- 状態6 ● ws: '\r' . ws, true/false/null/{/[/"/0/ONENINE/- 状態7 ● ws: '\t' . ws, true/false/null/{/[/"/0/ONENINE/-

Slide 26

Slide 26 text

手順2: 状態を実装する

Slide 27

Slide 27 text

状態を実装する 各項のドットの次の記号を見る ● 終端記号なら shift ○ 遷移先の状態番号 ● 非終端記号なら goto ○ 遷移先の状態番号 ● 文法の終わりなら reduce ○ 文法の記号数 ○ 左辺の記号

Slide 28

Slide 28 text

状態を実装する 各項のドットの次の記号を見る ● 終端記号なら shift ○ 遷移先の状態番号 ● 非終端記号なら goto ○ 遷移先の状態番号 ● 文法の終わりなら reduce ○ 文法の記号数 ○ 左辺の記号 状態0 ● ws: . ' ' ws, true/false/null/{/[/"/0/ONENINE/- ● ws: . '\n' ws, true/false/null/{/[/"/0/ONENINE/- ● ws: . '\r' ws, true/false/null/{/[/"/0/ONENINE/- ● ws: . '\t' ws, true/false/null/{/[/"/0/ONENINE/- State.new( { ' ' => [:shift, 4], '\n' => [:shift, 5], '\r' => [:shift, 6], '\t' => [:shift, 7], }, { } ),

Slide 29

Slide 29 text

状態を実装する 各項のドットの次の記号を見る ● 終端記号なら shift ○ 遷移先の状態番号 ● 非終端記号なら goto ○ 遷移先の状態番号 ● 文法の終わりなら reduce ○ 文法の記号数 ○ 左辺の記号 状態0 ● start: . json, EOF ● json: . element, EOF ● element: . ws value ws, EOF State.new( { ' ' => [:shift, 4], '\n' => [:shift, 5], '\r' => [:shift, 6], '\t' => [:shift, 7], }, { json: 1, element: 2, ws: 3, } ),

Slide 30

Slide 30 text

状態を実装する 各項のドットの次の記号を見る ● 終端記号なら shift ○ 遷移先の状態番号 ● 非終端記号なら goto ○ 遷移先の状態番号 ● 文法の終わりなら reduce ○ 文法の記号数 ○ 左辺の記号 状態0 ● ws: ., true/false/null/{/[/"/0/ONENINE/- State.new( { ' ' => [:shift, 4], '\n' => [:shift, 5], '\r' => [:shift, 6], '\t' => [:shift, 7], 'true' => [reduce, 0, :ws], 'false' => [reduce, 0, :ws], 'null' => [reduce, 0, :ws], '{' => [reduce, 0, :ws], '[' => [reduce, 0, :ws], ...(snip)... }, { json: 1, element: 2, ws: 3, } ),

Slide 31

Slide 31 text

完成品がこちら https://gist.github.com/junk0612/c6bc79776724ab8de1857b5d1fc1b360

Slide 32

Slide 32 text

完成品がこちら https://gist.github.com/junk0612/c6bc79776724ab8de1857b5d1fc1b360 ● 状態数: 246 ● ソースコード行数: 3543

Slide 33

Slide 33 text

ジェーソンの 手作りエルアールパーサー ~構文木生成を添えて~ 次 回

Slide 34

Slide 34 text

提 供 情報化技術を通じて社会と共生する