我在解决如何使用Haskell的 Text.Parsec.Indent 软件包提供的 indents 模块中的任何功能方面遇到麻烦,这是Parsec的一种附加组件。

所有这些功能做什么?如何使用它们?

我可以理解withBlock的简短Haddock描述,并且找到了有关如何使用withBlockrunIndentIndentParser类型hereherehere的示例。我还可以了解四个解析器 indentBrackets and friends的文档。但是很多事情仍然让我感到困惑。

特别是:

  • withBlock f a p
    do aa <- a
       pp <- block p
       return f aa pp
    

    同样,withBlock' a pdo {a; block p}
  • 有什么区别
  • indented and friends函数家族中,“引用水平”是什么?也就是说,什么是“引用”?
  • 再次,通过indented和 friend 功能,如何使用它们?除了withPos,它们似乎没有参数,并且都是IParser ()类型(IParser定义为thisthis),所以我猜测它们所能做的就是是否产生错误,并且应该出现在do块,但我无法弄清楚细节。

    我至少在source code中找到了有关withPos用法的一些示例,因此,如果我凝视了足够长的时间,我大概可以弄清楚。
  • <+/> 附带了一个有用的描述:“<+/>是缩进敏感的解析器,什么ap是monads”,如果您想花费几个 session 尝试将头围绕ap,然后弄清楚它与解析器的相似之处,则非常有用。然后引用<+/>定义另一个three combinators,从而使整个组都不适合新来者。

    我需要使用这些吗?我可以不理会它们而改用do吗?
  • 来自Parsec的普通 lexeme 组合器和 whiteSpace 解析器将在多 token 结构中间愉快地使用换行符而不会提示。但是在缩进风格的语言中,有时您希望停止解析词法构造,或者如果一行被折断并且下一行的缩进少于应有的范围,则抛出错误。我该如何在Parsec中执行此操作?
  • 在我尝试解析的language中,理想情况下,何时允许词法结构继续到下一行的规则应取决于在第一行的末尾或下一行的开始出现的标记。有没有一种简单的方法可以在Parsec中实现这一目标? (如果很困难,那么这时我不需要担心自己。)
  • 最佳答案

    所以,第一个提示是看IndentParser

    type IndentParser s u a = ParsecT s u (State SourcePos) a
    

    IE。这是一个ParsecT,它特别关注 SourcePos (一个抽象容器),该容器可用于访问(除其他外)当前列号。因此,它可能会将当前的“缩进级别”存储在SourcePos中。那是我对“引用水平”的含义的最初猜测。

    简而言之,indents为您提供了一种新的Parsec,它与上下文有关,尤其是对当前缩进敏感。我会不按顺序回答您的问题。

    (2)“引用级别”是在此缩进级别开始的当前解析器上下文状态中引用的“信仰”。更清楚地说,让我在(3)上给出一些测试用例。

    (3)为了开始尝试这些功能,我们将构建一个小的测试运行器。它将使用我们提供的字符串运行解析器,然后使用我们要修改的State解开内部initialPos部分。在代码中
    import Text.Parsec
    import Text.Parsec.Pos
    import Text.Parsec.Indent
    import Control.Monad.State
    
    testParse :: (SourcePos -> SourcePos)
              -> IndentParser String () a
              -> String -> Either ParseError a
    testParse f p src = fst $ flip runState (f $ initialPos "") $ runParserT p () "" src
    

    (请注意,这几乎是runIndent,除了我给后门修改initialPos。)

    现在我们来看看indented。通过检查源,我可以知道它做了两件事。首先,如果当前fail列号小于或等于存储在SourcePos中的SourcePos中存储的“引用级别”,它将为State。其次,它有点神秘地将State SourcePos的行计数器(不是列计数器)更新为当前。

    据我了解,只有第一个行为才是重要的。我们可以在这里看到区别。
    >>> testParse id indented ""
    Left (line 1, column 1): not indented
    
    >>> testParse id (spaces >> indented) "   "
    Right ()
    
    >>> testParse id (many (char 'x') >> indented) "xxxx"
    Right ()
    

    因此,为了使indented成功,我们需要消耗足够的空格(或其他任何东西!),以将我们的列位置压出“引用”列位置。否则,它会说“不缩进”。接下来的三个函数存在类似的行为:除非当前位置和引用位置在同一行上,否则same失败;如果当前列严格小于引用列,除非它们在同一行上,否则sameOrIndented失败;除非当前列和引用位置在同一行上,否则checkIndent失败。当前列和引用列匹配。
    withPos略有不同。它不仅是IndentParser,而且是IndentParser -combinator,它将输入的IndentParser转换为认为“引用列”(即SourcePos中的State)与我们称为withPos的位置完全相同的代码。

    顺便说一句,这给了我们另一个提示。它使我们知道我们有权更改引用列。

    (1)现在,让我们看一下使用新的较低级别的引用列运算符blockwithBlock的工作方式。 withBlock是根据block实现的,因此我们从block开始。
    -- simplified from the actual source
    block p = withPos $ many1 (checkIndent >> p)
    

    因此,block将“引用列”重置为当前列,然后从p消耗至少1个解析,只要每个缩进都与新设置的“引用列”相同。现在我们来看看withBlock
    withBlock f a p = withPos $ do
      r1 <- a
      r2 <- option [] (indented >> block p)
      return (f r1 r2)
    

    因此,它将“引用列”重置为当前列,解析单个a解析,尝试解析indentedblock p,然后使用f合并结果。您的实现几乎是正确的,除了您需要使用withPos选择正确的“引用列”。

    然后,一旦有了withBlockwithBlock' = withBlock (\_ bs -> bs)

    (5)因此,indented和friends正是执行此操作的工具:如果解析不正确地相对于withPos选择的“引用位置”缩进,它们将导致解析立即失败。

    (4)是的,在您学习如何在基础Applicative中使用 Parsec style解析之前,请不要担心这些家伙。它通常是一种更简洁,更快,更简单的解析方法。有时它们甚至更强大,但是如果您了解Monad,那么它们几乎总是完全等效的。

    (6)这就是症结所在。到目前为止提到的工具只有在您可以使用withPos描述您想要的缩进时,才可以缩进失败。很快,我认为不可能根据其他解析的成功或失败来指定withPos,因此您必须更进一步。幸运的是,使IndentParser起作用的机制是显而易见的-它只是一个内部State monad,其中包含SourcePos。您可以使用lift :: MonadTrans t => m a -> t m a操纵此内部状态,并根据需要设置“引用列”。

    干杯!

    09-10 16:19