将普通的attoparsec解析器代码转换为基于管道/管道

时间:2014-06-05 11:21:29

标签: parsing haskell conduit attoparsec haskell-pipes

我使用attoparsec编写了以下解析代码:

data Test = Test {
  a :: Int,
  b :: Int
  } deriving (Show)

testParser :: Parser Test
testParser = do
  a <- decimal
  tab
  b <- decimal
  return $ Test a b

tParser :: Parser [Test]
tParser =  many' $ testParser <* endOfLine

这适用于小型文件,我执行它是这样的:

main :: IO ()
main = do
  text <- TL.readFile "./testFile"
  let (Right a) = parseOnly (manyTill anyChar endOfLine *> tParser) text
  print a  

但是当文件大小超过70MB时,它会占用大量内存。作为解决方案,我想我会使用attoparsec-conduit。经过他们API后,我不确定如何让他们一起工作。我的解析器的类型为Parser Test,但sinkParser实际上接受Parser a b类型的解析器。我对如何在常量内存中执行此解析器感兴趣? (基于管道的解决方案也是可以接受的,但我不习惯Pipes API。)

2 个答案:

答案 0 :(得分:5)

Parser的第一个类型参数只是输入的数据类型(TextByteString)。您可以将testParser函数作为sinkParser的参数提供,它可以正常工作。这是一个简短的例子:

{-# LANGUAGE OverloadedStrings #-}
import           Conduit                 (liftIO, mapM_C, runResourceT,
                                          sourceFile, ($$), (=$))
import           Data.Attoparsec.Text    (Parser, decimal, endOfLine, space)
import           Data.Conduit.Attoparsec (conduitParser)

data Test = Test {
  a :: Int,
  b :: Int
  } deriving (Show)

testParser :: Parser Test
testParser = do
  a <- decimal
  space
  b <- decimal
  endOfLine
  return $ Test a b

main :: IO ()
main = runResourceT
     $ sourceFile "foo.txt"
    $$ conduitParser testParser
    =$ mapM_C (liftIO . print)

答案 1 :(得分:5)

以下是pipes解决方案(假设您使用的是基于Text的解析器):

import Pipes
import Pipes.Text.IO (fromHandle)
import Pipes.Attoparsec (parsed)
import qualified System.IO as IO

main = IO.withFile "./testfile" IO.ReadMode $ \handle -> runEffect $
    for (parsed (testParser <* endOfLine) (fromHandle handle)) (lift . print)