Рекурсивные грамматики в 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

Спасибо!

Ответ 1

Как вы указали, проблема в том, что ваш парсер для приложения вызывает expr рекурсивно, и поэтому существует бесконечный цикл. Парсер необходимо написать так, чтобы он всегда потреблял некоторый ввод, а затем решает, что делать.

В случае лямбда-исчисления сложно понять приложение и переменную, потому что если у вас есть вход вроде 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 }

Это сначала анализирует переменную, а затем либо анализирует другое выражение (в этом случае это приложение), либо просто возвращает переменную (если нет выражения, следующего за переменной). Остальные правила аналогичны:

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 ])

Я просто избегал необходимости явной мутации, используя parse.Delay() (что позволяет создавать рекурсивные ссылки на значения). В принципе, его можно было бы написать как:

and expr = parse {
  return! choice [ lam; varApp; brac ] }

... но по какой-то причине FParsec не реализует член ReturnFrom, который необходим, если вы хотите использовать return! в выражениях вычислений.