【问题标题】:Translate grammar production to Parsec将语法生成转换为 Parsec
【发布时间】:2015-03-25 14:03:03
【问题描述】:

我正在尝试转换以下语法产生式

callExpr:
    primaryExpr
  | callExpr primaryExpr

到 Haskell 中的 Parsec 表达式。

显然问题在于它是左递归的,所以我试图解析它的递归上升风格。我试图实现的伪代码是:

e = primaryExp
while(true) {
    e2 = primaryExp
    if(e2 failed) break;
    e = CallExpr(e, e2)
}

我尝试将其翻译成 Haskell 是:

callExpr :: IParser Expr
callExpr = do
    e <- primaryExpr
    return $ callExpr' e
  where
    callExpr' e = do
        e2m <- optionMaybe primaryExpr
        e' <- maybe e (\e2 -> callExpr' (CallExpr e e2)) e2m
        return e'

primaryExpr 的类型为 IParser Expr 而IParser被定义为

type IParser a = ParsecT String () (State SourcePos) a

然而,这给了我以下类型错误:

Couldn't match type `ParsecT String () (State SourcePos) t0'
              with `Expr'
Expected type: ParsecT String () (State SourcePos) Expr
  Actual type: ParsecT
                 String
                 ()
                 (State SourcePos)
                 (ParsecT String () (State SourcePos) t0)
In a stmt of a 'do' block: return $ callExpr' e
In the expression:
  do { e <- primaryExpr;
       return $ callExpr' e }
In an equation for `callExpr':
    callExpr
      = do { e <- primaryExpr;
             return $ callExpr' e }
      where
          callExpr' e
            = do { e2m <- optionMaybe primaryExpr;
                   .... }

如何修复这种类型的错误?

【问题讨论】:

    标签: parsing haskell functional-programming parsec left-recursion


    【解决方案1】:

    使用chainl1chainl1 p op 以左关联的方式解析一个或多个 p-s,由 op-s 分隔。 op 返回一个二进制函数,用于将两边的p-s 的结果组合成一个结果。

    由于您的语法似乎没有分隔符,您可以将chainl1 与只返回组合函数的op 一起使用:

    callExpr :: IParser Expr
    callExpr = chainl1 primaryExpr (return CallExpr)
    

    至于您的callExpr 实现,我可以发现两个错误。

    首先,您使用return $ callExpr' e,但callExpr' e 已经是一元值,因此只需callExpr' e 是正确的。

    其次,在maybe e (\e2 -&gt; callExpr' (CallExpr e e2)) e2m中,默认的e应该是一元的(不然怎么绑定到e'?),所以应该是return e

    【讨论】:

    • 你能解释一下为什么需要这个组合器吗?有什么比使用many 然后折叠结果更好?我还在努力学习解析的基础知识。
    • 它可能会更快一些,但我认为这主要是约定和意图交流。我认为解析器组合器通常应该尽可能地类似于“纯”CFG 符号,并且应该尽量减少事后 AST 操作的数量。我们代码的读者应该能够清楚地看到语法,并且不会被操作细节过度负担。左递归是我们必须偏离抽象符号的一点,因此我们尝试将其封装在chainl中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多