6

私がこのパーサーを持っていると仮定します:

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;しかし、との両方のエラーが必要な場合のベスト プラクティスは何3+3;ですか? 基本的には一番近い「;」までスキャンしたい ただし、致命的なエラーが発生した場合のみ。その場合は、エラーを集計したいと思います。

敬具、 ラッセ・エスペホルト

更新: recoverWithいい解決策です、ありがとう! しかし、与えられた:

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

test myProgram "monkey"

[]エラーなしで取得できると思います。または、もう少し「公平」に:

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

1 に答える 1

12

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
于 2012-02-12T14:48:55.077 に答える