Apakah parsing dan lexing terpisah melewati praktik yang baik dengan combinator parser?

18

Ketika saya mulai menggunakan kombinator parser, reaksi pertama saya adalah rasa pembebasan dari apa yang terasa seperti perbedaan buatan antara parsing dan lexing. Tiba-tiba semuanya hanya parsing!

Namun, saya baru-baru ini menemukan posting ini di codereview.stackexchange menggambarkan seseorang mengembalikan perbedaan ini. Pada awalnya saya pikir ini sangat konyol bagi mereka, tetapi kemudian fakta bahwa fungsi ada di Parsec untuk mendukung perilaku ini membuat saya mempertanyakan diri saya sendiri.

Apa keuntungan / kerugian dari penguraian pada aliran yang sudah lexed di combinator parser?

Eli Frey
sumber
Tolong bisakah seseorang menambahkan tag [parser-combinator]?
Eli Frey

Jawaban:

15

Dalam penguraian, kami paling sering memahami analisis bahasa bebas konteks. Bahasa bebas konteks lebih kuat daripada bahasa biasa, karenanya parser dapat (paling sering) melakukan pekerjaan penganalisa leksikal dengan segera.

Tapi, ini a) cukup tidak wajar b) sering tidak efisien.

Untuk a), jika saya berpikir tentang bagaimana misalnya ifekspresi terlihat, saya pikir JIKA expr THEN expr ELSE expr dan bukan 'i' 'f', mungkin beberapa spasi, maka karakter apa pun yang dapat dimulai dengan ekspresi, dll. Anda mendapatkan ide.

Untuk b) ada alat yang kuat yang melakukan pekerjaan yang sangat baik untuk mengenali entitas leksikal, seperti pengidentifikasi, literal, kurung dari semua jenis, dll. Mereka akan melakukan pekerjaan mereka dalam waktu singkat dan memberi Anda antarmuka yang bagus: daftar token. Jangan khawatir tentang melewatkan spasi di parser lagi, parser Anda akan jauh lebih abstrak ketika berurusan dengan token dan bukan dengan karakter.

Lagi pula, jika Anda pikir seorang parser harus sibuk dengan hal-hal tingkat rendah, mengapa kemudian memproses karakter? Orang bisa menulisnya juga pada level bit! Anda lihat, parser seperti itu yang bekerja pada level bit akan hampir tidak bisa dipahami. Itu sama dengan karakter dan token.

Hanya 2 sen saya.

Ingo
sumber
3
Hanya demi presisi: pengurai selalu dapat melakukan pekerjaan penganalisa leksikal.
Giorgio
Juga, mengenai efisiensi: Saya tidak yakin apakah parser akan kurang efisien (lebih lambat). Saya berharap bahwa tata bahasa yang dihasilkan akan mengandung sub-tata bahasa yang menggambarkan bahasa reguler, dan kode untuk sub-tata bahasa itu akan secepat penganalisa leksikal yang sesuai. IMO intinya adalah (a): betapa alami, intuitifnya bekerja dengan parser yang lebih sederhana dan lebih abstrak.
Giorgio
@Giorgio - Mengenai komentar pertama Anda: Anda benar. Apa yang ada dalam pikiran saya di sini adalah kasus-kasus di mana lexer secara pragmatis melakukan beberapa pekerjaan yang membuat tata bahasa lebih mudah, sehingga seseorang dapat menggunakan LALR (1) daripada LALR (2).
Ingo
2
Saya telah menghapus penerimaan saya atas jawaban Anda setelah percobaan dan refleksi lebih lanjut. Sepertinya kalian berdua datang dari dunia Antlr et all. Mempertimbangkan sifat kelas pertama dari parser combinator, saya sering hanya mendefinisikan parser pembungkus untuk parser token saya meninggalkan setiap token sebagai satu nama dalam lapisan parser parser. misalnya contoh if Anda akan terlihat seperti if = string "if" >> expr >> string "then" >> expr >> string "else" >> expr.
Eli Frey
1
Kinerja masih merupakan pertanyaan terbuka, saya akan melakukan beberapa tolok ukur.
Eli Frey
8

Semua orang menyarankan bahwa memisahkan lexing dan parsing adalah "praktik yang baik" - saya harus tidak setuju - dalam banyak kasus melakukan lexing dan parsing dalam sekali jalan memberikan lebih banyak kekuatan, dan implikasi kinerja tidak seburuk yang disajikan dalam jawaban lain (lihat Packrat ).

Pendekatan ini bersinar ketika seseorang harus mencampur sejumlah bahasa yang berbeda dalam aliran input tunggal. Ini tidak hanya dibutuhkan oleh bahasa berorientasi metaprogramming yang aneh seperti Katahdin dan sejenisnya , tetapi untuk aplikasi yang lebih umum juga, seperti pemrograman literasi (pencampuran lateks dan, katakanlah, C ++), menggunakan HTML dalam komentar, memasukkan Javascript ke dalam HTML, dan begitu seterusnya.

Logika SK
sumber
Dalam jawaban saya, saya menyarankan bahwa ini adalah "praktik yang baik dalam konteks tertentu" dan bukan bahwa itu adalah "praktik yang lebih baik dalam semua konteks".
Giorgio
5

Alat analisis leksikal mengenali bahasa biasa dan parser mengenali bahasa bebas konteks. Karena setiap bahasa reguler juga bebas konteks (dapat didefinisikan oleh apa yang disebut tata bahasa linier-kanan ), pengurai juga dapat mengenali bahasa biasa dan perbedaan antara penganalisa parser dan leksikal tampaknya menambah beberapa kompleksitas yang tidak perlu: satu konteks tunggal tata bahasa gratis (parser) bisa melakukan pekerjaan parser dan penganalisa leksikal.

Di sisi lain, dapat berguna untuk menangkap beberapa elemen dari bahasa bebas konteks melalui bahasa reguler (dan karena itu penganalisa leksikal) karena

  1. Seringkali elemen-elemen ini muncul begitu sering sehingga mereka dapat ditangani dengan cara standar: mengenali angka dan string literal, kata kunci, pengidentifikasi, melewatkan ruang putih, dan sebagainya.
  2. Mendefinisikan bahasa token secara teratur membuat tata bahasa bebas konteks yang dihasilkan menjadi lebih mudah, misalnya seseorang dapat beralasan dalam hal pengidentifikasi, bukan dalam hal karakter individu, atau seseorang dapat mengabaikan ruang putih sepenuhnya jika itu tidak relevan untuk bahasa tertentu.

Jadi memisahkan parsing dari analisis leksikal memiliki keuntungan bahwa Anda dapat bekerja dengan tata bahasa bebas konteks yang lebih sederhana dan merangkum beberapa tugas dasar (sering rutin) dalam penganalisa leksikal (divide et impera).

EDIT

Saya tidak terbiasa dengan kombinator parser jadi saya tidak yakin bagaimana pertimbangan di atas berlaku dalam konteks itu. Kesan saya adalah bahwa bahkan jika dengan kombinator parser satu hanya memiliki satu tata bahasa bebas konteks, membedakan antara dua tingkat (analisis leksikal / parsing) dapat membantu membuat tata bahasa ini lebih modular. Seperti yang dikatakan, lapisan analisis leksikal yang lebih rendah dapat berisi parser dasar yang dapat digunakan kembali untuk pengidentifikasi, literal, dan sebagainya.

Giorgio
sumber
2
Lexemes jatuh ke dalam tata bahasa reguler bukan secara alami, tetapi dengan konvensi, karena semua lexers dibangun di atas mesin ekspresi reguler. Ini membatasi kekuatan ekspresif bahasa yang dapat Anda desain.
SK-logic
1
Bisakah Anda memberikan contoh bahasa yang sesuai untuk mendefinisikan leksem yang tidak dapat digambarkan sebagai bahasa biasa?
Giorgio
1
misalnya, dalam beberapa bahasa khusus domain yang saya buat, pengidentifikasi bisa berupa ekspresi TeX, yang menyederhanakan pencetakan kode, misalnya ekspresi seperti \alpha'_1 (K_0, \vec{T}), di mana \ alpha'_1, K_0 dan \ vec {T} adalah pengidentifikasi.
SK-logic
1
Diberi tata bahasa bebas konteks, Anda selalu dapat menggunakan N non-terminal dan memperlakukan kata-kata yang dapat diturunkan sebagai unit yang memiliki makna yang berguna dalam diri mereka sendiri (misalnya ekspresi, istilah, angka, pernyataan). Ini dapat dilakukan terlepas dari bagaimana Anda mengurai unit itu (parser, parser + lexer, dll). IMO pilihan parser + lexer lebih bersifat teknis (bagaimana mengimplementasikan parsing) daripada semantik (apa arti dari blok kode sumber yang Anda parse). Mungkin saya mengabaikan sesuatu tetapi dua aspek terlihat ortogonal bagi saya.
Giorgio
3
Jadi, saya setuju dengan Anda: jika Anda mendefinisikan beberapa blok bangunan dasar sembarang ( leksem ) dan ingin menggunakan penganalisa leksikal untuk mengenalinya, ini tidak selalu mungkin. Saya hanya ingin tahu apakah ini adalah tujuan dari seorang lexer. Sejauh yang saya mengerti, tujuan penganalisa leksikal lebih bersifat teknis: menghilangkan beberapa detail implementasi tingkat rendah yang membosankan dari pengurai.
Giorgio
3

Sederhananya, lexing dan parsing harus dipisahkan karena kompleksnya berbeda. Lexing adalah DFA (deterministic finite automaton) dan parser adalah PDA (push-down automaton). Ini berarti bahwa parsing secara inheren mengkonsumsi lebih banyak sumber daya daripada lexing, dan ada teknik optimasi khusus yang tersedia untuk DFA saja. Selain itu, menulis mesin keadaan terbatas jauh lebih kompleks, dan lebih mudah untuk diotomatisasi.

Anda menjadi boros dengan menggunakan algoritma parsing ke lex.

DeadMG
sumber
Jika Anda menggunakan parser untuk melakukan analisis leksikal, PDA tidak akan pernah menggunakan stack, itu pada dasarnya akan berfungsi sebagai DFA: hanya mengonsumsi input dan melompat antar negara. Saya tidak 100% yakin, tapi saya pikir teknik optimasi (mengurangi jumlah status) yang dapat diterapkan pada DFA juga dapat diterapkan pada PDA. Tapi ya: lebih mudah untuk menulis analisa leksikal seperti itu tanpa menggunakan alat yang lebih kuat, dan kemudian menulis parser yang lebih sederhana di atasnya.
Giorgio
Selain itu, itu membuat semuanya lebih fleksibel dan dapat dikelola. Sebagai contoh, misalkan kita memiliki parser untuk bahasa Haskell tanpa aturan tata letak (yaitu, dengan titik koma dan kurung kurawal). Jika kita memiliki lexer yang terpisah, kita sekarang bisa menambahkan aturan tata letak dengan hanya melakukan pass token lain, menambahkan kawat gigi dan titik koma sesuai kebutuhan. Atau, untuk contoh yang lebih mudah: misalkan kita mulai dengan bahasa yang mendukung karakter ASCII hanya dalam pengidentifikasi dan sekarang kami ingin mendukung huruf unicode dalam pengidentifikasi.
Ingo
1
@ Ingo, dan mengapa Anda perlu melakukannya di lexer terpisah? Keluarkan saja terminal-terminal itu.
SK-logic
1
@ SK-logic: Saya tidak yakin saya mengerti pertanyaan Anda. Mengapa lexer yang terpisah mungkin merupakan pilihan yang baik saya telah mencoba untuk membuktikan dalam posting saya.
Ingo
Giorgio, tidak. Tumpukan adalah komponen penting dari parser gaya LALR normal. Melakukan lexing dengan parser adalah pemborosan memori yang mengerikan (baik penyimpanan statis dan dialokasikan secara dinamis) dan akan jauh lebih lambat. Model Lexer / Parser efisien - gunakan saja :)
riwalk
1

Salah satu keuntungan utama parse / lex yang terpisah adalah representasi perantara - aliran token. Ini dapat diproses dengan berbagai cara yang tidak mungkin dilakukan dengan lex / parse gabungan.

Yang mengatakan, saya telah menemukan bahwa yang baik layak rekursif bisa lebih rumit dan lebih mudah untuk bekerja dengan belajar beberapa generator parser, dan harus mencari cara untuk mengekspresikan kelemahan grammer dalam aturan generator parser.

sylvanaar
sumber
Bisakah Anda menjelaskan lebih lanjut tentang tata bahasa yang lebih mudah diekspresikan pada aliran prefabbed kemudian dilakukan pada waktu parse? Saya hanya memiliki pengalaman menerapkan bahasa mainan dan sedikit format data, jadi mungkin saya telah melewatkan sesuatu. Pernahkah Anda memperhatikan adanya karakteristik kinerja antara RD parser / lex combo yang digulung dengan tangan dan generator yang diberi BNF?
Eli Frey