我如何使用与另一个 Parsec 解析器具有不同流类型的 Parsec 解析器?

How can I use a Parsec parser which has a different stream type than another Parsec parser?

我有一个用 Text 作为流类型编写的解析器,而默认情况下 Text.Parsec.String 模块使用 String 否则。

如何在 Parsec String b c 的上下文中使用自定义编写的解析器 (Parsec Text b c)?

基本上我似乎需要这样一个功能:

f :: Parsec Text b c -> Parsec String b c
f = undefined

虽然听起来可行,但做起来似乎相当复杂。

这很可怕,但相对简单。思路是利用low-level函数runParsecTmkPT对解析器进行解构和重构,用适配器括起来修改传入和传出状态的流类型:

import Text.Parsec
import Data.Text (Text)
import qualified Data.Text as Text

stringParser :: (Monad m) => ParsecT Text u m a -> ParsecT String u m a
stringParser p = mkPT $ \st -> (fmap . fmap . fmap) outReply $ runParsecT p (inState st)
  where inState :: State String u -> State Text u
        inState  (State i pos u) = State (Text.pack i) pos u
        outReply :: Reply Text u a -> Reply String u a
        outReply (Ok a (State i pos u) e) = Ok a (State (Text.unpack i) pos u) e
        outReply (Error e) = Error e

它似乎工作正常:

myTextParser :: Parsec Text () String
myTextParser = (:) <$> oneOf "abc" <*> many letter

myStringParser :: Parsec String () (String, String)
myStringParser = (,) <$> p <* spaces <*> p
  where p = stringParser myTextParser

main = do
  print =<< parseTest myStringParser "avocado butter"
  print =<< parseTest myStringParser "apple error"

给予:

λ> main
("avocado","butter")
()
parse error at (line 1, column 7):
unexpected "e"
expecting space
()

但是,这里可能存在一些严重的性能问题,除非它被用于小型玩具解析器。 pack 调用将获取整个传入流并将其转换为 Text 值。如果您从惰性 String 解析(例如,从惰性 I/O 调用),第一次使用转换后的解析器会将整个字符串作为 Text 读入内存并将其抽取作为 String 退出;对同一个解析器的进一步调用将 re-pack 剩余的流每次都为 Text。切换到惰性 Text 并没有多大帮助,因为 pack 仍然会将整个输入打包到“惰性”Text 值中。

您需要 运行 一些 tests/benchmarks 来查看您的应用程序是否可以接受这种性能损失。一般来说,重写 Text 解析器(或者看看它是否会用抽象流类型编译)将是一个更好的方法。

完整代码示例:

{-# OPTIONS_GHC -Wall #-}

import Text.Parsec
import Data.Text (Text)
import qualified Data.Text as Text

stringParser :: (Monad m) => ParsecT Text u m a -> ParsecT String u m a
stringParser p = mkPT $ \st -> (fmap . fmap . fmap) outReply $ runParsecT p (inState st)
  where inState :: State String u -> State Text u
        inState  (State i pos u) = State (Text.pack i) pos u
        outReply :: Reply Text u a -> Reply String u a
        outReply (Ok a (State i pos u) e) = Ok a (State (Text.unpack i) pos u) e
        outReply (Error e) = Error e

myTextParser :: Parsec Text () String
myTextParser = (:) <$> oneOf "abc" <*> many letter

myStringParser :: Parsec String () (String, String)
myStringParser = (,) <$> p <* spaces <*> p
  where p = stringParser myTextParser

main = do
  print =<< parseTest myStringParser "avocado butter"
  print =<< parseTest myStringParser "apple error"