11

我无法弄清楚如何使用Haskell 包Text.Parsec.Indent提供的模块中的任何功能indents,这是 Parsec 的一种附加组件。

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

我可以理解 Haddock 的简短描述withBlock,并且我已经找到了如何使用的示例withBlock,以及此处此处此处runIndentIndentParser类型。我也能看懂这四个解析器和朋友的文档。但是很多事情还是让我很困惑。indentBrackets

尤其:

  1. withBlock f a p和有什么区别

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

    withBlock' a p同样,和之间有什么区别do {a; block p}

  2. 在功能indented和朋友的家庭中,什么是“参考水平”?也就是说,什么是“参考”?

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

    我至少确实在源代码中找到了一些有关使用的示例withPos所以如果我盯着它看足够长的时间,我可能会弄清楚。

  4. <+/>附带有用的描述“<code><+/> is to indentationsensitive parser what apis to monads”如果你想花几个会话试图绕开你的脑袋ap然后弄清楚它与解析器的相似之处,这非常有用。然后参考 定义其他三个组合<+/>子,使整个组无法被新人接近。

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

  5. Parsec的普通lexeme组合器和whiteSpace解析器将愉快地在多令牌构造中间使用换行符而不会抱怨。但是在缩进风格的语言中,有时您想停止解析词法结构,或者如果一行中断并且下一行的缩进少于应有的缩进量,则抛出错误。我该如何在 Parsec 中执行此操作?

  6. 在我试图解析的语言中,理想情况下,何时允许词汇结构继续到下一行的规则应该取决于第一行末尾或下一行开头出现的标记。有没有一种简单的方法可以在 Parsec 中实现这一目标?(如果这很困难,那么这不是我现在需要关心的事情。)

4

1 回答 1

11

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

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

即,它是ParsecT一个额外的密切关注SourcePos,一个抽象容器,可用于访问当前号等。因此,它可能将当前的“缩进级别”存储在SourcePos. 这就是我对“参考水平”意味着什么的初步猜测。

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


(2) “引用级别”是在当前解析器上下文状态中引用的“信念”,该缩进级别从哪里开始。为了更清楚,让我在(3)上给出一些测试用例。

(3) 为了开始试验这些功能,我们将构建一个小测试运行器。它将使用我们给它的字符串运行解析器,然后使用我们要修改State的 an 来解开内部部分。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略有不同。它不仅仅是 a IndentParser,它还是一个IndentParser-combinator——它将输入IndentParser转换为一个认为“参考列”(SourcePos中的State)正是我们调用withPos.

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

(1) 现在让我们看看如何使用我们新的、较低级别的引用列操作符block。是根据 实现的,所以我们将从 开始。withBlockwithBlockblockblock

-- simplified from the actual source
block p = withPos $ many1 (checkIndent >> p)

因此,block将“参考列”重置为当前列的任何内容,然后消耗至少 1 个解析,p只要每个解析与这个新设置的“参考列”缩进相同。现在我们可以看看withBlock

withBlock f a p = withPos $ do
  r1 <- a
  r2 <- option [] (indented >> block p)
  return (f r1 r2)

因此,它将“参考列”重置为当前列,解析单个a解析,尝试解析一个indented blocks p,然后使用f. 您的实现几乎是正确的,只是您需要使用withPos选择正确的“参考列”。

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

(5) 所以,indented和朋友正是这样做的工具:如果相对于 . 选择的“参考位置”缩进不正确,它们将导致解析立即失败withPos

Applicative(4) 是的,在你学会如何在 base 中使用样式解析之前,不要担心这些人Parsec。它通常是一种更清洁、更快、更简单的指定解析的方法。有时它们甚至更强大,但如果你理解Monads 那么它们几乎总是完全等价的。

(6) 这就是症结所在。到目前为止提到的工具只有在您可以使用withPos. 很快,我认为不可能withPos根据其他解析的成功或失败来指定......所以你必须更深入地了解。幸运的是,使IndentParsers 工作的机制是显而易见的——它只是一个State包含SourcePos. 您可以使用它lift :: MonadTrans t => m a -> t m a来操作此内部状态并根据需要设置“参考列”。

干杯!

于 2013-03-24T01:09:13.327 回答