当行尾序列是 CRLF 时,为什么这个解析器总是失败?

Why does this parser always fail when the end-of-line sequence is CRLF?

这个简单的解析器应该解析

形式的消息
key: value\r\nkey: value\r\n\r\nkey: value\r\nkey: value\r\n\r\n

一个EOL作为字段分隔符,两个EOL作为消息分隔符。当 EOL 分隔符为 \nparseWith 总是 returns 在 \r\n.

时失败
parsePair = do
  key <- B8.takeTill (==':')
  _ <- B8.char ':'
  _ <- B8.char ' '
  value <- B8.manyTill B8.anyChar endOfLine
  return (key, value)

parseListPairs = sepBy parsePair endOfLine <* endOfLine

parseMsg = sepBy parseListPairs endOfLine <* endOfLine

我假设您正在使用这些导入:

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Attoparsec.ByteString.Char8 as B8
import Data.Attoparsec.ByteString.Char8

问题是 endOfLine 消耗了行尾,所以也许你真的想要这样的东西:

parseListPairs = B8.many1 parsePair <* endOfInput

例如,这个有效:

ghci> parseOnly parseListPairs "k: v\r\nk2: v2\r\n"
Right [("k","v"),("k2","v2")]

更新:

要解析多条消息,您可以使用:

parseListPairs = B8.manyTill parsePair endOfLine
parseMsgs = B8.manyTill parseListPairs endOfInput

ghci> test3 = parseOnly parseMsgs "k1: v1\r\nk2: v2\r\n\r\nk3: v3\r\nk4: v4\r\n\r\n"
Right [[("k1","v1"),("k2","v2")],[("k3","v3"),("k4","v4")]]

问题

您的代码不是独立的,实际问题不清楚。但是,我怀疑您的问题实际上是由密钥的解析方式引起的;特别是,根据您的解析器,\r\nk 之类的内容是有效密钥:

λ> parseOnly parsePair "\r\nk: v\r\n"
Right ("\r\nk","v")

这需要解决。

此外,由于一个 EOL 分离 (而不是 终止 )键值对,因此不应在parsePair 解析器结束。

另一个切线问题:因为你使用 many1 组合器而不是面向 ByteString 的解析器(例如 takeTill),你的值的类型是 String 而不是 ByteString。在这里,这可能 不是 你想要的,因为它首先违背了使用 ByteString 的目的。参见 Performance considerations

解决方案

我建议进行以下重构:

{-# LANGUAGE OverloadedStrings #-}

import Data.ByteString ( ByteString )

import Data.Attoparsec.ByteString.Char8 ( Parser
                                        , count
                                        , endOfLine
                                        , parseOnly
                                        , sepBy
                                        , string
                                        , takeTill
                                        )

-- convenient type synonyms
type KVPair = (ByteString, ByteString)
type Msg = [KVPair]

pair :: Parser KVPair
pair = do
    k <- key
    _ <- string ": "
    v <- value
    return (k, v)
  where
    key     = takeTill (\c -> c == ':' || isEOL c)
    value   = takeTill isEOL
    isEOL c = c == '\n' || c == '\r'

-- one EOL separates key-value pairs
msg :: Parser Msg
msg = sepBy pair endOfLine

-- two EOLs separate messages
msgs :: Parser [Msg]
msgs = sepBy msg (count 2 endOfLine)

为了与 attoparsec 的解析器保持一致,我已经重命名了您的解析器,其中 none 的解析器以 "parse" 作为前缀:

  • parsePair --> pair
  • parseListPairs --> msg
  • parseMsg --> msgs

GHCi 中的测试

λ> parseOnly keyValuePair  "\r\nk: v"
Left "string"

很好;在这种情况下,您确实想要失败。

λ> parseOnly keyValuePair  "k: v"
Right ("k","v")

λ> parseOnly msg "k: v\r\nk2: v2\r\n"
Right [("k","v"),("k2","v2")]

λ> parseOnly msgs  "k1: v1\r\nk2: v2\r\n\r\nk3: v3\r\nk4: v4"
Right [[("k1","v1"),("k2","v2")],[("k3","v3"),("k4","v4")]]

λ> parseOnly msgs "k: v"
Right [[("k","v")]]