所以,第一个提示是看一下IndentParser
type IndentParser s u a = ParsecT s u (State SourcePos) a
IE。它是ParsecT
密切关注SourcePos http://hackage.haskell.org/packages/archive/parsec/3.1.2/doc/html/Text-Parsec-Pos.html#t%3aSourcePos,一个抽象容器,可用于访问当前的内容等column数字。因此,它可能将当前的“缩进级别”存储在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
(请注意,这是almost runIndent
,除了我给了一个后门来修改initialPos
.)
现在我们可以看一下indented
。通过检查来源,我可以看出它做了两件事。首先,它会fail
if the current SourcePos
列号小于或等于存储在中的“引用级别”SourcePos
存储在State
。其次,它有点神秘地更新了State
SourcePos
's line计数器(不是列计数器)是当前的。
据我了解,只有第一个行为很重要。我们可以在这里看到差异。
>>> 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
in the State
)正是我们打电话时的位置withPos
.
顺便说一句,这给了我们另一个提示。它让我们知道我们有权更改参考列。
(1) 现在我们来看看如何block
and withBlock
使用我们新的、较低级别的参考列运算符进行工作。withBlock
是根据以下方面实施的block
,所以我们将从block
.
-- simplified from the actual source
block p = withPos $ many1 (checkIndent >> p)
So, block
将“参考列”重置为当前列,然后至少消耗 1 次解析p
只要每个列的缩进与新设置的“参考列”相同即可。现在我们可以看一下withBlock
withBlock f a p = withPos $ do
r1 <- a
r2 <- option [] (indented >> block p)
return (f r1 r2)
因此,它将“参考列”重置为当前列,解析单个a
解析,尝试解析indented
block
of p
s,然后使用组合结果f
。你的实现是almost正确,除了您需要使用withPos
选择正确的“参考列”。
然后,一旦你有了withBlock
, withBlock' = withBlock (\_ bs -> bs)
.
(5) So, indented
和朋友正是执行此操作的工具:如果相对于选择的“参考位置”缩进不正确,它们将导致解析立即失败withPos
.
(4) 是的,在你学会如何使用之前不要担心这些家伙Applicative style http://learnyouahaskell.com/for-a-few-monads-more#useful-monadic-functions解析基数Parsec
。它通常是一种更干净、更快、更简单的指定解析方法。有时它们甚至更强大,但如果你明白Monad
那么它们几乎总是完全等效的。
(6) 这就是症结所在。如果您可以使用以下命令描述您想要的缩进,那么到目前为止提到的工具只能导致缩进失败withPos
。很快,我认为不可能指定withPos
基于其他解析的成功或失败......所以你必须更深入地了解。幸运的是,这个机制使得IndentParser
其工作是显而易见的——这只是一个内在的State
单子包含SourcePos
。您可以使用lift :: MonadTrans t => m a -> t m a
操纵这个内部状态并设置“参考列”,只要你喜欢。
Cheers!