当行尾序列是 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 分隔符为 \n
但 parseWith
总是 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")]]
这个简单的解析器应该解析
形式的消息key: value\r\nkey: value\r\n\r\nkey: value\r\nkey: value\r\n\r\n
一个EOL作为字段分隔符,两个EOL作为消息分隔符。当 EOL 分隔符为 \n
但 parseWith
总是 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")]]