如何使用最新版本的Parsec.Indent库?

How to use the latest version of the Parsec.Indent library?

这个问题似乎是 this question, however either Parsec or the Indent library has changed since 2012 and none of the old examples I have found for the indent library compile with the latest versions 的重复。

我想为一种编程语言制作一个解析器,其中缩进是语法的一部分(用于指示范围),为了实现这一点,我想使用 Text.Parsec.Indent 库,但我我不知道如何使用它。我很清楚必须制作一些 modifications/custom 解析器类型,但我对 State monad 的有限知识和对 parsec 的表面理解似乎还不够。

假设您想为一个简单的整数列表创建一个解析器,如下所示。如何实现这一目标?

mylist
    fstitem
    snditem

我尝试基于互联网上流传的一些旧示例创建一个简单的解析器,看起来像这样,但它显然会产生一些类型错误:

import Control.Monad.State

import Text.Parsec hiding (State)
import Text.Parsec.Indent
import Text.Parsec.Pos

type IParser a = ParsecT String () (State SourcePos) a

parseInt :: IParser Integer
parseInt = read <$> many1 digit

parseIndentedInt :: IParser Integer
parseIndentedInt = indented *> parseInt

特别是这些:

Frontend/Parser.hs:14:20: error:
    • Couldn't match type ‘Control.Monad.Trans.Reader.ReaderT
                             Text.Parsec.Indent.Internal.Indentation m0’
                     with ‘StateT SourcePos Data.Functor.Identity.Identity’
      Expected type: IParser Integer
        Actual type: ParsecT String () (IndentT m0) Integer
    • In the expression: indented *> parseInt
      In an equation for ‘parseIndentedInt’:
          parseIndentedInt = indented *> parseInt
   |
14 | parseIndentedInt = indented *> parseInt
   |                    ^^^^^^^^^^^^^^^^^^^^

Frontend/Parser.hs:14:32: error:
    • Couldn't match type ‘StateT
                             SourcePos Data.Functor.Identity.Identity’
                     with ‘Control.Monad.Trans.Reader.ReaderT
                             Text.Parsec.Indent.Internal.Indentation m0’
      Expected type: ParsecT String () (IndentT m0) Integer
        Actual type: IParser Integer
    • In the second argument of ‘(*>)’, namely ‘parseInt’
      In the expression: indented *> parseInt
      In an equation for ‘parseIndentedInt’:
          parseIndentedInt = indented *> parseInt
   |
14 | parseIndentedInt = indented *> parseInt
   |                                ^^^^^^^^
Failed, no modules loaded.

好的,在深入研究源代码并查看缩进 GitHub 存储库中的测试后,我设法创建了一个工作示例。

下面的代码可以解析一个简单的缩进列表:

import Text.Parsec        as Parsec
import Text.Parsec.Indent as Indent

data ExampleList = ExampleList String [ExampleList] 
                 deriving (Eq, Show)

plistItem :: Indent.IndentParser String () String
plistItem = Parsec.many1 Parsec.lower <* Parsec.spaces

pList :: Indent.IndentParser String () ExampleList
pList = Indent.withPos (ExampleList <$> plistItem <*> Parsec.many (Indent.indented *> pList))

useParser :: Indent.IndentParser String () a -> String -> a
useParser p src = helper res
                where res = Indent.runIndent $ Parsec.runParserT (p <* Parsec.eof) () "<test>" src
                      helper (Left err) = error "Parse error"
                      helper (Right ok) = ok

用法示例:

*Main> useParser pList "mylist\n\tfstitem\n\tsnditem"
ExampleList "mylist" [ExampleList "fstitem" [],ExampleList "snditem" []]

请注意,useParser 函数执行了一些操作,实际上从 Either monad 中获取结果,并将文件结尾解析器放在提供的解析器后面。根据您的应用程序,您可能想要更改此设置。

另外,类型签名可以用这样的东西来缩短:

type IParser a = Indent.IndentParser String () a

plistItem :: IParser String
pList :: IParser ExampleList
useParser :: IParser a -> String -> a