在秒差距匹配字节串(Matching bytestrings in Parsec)

2019-08-17 16:36发布

我目前正在使用完全CSV分析器在真实世界哈斯克尔呈现。 为了我试图修改使用的代码ByteString ,而不是String ,但有一个string组合子这只是工作的String

是否有一个秒差距组合子相似的string与工程ByteString ,而无需做转换来回?

我见过有一个处理一个替代解析器ByteStringattoparsec ,但我宁愿坚持使用秒差距,因为我只是在学习如何使用它。

Answer 1:

我假设你开始喜欢的东西

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秒。 这使得秒差距的行和列数错误报告明智地工作,也使您可以使用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荷兰国际集团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指令,在定义cellquotedCell 。 您可以尝试让,而不是“制定严密的字节串的一个实例来代替Text.Parsec.ByteString模块StreamChar标记类型,”你让字节串的实例StreamWord8标记类型,但是这不会帮助你效率,它只是给你一个头痛试图重新实现所有的功能sourcePos保持跟踪你的位置在输入的错误消息。

不,方法,使之更有效地将改变类型的charquotedCharstringParser [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就效率而言,因为他们没有成本。

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



Answer 2:

我不这么认为。 您将需要创建自己使用一个tokens 。 虽然它的文档是有点...不存在的,前两个参数是一个函数的使用来显示期望的令牌的错误消息,并更新将在错误打印的源的位置的函数。



文章来源: Matching bytestrings in Parsec