Как вы указали, проблема в том, что ваш синтаксический анализатор для application вызывает рекурсивно 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!
в выражениях вычислений.