【问题标题】:How to parse this grammar in Parsec? (unusual case of left recursion)如何在 Parsec 中解析这个语法? (左递归的不寻常情况)
【发布时间】:2016-09-30 08:56:50
【问题描述】:

我是 Haskell、Parsec 和编写解析器的初学者。我正在尝试解析一种简单的语言,它(为了这个问题而进一步简化它)仅由嵌套括号的字符串组成,例如[[][]][].

我有下面的 Haskell 代码,它工作正常。但是,我想扩展它,以便不匹配的括号将匹配字符串的末尾。例如,]][][[ 应该等价于[[]][][[]],而[]] 应该等价于[[]]。对匹配字符串结尾的左括号执行此操作很容易,但对匹配字符串开头的右括号执行此操作会导致左递归和无限循环,我还没有找到解决此问题的方法。我不确定原因是否与我对语法的思考方式或我使用 Parsec 库的方式有关,但无论哪种方式,我都希望能看到前进的方向。

这是我的工作代码:

{-# LANGUAGE NoMonomorphismRestriction #-}

import qualified Text.Parsec as Parsec

import Control.Applicative

-- for testing
parse rule text = Parsec.parse rule "(source)" text

data Expr = Brackets [Expr]
        deriving(Show)

openBracket = Parsec.char '['
closeBracket = Parsec.char ']'

parseBrackets = do
        expr <- Parsec.between openBracket closeBracket parseExpr
        return $ Brackets expr

parseExpr = Parsec.many parseBrackets

如果我想让右括号匹配字符串的结尾,我可以将closeBracket 的定义更改为

closeBracket = (Parsec.char ']' >> return ()) <|> Parsec.eof

但是,尽管经过大量试验和错误,我还没有找到将不匹配的]s 与字符串开头匹配的解决方案。我知道 Parsec 中左递归的常用解决方案是 chainl1 函数,但这似乎对中缀运算符非常专业,我在这里看不到使用它的方法。

【问题讨论】:

    标签: parsing haskell parsec


    【解决方案1】:

    这是我对这个的看法:

    import qualified Text.Parsec as Parsec
    import Text.Parsec.String (Parser)
    import Control.Monad (void)
    import Control.Applicative
    
    data Expr = Brackets [Expr]
            deriving(Show)
    
    parseTopLevel :: Parser [Expr]
    parseTopLevel =
        ((:) <$> parseStart <*> parseExpr) <|> parseExpr
    
    parseStart :: Parser Expr
    parseStart = do
        closeBracket
        go (Brackets [])
      where
        go r = (closeBracket *> go (Brackets [r])) <|> return r
    
    parseBrackets :: Parser Expr
    parseBrackets = do
            expr <- Parsec.between openBracket closeBracket parseExpr
            return $ Brackets expr
    
    parseExpr :: Parser [Expr]
    parseExpr = Parsec.many parseBrackets
    
    openBracket :: Parser ()
    openBracket = void $ Parsec.char '['
    
    closeBracket :: Parser ()
    closeBracket = (void $ Parsec.char ']') <|> Parsec.eof
    

    如您所见,为了解析字符串开头的不平衡括号,我无法使用 parsec 附带的任何组合器,我只是编写了自己的 parseStart。剩下的就是你自己写的代码了。

    这是它在您的示例中返回的内容:

    λ> Parsec.parse parseTopLevel "" "]][][["
    Right [Brackets [Brackets []],Brackets [],Brackets [Brackets []]]
    λ> Parsec.parse parseTopLevel "" "[[]][][[]]"
    Right [Brackets [Brackets []],Brackets [],Brackets [Brackets []]]
    

    如您所见,它为 ]][][[[[]][][[]] 返回完全相同的内容。

    【讨论】:

    • 这很棒,我从中学到了很多。 (我特别欣赏你用来为解析器提供可读类型的魔法,而不是像我以前那样的 Parsec.Stream s m Char =&gt; Parsec.ParsecT s u m [Expr] 之类的东西。)但是如果你的解析器首先找到一组匹配的括号,它似乎会忽略不匹配的 ']',所以 @ 987654327@ 相当于 [] 而不是 [[]]。 (我已经在问题中添加了那个测试用例,我很抱歉没有让这个意图一开始就更清楚。也许一旦我弄清楚你的 parseStart 函数在做什么,我可以自己修复它。)
    • 我发布了一个解决这个问题的自我答案,虽然感觉有点像作弊......
    【解决方案2】:

    这是一个基于redneb's improvements to my code 的自我回答。这个版本涵盖了像[]] 这样的情况,redneb 的代码会忽略不匹配的]s,而不是将它们与字符串的开头进行匹配。

    此代码的工作原理是将整个表达式解析为] 分隔的平衡表达式列表,然后从该列表显式构建解析树。这感觉有点像是承认失败,因为解析树的构建发生在与实际解析不同的步骤中。再一次,这似乎是chainl1 的工作方式,所以也许这毕竟是“正确的方式”。如果其他人有更好的解决方案,我不会接受我自己的答案。

    import qualified Text.Parsec as Parsec
    import Text.Parsec.String (Parser)
    import Control.Monad (void)
    import Control.Applicative
    
    -- for testing
    parse rule text = Parsec.parse rule "(source)" text
    
    data Expr = Brackets [Expr]
            deriving(Show)
    
    parseTopLevel :: Parser [Expr]
    parseTopLevel = do
            exprList <- parseExprAsList
            return $ composeExpr exprList
    
    composeExpr :: [[Expr]] -> [Expr]
    composeExpr [exprList] = exprList
    composeExpr (exprList:next:tail) = composeExpr $ (Brackets exprList:next) : tail
    
    parseExprAsList :: Parser [[Expr]]
    parseExprAsList = Parsec.sepBy parseBalancedExpr (Parsec.char ']')
    
    parseBalancedExpr :: Parser [Expr]
    parseBalancedExpr = Parsec.many parseBrackets
    
    parseBrackets :: Parser Expr
    parseBrackets = do
            expr <- Parsec.between openBracket closeBracket parseBalancedExpr
            return $ Brackets expr
    
    openBracket :: Parser ()
    openBracket = void $ Parsec.char '['
    
    closeBracket :: Parser ()
    closeBracket = (void $ Parsec.char ']') <|> Parsec.eof
    

    一些测试用例:

    *Main> parse parseTopLevel "[]]"
    Right [Brackets [Brackets []]]
    *Main> parse parseTopLevel "[[]]"
    Right [Brackets [Brackets []]]
    
    *Main> parse parseTopLevel "]["
    Right [Brackets [],Brackets []]
    *Main> parse parseTopLevel "[][]"
    Right [Brackets [],Brackets []]
    
    *Main> parse parseTopLevel "[]][]]]"
    Right [Brackets [Brackets [Brackets [Brackets []],Brackets []]]]
    *Main> parse parseTopLevel "[[[[]][]]"
    Right [Brackets [Brackets [Brackets [Brackets []],Brackets []]]]
    

    【讨论】:

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