FParsec не имеет встроенной поддержки для восстановления после фатальных ошибок синтаксического анализатора, которые позволили бы вам получать частичные результаты синтаксического анализа и собирать ошибки с нескольких позиций. Однако для этой цели довольно просто определить пользовательскую функцию комбинатора.
Например, чтобы исправить ошибки в вашем простом парсере операторов, вы можете определить следующий recoverWith
комбинатор:
open FParsec
type UserState = {
Errors: (string * ParserError) list
} with
static member Create() = {Errors = []}
type Parser<'t> = Parser<'t, UserState>
// recover from error by skipping to the char after the next newline or ';'
let recoverWith errorResult (p: Parser<_>) : Parser<_> =
fun stream ->
let stateTag = stream.StateTag
let mutable reply = p stream
if reply.Status <> Ok then // the parser failed
let error = ParserError(stream.Position, stream.UserState, reply.Error)
let errorMsg = error.ToString(stream)
stream.SkipCharsOrNewlinesWhile(fun c -> c <> ';' && c <> '\n') |> ignore
stream.ReadCharOrNewline() |> ignore
// To prevent infinite recovery attempts in certain situations,
// the following check makes sure that either the parser p
// or our stream.Skip... commands consumed some input.
if stream.StateTag <> stateTag then
let oldErrors = stream.UserState.Errors
stream.UserState <- {Errors = (errorMsg, error)::oldErrors}
reply <- Reply(errorResult)
reply
Вы можете использовать этот комбинатор следующим образом:
let myStatement =
choice [
pchar '2' >>. pchar '+' .>> pchar '3' .>> pchar ';'
pchar '3' >>. pchar '*' .>> pchar '4' .>> pchar ';'
]
let myProgram =
many (myStatement |> recoverWith '�') .>> eof
let test p str =
let printErrors (errorMsgs: (string * ParserError) list) =
for msg, _ in List.rev errorMsgs do
printfn "%s" msg
match runParserOnString p (UserState.Create()) "" str with
| Success(result, {Errors = []}, _) -> printfn "Success: %A" result
| Success(result, {Errors = errors}, _) ->
printfn "Result with errors: %A\n" result
printErrors errors
| Failure(errorMsg, error, {Errors = errors}) ->
printfn "Failure: %s" errorMsg
printErrors ((errorMsg, error)::errors)
Тестирование с test myProgram "2+3;2*4;3*4;3+3"
даст результат:
Result with errors: ['+'; '�'; '*'; '�']
Error in Ln: 1 Col: 6
2+3;2*4;3*4;3+3
^
Expecting: '+'
Error in Ln: 1 Col: 14
2+3;2*4;3*4;3+3
^
Expecting: '*'
Обновление:
Хм, я думал, что вы хотите восстановиться после фатальной ошибки, чтобы собрать несколько сообщений об ошибках и, возможно, получить частичный результат. Например, что-то, что было бы полезно для подсветки синтаксиса или позволяло пользователям исправлять более одной ошибки одновременно.
Похоже, ваше обновление предполагает, что вы просто хотите игнорировать части ввода в случае ошибки синтаксического анализатора, которая намного проще:
let skip1ToNextStatement =
notEmpty // requires at least one char to be skipped
(skipManySatisfy (fun c -> c <> ';' && c <> '\n')
>>. optional anyChar) // optional since we might be at the EOF
let myProgram =
many (attempt myStatement <|> (skip1ToNextStatement >>% '�'))
|>> List.filter (fun c -> c <> '�')
Обновление 2:
Ниже приведена версия recoverWith
, которая не агрегирует ошибки и пытается восстановить ее только после того, как анализатор аргументов использовал входные данные (или изменил состояние анализатора любым другим способом):
let recoverWith2 errorResult (p: Parser<_>) : Parser<_> =
fun stream ->
let stateTag = stream.StateTag
let mutable reply = p stream
if reply.Status <> Ok && stream.StateTag <> stateTag then
stream.SkipCharsOrNewlinesWhile(fun c -> c <> ';' && c <> '\n') |> ignore
stream.ReadCharOrNewline() |> ignore
reply <- Reply(errorResult)
reply