Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

LR で JSON パーサーを作る / Coding LR JSON Parser

LR で JSON パーサーを作る / Coding LR JSON Parser

大阪Ruby会議04でのスポンサーLTです。
番組内で作成したパーサーはこちら → https://gist.github.com/junk0612/c6bc79776724ab8de1857b5d1fc1b360

Junichi Kobayashi

August 24, 2024
Tweet

More Decks by Junichi Kobayashi

Other Decks in Programming

Transcript

  1. LR で JSON パーサーを作る 小林 純一 (@junk0612) 株式会社永和システムマネジメント アジャイル事業部 大阪

    Ruby 会議 04 中之島フェスティバルタワー・ウエスト 2024/08/24 (土) Sponsor LT
  2. 小林 純一 • X / GitHub: @junk0612 • 永和システムマネジメント ◦

    Rails エンジニア ◦ 構文解析器研究部員 • Lrama のコミッター • 趣味 ◦ パーサー ◦ 音楽ゲーム ◦ ボードゲーム ◦ 俳句
  3. LR パーサーの雛形を作る class JSONParser class State def initialize(actions, gotos) @actions

    = actions @gotos = gotos end end def initialize @states = [] @stack = [0] # 最初は状態0からスタート end end
  4. 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
  5. 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
  6. 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
  7. 各状態を計算する 状態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. 遷移先状態の カーネル項を計算する
  8. 各状態を計算する 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
  9. 各状態を計算する 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/-
  10. 状態を実装する 各項のドットの次の記号を見る • 終端記号なら shift ◦ 遷移先の状態番号 • 非終端記号なら goto

    ◦ 遷移先の状態番号 • 文法の終わりなら reduce ◦ 文法の記号数 ◦ 左辺の記号
  11. 状態を実装する 各項のドットの次の記号を見る • 終端記号なら 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], }, { } ),
  12. 状態を実装する 各項のドットの次の記号を見る • 終端記号なら 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, } ),
  13. 状態を実装する 各項のドットの次の記号を見る • 終端記号なら 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, } ),