2015-09-03 5 views
3

Этот простой парсер, как ожидается, для разбора сообщений видаПочему этот анализатор всегда терпит неудачу, когда последовательность конца строки является CRLF?

key: value\r\nkey: value\r\n\r\nkey: value\r\nkey: value\r\n\r\n 

One EOL выступает в качестве разделителя полей, а также двойной EOL выступает в качестве разделителя сообщений. Он отлично работает, когда разделитель EOL равен \n, но parseWith всегда возвращает fail, когда он равен \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 
+1

Откуда вы взяли 'endOfLine', или как вы его определили? Что означает 'B8'? Я могу сделать обоснованное предположение, но все же ... Вам нужно сделать код в своем вопросе самодостаточным, добавив свои операторы импорта и пользовательские определения. Вероятно, проблема заключается в том, что 'endOfLine' определяется для разбора' \ n', а не '\ r \ n'. Вам нужно будет определить свой собственный парсер 'endOfLine' для' \ r \ n'. – Jubobs

+0

@Jubobs Наверное, нет. Скорее всего, это [endOfLine' attoparsec] (https://hackage.haskell.org/package/attoparsec-0.13.0.1/docs/Data-Attoparsec-Text.html#v:endOfLine). – duplode

+1

@duplode Вы правы, но соответствующая ссылка, вероятно, [эта] (http://hackage.haskell.org/package/attoparsec-0.13.0.1/docs/Data-Attoparsec-ByteString-Char8.html#g : 13), как это было предложено под названием «B8». – Jubobs

ответ

3

Я предполагаю, что вы используете этот импорт:

{-# 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")] 

Update:

Для разбора несколько сообщений вы можете использовать:

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")]] 
+1

Это определенно лучше, но в какой-то момент все еще не получается, мне нужно пересмотреть структуру и выполнить некоторые тесты. – concept3d

+0

'parseOnly parseMsgs" k: v "' fail, но должен давать 'Right [[(" k "," v ")]]'. – Jubobs

1

Проблемы

Вашего код не самодостаточный и актуальная проблема остается неясной. Тем не менее, я подозреваю, что ваши проблемы на самом деле вызваны тем, как анализируются ключи; в частности, что-то вроде \r\nk является действительным ключом, в соответствии с вашим анализатором:

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

Это должно быть исправлено.

Кроме того, так как один оконечный отделяет (а не заканчивается) пар ключ-значением, оконечные не следует употреблять в конце вашего 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-х, ни один из которых "разбора" в качестве префикса:

  • 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")]] 
+0

Насколько я понимаю, соображения производительности, моя первоначальная реализация использовала Bytestring 'parsePair :: Parser (ByteString, ByteString)' и takeTill/takeWhile комбинаторы, на самом деле это было очень похоже на то, которое вы предоставили, оно также потерпело неудачу, но я не смог найти проблема, и я опубликовал другую версию, теперь, когда я увидел ваш impl, я подозреваю, что моя проблема была в определении isEndOfLine – concept3d

+0

. Я закончил с бесконечным циклом, таким же результатом, как и с моей первоначальной реализацией, я попытаюсь разобраться в проблеме. Существует также проблема, которую я читаю из сокета. эта реализация отлично работала с parseOnly в первый раз. – concept3d

+0

@ concept3d Просто любопытно: вы что-то достигли этого? – Jubobs