递归解析器

时间:2018-09-06 07:24:16

标签: parsing haskell megaparsec

我需要使用Megaparsec解析像这样的数据结构

data Foo
    = Simple String
    | Dotted Foo String

我可以用点号分隔字母数字字符串。

例如,abc应该解析为Simple "abc"abc.def应该解析为Dotted (Simple "abc") "def"

我现在的解析器就像

fooParser :: Parser Foo
fooParser
    = Simple <$> alphaNum
    <|> do
        foo <- fooParser
        constant "."
        end <- alphaNum
        pure $ Dotted foo end

这对于Simple来说很好用,但是它不能解析任何Dotted,因为第一个选项总是成功地解析了字符串的第一部分。

哪个是修复我的解析器的最佳选择?

1 个答案:

答案 0 :(得分:5)

  

它不解析任何Dotted,因为第一个选项总是成功解析字符串的第一部分。

通过更改替代方案的顺序,可以很容易地解决该问题。通常,只要您有一个总是可以匹配的替代方案,该替代方案就必须排在最后。

但是,这只会导致您遇到下一个问题:您的Dotted解析器是左递归的,而parsec不支持,这意味着在实际到达后将导致无限递归。

通常,当我们想将左递归语法与不能处理左递归的解析算法一起使用时,我们用重复替换递归,然后对结果列表进行左折。因此,给出原始语法:

foo ::= alphanum
      | foo "." alphanum

我们可以使用重复这样重写它:

foo ::= alphanum ("." alphanum)*

现在,对Parsec的最直接转换将对many使用*,然后将结果列表左对齐。但是,我们可能会注意到,rule ("seperator" rule)*可以更简单地匹配模式sepBy1。所以这给了我们:

fooParser =
  do
    first : rest <- sepBy1 alphanum $ constant "."
    return $ foldl Dotted (Simple first) rest