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 * 3
harus 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.)
sumber
x+y+z
.+
, jadix+y+z
memang ambigu karenanya salah.Jawaban:
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.
sumber
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.
sumber
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).
sumber