Комбинаторы парсеров, такие как FParsec, не эквивалентны грамматикам БНФ. Большая разница в том, что когда у вас есть альтернатива (<|>
в FParsec), дела рассматриваются в порядке . Если левый парсер успешен, то он возвращается, а правый парсер не пробуется. Если левый синтаксический анализатор дает сбой после использования некоторого ввода, то возвращается ошибка и правый синтаксический анализатор также не пробуется. Только в том случае, если левый синтаксический анализатор дает сбой без использования какого-либо ввода, пробуется правый синтаксический анализатор. [1]
В вашем pNumber <|> pMultiply
, pNumber
успешно и сразу же возвращается без попытки сделать pMultiply
. Можно подумать, чтобы исправить это, написав pMultiply <|> pNumber
вместо этого, но это тоже нехорошо: при анализе последнего числа pMultiply
не сможет найти *
после использования некоторого ввода для его parseExpr
, поэтому все парсинг будет помечен как сбойный.
Как правило, вы хотите максимально использовать функции комбинатора FParsec, и в этом случае наилучшим решением, вероятно, будет использование chainl1
.
let pNumber = pfloat .>> spaces |>> Float
let pTimes = pstring "*" .>> spaces >>% (fun x y -> Multiply (x, y))
let pMultiply = chainl1 pNumber pTimes
Если ваша цель состояла в том, чтобы научиться использовать грамматики BNF, вы, вероятно, захотите взглянуть на FsLex и FsYacc , а не на FParsec.
[1] Есть функция attempt
, которая превращает сбой в потреблении в сбой, не потребляющий, но ее следует использовать как можно реже.