我无法解决如何使用中的任何功能 Text.Parsec.Indent
模块提供的 indents
Haskell的包,这是Parsec的一种附加组件。
所有这些功能有什么作用?它们如何使用?
我能理解简单的Haddock描述 withBlock
,我找到了如何使用的例子 withBlock
, runIndent
和 IndentParser
类型 这里, 这里 和 这里。我也可以理解四个解析器的文档 indentBrackets
和朋友。但是很多事情仍然令我感到困惑。
尤其是:
有什么区别 withBlock f a p
和
do aa <- a
pp <- block p
return f aa pp
同样,它们之间有什么区别 withBlock' a p
和 do {a; block p}
在功能家庭 indented
和朋友什么是'参考水平'?那就是什么是'参考'?
再次,与功能 indented
和朋友们,他们如何被使用?除了 withPos
,看起来他们没有参数,而且都是类型 IParser ()
(IParser定义为 这个 要么 这个所以我猜他们所能做的只是产生一个错误,而不是它们应该出现在 do
块,但我无法弄清楚细节。
我至少找到了一些关于使用的例子 withPos
在里面 源代码所以如果我盯着它看足够久,我可能会想到这一点。
<+/>
附带有用的描述“<+/>
是缩进敏感的解析器是什么 ap
对monads来说“如果你想花几个小时试图包围你的话,那就太棒了 ap
然后弄清楚它是如何类似于解析器的。另一个 三个组合器 然后参考定义 <+/>
,使整个小组无法接近新人。
我需要使用这些吗?我可以忽略它们并使用它 do
代替?
平凡的 lexeme
组合和 whiteSpace
来自Parsec的解析器很乐意在多令牌构造中间使用换行而不会抱怨。但是在缩进风格的语言中,有时你想停止解析一个词法结构,或者如果一行被破坏而下一行的缩进量小于应该的那么就会抛出一个错误。我如何在Parsec中这样做?
在里面 语言 我试图解析,理想情况下,允许词法结构继续到下一行的规则应该取决于第一行末尾或后续行开头出现的令牌。在Parsec有一个简单的方法来实现这一目标吗? (如果困难则不是我现在需要关注的事情。)
所以,第一个提示是看一看 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
的 p
s,然后结合使用结果 f
。你的实施是 几乎 正确,除了你需要使用 withPos
选择正确的“参考栏目”。
然后,一旦你有 withBlock
, withBlock' = 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
操纵这个内部状态并设置你喜欢的“参考列”。
干杯!