4

我目前正在尝试使用Real World Haskell中提供的 Full CSV Parser 。为了我尝试修改代码以使用ByteString而不是String,但是有一个string只适用于String.

是否有类似于与 一起使用的 Parsec 组合器stringByteString而无需来回进行转换?

我已经看到有一个替代解析器可以处理ByteString: attoparsec,但我更愿意坚持使用 Parsec,因为我只是在学习如何使用它。

4

2 回答 2

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,将所有读取的字节转换为Chars。这使 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]或类似?因此,我在下面进行了第二次尝试。I am still importing 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的定义中的那两条指令。您可能会尝试替换 Text.Parsec.ByteString 模块,以便“让严格的 ByteStrings 成为具有令牌类型的实例”,而不是让 ByteStrings 成为具有令牌类型的实例,但这不会帮助您提高效率,它会只是让您头疼尝试重新实现所有 sourcePos 函数以跟踪您在输入中的位置以获取错误消息。cellquotedCellStreamCharStreamWord8

不,提高效率的方法是分别更改char,和to的类型以及quotedCharand stringto和Parser [Word8]的类型。您甚至可以将类型更改为。必要的更改如下所示:linecsvFileParser [[Word8]]Parser [[[Word8]]]eolParser ()

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,因为它们没有任何成本。

如果这不能回答你的问题,请说什么。

于 2013-03-22T17:00:44.710 回答
0

我不相信。您需要自己使用tokens. 虽然它的文档有点......不存在,但前两个参数是一个用于在错误消息中显示预期标记的函数和一个用于更新将在错误中打印的源位置的函数。

于 2013-03-16T22:30:00.970 回答