FParsec: как пропустить `много` сбоев парсера в сообщениях об ошибках - PullRequest
4 голосов
/ 25 мая 2019

Рассмотрим этот синтаксический анализатор, который преобразует цифровые строки в int s:

let toInt (s:string) = 
    match Int32.TryParse(s) with
    | (true, n) -> preturn n
    | _         -> fail "Number must be below 2147483648"

let naturalNum = many1Chars digit >>= toInt <?> "natural number"

Когда я запускаю его на нечисловых строках, таких как "abc", он показывает правильное сообщение об ошибке:

Error in Ln: 1 Col: 1
abc
^
Expecting: natural number

Но когда я даю ему числовую строку, превышающую диапазон int, это дает следующее непродуктивное сообщение:

Error in Ln: 1 Col: 17
9999999999999999
                ^
Note: The error occurred at the end of the input stream.
Expecting: decimal digit
Other error messages:
  Number must be below 2147483648

Основное сообщение "Expecting: decimal digit" не имеет смысла, потому что мы должныуже много цифр.

Есть ли способ избавиться от него и показать только "Number must be below 2147483648"?

Полный пример:

open System
open FParsec

[<EntryPoint>]
let main argv =
    let toInt (s:string) = 
        match Int32.TryParse(s) with
        | (true, n) -> preturn n
        | _         -> fail "Number must be below 2147483648"

    let naturalNum = many1Chars digit >>= toInt <?> "natural number"

    match run naturalNum "9999999999999999" with
    | Failure (msg, _, _) -> printfn "%s" msg
    | Success (a, _, _)   -> printfn "%A" a

    0

Ответы [ 2 ]

1 голос
/ 29 мая 2019

Эффект, который вы видите, состоит в том, что первый синтаксический анализатор вашей последовательности завершается успешно, но также генерирует сообщение об ошибке (потому что он может потреблять еще больше цифр). Ваш второй синтаксический анализатор больше не потребляет ввода, и в случае его сбоя FParsec объединит сообщения об ошибках двух последовательных анализаторов ( Руководство по объединению сообщений об ошибках ).

Решением будет создание небольшой оболочки для синтаксического анализатора, которая удаляет сообщения об ошибках из результата в случае Ok. Затем при выполнении последовательности со вторым анализатором остается только сообщение второго анализатора.

Неопробованный код от макушки головы:

let purify p =
    fun stream ->
        let res = p stream
        match res.Status with
            | Ok -> Reply(res.Result)
            | _ -> res


let naturalNum = purify (many1Chars digit) >>= toInt <?> "natural number"
1 голос
/ 29 мая 2019

Я думаю, что корень проблемы здесь в том, что это несинтаксическая проблема, которая не вписывается в модель прогнозирующего синтаксического анализатора.Если бы вы могли выразить «слишком много цифр» синтаксическим способом, это имело бы смысл и для синтаксического анализатора, но вместо этого он вернется назад и попытается потреблять больше входных данных.Поэтому я думаю, что самым чистым решением было бы сделать преобразование int в отдельный проход после синтаксического анализа.

При этом FParsec кажется достаточно гибким, чтобы вы все равно могли его взломать вместе.Это делает то, что вы спрашиваете, я думаю:

let naturalNum: Parser<int, _> =
    fun stream ->
        let reply = many1Chars digit stream
        match reply.Status with
            | Ok ->
                match Int32.TryParse(reply.Result) with
                | (true, n) -> Reply(n)
                | _         -> Reply(Error, messageError "Number must be below 2147483648")                
            | _ ->
                Reply(Error, reply.Error)

Или, если вам нужно сообщение об ошибке «натуральное число» вместо «десятичной цифры», замените последнюю строку на:

Reply(Error, messageError "Expecting: natural number")
...