Fix basic errors with FParsec

Suppose I have this parser:

let test p str = match run p str with | Success(result, _, _) -> printfn "Success: %A" result | Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg let myStatement = choice (seq [ pchar '2' >>. pchar '+' .>> pchar '3' .>> pchar ';'; pchar '3' >>. pchar '*' .>> pchar '4' .>> pchar ';'; ]) let myProgram = many myStatement test myProgram "2+3;3*4;3*4;" // Success: ['+'; '*'; '*'] 

Now "2+3;2*4;3*4;3+3;" will fail with an error of about 2*4; . But what is the best if I need an error for 2*4; and 3+3; ? Basically, I want to scan to the nearest ';' but only in case of a fatal error. And if that happens, I want to collect errors.

Regards, Lasse Espeholt

Update: recoverWith is a nice solution, thanks! But considering:

 let myProgram = (many1 (myStatement |> recoverWith ' ')) <|>% [] test myProgram "monkey" 

I expect to get [] without errors. Or maybe a little more "fair":

 let myProgram = (attempt (many1 (myStatement |> recoverWith ' '))) <|>% [] 
+4
source share
1 answer

FParsec does not have built-in support for recovering from fatal parser errors, which will allow you to get partial parser results and collect errors from multiple positions. However, for this purpose, it is fairly easy to define a custom combinatorial function.

For example, to recover errors in your simple parser, you can define the following recoverWith combinator:

 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 

Then you can use this combinator as follows:

 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) 

Testing with test myProgram "2+3;2*4;3*4;3+3" will lead to the conclusion:

  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: '*'

Update:

Hmm, I thought you wanted to repair a fatal error in order to collect some error messages and possibly produce a partial result. Something that, for example, would be useful for syntax highlighting or would allow your users to fix more than one error at a time.

Your update seems to suggest that you just want to ignore parts of the input in case of a parser error, which is much simpler:

 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 <> ' ') 

Update 2:

Below is the recoverWith version, which does not combine errors and only tries to recover from errors if the argument parser consumed input (or changed the state of the analyzer in any other way):

 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 
+8
source

Source: https://habr.com/ru/post/1396025/


All Articles