У меня возникла проблема с парсерами, имеющими две ветви рекурсии. Чтобы продемонстрировать проблему проще, я использую простую грамматику лямбда-исчисления из статьи, написанной Лукой Болоньезе в качестве примера:
<expression> ::= <name> | <function> | <application>
<name> ::= nonblank character sequence
<function> ::= \ <name> . <body>
<body> ::= <expression>
<application> ::= ( <function expression> <argument expression> )
<function expression> ::= <expression>
<argument expression> ::= <expression>
Парсер в статье довольно лаконичен:
let ws = " \t\n"
let specialChars = ".)(\\\n"
let pWs = spaces
let pName = manyChars (noneOf (ws + specialChars)) |>> EName
let pExpr, pExprRef = createParserForwardedToRef<Expression, Unit>()
let curry2 f a b = f(a,b)
let pFunction = pchar '\\' >>. pipe2 pName (pchar '.' >>. pExpr) (curry2 Function)
let pApplication = pchar '(' >>. pipe2 pExpr (pWs >>. pExpr) (curry2 Application)
.>> pWs .>> pchar ')'
do pExprRef := pFunction <|> pApplication <|> pName
let pExpressions = sepBy pExpr spaces1
let fparseString text =
match run pExpressions text with
| Success(result, _, _) -> result
| Failure(errorMsg, _, _) -> failwith (sprintf "Failure: %s" errorMsg)
Я заинтересован в pApplication
, поскольку они состоят из двух pExpr
с, что, в свою очередь, также может составлять pApplication
с. Синтаксическому анализатору не хватает места в стеке в следующем тесте:
let generateString level =
let rec loop i =
seq {
if i < level then
yield "("
yield! loop level
yield " "
yield! loop (i+1)
yield ")"
else
yield "(x x)"
}
loop 0 |> String.concat ""
let N = 5000
let s = generateString N;;
let _ = fparseString s;;
Как переписать синтаксический анализатор так, чтобы он был хвостово-рекурсивным?
Я обнаружил проблему при попытке написать парсер для языка, похожего на Лисп, и проверить его на реальных тестах. У меня есть Term
и VarBinding
, которые являются взаимно рекурсивными типами, и let
синтаксический анализатор, который показывает ту же проблему, что и pApplication
выше. Мой синтаксический анализатор на github на случай, если анализ неверен в отношении нерекурсивной проблемы.