正确解析Haskell中uu-parsinglib中的行缩进

时间:2013-08-14 16:45:22

标签: parsing haskell parsec uu-parsinglib

我想创建一个解析器组合器,它将收集当前位置以下的所有行,缩进级别将大于或等于i。我认为这个想法很简单:

消耗一条线 - 如果它的缩进是:

  • ok - >做下一行
  • 错误 - >失败

让我们考虑以下代码:

import qualified Text.ParserCombinators.UU as UU
import           Text.ParserCombinators.UU hiding(parse)
import           Text.ParserCombinators.UU.BasicInstances hiding (Parser)

-- end of line
pEOL   = pSym '\n'

pSpace = pSym ' '
pTab   = pSym '\t'

indentOf s = case s of
    ' '  -> 1
    '\t' -> 4

-- return the indentation level (number of spaces on the beginning of the line)
pIndent = (+) <$> (indentOf <$> (pSpace <|> pTab)) <*> pIndent `opt` 0

-- returns tuple of (indentation level, result of parsing the second argument)
pIndentLine p = (,) <$> pIndent <*> p <* pEOL

-- SHOULD collect all lines below witch indentations greater or equal i
myParse p i = do
    (lind, expr) <- pIndentLine p
    if lind < i
        then pFail
        else do
            rest <- myParse p i `opt` []
            return $ expr:rest

-- sample inputs
s1 = " a\
   \\n a\
   \\n"

s2 = " a\
   \\na\
   \\n"

-- execution
pProgram = myParse (pSym 'a') 1 

parse p s = UU.parse ( (,) <$> p <*> pEnd) (createStr (LineColPos 0 0 0) s)

main :: IO ()
main = do 
    print $ parse pProgram s1
    print $ parse pProgram s2
    return ()

提供以下输出:

("aa",[])
Test.hs: no correcting alternative found

s1的结果是正确的。 s2的结果应首先消耗“a”并停止消费。这个错误来自哪里?

1 个答案:

答案 0 :(得分:1)

您正在构建的解析器将始终尝试继续;如有必要,将丢弃或添加输入。然而,pFail是一个死胡同。它充当<|>的单位元素。

在您的解析器中,如果输入不符合解析器识别的语言,则不存在其他替代方法。在您的规范中,您说您希望解析器在输入s2上失败。现在它失败并显示一条消息说失败了,你很惊讶。

也许你不希望它失败,但你想停止接受进一步的输入?在这种情况下 将pFail替换为return []

请注意文字:

do
    rest <- myParse p i `opt` []
    return $ expr:rest

可以替换为(expr:) <$> (myParse p i `opt` [])

解决问题的一种自然方式可能就像

pIndented p = do i <- pGetIndent
             (:) <$> p <* pEOL  <*> pMany (pToken (take i (repeat ' ')) *> p <* pEOL)

pIndent = length <$> pMany (pSym ' ')