如何创建不挂起的递归中缀解析器?

时间:2019-07-15 08:04:22

标签: haskell

考虑以下代码:

import Text.ParserCombinators.ReadP

prefixExpr :: ReadP Int
prefixExpr = choice [readPlus, readS_to_P reads]
  where
    readPlus = do
        string "+ "
        s1 <- prefixExpr
        string " "
        s2 <- prefixExpr
        return $ s1 + s2

p $. s = case [ x | (x,"") <- p `readP_to_S` s ] of
    [x] -> Right x
    [ ] -> Left "mkRead: no parse"
    _   -> Left "mkRead: ambiguous parse"

效果很好!

λ prefixExpr $. "+ 1 + 2 3"
Right 6

但是此变体不能:

infixExpr :: ReadP Int
infixExpr = choice [readPlus, readS_to_P reads]
  where
    readPlus = do
        s1 <- infixExpr
        string " + "
        s2 <- infixExpr
        return $ s1 + s2

实际上,它挂了。

λ infixExpr $. "1 + 2 + 3"
^CInterrupted.
λ infixExpr $. "1"
^CInterrupted.

我了解到infixExpr进入无限递归试图确定“ 1”是否为数字 或表达式的开头,但我想知道如何避免这种情况。我真的不需要我 程序可以无限期地探索所有可能性,只是选择最明显的可能性而无视 其他人一共。我该怎么办?

我尝试的一件事是对递归深度进行限制。

infixExpr :: Int -> ReadP Int
infixExpr 0 = number
infixExpr n = choice [plus, number]
  where
    plus = do
        s1 <- infixExpr (n - 1)
        string " + "
        s2 <- infixExpr (n - 1)
        return $ s1 + s2

这是可行的,但是由于它的指数复杂性,实际上是没有用的。

1 个答案:

答案 0 :(得分:3)

如@chi所建议,通过分解来消除左递归:

infixExpr :: ReadP Int
infixExpr = number >>= \x -> choice [plus x, eof >> return x]
  where
    plus x = do
        string " + "
        y <- infixExpr
        return $ x + y

有效:

λ infixExpr $. "1"                
Right 1
λ infixExpr $. "1 + 2"
Right 3
λ infixExpr $. "1 + 2 + 3"

但是我对此并不满意。我希望原来的语法能起作用,因为它看起来更像是人类的思维。


说句公道话,只需很小的编辑,就可以更轻松地实现相同的效果:

infixExpr :: ReadP Int
infixExpr = choice [plus, number]
  where
    plus = do
        s1 <- number  -- Notice!
        string " + "
        s2 <- infixExpr
        return $ s1 + s2

我前一段时间已经想通了,但是我真的想对plus的定义保持对称。