読者です 読者をやめる 読者になる 読者になる

みどりねこ日記

よくわからないけど、頑張りますよ。

パーサの連接とか

前回までに出てきた基本的なパーサをくっつけることで複雑なパーサが書けるわけですが、 今回はそのような連接とかを手助けしてくれるパーサコンビネータを紹介?します。

Kern のパーサコンビネータはシーケンス化、反復、選択、それと括弧の対応とかの面倒を見てくれるみたいです。

例のごとく kern をロード。

(use 'blancas.kern.core)

skip-ws は入力のはじめにあるすべての空白をスキップ。

doc.core> (run (skip-ws letter) "\t \n x")
\x
nil

<|> はあたえられたパーサを成功するまで試す。すべてのパーサが失敗するか、入力を消費するパーサが失敗すると全体として失敗する。

doc.core> (run (<|> digit letter) "x")
\x
nil
doc.core> (run (<|> digit letter) "9")
\9
nil
doc.core> (run (<|> digit letter tab) "?")
line 1 column 1
unexpected \?
expecting digit, letter or tab
nil

で、 >> は1つ以上のパーサを適用する。最後のパーサの結果だけを返す。

doc.core> (run (>> digit digit letter) "91x")
\x
nil

<< は1つ以上のパーサを適用する。最初のパーサの結果だけを返す。

doc.core> (run (<< upper (sym* \-) digit digit) "F-16")
\F
nil

<$> は与えられたパーサを入力に適用して、そのあとパースした結果に対して与えられた関数を適用、その結果を返す。

doc.core> (run (<$> #(* % %) dec-num) "12")
144
nil

<*> は2つ以上のパーサを適用して、それらの結果をベクタにして返す。

doc.core> (run (<*> dec-num tab oct-num tab hex-num) "737\t747\tA340")
[737 \tab 487 \tab 41792]
nil

<:> はバックトラックするパーサらしい。もし失敗しても消費した入力は回復。

doc.core> (:input (parse (<:> float-num) "5005.a"))
(\5 \0 \0 \5 \. \a)

many は入力に対しパーサを0回以上、失敗するまで適用。many1 は1回以上で、最低1回生功しないと失敗。

doc.core> (run (many letter) "123")
[]
nil
doc.core> (run (many letter) "xyz")
[\x \y \z]
nil
doc.core> (run (many1 letter) "xyz")
[\x \y \z]
nil
doc.core> (run (many1 letter) "123")
line 1 column 1
unexpected \1
expecting letter
nil

optional はパーサが成功するか消費を行わかなった場合に全体として成功する。

doc.core> (run (<+> (optional (sym* \-)) dec-num) "-45")
"-45"
nil
doc.core> (run (<+> (optional (sym* \-)) dec-num) "45") 
"45"
nil
doc.core> (run (<+> (optional float-num) letter) "45.A")
line 1 column 4
unexpected \A
expecting digit
nil

option は与えられたパーサを適用する。もし入力を消費せずに失敗したら、与えられた値を結果として返すっぽい。

doc.core> (run (option 3.1416 float-num) "foo")
3.1416
nil

skip は1つ以上のパーサを取ってそれらの結果をすべて飛ばす。結果として nil を返す。

doc.core> (run (skip letter digit letter digit) "A5E8") 
nil
nil

skip-many はパーサを0回以上適用し結果を飛ばす。nil を返す。skip-many は1回以上。

doc.core> (run (skip-many letter) "xyz")
nil
nil
doc.core> (run (skip-many1 letter) "xyz") 
nil
nil
doc.core> (run (skip-many1 letter) "123")
line 1 column 1
unexpected \1
expecting letter
nil

sep-by は与えられたパーサによって分けたものに対して別のパーサを0回以上適用する。sep-by1 は1回以上。

doc.core> (run (sep-by (sym* \&) (many1 letter)) "a&bb&ccc&ddd")
[[\a] [\b \b] [\c \c \c] [\d \d \d]]
nil

end-by は与えられたパーサによって別れる/終わるものに対して別のパーサを0回以上適用する。end-by1 は1回以上。

doc.core> (run (end-by (sym* \&) (many1 letter)) "a&bb&ccc&")
[[\a] [\b \b] [\c \c \c]]
nil

between は1,2つ目のパーサの間のちに対して3つ目のパーサを適用する。

doc.core> (run (between (sym \() (sym \)) dec-num) "(1984)")
1984
nil

<+> はひとつ以上のパーサをとる。で、結果を潰してくっつけて、文字列にする。

doc.core> (run (<+> letter (many alpha-num)) "a177")
"a177"
nil