Почему этот синтаксический анализатор всегда терпит неудачу, когда последовательность конца строки - CRLF?
Этот простой синтаксический анализатор должен анализировать сообщения в форме
key: value\r\nkey: value\r\n\r\nkey: value\r\nkey: value\r\n\r\n
Один EOL действует как разделитель полей, а двойной EOL действует как разделитель сообщений. Он прекрасно работает, когда разделитель EOL \n
но parseWith
всегда возвращает неудачу, когда это \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
2 ответа
Я предполагаю, что вы используете этот импорт:
{-# 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 разделяет (а не завершает) пары ключ-значение, EOL не должен использоваться в конце вашего parsePair
синтаксический анализатор.
Еще одна касательная проблема: потому что вы используете many1
комбинатор вместо ByteString
-ориентированные парсеры (такие как takeTill
), ваши значения имеют тип String
вместо ByteString
, Это, вероятно, не то, что вы хотите, здесь, потому что это побеждает цель использования ByteString
на первом месте.; см. вопросы производительности.
Решение
Я предлагаю следующий рефакторинг:
{-# 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
s, ни один из которых не имеет "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")]]