Я думаю, что вам, вероятно, нужен метод 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)
Пример использования (который также является тестовым кодом, который янаписал, чтобы подтвердить, что это работает):
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";}
Редактировать: Стефан Толксдорф (автор 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
(Я не на 100% доволен размещением кортежей здесь, так как это приводит к кортежу внутри кортежа в кортеже.Другой порядок комбинаторов может дать более приятную сигнатуру, но поскольку это внутренняя функция, не предназначенная для общего пользования, скверное вложение кортежей в сигнатуру функции может не иметь большого значения, поэтому я оставил все как есть.переставить его, если вы хотите улучшить сигнатуру функции).
Тот же тестовый код из моего исходного ответа отлично работает с этой обновленной версией функции и печатает тот же результат: начальная позиция (строка 1, столбец 1), конечная позиция (строка 1, столбец 4) и проанализированный текст "3.5"
.