问题描述
是否有一些“简单”的方法(例如我在 Attoparsec 或其他库中缺少的东西)来将定义的 Attoparsec 解析器从 ByteString 解析为从 Text 解析的解析器?
例如我有:
import Data.Attoparsec.ByteString.Char8
myTypeByteStringParser :: Parser MyType
有什么方法可以转化成:
import Data.Attoparsec.Text
myTypeTextParser :: Parser MyType
它确实看起来像 contramap
(来自 hoogling 类型签名)但可能无法为 Parser 定义逆变器?
解决方法
我不确定这在一般情况下是否可行。 Parser
中定义的 Attoparsec
类型看起来不太适合修改输入类型。因此,如果您想将 Text
解析器与 ByteString
解析器结合使用,恐怕您的运气不佳。
也就是说,如果您希望能够在某个输入 ByteString
上运行 Text
解析器,您可以通过首先转换 Text
输入来解决这个问题变成 ByteString
。例如:
import Data.Text.Encoding
import Data.Attoparsec.ByteString.Char8
-- parse :: Parser a -> ByteString -> Result a
-- this is given by Attoparsec
parseText :: Parser a -> Text -> Result a
parseText p = parse p . encodeUtf8
同样,您可以使用 Text
(或根据需要使用不同的编码器/解码器)将 ByteString
解析器转换为 decodeUtf8
解析器。
这通常是可能的,您不需要分叉 attoparsec
。 attoparsec
不体谅地暴露了它的内部结构,但不要让它阻止我们:
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE QuasiQuotes #-}
module Parsers where
import qualified Data.Attoparsec.ByteString as AB
import qualified Data.Attoparsec.Internal.Types as AIT
import qualified Data.Attoparsec.Text as AT
import Data.ByteString (ByteString)
import qualified Data.ByteString.Internal as BI
import Data.Text (Text)
import Data.Text.Encoding (decodeUtf8,encodeUtf8)
import qualified Data.Text.Internal as TI
import Unsafe.TrueName
bsToTextState :: AIT.State ByteString -> AIT.State Text
bsToTextState = bufferText . decodeUtf8 . unbufferBS where
unbufferBS :: AIT.State ByteString -> ByteString
unbufferBS [truename| ''AIT.State
Data.Attoparsec.ByteString.Buffer.Buffer
Buf | fp off len _ _ |] = BI.PS fp off len
bufferText :: Text -> AIT.State Text
bufferText (TI.Text arr off len) = [truename| ''AIT.State
Data.Attoparsec.Text.Buffer.Buffer
Buf |] arr off len len 0
textToBSState :: AIT.State Text -> AIT.State ByteString
textToBSState = bufferBS . encodeUtf8 . unbufferText where
unbufferText :: AIT.State Text -> Text
unbufferText [truename| ''AIT.State
Data.Attoparsec.Text.Buffer.Buffer
Buf | arr off len _ _ |] = TI.Text arr off len
bufferBS :: ByteString -> AIT.State ByteString
bufferBS (BI.PS fp off len) = [truename| ''AIT.State
Data.Attoparsec.ByteString.Buffer.Buffer
Buf |] fp off len len 0
mapIResult :: (i -> j) -> (j -> i) -> AIT.IResult i a -> AIT.IResult j a
mapIResult f g = go where
go = \case
AIT.Fail i ctx msg -> AIT.Fail (f i) ctx msg
AIT.Partial k -> AIT.Partial (go . k . g)
AIT.Done i r -> AIT.Done (f i) r
mapFailure :: (i -> j) -> (j -> i) -> (AIT.State j -> AIT.State i) ->
AIT.Failure i (AIT.State i) r -> AIT.Failure j (AIT.State j) r
mapFailure f g h k st p m ctx msg = mapIResult f g $ k (h st) p m ctx msg
mapSuccess :: (i -> j) -> (j -> i) -> (AIT.State j -> AIT.State i) ->
AIT.Success i (AIT.State i) a r -> AIT.Success j (AIT.State j) a r
mapSuccess f g h k st p m a = mapIResult f g $ k (h st) p m a
bsToTextParser :: AB.Parser a -> AT.Parser a
bsToTextParser (AIT.Parser bsP) = AIT.Parser textP where
textP st p m f s = mapIResult decodeUtf8 encodeUtf8 $ bsP
(textToBSState st) p m
(mapFailure encodeUtf8 decodeUtf8 bsToTextState f)
(mapSuccess encodeUtf8 decodeUtf8 bsToTextState s)
textToBSParser :: AT.Parser a -> AB.Parser a
textToBSParser (AIT.Parser textP) = AIT.Parser bsP where
bsP st p m f s = mapIResult encodeUtf8 decodeUtf8 $ textP
(bsToTextState st) p m
(mapFailure decodeUtf8 encodeUtf8 textToBSState f)
(mapSuccess decodeUtf8 encodeUtf8 textToBSState s)
{,un}buffer{BS,Text}
改编自相应的 internal modules Data.Attoparsec.{ByteString,Text}.Buffer
。
不过,这是我更新 true-name
以使用更新的 GHC 的一个很好的借口。根据您的最新情况,您可能需要 WIP from GitHub。
对于性能来说可能并不糟糕,只要你记住每次使用 textToBSParser
时,整个输入都会通过 encodeUtf8
通过 decodeUtf8
返回,对于 bsToTextParser
反之亦然。如果您只在顶级转换 Parser
一次,它应该与其他答案建议的简单转换输入没有太大区别。
PS:我还没有测试过
$ ghci -XOverloadedStrings parsers.hs
*Parsers> textToBSParser AT.scientific `AB.parseTest` "123 "
Done " " 123.0
PPS:对于您自己的解析器,您可以利用 OverloadedStrings
并使用 p :: IsString s => AIT.Parser s a
编译指示编写 {-# SPECIALISE p :: AT.Parser a #-}
。我还没有研究过这个想法的可行性。