为了在以后的步骤中创建更好的错误消息,我想保存解析器成功的位置以及文本。获取位置似乎非常容易(因为有getPosition
解析器),但是我不知道如何访问文本。
假设我使用这种类型保存位置
type SourceLocation = {
from: Position
to: Position
text: string
}
我想创建一个函数,该函数将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
由于解析器只是接受CharStream
的函数,我以为可以将流与Index
一起从我的位置使用来获取文本,但是我没有看到获取此文本的方法。
那么获取解析器成功的文本的正确方法是什么?
答案 0 :(得分:5)
我认为您可能想要的是CharStream.ReadFrom
method:
返回介于
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)
用法示例(也恰好是我编写以确认其有效的测试代码):
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";}
编辑:Stephan Tolksdorf(FParsec的作者)在评论中指出withSkippedString组合器存在。那可能会更简单,因为您不必自己编写消耗CharStream
的函数。 (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
(我对这里的元组排列不满意,因为它会导致一个元组内的一个元组内的一个元组。不同的组合器顺序可能会产生更好的签名。但是,由于它不是一个内部函数对于公共消费而言,在函数签名中嵌套一个讨厌的元组可能没什么大不了的,所以我将其保留原样。如果您想要更好的函数签名,则可以重新安排它。)
在此功能的更新版本中,原始答案中的相同测试代码可以正常运行,并输出相同的结果:开始位置(第1行,第1行),结束位置(第1行,第4行)和已解析的文本"3.5"
。