attoparsec iteratee
-
28-10-2019 - |
質問
繰り返しについて少し学ぶことを望んでいました。data.iterateeとdata.attoparsec.iterateeを使用して、作成したシンプルなパーサーを再実装しました。私はかなり困惑しています。以下に解析できる簡単な例があります 1 ファイルからの行。私のパーサーは一度に1行を読み取るので、それが完了するまで繰り返しにラインを供給する方法が必要です。私はこれをグーグルで見つけたことをすべて読んだことがありますが、Iteratee/Enumeratorsの多くの資料はかなり進んでいます。これは重要なコードの一部です。
-- There are more imports above.
import Data.Attoparsec.Iteratee
import Data.Iteratee (joinI, run)
import Data.Iteratee.IO (defaultBufSize, enumFile)
line :: Parser ByteString -- left the implementation out (it doesn't check for
new line)
iter = parserToIteratee line
main = do
p <- liftM head getArgs
i <- enumFile defaultBufSize p $ iter
i' <- run i
print i'
この例は、複数の行を持つファイルから1つの行を解析および印刷します。元のスクリプトは、バイテストリングのリストにパーサーをマッピングしました。だから私はここで同じことをしたいと思います。私は見つけました enumLines
Iterateeでは、私はそれをどのように使用するかを理解することはできません。多分私はその目的を誤解していますか?
解決
パーサーは一度にラインで動作するため、attoparsec-olateeを使用する必要さえありません。私はこれを次のように書きます:
import Data.Iteratee as I
import Data.Iteratee.Char
import Data.Attoparsec as A
parser :: Parser ParseOutput
type POut = Either String ParseOutput
processLines :: Iteratee ByteString IO [POut]
processLines = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) stream2list
これを理解するための鍵は、「列挙」です。これは、ストリームコンバーターの単なる用語です。 1つのストリームタイプのストリームプロセッサ(Iteratee)を使用し、それを別のストリームで動作させるように変換します。両方 enumLinesBS
と mapStream
列挙されています。
パーサーを複数の行にマッピングするには、 mapStream
十分なものです:
i1 :: Iteratee [ByteString] IO (Iteratee [POut] IO [POut]
i1 = mapStream (A.parseOnly parser) stream2list
ネストされた反復は、これがのストリームを変換することを意味します [ByteString]
の流れへ [POut]
, 、そして最終的なiteratee(stream2List)が実行されると、そのストリームは [POut]
. 。したがって、今では、繰り返しに相当する必要があります lines
そのストリームを作成します [ByteString]
, 、それが何ですか enumLinesBS
行う:
i2 :: Iteratee ByteString IO (Iteratee [ByteString] IO (Iteratee [POut] m [POut])))
i2 = enumLinesBS $ mapStream f stream2list
しかし、この機能は、すべてのネスティングのために使用するのがかなり扱いにくいです。私たちが本当に望んでいるのは、ストリームコンバーター間で出力を直接パイプする方法であり、最後にすべてを単一の反復に簡素化します。これを行うには、使用します joinI
, (><>)
, 、 と (><>)
:
e1 :: Iteratee [POut] IO a -> Iteratee ByteString IO (Iteratee [POut] IO a)
e1 = enumLinesBS ><> mapStream (A.parseOnly parser)
i' :: Iteratee ByteString IO [POut]
i' = joinI $ e1 stream2list
これは私が上記の書き方に相当します。 e1
インラインド。
ただし、まだ重要な要素が残っています。この関数は、単にリストの解析を返すだけです。通常、結果を折りたたむなど、他のことをしたいと思うでしょう。
編集: Data.Iteratee.ListLike.mapM_
多くの場合、消費者を作成するのに役立ちます。その時点で、ストリームの各要素は解析結果ですので、それらを印刷したい場合は使用できます
consumeParse :: Iteratee [POut] IO ()
consumeParse = I.mapM_ (either (\e -> return ()) print)
processLines2 :: Iteratee ByteString IO ()
processLines2 = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) consumeParse
これにより、成功した区画だけが印刷されます。エラーをSTDERに簡単に報告したり、他の方法でも簡単に処理できます。