Memilah tata bahasa bebas konteks yang sewenang-wenang, sebagian besar cuplikan singkat

20

Saya ingin mengurai bahasa khusus domain yang ditentukan pengguna. Bahasa-bahasa ini biasanya dekat dengan notasi matematika (saya tidak menguraikan bahasa alami). Pengguna menentukan DSL mereka dalam notasi BNF, seperti ini:

expr ::= LiteralInteger
       | ( expr )
       | expr + expr
       | expr * expr

Input suka 1 + ( 2 * 3 )harus diterima, sedangkan input suka 1 +harus ditolak sebagai salah, dan input suka 1 + 2 * 3harus ditolak sebagai ambigu.

Kesulitan utama di sini adalah mengatasi tata bahasa yang ambigu dengan cara yang ramah pengguna. Membatasi tata bahasa agar tidak ambigu bukanlah pilihan: begitulah bahasa - idenya adalah bahwa penulis lebih suka menghilangkan tanda kurung ketika mereka tidak perlu untuk menghindari ambiguitas. Selama sebuah ekspresi tidak ambigu, saya perlu menguraikannya, dan jika tidak, saya perlu menolaknya.

Pengurai saya harus bekerja pada tata bahasa bebas konteks apa pun, bahkan yang ambigu, dan harus menerima semua input yang tidak ambigu. Saya membutuhkan pohon parse untuk semua input yang diterima. Untuk input yang tidak valid atau ambigu, saya idealnya menginginkan pesan kesalahan yang bagus, tetapi untuk memulainya saya akan mengambil apa yang bisa saya dapatkan.

Saya biasanya akan meminta parser pada input yang relatif pendek, dengan input yang lebih lama sesekali. Jadi algoritma asimptotically lebih cepat mungkin bukan pilihan terbaik. Saya ingin mengoptimalkan distribusi sekitar 80% input kurang dari 20 simbol, 19% antara 20 dan 50 simbol, dan 1% input lebih jarang. Kecepatan untuk input yang tidak valid bukan masalah utama. Selanjutnya, saya mengharapkan modifikasi DSL sekitar setiap 1000 hingga 100000 input; Saya dapat menghabiskan beberapa detik untuk memproses kembali tata bahasa saya, bukan beberapa menit.

Algoritma penguraian apa yang harus saya selidiki, mengingat ukuran input tipikal saya? Haruskah pelaporan kesalahan menjadi faktor dalam pilihan saya, atau haruskah saya berkonsentrasi pada penguraian input yang tidak ambigu dan mungkin menjalankan pengurai yang benar-benar terpisah dan lebih lambat untuk memberikan umpan balik kesalahan?

(Dalam proyek di mana saya membutuhkannya (beberapa waktu lalu), saya menggunakan CYK , yang tidak terlalu sulit untuk diterapkan dan bekerja secara memadai untuk ukuran input saya tetapi tidak menghasilkan kesalahan yang sangat bagus.)

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Terutama laporan kesalahan yang baik tampaknya sulit dicapai. Anda mungkin memiliki lebih dari satu perubahan lokal yang mengarah ke input yang diterima jika terjadi tata bahasa yang ambigu.
Raphael
Saya baru saja menjawab di bawah ini. Agak canggung untuk menjawab modifikasi pertanyaan lama yang sudah memiliki jawaban yang diterima dengan baik. Jelas saya tidak seharusnya menjawab dengan cara yang sama, tetapi pengguna akan membaca kedua jawaban seolah-olah mereka menjawab pertanyaan yang sama.
babou
Apakah Anda benar-benar mengharapkan pesan kesalahan untuk input yang ambigu, jika pengguna menulis x+y+z.
babou
@ Babou Saya tidak mengubah pertanyaan, saya hanya menambahkan klarifikasi yang diminta dalam komentar (sekarang dihapus). Untuk tata bahasa kecil yang diberikan di sini, saya belum menentukan asosiatif untuk +, jadi x+y+zmemang ambigu karenanya salah.
Gilles 'SANGAT berhenti menjadi jahat'
Yah, itu adalah kalimat terakhir Anda, baru saja ditambahkan, meskipun di antara tanda kurung. Anda sepertinya berkata: Saya akhirnya melakukannya dengan CYK, tetapi tidak lagi memadai untuk beberapa alasan. Dan saya ingin tahu apa alasan tepatnya ... Anda, sekarang , adalah orang yang paling berpengalaman dengan jenis masalah Anda dan solusi yang Anda gunakan, jadi orang akan mengharapkan lebih banyak informasi dari Anda jika jawaban lebih lanjut diberikan.
babou

Jawaban:

19

Mungkin algoritma yang ideal untuk kebutuhan Anda adalah Generalized LL parsing , atau GLL. Ini adalah algoritma yang sangat baru (makalah ini diterbitkan pada 2010). Di satu sisi, itu adalah algoritma Earley yang ditambah dengan graph structured stack (GSS), dan menggunakan LL (1) lookahead.

Algoritma ini sangat mirip dengan LL (1) yang lama, kecuali ia tidak menolak tata bahasa jika bukan LL (1): hanya mencoba semua parsing LL (1) yang mungkin. Ini menggunakan grafik diarahkan untuk setiap titik di parse, yang berarti bahwa jika keadaan parse yang telah ditangani sebelumnya, itu hanya menggabungkan dua simpul ini. Ini membuatnya cocok bahkan untuk tata bahasa kiri-rekursif, tidak seperti LL. Untuk detail yang pasti tentang cara kerja bagian dalamnya, bacalah kertasnya (kertas ini cukup mudah dibaca, meskipun sup label membutuhkan ketekunan).

Algoritma ini memiliki sejumlah keunggulan yang jelas yang relevan dengan kebutuhan Anda dibandingkan dengan algoritma penguraian umum lainnya (yang saya tahu). Pertama, implementasinya sangat mudah: Saya pikir hanya Earley yang lebih mudah diimplementasikan. Kedua, kinerja cukup baik: pada kenyataannya, itu menjadi sama cepatnya dengan LL (1) pada tata bahasa yang LL (1). Ketiga, memulihkan parse cukup mudah, dan memeriksa apakah ada lebih dari satu kemungkinan parse juga.

Keuntungan utama yang dimiliki GLL adalah bahwa ia didasarkan pada LL (1) dan oleh karena itu sangat mudah untuk dipahami dan didebug, ketika diimplementasikan, ketika merancang tata bahasa dan juga saat mengurai input. Selain itu, ini juga membuat penanganan kesalahan lebih mudah: Anda tahu persis di mana parses yang mungkin terlantar dan bagaimana mereka bisa berlanjut. Anda dapat dengan mudah memberikan kemungkinan parse pada titik kesalahan dan, katakanlah, 3 poin terakhir di mana parse terdampar. Anda mungkin memilih untuk mencoba memulihkan dari kesalahan, dan menandai produksi yang diuraikan oleh parse yang paling jauh sebagai 'selesai' untuk parse itu, dan lihat apakah parsing dapat dilanjutkan setelah itu (katakanlah seseorang lupa tanda kurung). Anda bahkan bisa melakukan itu untuk, katakanlah, 5 parses yang mendapat jarak terjauh.

Satu-satunya downside ke algoritma adalah bahwa itu baru, yang berarti tidak ada implementasi mapan tersedia. Ini mungkin bukan masalah bagi Anda - saya sudah menerapkan algoritma sendiri, dan itu cukup mudah dilakukan.

Alex ten Brink
sumber
Senang belajar sesuatu yang baru. Ketika saya membutuhkan ini (beberapa tahun yang lalu, dalam proyek yang ingin saya hidupkan kembali suatu hari), saya menggunakan CYK, terutama karena itu adalah algoritma pertama yang saya temukan. Bagaimana GLL menangani input yang ambigu? Artikel itu sepertinya tidak membahas hal ini, tetapi saya hanya menskilnya.
Gilles 'SO- stop being evil'
@Gilles: itu membangun tumpukan terstruktur grafik, dan semua (berpotensi banyak secara eksponensial) diuraikan secara ringkas dalam grafik ini, mirip dengan cara kerja GLR. Jika saya ingat dengan benar, makalah yang disebutkan dalam cstheory.stackexchange.com/questions/7374/… membahas hal ini.
Alex ten Brink
@Gilles Pengurai 2010 ini sepertinya harus diprogram dengan tangan dari tata bahasa, tidak terlalu memadai jika Anda memiliki beberapa bahasa, atau jika Anda sering memodifikasi bahasa. Teknik untuk pembangkitan otomatis dari tata bahasa parser umum yang mengikuti strategi apa pun yang dipilih (LL, LR atau lainnya) dan menghasilkan hutan dari semua parse telah dikenal selama sekitar 40 tahun. Namun ada masalah tersembunyi mengenai kompleksitas dan pengorganisasian grafik yang mewakili parse. Jumlah parse mungkin lebih buruk daripada eksponensial: tak terbatas. Pemulihan kesalahan dapat menggunakan teknik independen yang lebih sistematis, parser.
babou
Bagaimana GLL terkait dengan LL (*) ditemukan di ANTLR?
Raphael
6

Perusahaan saya (Desain Semantik) telah menggunakan parser GLR dengan sangat sukses untuk melakukan persis apa yang disarankan OP dalam parsing kedua bahasa khusus domain, dan parsing bahasa pemrograman "klasik", dengan Perangkat Rekayasa Ulang Perangkat Lunak DMS kami. Ini mendukung transformasi program sumber-ke-sumber yang digunakan untuk restrukturisasi program skala besar / reverse engineering / pembuatan kode maju. Ini termasuk perbaikan otomatis kesalahan sintaks dengan cara yang cukup praktis. Menggunakan GLR sebagai dasar, dan beberapa perubahan lainnya (predikat semantik, input token-set daripada input token, ...) kami telah berhasil membuat parser untuk sekitar 40 bahasa.

Sama pentingnya dengan kemampuan untuk menguraikan contoh bahasa penuh, GLR juga telah terbukti sangat berguna dalam mem-parsing aturan penulisan ulang sumber-ke-sumber . Ini adalah fragmen program dengan konteks yang jauh lebih sedikit daripada program penuh, dan dengan demikian umumnya memiliki lebih banyak ambiguitas. Kami menggunakan anotasi khusus (misalnya, bersikeras bahwa frasa sesuai dengan tata bahasa spesifik nonterminal) untuk membantu menyelesaikan ambiguitas tersebut selama / setelah menguraikan aturan. Dengan mengatur mesin parsing GLR dan alat-alat di sekitarnya, kami mendapatkan parser untuk menulis ulang aturan menjadi "gratis" setelah kami memiliki parser untuk bahasanya. Mesin DMS memiliki applier aturan penulisan ulang bawaan yang kemudian dapat digunakan untuk menerapkan aturan ini untuk melakukan perubahan kode yang diinginkan.

Mungkin hasil kami yang paling spektakuler adalah kemampuan untuk mengurai C ++ 14 penuh , terlepas dari semua ambiguitas, menggunakan tata bahasa bebas konteks sebagai dasar. Saya perhatikan bahwa semua kompiler C ++ klasik (GCC, Clang) telah melepaskan kemampuan untuk melakukan ini dan menggunakan parser tulisan tangan (yang IMHO membuat mereka jauh lebih sulit untuk dipelihara, tetapi kemudian, itu bukan masalah saya). Kami telah menggunakan mesin ini untuk melakukan perubahan besar pada arsitektur sistem C ++ besar.

Dari segi kinerja, parser GLR kami cukup cepat: puluhan ribu baris per detik. Ini jauh di bawah keadaan seni, tetapi kami tidak melakukan upaya serius untuk mengoptimalkan ini, dan beberapa hambatan dalam pemrosesan aliran karakter (Unicode penuh). Untuk membuat parser seperti itu, kami memproses dahulu tata bahasa bebas konteks menggunakan sesuatu yang cukup dekat dengan generator parser LR (1); ini biasanya berjalan pada workstation modern dalam sepuluh detik pada tata bahasa besar seukuran C ++. Anehnya, untuk bahasa yang sangat kompleks seperti COBOL dan C ++ modern, generasi lexers ternyata membutuhkan waktu sekitar satu menit; beberapa DFA yang didefinisikan melalui Unicode menjadi sangat berbulu. Saya baru saja melakukan Ruby (dengan subgrammar penuh untuk regexps yang luar biasa) sebagai latihan jari; DMS dapat memproses lexer dan tata bahasa bersama dalam waktu sekitar 8 detik.

Ira Baxter
sumber
@Raphael: Tautan "perubahan besar" menunjuk ke sekumpulan makalah teknis gaya akademik, termasuk beberapa di rekayasa ulang arsitektur C ++, satu di mesin DMS itu sendiri (agak tua tapi menggambarkan dasar-dasarnya dengan baik), dan satu di topik eksotis penangkapan desain dan penggunaan kembali, yang merupakan motivasi asli untuk DMS (masih belum tercapai, sayangnya, tapi DMS ternyata cukup berguna).
Ira Baxter
1

Ada banyak parser bebas konteks umum yang dapat mengurai kalimat ambigu (menurut tata bahasa yang ambigu). Mereka datang dengan berbagai nama, terutama pemrograman dinamis atau parser bagan. Yang paling terkenal, dan paling sederhana, mungkin adalah parser CYK yang telah Anda gunakan. Generalitas itu diperlukan karena Anda harus menangani beberapa parse dan mungkin tidak tahu sampai akhir apakah Anda berurusan dengan ambiguitas atau tidak.

Dari apa yang Anda katakan, saya akan berpikir bahwa CYK bukanlah pilihan yang buruk. Anda mungkin tidak memiliki banyak keuntungan dengan menambahkan prediktif (LL atau LR), dan mungkin sebenarnya memiliki biaya dengan membedakan perhitungan yang harus digabungkan daripada didiskriminasi (terutama dalam kasus LR). Mereka juga mungkin memiliki biaya yang sesuai dalam ukuran hutan parse yang dihasilkan (yang mungkin memiliki peran dalam kesalahan ambiguitas). Sebenarnya, sementara saya tidak yakin bagaimana membandingkan secara formal kecukupan dari algoritma yang lebih canggih, saya tahu bahwa CYK memang memberikan pembagian komputasi yang baik.

Sekarang, saya tidak percaya ada banyak literatur tentang parser CF umum untuk tata bahasa yang ambigu yang seharusnya hanya menerima input yang tidak ambigu. Saya tidak ingat melihat apa pun, mungkin karena bahkan untuk dokumen teknis, atau bahkan bahasa pemrograman, ambiguitas sintaksis dapat diterima asalkan dapat diselesaikan dengan cara lain (misalnya ambiguitas dalam ekspresi ADA).

Saya sebenarnya bertanya-tanya mengapa Anda ingin mengubah algoritme Anda, daripada tetap berpegang pada apa yang Anda miliki. Itu mungkin membantu saya memahami perubahan seperti apa yang paling membantu Anda. Apakah ini masalah kecepatan, apakah itu representasi parse, atau apakah itu deteksi kesalahan dan pemulihan?

Cara terbaik untuk mewakili banyak parse adalah dengan hutan bersama, yang hanya merupakan tata bahasa bebas konteks yang hanya menghasilkan input Anda, tetapi dengan semua pohon parse yang sama persis dengan tata bahasa DSL. Itu membuatnya sangat mudah untuk dipahami dan diproses. Untuk lebih jelasnya, saya sarankan Anda melihat jawaban yang saya berikan di situs linguistik. Saya mengerti bahwa Anda tidak tertarik untuk mendapatkan hutan parse, tetapi representasi yang tepat dari hutan parse dapat membantu Anda memberikan pesan yang lebih baik tentang masalah ambiguitas. Ini juga dapat membantu Anda memutuskan bahwa ambiguitas tidak penting dalam beberapa kasus (asosiatif) jika Anda ingin melakukannya.

Anda menyebutkan batasan waktu pemrosesan tata bahasa DSL Anda, tetapi tidak memberikan petunjuk tentang ukurannya (yang tidak berarti bahwa saya bisa menjawab dengan angka-angka yang Anda lakukan).

Beberapa pemrosesan kesalahan dapat diintegrasikan dalam algoritma CF umum ini dengan cara sederhana. Tapi saya perlu memahami apa jenis pengolahan kesalahan yang Anda harapkan lebih afirmatif. Anda punya beberapa contoh.

Saya agak tidak nyaman untuk mengatakan lebih banyak, karena saya tidak mengerti apa sebenarnya motivasi dan kendala Anda. Atas dasar apa yang Anda katakan, saya akan tetap menggunakan CYK (dan saya tahu algoritma lainnya dan beberapa propertinya).

babou
sumber