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"
将产生输出:
结果错误:['+'; ''; '*'; ''']
Ln 中的错误:1 列:6
2+3;2*4;3*4;3+3
^
期待:'+'
Ln 中的错误:1 列:14
2+3;2*4;3*4;3+3
^
期待:'*'
更新:
嗯,我以为您想从致命错误中恢复,以便收集多个错误消息并可能产生部分结果。例如,对于语法高亮或允许您的用户一次修复多个错误很有用的东西。
您的更新似乎表明您只想在出现解析器错误时忽略部分输入,这要简单得多:
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