Pertanyaan FParsec: Bagaimana cara menyimpan teks di mana parser berhasil


Untuk membuat pesan kesalahan yang lebih baik dalam langkah berikutnya saya ingin menyimpan posisi di mana parser berhasil serta teks. Mendapatkan posisi tampaknya cukup mudah (karena memang ada getPosition parser), tetapi saya tidak tahu bagaimana saya bisa mengakses teks.

Katakanlah saya memiliki tipe ini untuk menyimpan lokasi

type SourceLocation = {
    from: Position
    to: Position
    text: string
}

dan saya ingin membuat fungsi, yang menambahkan a SourceLocation ke hasil parser lain:

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

Karena parser hanya berfungsi mengambil CharStream Saya pikir saya bisa menggunakan aliran bersama dengan Index dari lokasi saya untuk mendapatkan teks, tetapi saya tidak melihat metode untuk mendapatkan teks ini.

Jadi, apa cara yang benar untuk mendapatkan teks yang mana parser berhasil?


5
2018-06-25 06:28


asal


Jawaban:


Saya pikir apa yang mungkin Anda inginkan adalah CharStream.ReadFrom metode:

Mengembalikan string dengan karakter antara indeks stateWhereStringBegins (inklusif) dan arus Index aliran (eksklusif).

Apa yang akan Anda lakukan adalah ini:

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)

Contoh penggunaan (yang juga merupakan kode pengujian yang saya tulis untuk memastikan bahwa ini berfungsi):

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";}

Edit: Stephan Tolksdorf (penulis FParsec) menunjukkan dalam komentar bahwa denganSkippedString kombinator ada. Yang itu mungkin akan lebih sederhana, karena Anda tidak perlu menulis CharStream-Mengambil fungsi sendiri. (Itu skipped kombinator akan mengembalikan string yang cocok dengan parser, tetapi tanpa mengembalikan hasil parser, sedangkan withSkippedString melewati kedua hasil parser dan string dilewatkan ke fungsi yang Anda berikan). Dengan menggunakan withSkippedString kombinator, Anda dapat menggunakan dokumen asli Anda trackLocation berfungsi hanya dengan perubahan minimal. Versi terbaru dari trackLocation akan terlihat seperti ini:

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

(Saya tidak 100% senang dengan pengaturan tupel di sini, karena menghasilkan tuple dalam tuple dalam tuple. Urutan kombinator yang berbeda mungkin menghasilkan tanda tangan yang lebih baik. Tapi karena itu fungsi internal tidak dimaksudkan untuk konsumsi publik , tuple-nesting yang buruk dalam tanda tangan fungsi mungkin bukan masalah besar, jadi saya membiarkannya seperti itu. Terserah Anda untuk mengaturnya kembali jika Anda menginginkan tanda tangan fungsi yang lebih baik).

Kode pengujian yang sama dari jawaban asli saya berjalan baik dengan versi terbaru dari fungsi ini, dan mencetak hasil yang sama: posisi awal (Jalur 1, Kol 1), posisi akhir (Jalur 1, Kol 4), dan teks yang diuraikan "3.5".


5
2018-06-25 08:33