Conduit 和 Attoparsec - 提取分隔文本

Conduit and Attoparsec - extracting delimited text

假设我有一个文档,其中的文本由 Jade 风格的括号分隔,例如 {{foo}}。我写了一个 Attoparsec 解析器,似乎可以正确提取 foo

findFoos :: Parser [T.Text]
findFoos = many $ do
  manyTill anyChar (string "{{")
  manyTill letter (string "}}")

测试表明它有效:

> parseOnly findFoos "{{foo}}"
Right ["foo"]
> parseOnly findFoos "{{foo}} "
Right ["foo"]

现在,有了 conduit-extra 中的 Data.Conduit.Attoparsec 模块,我似乎 运行 出现了奇怪的行为:

> yield "{{foo}}" $= (mapOutput snd $ CA.conduitParser findFoos) $$ CL.mapM_ print
["foo"]
> yield "{{foo}} " $= (mapOutput snd $ CA.conduitParser findFoos) $$ CL.mapM_ print
-- floods stdout with empty lists

这是期望的行为吗?我应该在这里使用管道实用程序吗?对此的任何帮助都是巨大的!

因为它使用 manyfindFoos 将 return [] 在找不到任何分隔文本时不消耗输入。

另一方面,conduitParser 对流重复 应用解析器,return 分析每个已解析的值,直到耗尽流。

"{{foo}} " 的问题是解析器将消耗 {{foo}},但流中的空白 space 仍未被消耗,因此解析器的进一步调用总是 return [].

如果您重新定义 findFoos 以一次使用一个带引号的元素,包括尾随空格,它应该可以工作:

findFoos' :: Parser String
findFoos' = do
   manyTill anyChar (string "{{")
   manyTill letter (string "}}") <* skipSpace

现实世界的例子在括号文本之间会有其他字符,所以在每次解析后跳过 "extra stuff"(不消耗任何 {{ 左大括号进行下一次解析)会有点更多参与。

也许像下面这样的东西会起作用:

findFoos'' :: Parser String
findFoos'' = do
    manyTill anyChar (string "{{")
    manyTill letter (string "}}") <* skipMany everythingExceptOpeningBraces
  where 
    -- is there a simpler / more efficient way of doing this?
    everythingExceptOpeningBraces =
        -- skip one or more non-braces
        (skip (/='{') *> skipWhile (/='{'))
        <|> 
        -- skip single brace followed by non-brace character
        (skip (=='{') *> skip (/='{'))
        <|>
        -- skip a brace at the very end 
        (skip (=='{') *> endOfInput)

(但是,如果流中没有任何带括号的文本,此解析器将失败。也许您可以构建一个 Parser (Maybe Text) that returns Nothing 在这种情况下.)