Dalam istilah awam, apa yang tersisa adalah rekursi?

12

Menurut satu halaman di code.google.com, "rekursi kiri" didefinisikan sebagai berikut:

Rekursi kiri hanya mengacu pada sembarang rekursif nonterminal yang, ketika menghasilkan bentuk sentensial yang mengandung dirinya sendiri, salinan baru itu sendiri muncul di sebelah kiri aturan produksi.

Wikipedia menawarkan dua definisi berbeda:

  1. Dalam hal tata bahasa bebas konteks, r non-terminal r kiri-rekursif jika simbol paling kiri di salah satu produksi r ('alternatif') baik langsung (langsung / langsung kiri-rekursif) atau melalui beberapa non-terminal lainnya definisi (tidak langsung / tersembunyi-rekursif kiri) ditulis ulang untuk r lagi.

  2. "Tata bahasa adalah rekursif kiri jika kita dapat menemukan beberapa non-terminal A yang pada akhirnya akan mendapatkan bentuk sentensial dengan dirinya sendiri sebagai simbol kiri."

Saya baru saja memulai dengan penciptaan bahasa di sini, dan saya melakukannya di waktu luang saya. Namun ketika turun untuk memilih parser bahasa, apakah rekursi kiri didukung oleh parser ini atau parser itu adalah masalah yang langsung muncul di depan dan tengah. Mencari istilah seperti "bentuk sentensial" hanya mengarah pada daftar jargon lebih lanjut, tetapi perbedaan rekursi "kiri" hampir harus menjadi sesuatu yang sangat sederhana. Tolong terjemahkan?

Panzercrisis
sumber

Jawaban:

21

Aturan Rdibiarkan rekursif jika, untuk mengetahui apakah Rkecocokan, Anda harus terlebih dahulu menemukan Rkecocokan. Ini terjadi ketika Rmuncul, secara langsung atau tidak langsung, sebagai istilah pertama dalam beberapa produksi itu sendiri.

Bayangkan versi mainan tata bahasa untuk ekspresi matematika, dengan hanya penambahan dan perkalian untuk menghindari gangguan:

Expression ::= Multiplication '+' Expression
            || Multiplication

Multiplication ::= Term '*' Term
                 || Term

Term ::= Number | Variable

Seperti yang tertulis, tidak ada rekursi kiri di sini - kita bisa meneruskan tata bahasa ini ke pengurai keturunan rekursif.

Tetapi Andaikan Anda mencoba menulis seperti ini:

Expression ::= Expression '*' Expression
            || Expression '+' Expression
            || Term

Term ::= Number | Variable

Ini adalah tata bahasa, dan beberapa parser dapat mengatasinya, tetapi parser keturunan rekursif dan parser LL tidak bisa - karena aturan untuk Expressionmemulai dengan Expressionsendirinya. Harus jelas mengapa dalam parser rekursif-keturunan ini mengarah pada rekursi tanpa batas tanpa benar-benar mengonsumsi input apa pun.

Tidak masalah apakah aturan itu merujuk pada dirinya sendiri secara langsung atau tidak langsung; jika Amemiliki alternatif yang dimulai dengan B, dan Bmemiliki alternatif yang dimulai dengan A, maka Adan Bkeduanya tidak langsung rekursif, dan dalam parser keturunan rekursif fungsi pencocokan mereka akan menyebabkan rekursi timbal balik yang tak berujung.

hobbs
sumber
Jadi dalam contoh kedua, jika Anda mengubah hal pertama setelah ::=dari Expressionmenjadi Term, dan jika Anda melakukan hal yang sama setelah yang pertama ||, itu tidak lagi akan menjadi rekursif kiri? Tetapi jika Anda hanya melakukannya setelah itu ::=, tetapi tidak ||, itu masih akan tetap rekursif?
Panzercrisis
Sepertinya Anda mengatakan bahwa banyak parser bergerak dari kiri ke kanan, berhenti di setiap simbol dan mengevaluasinya secara rekursif di tempat. Dalam hal ini, jika yang pertama Expressiondiganti dengan Term, baik setelah ::=dan setelah yang pertama ||, semuanya akan baik-baik saja; karena cepat atau lambat, itu akan mengalami sesuatu yang bukan Numberatau bukan Variable, sehingga dapat menentukan bahwa ada sesuatu yang bukan Expressiontanpa eksekusi lebih lanjut ...
Panzercrisis
... Tetapi jika salah satu dari mereka masih memulainya Expression, itu akan berpotensi menemukan sesuatu yang bukan Term, dan hanya akan terus memeriksa apakah semuanya Expressionberulang. Apakah ini?
Panzercrisis
1
@Panzercrisis kurang lebih. Anda benar-benar perlu mencari makna LL, LR, dan parser rekursif-keturunan.
hobbs
Secara teknis ini akurat, tetapi mungkin tidak cukup sederhana (istilah awam). Saya juga akan menambahkan bahwa dalam prakteknya, parser LL biasanya akan memiliki kemampuan untuk mendeteksi rekursi dan menghindarinya (berpotensi menolak string yang dibuat-buat yang valid dalam proses), serta fakta bahwa dalam praktiknya sebagian besar bahasa pemrograman memiliki tata bahasa yang didefinisikan dalam sedemikian rupa untuk menghindari rekursi tak terbatas.
4

Saya akan mencoba memasukkannya ke dalam istilah awam.

Jika Anda berpikir tentang pohon parse (bukan AST, tetapi kunjungan parser dan perluasan input), rekursi kiri menghasilkan pohon yang tumbuh ke kiri dan ke bawah. Rekursi yang tepat justru sebaliknya.

Sebagai contoh, tata bahasa umum dalam kompiler adalah daftar item. Mari kita ambil daftar string ("merah", "hijau", "biru") dan menguraikannya. Saya bisa menulis tata bahasa beberapa cara. Contoh-contoh berikut adalah rekursif langsung kiri atau kanan, masing-masing:

arg_list:                           arg_list:
      STRING                              STRING
    | arg_list ',' STRING               | STRING ',' arg_list 

Pohon-pohon untuk parse ini:

         (arg_list)                       (arg_list)
          /      \                         /      \
      (arg_list)  BLUE                  RED     (arg_list)
       /       \                                 /      \
   (arg_list) GREEN                          GREEN    (arg_list)
    /                                                  /
 RED                                                BLUE

Perhatikan bagaimana ia tumbuh ke arah rekursi.

Ini sebenarnya bukan masalah, tidak apa-apa jika ingin menulis tata bahasa rekursif kiri ... jika alat parser Anda dapat mengatasinya. Parser bottom up menanganinya dengan baik. Begitu juga parser LL yang lebih modern. Masalah dengan tata bahasa rekursif bukanlah rekursi, itu adalah rekursi tanpa memajukan pengurai, atau, berulang tanpa memakan token. Jika kita selalu mengonsumsi setidaknya 1 token saat kita kambuh, kita akhirnya mencapai akhir penguraian. Rekursi kiri didefinisikan sebagai berulang tanpa mengkonsumsi, yang merupakan loop tak terbatas.

Batasan ini adalah murni implementasi detail penerapan tata bahasa dengan parser LL top-down naif (parser keturunan rekursif). Jika Anda ingin tetap menggunakan tata bahasa rekursif kiri, Anda dapat mengatasinya dengan menulis ulang produksi untuk mengkonsumsi setidaknya 1 token sebelum berulang, jadi ini memastikan kami tidak pernah terjebak dalam lingkaran non-produktif. Untuk aturan tata bahasa yang kiri-rekursif, kita dapat menulis ulang dengan menambahkan aturan menengah yang meratakan tata bahasa hanya satu tingkat lookahead, menggunakan token di antara produksi rekursif. (CATATAN: Saya tidak mengatakan ini adalah satu-satunya cara atau cara yang disukai untuk menulis ulang tata bahasa, hanya menunjukkan aturan umum. Dalam contoh sederhana ini, opsi terbaik adalah menggunakan bentuk rekursif yang tepat). Karena pendekatan ini digeneralisasi, generator parser dapat mengimplementasikannya tanpa melibatkan programmer (secara teoritis). Dalam praktiknya, saya percaya ANTLR 4 sekarang melakukan hal itu.

Untuk tata bahasa di atas, implementasi LL menampilkan rekursi kiri akan terlihat seperti ini. Parser akan mulai dengan memprediksi daftar ...

bool match_list()
{
    if(lookahead-predicts-something-besides-comma) {
       match_STRING();
    } else if(lookahead-is-comma) {
       match_list();   // left-recursion, infinite loop/stack overflow
       match(',');
       match_STRING();
    } else {
       throw new ParseException();
    }
}

Pada kenyataannya, apa yang sebenarnya kita hadapi adalah "implementasi naif", yaitu. kami awalnya memprediksikan kalimat yang diberikan, kemudian secara rekursif memanggil fungsi untuk prediksi itu, dan fungsi itu secara naif memanggil prediksi yang sama lagi.

Parser bottom-up tidak memiliki masalah aturan rekursif di kedua arah, karena mereka tidak mem-reparsing awal kalimat, mereka bekerja dengan menyatukan kalimat kembali.

Rekursi dalam tata bahasa hanya masalah jika kita menghasilkan dari atas ke bawah, yaitu. parser kami bekerja dengan "memperluas" prediksi kami saat kami mengkonsumsi token. Jika alih-alih berkembang, kami runtuh (produksi "berkurang"), seperti pada pengurai bottom-up LALR (Yacc / Bison), maka rekursi dari kedua sisi bukanlah masalah.

codenheim
sumber