在Parsec中进行流读取和写入

时间:2015-04-14 02:06:38

标签: haskell parsec

给定Parser库中的parsec,有什么好的方法可以进行流式读取(来自输入文件)和写入(将已解析的blob / line附加到输出文件)。以下是Text.Parsec.ByteString的示例:

main    = do{ result <- parseFromFile numbers "digits.txt"
             ; case result of
                 Left err  -> print err
                 Right xs  -> print (sum xs)
             }

以上示例仅从“digits.txt”读取,并且在看到所有输入之前不会有输出。让我们假设我们不是收集所有值并进行上面的缩减(sum),而是希望以流方式将所有xs写入“digitsOut.txt”(从digits.txt读取行)并写出digitsOut.txt)。给定parseFromFile签名,在我们看到所有输入之前,似乎我们不能懒惰地流输出并将输出传递给输出文件。这是类型签名:

parseFromFile :: Parser a -> String -> IO (Either ParseError a)

因此,似乎为了确定是否存在错误,它将需要整个输入。如果我没有弄错,代码在看到所有输入之前都无法写入输出。有没有其他方法可以使用Parsec(而不是AttoParsec进行流输入和输出,如果可以避免的话 - 我希望能够访问Parsec的错误报告。我试图解析的文件很大(50 + GB)。所以,我需要使用流输入和输出来连接解析器代码。如果某个地方有一个很好的例子,那么指针就会受到赞赏。

更新

AttoParsec documentation找到,Parsec无法逐步消耗输入。因此,无法在Parsec中进行流式传输。我现在将解析器重构为AttoParsec

2 个答案:

答案 0 :(得分:3)

Parsec通常专注于少量复杂数据。想想编程和标记语言,困难的二进制格式等。这有两个影响:

  1. 针对错误消息(复杂数据需要)优化性能(不需要少量数据)
  2. 它不进行流解析(再次,不需要少量数据)
  3. Attoparsec采用相反的方法:它专注于大量(相对)简单数据。考虑挖掘日志文件,读取测量值,从互联网收集数据流等。这有两个影响:

    1. 针对错误消息的性能进行了优化
    2. 围绕流媒体构建
    3. 使用Attoparsec进行流解析的原理是你选择解析一些东西,当它消耗了你所有的输入时,它会返回一个值,表明它期望得到更多的输入。

        

      但是,请注意,即使使用Attoparsec解析它,一个巨大的文件仍可能占用大量内存。这是因为Attoparsec(与Parsec相对)在无法匹配解析器时总是回溯,因此它不能丢弃“已经解析”的数据,因为以后可能需要它。

           

      正如评论中暗示的那样,解决方案是只为数据的一个部分编写解析器(类似于日志文件的一行),然后从Haskell代码中为每一行重复运行该解析器。你的日志文件。这允许解析占用恒定的空间。

答案 1 :(得分:0)

  

给定parseFromFile签名,看起来我们可以懒得流式传输并将输出传递给输出文件,直到我们看到所有输入。 [...]因此,似乎为了确定是否存在错误,它将需要整个输入。如果我没有弄错的话,代码不能写输出,直到它看到所有的输入。

Attoparsec与Parsec一样都是如此。虽然Attoparsec让您可以更好地控制输入的增量消耗,但仍然无法让解析器表达输出的增量生成。解析器必须在报告成功之前运行完成,获取解析器输出的唯一方法是通过表示解析器成功的数据构造函数。