FParsec:如何保存解析器成功的文本

dan*_*iol 6 f# parsing fparsec

为了在后面的步骤中创建更好的错误消息,我想保存解析器成功的位置以及文本.获取位置似乎很容易(因为有getPosition解析器),但我不知道如何访问文本.

假设我有这种类型来保存位置

type SourceLocation = {
    from: Position
    to: Position
    text: string
}
Run Code Online (Sandbox Code Playgroud)

我想创建一个函数,它将SourceLocation另一个解析器的结果添加到:

let trackLocation (parser: Parser<'A, 'B>): Parser<SourceLocation * 'A, 'B> =
    let mkLocation ((start: Position, data: 'A), stop: Position: 'Positon) =
        let location = { from = start; to = stop }  // how do I get the text?
        in (location, data)
    getPosition .>>. parser .>>. getPositon |>> mkLocation
Run Code Online (Sandbox Code Playgroud)

由于解析器只是函数,CharStream我认为我可以将流与Index我的位置一起使用来获取文本,但我没有看到获取此文本的方法.

那么获取解析器成功的文本的正确方法是什么?

rmu*_*unn 5

我想你可能想要的是CharStream.ReadFrom方法:

返回一个字符串,其中stateWhereStringBegins包含(包含)索引和Index流的当前值(不包括)之间的字符.

你要做的是:

let trackLocation (parser: Parser<'A, 'B>): Parser<SourceLocation * 'A, 'B> =
    fun (stream : CharStream<'B>) ->
        let oldState = stream.State
        let parseResult = parser stream
        if parseResult.Status = Ok then
            let newState = stream.State
            let matchedText = stream.ReadFrom (oldState, true)
            // Or (oldState, false) if you DON'T want to normalize newlines
            let location = { from = oldState.GetPosition stream
                             ``to`` = newState.GetPosition stream
                             text = matchedText }
            let result = (location, parseResult.Result)
            Reply(result)
        else
            Reply(parseResult.Status, parseResult.Error)
Run Code Online (Sandbox Code Playgroud)

用法示例(也恰好是我为确认它有效而编写的测试代码):

let pThing = trackLocation pfloat
let test p str =
    match run p str with
    | Success((loc, result), _, _)   -> printfn "Success: %A at location: %A" result loc; result
    | Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg; 0.0
test pThing "3.5"
// Prints: Success: 3.5 at location: {from = (Ln: 1, Col: 1);
//                                    to = (Ln: 1, Col: 4);
//                                    text = "3.5";}
Run Code Online (Sandbox Code Playgroud)

编辑: Stephan Tolksdorf(FParsec的作者)在评论中指出withSkippedString组合子存在.那个可能会更简单,因为你不必CharStream自己编写-consuming函数.(skipped组合器将返回解析器匹配的字符串,但不返回解析器的结果,而withSkippedString将解析器的结果跳过的字符串传递给您提供的函数).通过使用withSkippedString组合器,您可以使用原始trackLocation功能,只需进行少量更改.更新版本trackLocation将如下所示:

let trackLocation (parser: Parser<'A, 'B>): Parser<SourceLocation * 'A, 'B> =
    let mkLocation ((start: Position, (text: string, data: 'A)), stop: Position) =
        let location = { from = start; ``to`` = stop; text = text }
        in (location, data)
    getPosition .>>. (parser |> withSkippedString (fun a b -> a,b)) .>>. getPosition |>> mkLocation
Run Code Online (Sandbox Code Playgroud)

(我对这里的元组排列不是100%满意,因为它会在元组中的元组内产生元组.不同的组合顺序可能会产生更好的签名.但是因为它是一个不用于公共消费的内部函数,功能签名中的一个令人讨厌的元组嵌套可能不是什么大问题,所以我按原样保留它.如果你想要一个更好的函数签名,由你来重新排列它.

我的原始答案中的相同测试代码可以使用此更新版本的函数运行,并打印相同的结果:开始位置(第1行,第1列),结束位置(第1行,第4列)和已解析的文本"3.5".