如何在不消耗的情况下使Attoparsec解析器成功(如parsec lookAhead)

时间:2012-11-02 20:10:53

标签: parsing haskell attoparsec

我写了一个快速的attoparsec解析器来遍历一个aspx文件并删除所有的样式属性,它的工作正常,除了它的一部分,我无法弄清楚如何使它成功匹配>没有消耗它。

这就是我所拥有的:

anyTill = manyTill anyChar
anyBetween start end = start *> anyTill end

styleWithQuotes = anyBetween (stringCI "style=\"") (stringCI "\"")
styleWithoutQuotes = anyBetween (stringCI "style=") (stringCI " " <|> ">")
everythingButStyles = manyTill anyChar (styleWithQuotes <|> styleWithoutQuotes) <|> many1 anyChar

我理解这部分是因为我在everythingButStyles中如何使用manyTill,这就是我如何积极地放弃所有样式的东西,但在styleWithoutQuotes我需要它来匹配“&gt;”作为一个结束,但不消耗它,在parsec中我会完成lookAhead ">"但我不能在attoparsec中这样做。

2 个答案:

答案 0 :(得分:5)

anyBetween start end = start *> anyTill end

你的anyBetween解析器吃了它的最后一个字符因为anyTill确实 - 它被设计为解析结束标记,但假设你不想让输入中的右括号再次解析。

请注意,您的end解析器都是单字符解析器,因此我们可以更改功能以使用它:

anyBetween'' start ends = start *> many (satisfy (not.flip elem ends))

但是many效率不如Attoparsec的takeWhile,你应该尽可能多地使用它,所以如果你已经完成了

import qualified Data.Attoparsec.Text as A

然后

anyBetween' start ends = start *> A.takeWhile (not.flip elem ends)

应该做的伎俩,我们可以重写

styleWithoutQuotes = anyBetween' (stringCI "style=") [' ','>']

如果你希望它吃' '而不是'>'你可以明确地吃掉空格:

styleWithoutQuotes = anyBetween' (stringCI "style=") [' ','>'] 
                     <* A.takeWhile isSpace

寻求更多takeWhile

也许styleWithQuotes也可以通过重写来使用takeWhile,所以让我们在anyBetween的行上创建两个帮助器。它们从起始解析器到结束字符,并且有包容性和独占版本:

fromUptoExcl startP endChars = startP *> takeTill (flip elem endChars)
fromUptoIncl startP endChars = startP *> takeTill (flip elem endChars) <* anyChar

但我想从你所说的话来看,你希望styleWithoutQuotes成为一个混合体;它吃' '但不吃>

fromUptoEat startP endChars eatChars = 
            startP 
            *> takeTill (flip elem endChars) 
            <* satisfy (flip elem eatChars)

(所有这些都假设您的结束字符列表中包含少量字符,否则elem效率不高 - 如果您检查的是大型列表,则会有一些Set变体字母表。)

现在进行重写:

styleWithQuotes' = fromUptoIncl (stringCI "style=\"") "\""
styleWithoutQuotes' = fromUptoEat (stringCI "style=") " >" " "

整体解析器

everythingButStyles以某种方式使用<|>,这意味着如果找不到"style",它将会回溯,然后取走所有内容。这是一个可能很慢的事情的例子。问题是我们迟到了 - 在输入字符串的末尾,这是一个关于我们是否应该失败的选择的不好时机。让我们全力以赴,尝试

  1. 如果我们要失败,马上就会失败。
  2. 最大限度地使用Data.Attoparsec.Text.Internal
  3. 中更快的解析器

    想法:直到我们得到一个s,然后跳过那个样式的那个样式。

    notStyleNotEvenS = takeTill (flip elem "sS") 
    skipAnyStyle = (styleWithQuotes' <|> styleWithoutQuotes') *> notStyleNotEvenS 
                   <|> cons <$> anyChar <*> notStyleNotEvenS
    

    anyChar通常是sS,但没有必要再检查一下。

    noStyles = append <$> notStyleNotEvenS <*> many skipAnyStyle 
    
    parseNoStyles = parseOnly noStyles
    

答案 1 :(得分:4)

同时,lookAhead组合符已添加到attoparsec,因此现在可以使用lookAhead (char '>')lookAhead (string ">")来实现目标。

以下是介绍之前的解决方法。


您可以使用peekWord8构建非消费解析器,它只查看下一个字节(如果有的话)。由于ByteStringMonoid个实例,Parser ByteStringMonadPlus,您可以使用

lookGreater = do
    mbw <- peekWord8
    case mbw of
      Just 62 -> return ">"
      _ -> mzero

(62是'>'的代码点)要么找到'>'而不消耗它或失败。