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

Bee*_*tle 11 parsing haskell parsec indentation

我在如何使用Haskell包Text.Parsec.Indent提供的模块中的任何函数时遇到了麻烦indents,Haskell是Parsec的一种附加组件.

所有这些功能都做了什么?它们如何使用?

我能理解的简单的黑线鳕描述withBlock,我发现了如何使用的例子withBlock,runIndentIndentParser类型在这里,这里这里.我也可以理解四个解析器indentBrackets和朋友的文档.但是很多事情仍然令我感到困惑.

特别是:

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

    do aa <- a
       pp <- block p
       return f aa pp
    
    Run Code Online (Sandbox Code Playgroud)

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

  2. 在职能indented和朋友的家庭中,"参考水平"是什么?那就是什么是'参考'?

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

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

  4. <+/>附带有用的描述" <+/>是缩进敏感的解析器是什么ap是monads"这是伟大的,如果你想花几个会议试图包裹你的头ap,然后找出它是如何类似于解析器.然后参考其他三个组合器进行定义<+/>,使整个组无法接近新手.

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

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

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

J. *_*son 11

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

type IndentParser s u a = ParsecT s u (State SourcePos) a
Run Code Online (Sandbox Code Playgroud)

也就是说,这是一个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
Run Code Online (Sandbox Code Playgroud)

(请注意,这几乎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 ()
Run Code Online (Sandbox Code Playgroud)

因此,为了indented取得成功,我们需要消耗足够的空格(或其他任何东西!)以将列位置推出"参考"列位置.否则,它将失败说"没有缩进".接下来的三个函数存在类似的行为:same除非当前位置和引用位置在同一行上,sameOrIndented否则失败,如果当前列严格小于引用列则checkIndent失败,除非它们在同一行上,并且除非当前列和当前列失败参考列匹配.

withPos略有不同.它不仅仅是一个IndentParser,它是一个IndentParser组合器 - 它将输入IndentParser转换为一个认为"参考列"(SourcePos在其中State)正好在我们调用时的位置withPos.

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

(1)现在让我们来看看如何blockwithBlock工作中使用我们新的,较低的水平参考柱运营商.withBlock是以实现的方式实现的block,所以我们将从开始block.

-- simplified from the actual source
block p = withPos $ many1 (checkIndent >> p)
Run Code Online (Sandbox Code Playgroud)

因此,block将"引用列"重置为当前列的任何内容,然后消耗至少1个分析,p只要每个分析与新设置的"引用列"相同.现在我们来看看withBlock

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

因此,它重置"基准列"在当前列,解析单个a解析,尝试分析一个indented blockpS,然后将结果用结合f.您的实现几乎是正确的,除了您需要使用withPos选择正确的"参考列".

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

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

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

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

干杯!