如何编写更一般(但有效)的attoparsec版本的takeWhile1?

时间:2015-06-30 19:13:34

标签: haskell parser-combinators attoparsec

Data.Attoparsec.Text导出takeWhiletakeWhile1

  
takeWhile :: (Char -> Bool) -> Parser Text
     

只要谓词返回True,就消耗输入,并返回消耗的输入。

     

此解析器不会失败。如果谓词在输入的第一个字符上返回False,它将返回一个空字符串。

     

[...]

     
takeWhile1 :: (Char -> Bool) -> Parser Text
     

只要谓词返回True,就消耗输入,并返回消耗的输入。

     

此解析器要求谓词在至少一个输入字符上成功:如果谓词永远不会返回True或者没有输入,则它将失败。

attoparsec的文档鼓励用户

  

尽可能使用面向Text的解析器,例如takeWhile1代替many1 anyChar。两种解析器之间的性能差异大约为100倍。

这两个解析器非常有用,但我一直觉得需要更通用的takeWhile1版本,更具体地说,是一些假设的解析器

takeWhileLo :: (Char -> Bool) -> Int -> Parser Text
takeWhileLo f lo = undefined

将解析至少 lo个满足谓词f的字符,其中lo是任意非负整数。

我查看了takeWhile1's implementation,但它使用了一堆私有的Data.Attoparsec.Text.Internal函数,并且似乎不易推广。

我想出了以下应用实现:

{-# LANGUAGE OverloadedStrings #-}

import           Prelude                  hiding ( takeWhile )

import           Control.Applicative             ( (<*>) )
import           Data.Text                       ( Text )
import qualified Data.Text           as T

import           Data.Attoparsec.Text

takeWhileLo :: (Char -> Bool) -> Int -> Parser Text
takeWhileLo f lo =
  T.append . T.pack <$> count lo (satisfy f) <*> takeWhile f

按宣传方式运作,

λ> parseOnly (takeWhileLo (== 'a') 4) "aaa"
Left "not enough input"
λ> parseOnly (takeWhileLo (== 'a') 4) "aaaa"
Right "aaaa"
λ> parseOnly (takeWhileLo (== 'a') 4) "aaaaaaaaaaaaa"
Right "aaaaaaaaaaaaa"

但是需要打包count返回的中间结果列表让我担心,特别是对于lo很大的情况......这似乎违背了建议

  

尽可能使用面向Text的解析器[...]

我错过了什么吗?是否有更有效/惯用的方式来实现这样的takeWhileLo组合器?

1 个答案:

答案 0 :(得分:5)

Parser是一个monad,所以你可以检查返回值,如果长度不正确则会失败:

takeWhileLo :: (Char -> Bool) -> Int -> Parser Text
takeWhileLo f lo = do
  text <- takeWhile f
  case T.compareLength text lo of
    LT -> empty
    _  -> return text

compareLength来自text包。它比比较text的长度更有效,因为compareLength可能会短路。