如何解析这些操作?

时间:2019-08-16 03:43:35

标签: haskell

我的数据类型

data S = Fac S | Mul S S | Nat Integer
deriving (Show)

和定义为

的语法
S ::= S "!" | S "*" S | natural

到目前为止,我已经写完了

pa :: Parser S
pa = facst <|> multst <|> natst

facst = do
    s <- pa
    char '!'
    return (Fac s)

multst = do
    s1 <- pa
    char '*'
    s2 <- pa
    return (Mul s1 s2)

natst = do
    n <- natural
    return (Nat n)

但是事实和多事不起作用。 natst仅适用于单个整数,例如“ 5”,而不适用于“ 56”。 我已经尝试过了

natst = do
    n <- some natural
    return (Nat n)

但是会产生一个错误。 有人可以指出我正确的方向吗?

1 个答案:

答案 0 :(得分:2)

正如一些评论所建议的那样,您的语法是模棱两可的,并且包含左递归,因此像大多数解析器组合器库生成的递归下降解析器一样,这也是一个问题。参见例如How to remove ambiguity in the following grammar?Left recursion elimination的问题解答。

就您而言,

S ::= S "!" | S "*" S | natural

具有左递归,因为"!""*"的产生都以S开始。这是模棱两可的,因为目前尚不清楚S中哪个应该首先在"*"生成中派生:给定像1 * 2 * 3这样的表达式,应该产生这个

  *                  *
 / \                / \
1   *      or      *   3  ?
   / \            / \
  2   3          1   2

1 * (2 * 3)      (1 * 2) * 3

正如@melpomene指出的那样,它也是模棱两可的,因为1 * 2 !可能产生

  *            !
 / \           |
1   !    or    *
    |         / \
    2        1   2

1 * (2 !)    (1 * 2) !

一个既没有左递归也没有歧义的重写语法的例子(还有其他)是

S  ::= natural S₁
S₁ ::= "!" | "*" S | ε

使用这种语法,1 * 2 * 3将始终解析为<​​/ p>

S
1 S₁
1 * S
1 * 2 S₁
1 * 2 * S
1 * 2 * 3 S₁
1 * 2 * 3 ε

表示*变为右关联。并且1 * 2 !将始终解析为<​​/ p>

S
1 S₁
1 * S
1 * 2 S₁
1 * 2 !

意味着!的优先级比*高,我不知道是好是坏。

无论哪种方式,如果您希望解析器表达任意表达式,则可能要使用显式括号扩展语法,以便您可以覆盖每个运算符的默认优先级。


对于解析器本身,您可以直接根据重写的语法对其进行建模,例如:

parseS :: Parser S
parseS = do
  n <- natural
  f <- parseS1
  return (f n)

natural :: Parser S
natural = do
  n <- read <$> many1 digit
  return (Nat n)

parseS1 :: Parser (S -> S)
parseS1 = parseFac <|> parseMul <|> parseNat
  where
    parseFac = do
      char '!'
      return (\s -> Fac s)

    parseMul = do
      char '*'
      s2 <- parseS
      return (\s1 -> Mul s1 s2)

    parseNat = do
      eof -- ε
      return (\s -> s)

然后,您必须处理空格:

> parse parseS "" "1*2*3"
Right (Mul (Nat 1) (Mul (Nat 2) (Nat 3)))

> parse parseS "" "1 * 2 * 3"
Left (line 1, column 2):
unexpected ' '
expecting digit, "!", "*" or end of input

> parse parseS "" " 1*2*3"
Left (line 1, column 1):
unexpected " "
expecting digit

> parse parseS "" "1*2*3 "
Left (line 1, column 6):
unexpected ' '
expecting digit, "!", "*" or end of input

我会参考教程或书籍来正确完成这一部分。

最后,您可能想使用各种解析器组合器库的某些高级功能,例如chainr1Text.Megaparsec.Expr's makeExprParser,这些功能试图以一种不太麻烦的方式处理此类问题。不过,在使用它们之前,明智的做法是像您当前正在做的那样,通过手动进行解析器来了解它们是如何实现的。例如,如何转换上面的解析器,以使"*"是左关联的,或者"!"的优先级较低?