我目前正在尝试使用Real World Haskell中提供的 Full CSV Parser 。为了我尝试修改代码以使用ByteString
而不是String
,但是有一个string
只适用于String
.
是否有类似于与 一起使用的 Parsec 组合器string
,ByteString
而无需来回进行转换?
我已经看到有一个替代解析器可以处理ByteString
: attoparsec
,但我更愿意坚持使用 Parsec,因为我只是在学习如何使用它。
我目前正在尝试使用Real World Haskell中提供的 Full CSV Parser 。为了我尝试修改代码以使用ByteString
而不是String
,但是有一个string
只适用于String
.
是否有类似于与 一起使用的 Parsec 组合器string
,ByteString
而无需来回进行转换?
我已经看到有一个替代解析器可以处理ByteString
: attoparsec
,但我更愿意坚持使用 Parsec,因为我只是在学习如何使用它。
我假设你从类似的东西开始
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]
或类似?因此,我在下面进行了第二次尝试。I am still 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
的定义中的那两条指令。您可能会尝试替换 Text.Parsec.ByteString 模块,以便“让严格的 ByteStrings 成为具有令牌类型的实例”,而不是让 ByteStrings 成为具有令牌类型的实例,但这不会帮助您提高效率,它会只是让您头疼尝试重新实现所有 sourcePos 函数以跟踪您在输入中的位置以获取错误消息。cell
quotedCell
Stream
Char
Stream
Word8
不,提高效率的方法是分别更改char
,和to的类型以及quotedChar
and string
to和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
,因为它们没有任何成本。
如果这不能回答你的问题,请说什么。
我不相信。您需要自己使用tokens
. 虽然它的文档有点......不存在,但前两个参数是一个用于在错误消息中显示预期标记的函数和一个用于更新将在错误中打印的源位置的函数。