Почему этот синтаксический анализатор всегда терпит неудачу, когда последовательность конца строки - 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)

Я переименовал ваши парсеры, для согласованности с attoparsecs, ни один из которых не имеет "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")]]
Другие вопросы по тегам