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


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

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

我能理解简单的Haddock描述 withBlock,我找到了如何使用的例子 withBlockrunIndent 和 IndentParser 类型 这里这里 和 这里。我也可以理解四个解析器的文档 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定义为 这个 要么 这个所以我猜他们所能做的只是产生一个错误,而不是它们应该出现在 do 块,但我无法弄清楚细节。

    我至少找到了一些关于使用的例子 withPos 在里面 源代码所以如果我盯着它看足够久,我可能会想到这一点。

  4. <+/> 附带有用的描述“<+/> 是缩进敏感的解析器是什么 ap 对monads来说“如果你想花几个小时试图包围你的话,那就太棒了 ap 然后弄清楚它是如何类似于解析器的。另一个 三个组合器 然后参考定义 <+/>,使整个小组无法接近新人。

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

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

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


8724
2018-03-21 13:33


起源

你看过源代码了吗? (按照文档中类型签名右侧的“源”链接。)这至少应该有助于解决您的第一个问题。 - dave4420


答案:


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

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

即它是 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-it转换输入 IndentParser 成为一个认为“参考专栏”( SourcePos 在里面 State)正是我们打电话时的确切位置 withPos

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

(1)现在让我们来看看如何 block 和 withBlock 使用我们新的低级参考列运算符。 withBlock 是以实施的方式实施的 block,所以我们开始吧 block

-- 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  block 的 ps,然后结合使用结果 f。你的实施是 几乎 正确,除了你需要使用 withPos 选择正确的“参考栏目”。

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

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

(4)是的,在你学习如何使用之前不要担心这些人 Applicative 样式 在base中解析 Parsec。它通常是一种更清晰,更快速,更简单的指定解析方式。有时他们甚至更强大,但如果你理解 Monad然后他们几乎总是完全等同。

(6)这就是症结所在。到目前为止提到的工具只有在你能用你描述你想要的缩进时才能做缩进失败 withPos。很快,我认为不可能指定 withPos 基于其他解析的成功或失败......所以你必须更深入地进行另一个层次。幸运的是,这个机制 IndentParser工作是显而易见的 - 它只是一个内在的 State 含有的monad SourcePos。您可以使用 lift :: MonadTrans t => m a -> t m a 操纵这个内部状态并设置你喜欢的“参考列”。

干杯!


11
2018-03-24 01:09



对不起,还有一个问题。有什么区别 checkIndent 和 indented?看着 来源,有什么区别 checkIndent 和 same? - Beetle
我是否正确地思考这一点 getPosition 获得当前位置,而 get 从上次获取位置 withPos 被称为?大概是 withPos 位置可以以某种方式堆叠/嵌套?哦,是的,我可以从源头看到 withPos他们明白地做了。 - Beetle
哦,对不起,我是个白痴: same 强制项目在同一个 线 (不是列)作为参考,和 checkIndent 强制项目处于同一状态 柱 作为参考。而 indented 允许该项与引用位于同一列或后一列。 - Beetle
是啊, SourcePos 保持行数和列数,因此对于基于任一状态的解析有一些支持。 - J. Abrahamson