为什么parsecs“选择”组合似乎坚持第一选择?

时间:2011-04-18 02:20:12

标签: xml parsec haskell

在查看Real World Haskell中的CSV示例代码之后,我尝试构建一个小的XML解析器。但是关闭标签会因“意外”/“错误而错误输出。你能告诉我为什么我的“closeTag”解析器不起作用(或者可能不会被调用)吗?谢谢!

import Text.ParserCombinators.Parsec

xmlFile = manyTill line eof
line = manyTill tag eol
eol = char '\n'

word = many1 (noneOf "></")

tag = choice [openTag, closeTag, nullTag, word]

nullTag = between (char '<') (string "/>") word
closeTag = between (string "</") (char '>') word
openTag = between (char '<') (char '>')  tagContent
attrval = between (char '"') (char '"') word

atts = do {
        (char ' ')
        ; sepBy attr (char ' ')
}

attr = do {
                word
                ; char '='
                ; attrval
        }

tagContent = do {
                w <- word
                ; option []  atts
                ; return w
        }

parseXML :: String -> Either ParseError [[String]]
parseXML input = parse xmlFile "(unknown)" input

main =
    do c <- getContents
       case parse xmlFile "(stdin)" c of
            Left e -> do putStrLn "Error parsing input:"
                         print e
            Right r -> mapM_ print r

1 个答案:

答案 0 :(得分:14)

Parsec的策略本质上是LL(1),这意味着只要消耗了任何输入,它就会“提交”到当前分支。您的openTag解析器会使用<消耗char '<',这意味着如果>看到/而不是openTag,则整个解析失败而不是尝试新的选择。如果tag = word <|> (char '<' >> tagbody) where tagbody = do content <- tagcontent choice [ string "/>", char '>' ] 没有消耗任何输入并且失败,则会尝试另一种选择。 Parsec这样做是为了提高效率(替代方案是指数时间!)和合理的错误消息。

您有两种选择。当合理推出时,首选的选项是将语法分解,以便在不消耗输入的情况下做出所有选择,例如:

try

Modulo错误和风格(我的大脑现在有点油腻:-P)。

另一种方式,本地更改parsec的语义(以牺牲前面提到的错误消息和效率为代价 - 但它通常不是太糟糕,因为它是本地的),是使用允许解析器的nulltag = try $ between (char '<') (string "/>") word -- etc. 组合器消费输入仍然“轻轻地”失败,因此可以尝试另一种选择:

{{1}}

有时使用try比上面的因子更清晰,更容易,这可能会掩盖语言的“深层结构”。这是一种风格上的权衡。