FParsec中的递归语法

Ram*_*nir 11 f# parsing parsec fparsec

我决定查看FParsec,并尝试为λ表达式编写解析器.事实证明,渴望使递归解析变得困难.我怎么解决这个问题?

码:

open FParsec

type ?Expr =
    | Variable of char
    | Application of ?Expr * ?Expr
    | Lambda of char * ?Expr

let rec FV = function
    | Variable v -> Set.singleton v
    | Application (f, x) -> FV f + FV x
    | Lambda (x, m) -> FV m - Set.singleton x

let ?0 = FV >> (=) Set.empty

let apply f p =
    parse
        { let! v = p
          return f v }

let ? e =

    let expr, exprR = createParserForwardedToRef()

    let var = lower |> apply Variable

    let app = tuple2 expr expr
                 |> apply Application

    let lam = pipe2 (pchar '?' >>. many lower)
                        (pchar '.' >>. expr) (fun vs e ->
                                                List.foldBack (fun c e -> Lambda (c, e)) vs e)

    exprR := choice [
                    lam
                    app
                    var
                    (pchar '(' >>. expr .>> pchar ')')
                    ]

    run expr e
Run Code Online (Sandbox Code Playgroud)

谢谢!

Tom*_*cek 9

正如您所指出的,问题是您的应用程序解析器以递归方式调用expr,因此存在无限循环.需要编写解析器,使其始终消耗一些输入,然后决定要做什么.

在lambda演算的情况下,棘手的是识别应用程序变量,因为如果你有像x...那样的输入,那么第一个字符表明它可能是其中之一.

您可以合并应用程序变量的规则,如下所示:

let rec varApp = parse {
  let! first = lower |> apply Variable
  let! res = 
    choice [ expr |> apply (fun e -> Application(first, e))
             parse { return first } ]
  return res }
Run Code Online (Sandbox Code Playgroud)

这首先解析一个变量,然后解析另一个表达式(在这种情况下它是一个应用程序),或者它只返回变量(如果变量后面没有表达式).其余规则类似:

and lam = 
  pipe2 (pchar '?' >>. many lower)
        (pchar '.' >>. expr) (fun vs e ->
    List.foldBack (fun c e -> Lambda (c, e)) vs e)
and brac = pchar '(' >>. expr .>> pchar ')'
and expr = parse.Delay(fun () ->
  choice 
    [ lam; varApp; brac ])
Run Code Online (Sandbox Code Playgroud)

我只是通过使用避免了显式变异的需要parse.Delay()(这使得创建递归值引用成为可能).原则上,它可以写成:

and expr = parse {
  return! choice [ lam; varApp; brac ] }
Run Code Online (Sandbox Code Playgroud)

...但由于某种原因,ReturnFrom如果要return!在计算表达式中使用,FParsec不会实现所需的成员.

  • 谢谢你引起我的注意.从'parse'构建器对象中省略ReturnFrom成员是一种疏忽.在以前版本的F#中,隐式定义了ReturnFrom.(这个定义很简单.)这应该在FParsec 0.9中修复,但我忘记了.我刚刚检查了BitBucket存储库中的修复程序. (4认同)
  • 就我个人而言,由于此处描述的性能问题,我不再使用计算表达式语法来构造解析器:http://www.quanttec.com/fparsec/users-guide/where-is-the-monad.html#why-the-一元语法很慢 (2认同)
  • @Tomas Petricek-与FParsec的Seq.cache等效的是一种记忆组合器,如http://www.quanttec.com/fparsec/users-guide/tips-and-tricks.html#memoizing-parsers中所述,这样的组合器仅在某些具有频繁回溯解析器的特殊情况下才有助于提高性能。为了摆脱与计算表达式语法相关的开销,您要么需要更高级的编译器优化,要么需要使用元编程技术来本质上自己执行这些优化。 (2认同)