假设我有这个解析器:
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 '�'))) <|>% []
答案 0 :(得分:8)
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