2013-03-16 2 views
4

В настоящее время я пытаюсь использовать полный CSV-анализатор, представленный в Real World Haskell. Чтобы я попытался изменить код для использования ByteString вместо String, но есть комбинатор string, который работает только с String.Соответствие байтов в Parsec

Есть ли комбинатор Parsec, похожий на string, который работает с ByteString, без необходимости делать преобразования взад и вперед?

Я видел, что есть альтернативный синтаксический анализатор, который обрабатывает ByteString: attoparsec, но я бы предпочел придерживаться Parsec, так как я просто научился его использовать.

+3

'attoparsec' намного быстрее и имеет API, очень похожий на Parsec, поэтому я действительно рекомендую вам изучить его! –

+4

Я настоятельно рекомендую вам использовать библиотеку 'cassava' для анализа CSV. Это очень быстро (он использует 'attoparsec' внутри), и он очень прост в использовании. –

+0

Я предполагаю, что вы имеете в виду на обратной стороне? После этого вам придется делать свой собственный «пакет», т. Е. 'pack $ string" foobar "' (помня, чтобы думать о кодировании.) Кроме этого, '' строка Parsecs' отлично работает на 'ByteString' на стороне ввода. – Sarah

ответ

5

Я предполагаю, что вы начинаете с чем-то вроде

import Prelude hiding (getContents, putStrLn) 
import Data.ByteString 
import Text.Parsec.ByteString 

Вот что я получил до сих пор. Есть две версии. Оба компилируются. Наверное, и не то, что вы хотите, но они должны помочь в обсуждении и помочь вам прояснить ваш вопрос.

Что-то я заметил по пути:

  • Если вы import Text.Parsec.ByteString то это использует uncons из Data.ByteString.Char8, который, в свою очередь, использует w2c из Data.ByteString.Internal, чтобы преобразовать все прочитать байты Char s. Это позволяет сообщать об ошибках номера строки и столбца Parsec для разумного использования, а также позволяет без проблем использовать string и друзей.

Таким образом, простой вариант синтаксического анализа CSV, который делает именно то, что:

import Prelude hiding (getContents, putStrLn) 
import Data.ByteString (ByteString) 

import qualified Prelude (getContents, putStrLn) 
import qualified Data.ByteString as ByteString (getContents) 

import Text.Parsec 
import Text.Parsec.ByteString 

csvFile :: Parser [[String]] 
csvFile = endBy line eol 
line :: Parser [String] 
line = sepBy cell (char ',') 
cell :: Parser String 
cell = quotedCell <|> many (noneOf ",\n\r") 

quotedCell :: Parser String 
quotedCell = 
    do _ <- char '"' 
     content <- many quotedChar 
     _ <- char '"' <?> "quote at end of cell" 
     return content 

quotedChar :: Parser Char 
quotedChar = 
     noneOf "\"" 
    <|> try (string "\"\"" >> return '"') 

eol :: Parser String 
eol = try (string "\n\r") 
    <|> try (string "\r\n") 
    <|> string "\n" 
    <|> string "\r" 
    <?> "end of line" 

parseCSV :: ByteString -> Either ParseError [[String]] 
parseCSV = parse csvFile "(unknown)" 

main :: IO() 
main = 
    do c <- ByteString.getContents 
     case parse csvFile "(stdin)" c of 
      Left e -> do Prelude.putStrLn "Error parsing input:" 
         print e 
      Right r -> mapM_ print r 

Но это было так тривиально, чтобы получить работу, что я предполагаю, что это не может быть то, что вы хотите. Возможно, вы хотите, чтобы все оставалось ByteString или [Word8] или что-то подобное на всем протяжении? Отсюда моя вторая попытка ниже. Я все еще import ing Text.Parsec.ByteString, что может быть ошибкой, и код безнадежно пронизан конверсиями.

Но, он компилирует и имеет полные аннотации типов, и, следовательно, должны сделать звук отправной точкой.

import Prelude hiding (getContents, putStrLn) 
import Data.ByteString (ByteString) 
import Control.Monad (liftM) 

import qualified Prelude (getContents, putStrLn) 
import qualified Data.ByteString as ByteString (pack, getContents) 
import qualified Data.ByteString.Char8 as Char8 (pack) 

import Data.Word (Word8) 
import Data.ByteString.Internal (c2w) 

import Text.Parsec ((<|>), (<?>), parse, try, endBy, sepBy, many) 
import Text.Parsec.ByteString 
import Text.Parsec.Prim (tokens, tokenPrim) 
import Text.Parsec.Pos (updatePosChar, updatePosString) 
import Text.Parsec.Error (ParseError) 

csvFile :: Parser [[ByteString]] 
csvFile = endBy line eol 
line :: Parser [ByteString] 
line = sepBy cell (char ',') 
cell :: Parser ByteString 
cell = quotedCell <|> liftM ByteString.pack (many (noneOf ",\n\r")) 

quotedCell :: Parser ByteString 
quotedCell = 
    do _ <- char '"' 
     content <- many quotedChar 
     _ <- char '"' <?> "quote at end of cell" 
     return (ByteString.pack content) 

quotedChar :: Parser Word8 
quotedChar = 
     noneOf "\"" 
    <|> try (string "\"\"" >> return (c2w '"')) 

eol :: Parser ByteString 
eol = try (string "\n\r") 
    <|> try (string "\r\n") 
    <|> string "\n" 
    <|> string "\r" 
    <?> "end of line" 

parseCSV :: ByteString -> Either ParseError [[ByteString]] 
parseCSV = parse csvFile "(unknown)" 

main :: IO() 
main = 
    do c <- ByteString.getContents 
     case parse csvFile "(stdin)" c of 
      Left e -> do Prelude.putStrLn "Error parsing input:" 
         print e 
      Right r -> mapM_ print r 

-- replacements for some of the functions in the Parsec library 

noneOf :: String -> Parser Word8 
noneOf cs = satisfy (\b -> b `notElem` [c2w c | c <- cs]) 

char :: Char -> Parser Word8 
char c  = byte (c2w c) 

byte :: Word8 -> Parser Word8 
byte c  = satisfy (==c) <?> show [c] 

satisfy :: (Word8 -> Bool) -> Parser Word8 
satisfy f = tokenPrim (\c -> show [c]) 
         (\pos c _cs -> updatePosChar pos c) 
         (\c -> if f (c2w c) then Just (c2w c) else Nothing) 

string :: String -> Parser ByteString 
string s = liftM Char8.pack (tokens show updatePosString s) 

Возможно, ваша забота, эффективность-накрест, должны быть эти две ByteString.pack инструкции, в определениях cell и quotedCell. Вы можете попытаться заменить модуль Text.Parsec.ByteString так, чтобы вместо того, чтобы «сделать строгий ByteStrings экземпляр Stream с типом токена Char», вы делаете ByteStrings экземпляром Stream с типом токена Word8, но это не поможет вам с эффективность, это просто даст вам головную боль, пытающуюся перекомпоновать все функции sourcePos, чтобы отслеживать вашу позицию на входе для сообщений об ошибках.

Нет, способ сделать его более эффективным было бы изменить типы char, quotedChar и string к Parser [Word8] и типы line и csvFile к Parser [[Word8]] и Parser [[[Word8]]] соответственно. Вы можете даже изменить тип eol на Parser(). Необходимые изменения будут выглядеть примерно так:

cell :: Parser [Word8] 
cell = quotedCell <|> many (noneOf ",\n\r") 

quotedCell :: Parser [Word8] 
quotedCell = 
    do _ <- char '"' 
     content <- many quotedChar 
     _ <- char '"' <?> "quote at end of cell" 
     return content 

string :: String -> Parser [Word8] 
string s = [c2w c | c <- (tokens show updatePosString s)] 

Вам не нужно беспокоиться обо всех вызовов c2w с точки зрения эффективности обеспокоен, потому что они ничего не стоят.

Если это не ответит на ваш вопрос, скажите, что бы это было.

+1

Wow! Ваш ответ очень тщательный. Действительно, это будет сделано. –

+0

Beetle, ваш ответ импортирует так много разных модулей 'ByteString', я очень смущен тем, какие из них используются. Не могли бы вы прояснить это больше? – tolgap

+0

@tolgap: Во-первых, вам нужно взглянуть на [Глава 16 книги] (http://book.realworldhaskell.org/read/using-parsec.html#csv). Все 'import' s необходимы, поэтому я использую их все. Моя цель здесь заключалась в том, чтобы создать что-то, что скомпилировалось бы, так что, если бы автор расстроился, он мог посмотреть на различия между своим кодом и моим. Объяснения приведены в [документации для различных модулей] (https://www.haskell.org/hoogle/). Я не могу вспомнить какие-либо конкретные детали, когда я написал этот ответ два года назад. – Beetle

0

Я так не верю. Вам нужно будет создать его самостоятельно, используя tokens. Хотя документация для него немного ... несуществующая, первые два аргумента - это функция, используемая для отображения ожидаемых токенов в сообщении об ошибке и функции для обновления исходной позиции, которая будет напечатана с ошибками.

+0

Точно, функция «токенов» не документирована, поэтому я не пытался ее использовать напрямую. Тем не менее, я посмотрю на это в другой раз. –

+0

@ ArielD.MoyaSequeira Я просто сказал вам, что документация скажет, если бы там было ... –

Смежные вопросы