所以,第一个提示是看看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
。是根据 实现的,所以我们将从 开始。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
s p
,然后使用f
. 您的实现几乎是正确的,只是您需要使用withPos
选择正确的“参考列”。
然后,一旦你拥有withBlock
, withBlock' = withBlock (\_ bs -> bs)
.
(5) 所以,indented
和朋友正是这样做的工具:如果相对于 . 选择的“参考位置”缩进不正确,它们将导致解析立即失败withPos
。
Applicative
(4) 是的,在你学会如何在 base 中使用样式解析之前,不要担心这些人Parsec
。它通常是一种更清洁、更快、更简单的指定解析的方法。有时它们甚至更强大,但如果你理解Monad
s 那么它们几乎总是完全等价的。
(6) 这就是症结所在。到目前为止提到的工具只有在您可以使用withPos
. 很快,我认为不可能withPos
根据其他解析的成功或失败来指定......所以你必须更深入地了解。幸运的是,使IndentParser
s 工作的机制是显而易见的——它只是一个State
包含SourcePos
. 您可以使用它lift :: MonadTrans t => m a -> t m a
来操作此内部状态并根据需要设置“参考列”。
干杯!