Haskell / Parsec:你如何使用Text.Parsec.Indent中的函数?

时间:2013-03-21 13:33:20

标签: parsing haskell indentation parsec

我无法解决如何使用Text.Parsec.Indent包为Haskell提供的indents模块中的任何函数,这是Parsec的一种附加组件。

所有这些功能的作用是什么?它们如何使用?

我可以理解withBlock的简要Haddock描述,并且我找到了如何使用withBlockrunIndentIndentParser类型{{3}的示例},herehere。我也可以理解四个解析器here的文档。但很多事情仍让我感到困惑。

特别是:

  1. withBlock f a p

    之间有什么区别
    do aa <- a
       pp <- block p
       return f aa pp
    

    同样,withBlock' a pdo {a; block p}

  2. 之间有什么区别?
  3. 在函数族indentBrackets and friends中,“引用的级别”是什么?那就是什么是'参考'?

  4. 再次,使用函数indented和朋友,它们如何被使用?除了withPos之外,看起来它们没有参数,并且都是IParser ()类型(IParser定义为indented and friendsthis)所以我猜所有他们可以做的是产生错误与否,它们应该出现在do块中,但我无法弄清楚细节。

    我至少在this中找到了关于withPos用法的一些例子,所以如果我盯着它看足够久,我可能会想出来。

  5. source code附带了有用的说明“<+/>是缩进敏感的解析器ap对monads是什么”,如果您想要花几个会话试图换行,那就太棒了你的头围绕ap,然后弄清楚它是如何类似于解析器的。然后参考<+/>定义其他<+/>,使整个组无法接近新人。

    我需要使用这些吗?我可以忽略它们并改为使用do吗?

  6. 来自Parsec的普通three combinators组合器和lexeme解析器将很乐意在多令牌构造中间使用换行符而不会抱怨。但是在缩进式语言中,有时你想要停止解析一个词法结构,或者如果一行被破坏而下一行的缩进量小于应该的那么就会抛出一个错误。我如何在Parsec中这样做?

  7. 在我试图解析的whiteSpace中,理想情况下,允许词法结构继续到下一行的规则应该取决于第一行末尾出现的令牌或后续行的开头。在Parsec有一个简单的方法来实现这一目标吗? (如果困难的话,那么我现在不需要关注它。)

1 个答案:

答案 0 :(得分:11)

所以,第一个提示是看看IndentParser

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

即。这是ParsecTSourcePos进行额外密切关注,这是一个抽象容器,可用于访问当前号码等。因此,它可能会在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列号小于或等于{{1}中存储的“参考级别”,则SourcePosSourcePos } 存储在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 - 组合器 - 它将输入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解析,尝试解析indented blockp,然后使用f组合结果。您的实现几乎正确,但您需要使用withPos来选择正确的“参考列”。

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

(5)所以,indented和朋友正是这样做的工具:如果它被{{1}选择的“参考位置”错误地缩进,它们将导致解析立即失败}。

(4)是的,在您学习如何在基础withPos中使用Applicative style解析之前,请不要担心这些人。它通常是一种更清晰,更快速,更简单的指定解析方式。有时它们会更强大,但如果你理解Parsec,那么它们几乎总是完全等同。

(6)这就是症结所在。到目前为止提到的工具只能使用Monad来描述缩进失败。很快,我认为根据其他解析的成功或失败来指定withPos是不可能的...所以你必须更深入地进行另一个层次。幸运的是,使withPos工作的机制显而易见 - 它只是一个包含IndentParser的内部State monad。您可以使用SourcePos来操纵此内部状态,并根据需要设置“参考列”。

干杯!