数学解析器Haskell的瓶颈

时间:2013-12-06 20:02:48

标签: parsing haskell parser-combinators

我从维基书籍页面here获得了以下代码。它解析数学表达式,它对我正在处理的代码非常有效。虽然有一个问题,但当我开始在我的表达式中添加括号层时,程序会显着减慢,在某些时候崩溃我的计算机。它与我检查的运算符数量有关,运算符越多,我可以解析的括号越少。无论如何要绕过或解决这个瓶颈?

非常感谢任何帮助。

import Text.ParserCombinators.ReadP

-- slower
operators = [("Equality",'='),("Sum",'+'), ("Product",'*'), ("Division",'/'), ("Power",'^')]
-- faster
-- ~ operators = [("Sum",'+'), ("Product",'*'), ("Power",'^')]

skipWhitespace = do 
                 many (choice (map char [' ','\n']))
                 return ()

brackets p = do 
             skipWhitespace
             char '('
             r <- p
             skipWhitespace
             char ')'
             return r

data Tree op = Apply (Tree op) (Tree op) | Branch op (Tree op) (Tree op) | Leaf String deriving Show

leaf = chainl1 (brackets tree
              +++ do
                     skipWhitespace
                     s <- many1 (choice (map char "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-[]" ))
                     return (Leaf s))
             (return Apply)
tree = foldr (\(op,name) p ->
             let this = p +++ do 
                                 a <- p +++ brackets tree
                                 skipWhitespace
                                 char name
                                 b <- this
                                 return (Branch op a b)
              in this)
           (leaf +++ brackets tree)
           operators


readA str = fst $ last $ readP_to_S tree str 


main = do loop

loop = do 
    -- ~ try this
    -- ~ (a+b+(c*d))
    str <- getLine
    print $ last $ readP_to_S tree str
    loop

1 个答案:

答案 0 :(得分:2)

这是回溯中的经典问题(或者是并行解析,它们基本上是一回事)....回溯随着输入的大小呈指数增长(最坏),所以解析某些东西的时间会突然爆发。在实践中,回溯在大多数输入的语言解析中都可以正常工作,但是会使用递归中缀运算符表示法进行爆炸。您可以通过考虑可以解析多少可能的方法(使用组合&amp;和%运算符)来了解原因:

a & b % c & d

可以解析为

a & (b % (c & d))
a & ((b % c) & d)
(a & (b % c)) & d
((a & b) % c) & d

这就像2 ^(n-1)一样增长。对此的解决方案是在解析中更早地添加一些运算符预置信息,并抛弃除敏感案例之外的所有信息....您将需要一个额外的堆栈来保存挂起的运算符,但是您总是可以通过O中的中缀运算符表达式(1)。

像yacc这样的LR解析器为你做这个....使用解析器组合器你需要手动完成。在parsec中,有一个带有buildExpressionParser函数的Expr包,可以为你构建它。