【问题标题】:Removing Left Recursion in a Basic Expression Parser在基本表达式解析器中删除左递归
【发布时间】:2015-09-19 02:32:54
【问题描述】:

作为练习,我正在使用以下 GADT 为 Haskell 中定义的极其简单的语言实现解析器(我的项目的真正语法涉及更多表达式,但这个摘录足以解决这个问题):

data Expr a where
    I   :: Int -> Expr Int
    Add :: [Expr Int] -> Expr Int

解析函数如下:

expr :: Parser (Expr Int)
expr = foldl1 mplus
    [ lit 
    , add 
    ]   

lit :: Parser (Expr Int)
lit = I . read <$> some digit

add :: Parser (Expr Int)
add = do
  i0 <- expr
  is (== '+')
  i1 <- expr
  is <- many (is (== '+') *> expr)
  pure (Add (i0:i1:is))

由于表达式语法的左递归性质,当我尝试使用 1+1 解析器解析像 1+1 这样简单的东西时,解析器会陷入无限循环。

我已经看到了如何使用以下转换来排除网络中的左递归的示例:

S -> S a | b

变成这样的:

S -> b T
T -> a T

但我正在努力解决如何将其应用于我的解析器。

为了完整起见,这里是实际实现解析器的代码:

newtype Parser a = Parser
    { runParser :: String -> [(a, String)]
    }   

instance Functor Parser where
    fmap f (Parser p) = Parser $ \s ->
      fmap (\(a, r) -> (f a, r)) (p s)

instance Applicative Parser where
    pure a = Parser $ \s -> [(a, s)] 
    (<*>) (Parser f) (Parser p) = Parser $ \s ->
      concat $ fmap (\(f', r) -> fmap (\(a, r') -> (f' a, r')) (p r)) (f >

instance Alternative Parser where
    empty = Parser $ \s -> []
    (<|>) (Parser a) (Parser b) = Parser $ \s ->
      case a s of
        (r:rs) -> (r:rs)
        []     -> case b s of
                    (r:rs) -> (r:rs)
                    []     -> []

instance Monad Parser where
    return = pure
    (>>=) (Parser a) f = Parser $ \s ->
      concat $ fmap (\(r, rs) -> runParser (f r) rs) (a s)

instance MonadPlus Parser where
    mzero = empty
    mplus (Parser a) (Parser b) = Parser $ \s -> a s ++ b s 

char  = Parser $ \case (c:cs) -> [(c, cs)]; [] -> []
is p  = char >>= \c -> if p c then pure c else empty
digit = is isDigit

【问题讨论】:

  • 另外,您可以考虑使用attoparsec,而不是滚动您自己的解析框架。
  • @dfeuer,但那样我们就会错过练习的目的!不过,该运算符优先级看起来像是一个失败的好解决方案。理想情况下,我们可以让它与这个递归下降解析器一起工作。
  • 另请注意,mplus 通常应与 &lt;|&gt; 匹配。
  • 公平一点!但是,您可以考虑使用 attoparsec 测试解析器,以确保它们是问题所在,而不是您的框架。只是一个想法。

标签: parsing haskell left-recursion


【解决方案1】:

假设您要解析涉及文字、加法和乘法的非括号表达式。您可以通过按优先级减少列表来做到这一点。这是在attoparsec 中执行此操作的一种方法,它应该与您使用解析器执行的操作非常相似。我不是解析专家,所以可能会有一些错误或不恰当的地方。

import Data.Attoparsec.ByteString.Char8
import Control.Applicative

expr :: Parser (Expr Int)
expr = choice [add, mul, lit] <* skipSpace
-- choice is in Data.Attoparsec.Combinators, but is
-- actually a general Alternative operator.

add :: Parser (Expr Int)
add = Add <$> addList

addList :: Parser [Expr Int]
addList = (:) <$> addend <* skipSpace <* char '+' <*> (addList <|> ((:[]) <$> addend))

addend :: Parser (Expr Int)
addend = mul <|> multiplicand

mul :: Parser (Expr Int)
mul = Mul <$> mulList

mulList :: Parser [Expr Int]
mulList = (:) <$> multiplicand <* skipSpace <* char '*' <*> (mulList <|> ((:[]) <$> multiplicand))

multiplicand :: Parser (Expr Int)
multiplicand = lit

lit :: Parser (Expr Int)
lit = I <$> (skipSpace *> decimal)

【讨论】:

    猜你喜欢
    • 2022-01-07
    • 1970-01-01
    • 2015-08-03
    • 2013-03-08
    • 2011-02-08
    • 1970-01-01
    • 1970-01-01
    • 2017-02-18
    相关资源
    最近更新 更多