宝剣 PEG文法説明

PEG = Parsing Expression Grammar
再帰降下型のパーサを生成するための文法。
BNFに似た記法を使用するが、解析方法は異なる。
宝剣では、PEGに独自の拡張を施している。

基本

A <- e
という形で記述する。

A は「非終端記号」であり、このルールの名前となるシンボルで、宝剣では英大文字または英小文字またはアンダースコア  '_' で始まり、英大文字または英小文字またはアンダースコアまたは数字が続くシンボル。
e は「parsing expression」。

parsing expression は、構文解析を行ないそれが
成功すると、必要な分の入力を消費する(消費しないものもある)。
失敗すると、入力は消費しない。
エラーになると、その時点で異常終了する。

parsing expression は、終端記号 または 非終端記号 の組合せで表現される。


parsing expression の組合せのパターン


e1 e2 e3 ...
左から順にパースしていき、全部が成功したら成功

e1 / e2 / e3 ...
左から順にパースして、最初に成功したものがあればそれ以降右側にあるparsing expressionはパースせず、成功となる
全部失敗したら失敗

e*
0回以上の繰り返し

e+
1回以上の繰り返し

e?
0回または1回

&e
eが 成功すれば成功、但し入力は消費しない

!e
eが 失敗すれば成功、成功すれば失敗、入力は消費しない

(e)
eが 成功すれば成功



終端記号のパターン


"str"
文字列が一致すれば成功
文字列中の \n はLFコードに、 \r はCRコードに、 \t はタブコードに対応

[a-z]
1文字の文字コードが範囲内なら成功
宝剣では [a-fA-F] という書き方には対応していない
[a-f]/[A-F] と書く

.
どんな1文字とでも一致して成功
EOFとは一致せず失敗となる

EOF
入力がこれ以上無い場合に成功。
!. と同じ



拡張機能


%e
eが成功すれば成功だが、結果として出力されるSyntaxTreeの中に は含まれない

#
行末までコメント

'token'
文字列が一致し、かつ次の文字を先読みした時の条件が満たされれば成功
条件とは、ユーザーが定義した NotTokenPred が成功する事。
普通の言語なら、NotTokenPred は以下のように定義すれば良いだろう。
NotTokenPred <- !([a-z]/[A-Z]/[0-9]/"_")
注意点としては、NotTokenPredが入力を消費しないようにすべき である事。

$数字
ErrorCut機能。 これ自体は必ず成功する。
その後に続くparsing expressionが失敗すると、エラーとなってパースは異常終了する。
この時、数字がエラー表示プログラムに渡され、それに従ってエラーメッセージが出力される。
A <- B C $1 D / B E $2 F
この例では、B, C, E の時点で失敗した場合は A は失敗となり、
B C が成功した後に D が失敗した場合か、 B E が成功した後に F が失敗した場合はエラーとなる。
入力 結果
B C D 成功
B E F 成功
B D 失敗
B C F エラー1
B E G エラー2
A << spec precedence expression
演算子定義
A は、演算の対象となるオペランドを示すルール。
spec は、infixl, infixr, infixn, prefix, postfix, ternary のどれか1つ
infixl 中置演算子左結合
infixr 中置演算子右結合
infixn 中置演算子非結合
prefix 前置演算子
postfix 後置演算子
ternary 三項演算子
precedence は演算子の優先順位で、1以上の整数で、大きい方が優先順位が高い
expression は、演算子にマッチするparsing expression。三項演算子の場合は2つ必要
例)
A <- Number
A << infixl 10 ("+" / "-")
A << infixl 20 ("*" / "/")
A << infixr 30 "**"
A << prefix 40 "-"
A << ternary 5 "?" ":"
これは、優先順位によって整理され、内部的に以下のように展開されると考えれば良い。
A <- A__5
A__5 <- A__10 ("?" A__10 ":" A__10)?
A__10 <- A__20 (("+" / "-") A__20)*
A__20 <- A__30 (("*" / "/") A__30)*
A__30 <- A__40 ("**" A__40)*
A__40 <- "-"? A__0
A__0 <- Number
A <% e
ルール A を作成するが、パーサを自動生成しない。
C++で高速なパーサを自分で記述したい場合に使用する。
e はコメントアウトされた形で出力される。
詳細は、プログラミングガイドを参照。
$ctype locale
ロケール LC_CTYPElocaleにする
ルールよりも前に記述しなければならない。
例1) $ctype C
例2) $ctype ja_JP.SJIS
cygwin版の場合、 ja_JP.SJISC-SJIS に、 ja_JP.eucjpC-EUCJP に置換されてから適用される


注意

左再帰

BNFでよくやるように
A <- A "a" / "a"
とすると、入力位置に変更が無い状態で再び A を解析しようとしてしまい、無限ループになってしまう。
このような場合は
A <- "a"+
と書き直す。
左再帰のルールは実行時に検出され、エラーとなる。但しmemoize機能をオフにした場合は検出できない。

繰り返しによる入力の消費

繰り返し '*' '+' はマッチする部分の入力を全て消費する。そのため
"a"* "ab" は、決して aaab とはマッチしない。
"a"* は入力のうち aaa を消費してしまうので、"ab"b とマッチせず
失敗する。
このような場合は "a"+ "b" と書き直さなければならない。


参考

sample 以下に、簡単な整数の演算を行なうコマンドのサンプルがある。

宝剣自身のPEGは、
houken/peg.peg に全て定義されている。
これを houken にかけると、houken/PegParser.h と houken/PegParser.cpp と
(ほぼ)同じものが出力される。

リンク

WikiPediaのPEGの解説(英文)  http://en.wikipedia.org/wiki/Parsing_expression_grammar