Introduction to PEG

37
Introduction to PEG 構文解析友の会 水島 宏太

Transcript of Introduction to PEG

Page 1: Introduction to PEG

Introduction to PEG

構文解析友の会

水島 宏太

Page 2: Introduction to PEG

背景

多様な入力文字列を構文解析する必要性 (色々なフォーマットの)設定ファイル

Webのクローリング

"Cargo cult parsing" (from Yacc is dead)の流行

Googleで検索して正規表現を拾ってきて、テキトウにコピー&ペーストしてパーザを作ること

「だいたいの入力に対してそれなりにうまく働く」不完全なパーザ

Cargo cult parsingを追放せよ!

Page 3: Introduction to PEG

構文解析って何?

一言でいうと:

入力文字列を木構造(Abstract Syntax Tree)に組み立てる捜査

二種類に分けられる 自然言語の構文解析

構文解析の結果あいまい性が生じることがある

非自然言語の構文解析 ←今回扱うもの 構文解析の結果あいまい性が生じない

Page 4: Introduction to PEG

様々な構文解析アルゴリズム

CYK - O(n3) (S|LA)?LR(k) - O(n) LL(k) - O(n) LL(*) - O(n) GLR (Generalized LR) - O(n3) GLL (Generalized LL) - O(n3) PEG(or Packrat Parsing) - O(kn) (O(n))

一長一短がある

Page 5: Introduction to PEG

構文解析器をどうやって書く?

手書き どんな文法でも大体作れる

文法のメンテナンス/デバッグが大変

パーザジェネレータ 文法定義からパーザを自動生成

パーザコンビネータ

プログラミング言語のDSLとしてパーザを組み立てるものを用意する

ホスト言語のデバッグ環境をそのまま用意できる

Page 6: Introduction to PEG

YACC (LALR(1)) 最もメジャーなパーザジェネレータ

Rubyを初めとする様々な言語で採用

上位互換のbisonが一般に採用される

空白の有無等で意味が変わる文法は苦手 空白は通常字句解析で処理するため

トークンが再帰的な構造を含む文法は苦手

式埋め込み可能な文字列リテラル(Ruby) "1+2 = #{1+2}" ヒアドキュメント

Page 7: Introduction to PEG

JavaCC (LL(k) + α) Javaでメジャーなパーザジェネレータ

構文木の生成がちょっと楽

jjtree(プリプロセッサ)を使用した場合

空白の有無で意味が変わる文法は苦手

理由はyaccと同じ

トークンが再帰的な構造を含む文法は苦手

理由はyaccと同じ

Page 8: Introduction to PEG

何が言いたいか

一般的な構文解析アルゴリズムでは字句解析と構文解析の分離が前提

プログラミング言語用の字句解析器の限界 字句解析器は通常正規表現によって記述

再帰的な構造をうまく扱えない

パーザの文脈によってトークンの切り出し方が変わるような文法は苦手

Page 9: Introduction to PEG

そこでPEG(Ford04)ですよ

プログラミング言語等の文法の表記法 BNFと目的は類似

構文解析アルゴリズムの一種とも言える 決定的な任意のLR(k)言語や一部の文脈依存言

語を扱える

構文規則の集合 (N ← e)*

N: 構文規則名

e: 式(Parsing Expression)

Page 10: Introduction to PEG

Parsing Expression(1) N : 規則(非終端記号)の参照

"a" : 文字列a

ε : 空文字列

. : 任意の一文字

[...] : 文字クラス

e1 e2 : e1とe2の並び

e1 / e2 : e1を試し、失敗したらe2を試す e1 / e2 ≠ e2 / e1

Page 11: Introduction to PEG

Parsing Expression(2) e? : 0回または1回の繰り返し

e* : 0回以上の繰り返し

e+ : 1回以上の繰り返し

&e : And-predicate eがマッチしたら成功。入力を消費しない

!e : Not-predicate eがマッチしなければ成功。入力を消費しない

シンプル!

Page 12: Introduction to PEG

Desugaring Parsing Expression e? : (e / ε)

e* : N'; N'← e N' / ε

e+ : e e*

!e : !!(e)

Page 13: Introduction to PEG

PEGの解釈

入力に対して式がマッチするかを判定する 結果の値

マッチに成功した場合: 「消費」した文字列

失敗した場合: f(失敗したことを意味する値) 例: (式, 入力) → 結果 という形とする

("a", "ab") → "a"

("b", "ab") → f (&"a", "ab") → ""

("a"/"b", "ab") → "a"

(.*, "ab") → "ab"

Page 14: Introduction to PEG

PEGの例

Eが表している言語

a,b,cの文字と+から成る算術式

a,a+b,a+b+c,a+c,a+b+c+a, ...

E ← V "+" E / VV ← "a" / "b" / "c"

Page 15: Introduction to PEG

PEGで嬉しいこと

無限長の先読みが可能 字句解析不要→柔軟な文法が記述し易い

String interpolation, Here document等 曖昧性が無い

文法のconflictが起きない

C言語のif文のPEGによる記述

if_stmt ← IF LP expr RP stmt (ELSE stmt)?

Page 16: Introduction to PEG

PEGで嬉しくないこと

PEGの利点と表裏一体

空白の読み飛ばしなどを明示する必要がある 通常は字句解析で空白が処理される

マクロを導入すれば軽減できる

順序を入れ替えると意味が変わってしまう if_stmt ← IF LP expr RP stmt

/ IF LP expr RP stmt ELSE stmt

elseを含む文が解析できないPEG

Page 17: Introduction to PEG

PEGパーザの実装(関数型) 各構文規則を純粋な関数として実装

入力: 入力文字列

出力: 成功したかどうか

成功した場合は残りの文字列も一緒に返す

非終端記号の参照を関数呼び出しとして実装

Page 18: Introduction to PEG

PEGパーザの実装(関数型)

規則 V ← "a" V / "c"の実装を考える

def parse_V(input)

r1 = match("a", input)

if r1.succeed?

r2 = parse_V(r1.output)

if r2.succeed? then r2 else match("c", input) end

else

match("c", input)

end

end

Page 19: Introduction to PEG

PEGパーザの実装(手続き型) パーザが状態を持つ

入力文字列

今何文字目を解析しているか(カーソル) 構文規則を副作用のある関数として実装

入力: 無し

出力: 成功したかどうか

関数の中でカーソルを破壊的に更新

Page 20: Introduction to PEG

PEGパーザの実装(手続き型)

規則 V ← "a" V / "c"の実装を考える

def parse_V

backup_pos = current_pos

if match("a")

if parse_V then return true

rewind(backup_pos); return match("c")

else

return match("c")

end

end

Page 21: Introduction to PEG

Packrat Parsing(Ford02) PEGをベースにした構文解析アルゴリズム

入力の長さに対して線形時間で解析可能 実用的

アルゴリズムが非常に単純

Backtrack parsing + メモ化

プログラマが挙動を理解しやすい

Page 22: Introduction to PEG

メモ化(memoize) ≠memorize 一度計算した関数の結果を記憶しておく

同じ引数で呼び出された場合、結果を再利用

原則的に副作用の無い関数にしか使えない 副作用があるとメモ化した場合に結果が異なる

Page 23: Introduction to PEG

フィボナッチ関数

再帰的な定義: fib(n) = 1 if n = 1 or 2 fib(n) = fib(n – 1) + fib(n – 2)

定義にしたがって計算すると指数関数時間

Page 24: Introduction to PEG

メモ化されたフィボナッチ関数

同じ引数に対して、計算結果を再利用 fib(n) = m[n] if m[n] != null fib(n) = m[n]:=1 ; m[n] if n = 1 or 2 fib(n) = m[n]:=fib(n–1)+fib(n–2); m[n]

入力の大きさに対して線形時間

Page 25: Introduction to PEG

Backtrack parsing

E ← V "+" E / V V ← "a" / "b" / "c"

E(1)

V(1)

"a"

"+" E(3)

V(3)

"a" "b"

V(3)

"a" "b"

"+"

式: E, 入力:"a+b"

同じ計算を二度行う

Page 26: Introduction to PEG

Packrat parsing

E ← V "+" E / VV ← "a" / "b" / "c"

E(1)

V(1)

"a"

"+" E(3)

V(3)

"a" "b"

"+"

式: E, 入力:"a+b"

Vの解析結果を再利用

Page 27: Introduction to PEG

Backtrack ParsingとPackrat Parsingの性能比較

次の文法の言語を解析する時の性能を比較

Backtrackが頻発する入力を与える

入力: '(1)', '((1))', '(((1)))', …

E ← M M ← A Spacing MS / A MS ← "*" Spacing M / "/" Spacing M / "%" Spacing MA ← P Spacing AS / P AS ← "+" Spacing A / "-" Spacing A P ← Number / "(" Spacing E Spacing ")"

Page 28: Introduction to PEG

Backtrack ParsingとPackrat Parsingの性能比較

Page 29: Introduction to PEG

Packrat Parsingの欠点

必要な記憶領域が多い 入力文字列の長さに対して線形の記憶領域

大規模なファイルの解析には向かない

実行効率がイマイチ バックトラックやメモ化が影響

ほとんどのメモ化は無駄になっている可能性

Page 30: Introduction to PEG

PEG/Packrat Parser Generator PEGが提案されて10年以上

様々なものがある

Rats! (Java) Parboiled2 (Scala) Treetop (Ruby) Lpeg (Lua) PEG.js (JavaScript)

Page 31: Introduction to PEG

Rats! Javaのソースコードを生成 http://cs.nyu.edu/rgrimm/xtc/ (状態つき)Packrat Parserを生成

文法定義をモジュールに分割可能

ASTの自動生成

Page 32: Introduction to PEG

Parboiled2 Scala用

https://github.com/sirthias/parboiled2

Scalaマクロベースのジェネレータ

Scalaコードの一部として文法を記述

Page 33: Introduction to PEG

LPeg Lua用

http://www.inf.puc-rio.br/~roberto/lpeg/

文字列とのパターンマッチングライブラリ

Page 34: Introduction to PEG

Treetop Ruby用のPEGパーザジェネレータ

http://treetop.rubyforge.org/ Rubyプログラムに似たシンプルな文法記述

既存のgrammarを取り込んで新しいパーザを合成できる

Page 35: Introduction to PEG

PEG.js JavaScript用 http://pegjs.org/ Node.js対応

Page 36: Introduction to PEG

PEGに関する有名な未解決問題

PEL ⊃ CFL or not PEGはいくつかの文脈依存言語を表現可能

a^n b^n c^n CFGでは表現できない

では、逆は? 不明

回文がそれに相当するのではないかと考えられている

Page 37: Introduction to PEG

PEGに関するその他の研究

左再帰の導入(Warth et al, 2007) PEGの仮想機械(Medeiros et al, 2008) カットおよびカット自動挿入アルゴリズムの導

入 (Mizushima, et al., 2010) LL(*)アルゴリズム (Parr, et al., 2011)

PEGとカバーする範囲が重複する