17

Haskell Parsec的indents包提供了一种解析缩进式语言(如 Haskell 和 Python)的方法。它重新定义了Parser类型,那么如何使用 ParsecText.Parsec.Token模块导出的普通Parser类型的令牌解析器函数呢?

背景

Parsec 带有大量的模块。它们中的大多数导出了一堆有用的解析器(例如newlinefrom Text.Parsec.Char,它解析一个换行符)或解析器组合器(例如 count n pfrom Text.Parsec.Combinator,它运行解析器pn次)

但是,该模块Text.Parsec.Token希望导出由用户参数化的具有被解析语言特征的函数,例如,该函数将在解析“{”之后和解析“}”之前braces p运行解析器p ,忽略诸如注释之类的东西,其语法取决于您的语言。

实现这一点的方法Text.Parsec.Token是它导出一个makeTokenParser你调用的函数,给它你特定语言的参数(比如评论的样子),它返回一个包含所有函数的记录Text.Parsec.Token,适应你的语言指定的。

当然,在缩进风格的语言中,这些需要进一步调整(也许?这是我不确定的地方——我稍后会解释)所以我注意到(可能已经过时的)IndentParser 包提供了一个模块Text.ParserCombinators.Parsec.IndentParser.Token它看起来是Text.Parsec.Token.

我应该在某些时候提到所有 Parsec 解析器都是一元函数,所以它们对状态做了神奇的事情,以便错误消息可以说明错误出现在源文件的哪一行和哪一列

我的问题

由于几个小原因,在我看来indents或多或少是 IndentParser 的当前版本,但是它不提供看起来像的模块,Text.ParserCombinators.Parsec.IndentParser.Token它只提供Text.Parsec.Indent,所以我想知道如何获得所有令牌解析器Text.Parsec.Token(比如reserved "something"解析保留关键字“某物”,或者像braces我之前提到的那样)。

在我看来,(新的)Text.Parsec.Indent通过某种单子状态魔法来计算源代码的列位是什么,因此它不需要像whiteSpacefrom那样修改令牌解析器Text.Parsec.Token,这可能就是它的原因不提供替换模块。但是我遇到了类型问题。

你看,没有Text.Parsec.Indent,我所有的解析器都是类型Parser Something,其中 Something 是返回类型,并且Parser是 Text.Parsec.String 中定义的类型别名

type Parser = Parsec String ()

但是我使用自己的定义Text.Parsec.Indent而不是导入Text.Parsec.String

type Parser a = IndentParser String () a

这使我的所有类型的解析器,其中 IndentParser 在 Text.Parsec.Indent 中定义。但是我从中获得的令牌解析器类型错误。IndentParser String () SomethingmakeTokenParserText.Parsec.Token

如果现在这没有多大意义,那是因为我有点迷路了。类型问题在这里讨论了一下


我得到的错误是我尝试用另一个定义替换Parser上面的一个定义,但是当我尝试使用其中一个令牌解析器时Text.Parsec.Token,我得到了编译错误

Couldn't match expected type `Control.Monad.Trans.State.Lazy.State
                                Text.Parsec.Pos.SourcePos'
            with actual type `Data.Functor.Identity.Identity'
Expected type: P.GenTokenParser
                 String
                 ()
                 (Control.Monad.Trans.State.Lazy.State Text.Parsec.Pos.SourcePos)
  Actual type: P.TokenParser ()

链接

遗憾的是,上述示例都没有使用像 Text.Parsec.Token 中那样的令牌解析器。

4

1 回答 1

14

你想做什么?

听起来您希望在任何地方都将解析器定义为类型

Parser Something

(其中SomethingParser是返回类型)并通过隐藏和重新定义通常从Text.Parsec.String或类似导入的类型来使其工作。您仍然需要导入一些Text.Parsec.String, 以使 Stream 成为 monad 的实例;使用以下命令执行此操作:

import Text.Parsec.String ()

你的定义Parser是正确的。或者等效地(对于那些在评论中关注聊天的人)您可以使用

import Control.Monad.State
import Text.Parsec.Pos (SourcePos)

type Parser = ParsecT String () (State SourcePos)

并可能取消import Text.Parsec.Indent (IndentParser)出现此定义的文件中的 。

错误,墙上的错误

您的问题是您正在查看编译器错误消息的错误部分。你专注于

Couldn't match expected type `State SourcePos' with actual type `Identity'

当你应该专注于

Expected type: P.GenTokenParser ...
  Actual type: P.TokenParser ...

它编译!

你从哪里“导入”解析器Text.Parsec.Token,你实际上做什么,当然(正如你简要提到的)首先定义一个记录你的语言参数,然后将它传递给函数makeTokenParser,它返回一个包含令牌解析器的记录。

因此,您必须有一些看起来像这样的行:

import qualified Text.Parsec.Token as P

beetleDef :: P.LanguageDef st
beetleDef =
    haskellStyle {
        parameters, parameters etc.
        }

lexer :: P.TokenParser ()
lexer = P.makeTokenParser beetleDef

...但 aP.LanguageDef st只是 a GenLanguageDef String st Identity,而 aP.TokenParser ()真的是 a GenTokenParser String () Identity

您必须将类型声明更改为以下内容:

import Control.Monad.State
import Text.Parsec.Pos (SourcePos)
import qualified Text.Parsec.Token as P

beetleDef :: P.GenLanguageDef String st (State SourcePos)
beetleDef =
    haskellStyle {
        parameters, parameters etc.
        }

lexer :: P.GenTokenParser String () (State SourcePos)
lexer = P.makeTokenParser beetleDef

……就是这样!这将允许您的“导入”令牌解析器具有 type ,而不是(这是 的别名)并且您的代码现在应该编译。ParsecT String () (State SourcePos) SomethingParsec String () SomethingParsecT String () Identity Something

(为了最大的普遍性,我假设您可能Parser在一个文件中定义类型,该文件与您定义实际解析器函数的文件分开并由该文件导入。因此有两个重复的import语句。)

谢谢

非常感谢Daniel Fischer帮助我解决这个问题。

于 2013-03-11T16:19:06.297 回答