我目前正在尝试使用 Real World Haskell 中提供的Full CSV Parser。我试图修改代码以使用ByteString
代替String
,但有一个string
组合器可以与String
一起使用。
Parsec组合器是否与string
类似,可以与ByteString
一起使用,而无需来回转换?
我见过有一个替代解析器可以处理ByteString
:attoparsec
,但我更愿意坚持使用Parsec,因为我只是在学习如何使用它。
答案 0 :(得分:5)
我假设你是从
开始的import Prelude hiding (getContents, putStrLn)
import Data.ByteString
import Text.Parsec.ByteString
这是我到目前为止所得到的。有两个版本。都编译。可能两者都不是你想要的,但它们应该有助于讨论并帮助你澄清你的问题。
我一路上注意到的事情:
import Text.Parsec.ByteString
那么它使用来自Data.ByteString.Char8的uncons
,后者又使用Data.ByteString.Internal中的w2c
将所有读取的字节转换为{{1} }}秒。这使得Parsec的行号和列号错误报告能够合理地工作,并且还使您可以毫无问题地使用Char
和朋友。因此,简单版本的CSV解析器就是这样做的:
string
但是这种工作是如此微不足道,我认为它不可能是你想要的。也许您希望一切都保持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
,这可能是一个错误,代码无可救药地充斥着转化。
但是 ,它会编译并具有完整的类型注释,因此应该成为一个合理的起点。
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
说明。您可能会尝试替换Text.Parsec.ByteString模块,以便“使用quotedCell
令牌类型使Stream
的实例成为严格的ByteStrings”,而是使ByteStrings成为Char
的实例。 Stream
令牌类型,但这不会帮助您提高效率,只会让您头疼,尝试重新实现所有sourcePos函数以跟踪您在错误消息输入中的位置。
不,提高效率的方法是将Word8
,char
和quotedChar
的类型更改为string
以及{{1}的类型}和Parser [Word8]
分别为line
和csvFile
。您甚至可以将Parser [[Word8]]
的类型更改为Parser [[[Word8]]]
。必要的变化看起来像这样:
eol
就效率而言,您无需担心Parser ()
的所有电话,因为它们不需要任何费用。
如果这不能回答你的问题,请说明会是什么。
答案 1 :(得分:0)
我不相信。您需要使用tokens
自己创建一个。虽然它的文档有点......不存在,但前两个参数是用于在错误消息中显示预期标记的函数,以及用于更新将在错误中打印的源位置的函数。