次の CSV ドキュメントの単純な文法を考えてみましょう (ABNF の場合)。
csv = *crow
crow = *(ccell ',') ccell CR
ccell = "'" *(ALPHA / DIGIT) "'"
この文法を TSV (タブレータで区切られた値) ドキュメントに変換するコンバータを書きたいと思います。
tsv = *trow
trow = *(tcell HTAB) tcell CR
tcell = DQUOTE *(ALPHA / DIGIT) DQUOTE
まず、抽象構文木を記述する代数データ型を作成しましょう。理解を容易にするために、型の同義語が含まれています。
data XSV = [Row]
type Row = [Cell]
type Cell = String
この文法のパーサーを書くのはとても簡単です。ABNF を記述するかのようにパーサーを記述します。
csv :: Parser XSV
csv = XSV <$> many crow
crow :: Parser Row
crow = do cells <- ccell `sepBy` (char ',')
newline
return cells
ccell :: Parser Cell
ccell = do char '\''
content <- many (digit <|> letter)
char '\''
return content
このパーサーはdo
-notation を使用します。の後にdo
一連のステートメントが続きます。パーサーにとって、これらのステートメントは単なる他のパーサーです。<-
パーサーの結果をバインドするために使用できます。このように、複数の小さなパーサーを連鎖させて大きなパーサーを構築します。興味深い効果を得るために、特別なコンビネータを使用してパーサーを組み合わせることもできます (たとえばa <|> b
、a
またはを解析しb
、可能な限りmany a
多くa
の を解析します)。Parsec はデフォルトではバックトラックしないことに注意してください。文字を消費した後にパーサーが失敗する可能性がある場合は、接頭辞を付けてtry
、1 つのインスタンスのバックトラッキングを有効にします。try
解析が遅くなります。
その結果csv
、CSV ドキュメントを解析して抽象的な構文ツリーにするパーサーが作成されます。これを別の言語 (TSV など) に変換するのは簡単です。
xsvToTSV :: XSV -> String
xsvToTSV xst = unlines (map toLines xst) where
toLines = intersperse '\t'
これら 2 つのことを接続すると、変換関数が得られます。
csvToTSV :: String -> Maybe String
csvToTSV document = case parse csv "" document of
Left _ -> Nothing
Right xsv -> xsvToTSV xsv
そしてそれだけです!Parsec には、非常に洗練されたパーサーを構築するための関数が他にもたくさんあります。Real World Haskellという本には、パーサーに関する素晴らしい章がありますが、少し古くなっています。ただし、そのほとんどは依然として真実です。さらに質問がある場合は、お気軽にお問い合わせください。