使用FParsec进行基本错误恢复(Basic error recovery with FParsec)

系统教程 行业动态 更新时间:2024-06-14 16:57:18
使用FParsec进行基本错误恢复(Basic error recovery with FParsec)

假设我有这个解析器:

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: ['+'; '*'; '*']

现在, "2+3;2*4;3*4;3+3;" 会因2*4;左右的误差而失败2*4; 。 但是,如果我想要2*4;的误差,那么最佳做法是什么2*4; 和3+3; ? 基本上,我想扫描到最近的';' 但只有在有致命错误的情况下。 如果发生这种情况,我想汇总错误。

亲切的问候,Lasse Espeholt

更新: recoverWith是一个很好的解决方案,谢谢! 但鉴于:

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

我希望得到[]没有错误。 或者可能更“公平”:

let myProgram = (attempt (many1 (myStatement |> recoverWith '�'))) <|>% []

Assume I've 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 error around 2*4;. But what is best practice if I want both the error for 2*4; and 3+3;? Basically, I want to scan to the nearest ';' but only if there is a fatal error. And if that happens I want to aggregate the errors.

Kind regards, Lasse Espeholt

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

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

I would expect to get [] with no errors. Or maybe a bit more "fair":

let myProgram = (attempt (many1 (myStatement |> recoverWith '�'))) <|>% []

最满意答案

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

FParsec has no built-in support for recovering from fatal parser errors that would allow you to obtain partial parser results and collect errors from multiple positions. However, it's pretty easy to define a custom combinator function for this purpose.

For example, to recover from errors in your simple statement parser you could 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

You could then 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" would yield the output:

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 recover from a fatal error in order to collect multiple error messages and maybe produce a partial result. Something that would for example be useful for syntax highlighting or allowing 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:

The following is a version of recoverWith that doesn't aggregate errors and only tries to recover from an error if the argument parser consumed input (or changed the parser state 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

更多推荐

本文发布于:2023-04-12 20:38:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/dzcp/ea676ae5a8385ba203a5af44f05da884.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:错误   FParsec   Basic   recovery   error

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!