6

2 つの言語 (A と B) があるとします。私の目標は、A で見つかった構文を B と同等のものに変換するある種のプログラムを作成することです。現在、私の解決策は、Haskell の Parsec を使用してこのタスクを実行することです。しかし、Haskell と関数型プログラミングの初心者として、Parsec で単純な例を見つけることは非常に困難でした。私が Web で見つけた例は、不完全な例 (新しい Haskell プログラマーにとって苛立たしいもの) であるか、私の目標からかけ離れすぎているかのいずれかです。

それで、誰かが私が達成したいことに関連する何かのためにParsecを使用する驚くほど簡単で明確な例を私に提供できますか? または、私の目標と同様のチュートリアルもあるかもしれません。

ありがとう。

4

1 に答える 1

16

次の 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 <|> baまたはを解析し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という本には、パーサーに関する素晴らしい章がありますが、少し古くなっています。ただし、そのほとんどは依然として真実です。さらに質問がある場合は、お気軽にお問い合わせください。

于 2012-07-09T20:16:58.823 に答える