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 Col: 6
2+3;2*4;3*4;3+3
^
期待: '+'
Ln のエラー: 1 Col: 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