FParsec: Как сохранить текст, на котором преуспевает парсер - PullRequest
0 голосов
/ 25 июня 2018

Чтобы создать более качественные сообщения об ошибках на более позднем этапе, я хочу сохранить позиции, на которых выполняется анализатор, а также текст. Получить позиции кажется довольно легко (так как есть парсер 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 из своих мест для получения текста, но я не видел способа получить этот текст.

Так, как правильно получить текст, на котором преуспевает парсер?

1 Ответ

0 голосов
/ 25 июня 2018

Я думаю, что вам, вероятно, нужен метод 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".

...