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;?基本上,我想扫描到最近的';' 但前提是存在致命错误。如果发生这种情况,我想汇总错误。

亲切的问候, Lasse Espeholt

更新: 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 列: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
于 2012-02-12T14:48:55.077 回答