Apa yang harus dilakukan parsing tanpa pemindai dengan “Menggantung Masalah Lainnya”?

13

Saya tidak mengerti kalimat ini dari artikel Wikipedia tentang masalah Dangling Else :

[Masalah Dangling Else] adalah masalah yang sering muncul dalam konstruksi compiler, terutama parsing tanpa pemindai.

Dapatkah seseorang menjelaskan kepada saya bagaimana teknik parsing tanpa pemindai dapat memperburuk masalah ini? Tampaknya bagi saya bahwa masalahnya adalah tata bahasa - karena ini ambigu - bukan dengan pilihan teknik penguraian. Apa yang saya lewatkan?


sumber
2
Satu-satunya hal yang dapat saya pikirkan adalah bahwa parser tanpa pemindai memerlukan tata bahasa yang lebih kompleks, membuatnya lebih sulit untuk memberikan heuristik untuk menyelesaikan ambiguitas.
Giorgio
3
@Robert Harvey: Intinya asumsi ini harus tercermin oleh sintaksis pohon. Jika tata bahasa memungkinkan untuk mendapatkan dua pohon sintaks yang berbeda untuk string if a then if b then s1 else s2, maka tata bahasa bersifat ambigu.
Giorgio
1
@RobertHarvey cara umum untuk mendefinisikan bahasa menggunakan tata bahasa bebas konteks, ditambah banyak aturan yang mengacaukan tata bahasa, jika perlu.
2
Tidak semua parser tanpa pemindai yang dibuat sama. Untuk, katakanlah, PEG atau GLR, perilaku lain yang menggantung selalu dapat diprediksi.
SK-logic
1
[Masalah Menggantung Lainnya] tidak ada hubungannya dengan parsing tanpa pemindai. [Masalah Dangling Else] terkait dengan operasi pengurang shift LR (bottom up). AFAIK
ddur

Jawaban:

6

Tebakan terbaik saya adalah kalimat dalam artikel Wikipedia dihasilkan dari kesalahpahaman tentang karya E. Visser.

Tata bahasa untuk parser tanpa pemindai (yaitu tata bahasa yang mendeskripsikan suatu bahasa sebagai sekumpulan urutan karakter alih-alih sebagai sekuens urutan token dengan token yang dijelaskan secara terpisah sebagai rangkaian karakter) cenderung memiliki banyak ambiguitas. E. Kertas Visser Disambiguasi Filter untuk Pemindai LR Parser Generalized (*) mengusulkan beberapa mekanisme untuk memecahkan ambiguitas, salah satunya berguna untuk menyelesaikan masalah yang lain. Tetapi makalah ini tidak menyatakan bahwa ambiguitas yang tepat yang disebut "masalah menggantung lain" terkait dengan parser tanpa pemindai (atau bahkan bahwa mekanisme ini sangat berguna untuk parser tanpa pemindai).

Fakta bahwa ia mengusulkan suatu mekanisme untuk menyelesaikannya bukanlah pernyataan implisit sebagai mekanisme resolusi ambiguitas lain (prioritas dan prioritas operator) tampaknya juga sama sekali tidak terkait dengan sifat tanpa pemindai dari parser yang dianggap (pertimbangkan misalnya bahwa ambiguitas tersebut tidak dapat hadir dalam tata bahasa reguler karena mereka bersarang, sementara yang ditangani oleh aturan pertandingan terpanjang bisa).


(*) Yang mungkin merupakan makalah yang menjadi dasar artikel Wikipedia tentang parser tanpa pemindai bahkan jika mereka merujuk satu sama lain, juga oleh E. Visser, Parsless Generalized-LR Parsing .

Pemrogram
sumber
13

Hanya untuk menyatakan masalahnya, Masalah Dangling Else adalah ambiguitas dalam spesifikasi sintaksis kode di mana mungkin tidak jelas, dalam kasus ifs dan elses berikutnya, yang lain milik yang jika.

Contoh paling sederhana dan klasik:

if(conditionA)
if(conditionB)
   doFoo();
else
   doBar();

Tidak jelas, bagi mereka yang tidak tahu spesifikasi spesifik bahasa dengan hati, yang ifmendapatkan else(dan potongan kode khusus ini berlaku dalam setengah lusin bahasa, tetapi dapat melakukan berbeda di masing-masing).

Konstruksi Dangling Else menimbulkan masalah potensial untuk implementasi parser tanpa pemindai, karena strateginya adalah untuk menyeruput aliran file satu karakter pada satu waktu, hingga parser melihat bahwa ia memiliki cukup untuk tokenize (dicerna ke dalam perakitan atau bahasa perantara yang dikompilasi) . Ini memungkinkan parser untuk mempertahankan kondisi minimal; segera setelah ia berpikir ia memiliki informasi yang cukup untuk menulis token yang diuraikan ke file, ia akan melakukannya. Itulah tujuan akhir dari pengurai tanpa pemindai; kompilasi cepat, sederhana, ringan.

Dengan asumsi baris baru dan spasi putih sebelum atau setelah tanda baca tidak ada artinya (karena mereka dalam sebagian besar bahasa C-style), pernyataan ini akan muncul ke kompiler sebagai:

if(conditionA)if(conditionB)doFoo();else doBar;

Dapat mengurai dengan sempurna ke komputer, jadi mari kita lihat. Saya mendapatkan satu karakter pada satu waktu sampai saya memiliki:

if(conditionA)

Oh, saya tahu apa artinya itu (dalam C #), itu berarti " pushconditionA ke tumpukan eval dan kemudian panggil brfalseuntuk melompat ke pernyataan setelah titik koma berikutnya jika itu tidak benar". Saat ini saya tidak melihat tanda titik koma, jadi untuk saat ini saya akan mengatur lompatan offset saya ke ruang berikutnya setelah instruksi ini, dan saya akan menambah offset itu ketika saya memasukkan lebih banyak instruksi sampai saya melihat tanda titik koma. Terus mengurai ...

if(conditionB)

OK, ini di-parsing ke pasangan serupa dari operasi IL, dan langsung berjalan setelah instruksi saya baru saja diuraikan. Saya tidak melihat tanda titik koma, jadi saya akan menambah lompatan offset dari pernyataan saya sebelumnya dengan panjang dua perintah saya (satu untuk push dan satu untuk break) dan terus mencari.

doFoo();

Oke, itu mudah. Itu " calldoFoo". Dan apakah itu titik koma yang saya lihat? Nah, itu hebat, itulah akhir dari dialog. Saya akan menambah offset lompatan kedua blok saya dengan panjang kedua perintah ini dan lupa bahwa saya pernah peduli. Oke, pindah ...

else

... Uh oh. Ini tidak sesederhana kelihatannya. OK, saya lupa apa yang baru saja saya lakukan, tetapi elsesarana ada pernyataan istirahat bersyarat di suatu tempat yang sudah saya lihat, jadi biarkan saya melihat ke belakang ... ya, itu dia brfalse,, tepat setelah saya mendorong beberapa "conditionB" pada tumpukan, apa pun itu. OK, sekarang saya perlu tanpa syarat breaksebagai pernyataan selanjutnya. Pernyataan yang akan datang setelah itu sekarang pasti target istirahat bersyarat saya, jadi saya akan memastikan saya sudah benar, dan saya akan menambah istirahat tanpa syarat yang saya masukkan. Pindah ...

doBar();

Itu mudah. " calldoBar". Dan ada titik koma, dan saya tidak pernah melihat kawat gigi. Jadi, tanpa syarat breakharus melompat ke pernyataan berikutnya, apa pun itu, dan aku bisa lupa aku pernah peduli.


Jadi, apa yang kita miliki ... (catatan: ini jam 22:00 dan saya tidak merasa ingin mengubah bit offset ke heksadesimal atau mengisi shell IL lengkap dari suatu fungsi dengan perintah ini, jadi ini hanya pseudo-IL menggunakan nomor baris di mana biasanya ada byte byte):

ldarg.1 //conditionA
brfalse <line 6> //jumps to "break"
ldarg.2 //conditionB
brfalse <line 7> //jumps to "call doBar"
call doFoo
break <line 8> //jumps beyond statement in scope
call doBar
<line 8 is here>

Nah, yang sebenarnya dieksekusi dengan benar, JIKA aturannya (seperti dalam kebanyakan bahasa gaya-C) adalah yang elsepaling dekat if. Bertekad untuk mengikuti eksekusi nesting, ia akan mengeksekusi seperti ini, di mana jika conditionA salah, seluruh sisa potongan dilewati:

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();

... tetapi ia melakukannya dengan kebetulan, karena jeda yang terkait dengan ifpernyataan luar melompat ke breakpernyataan di bagian dalam if , yang mengambil pointer eksekusi melampaui seluruh pernyataan. Ini lompatan ekstra yang tidak dibutuhkan, dan jika contoh ini lebih kompleks, itu mungkin tidak lagi berfungsi jika diuraikan dan tokenized dengan cara ini.

Juga, bagaimana jika spesifikasi bahasa mengatakan bahwa menjuntai elsemilik yang pertama if, dan jika conditionA salah maka doBar dieksekusi, sedangkan jika conditionA benar tetapi bukan conditionB maka tidak ada yang terjadi, seperti itu?

if(conditionA)
    if(conditionB)
       doFoo();
else
   doBar();

Parser telah melupakan yang pertama yang ifpernah ada, dan algoritma parser sederhana ini tidak akan menghasilkan kode yang benar, untuk mengatakan tidak ada kode yang efisien.

Sekarang, parser bisa menjadi cukup pintar untuk mengingat huruf ifs dan elses untuk waktu yang lebih lama, tetapi jika spek bahasa mengatakan satu elsesetelah dua ifs cocok dengan yang pertama if, itu menyebabkan masalah dengan dua ifs dengan padanan elses:

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();
else
    doBaz();

Parser akan melihat yang pertama else, cocok dengan yang pertama if, kemudian melihat yang kedua dan panik "apa yang saya lakukan lagi" mode. Pada titik ini parser mendapatkan kode yang agak banyak dalam keadaan bisa berubah yang lebih baik telah didorong ke output filestream.

Ada solusi untuk semua masalah ini dan bagaimana-jika. Tetapi, baik kode yang diperlukan agar pintar meningkatkan kompleksitas algoritma parser, atau spesifikasi bahasa yang memungkinkan parser menjadi bodoh ini meningkatkan verbositas kode sumber bahasa, seperti dengan meminta terminasi pernyataan seperti end if, atau tanda kurung yang mengindikasikan bersarang blok jika ifpernyataan memiliki else(keduanya biasanya terlihat dalam gaya bahasa lain).

Ini hanya satu, contoh sederhana dari beberapa ifpernyataan, dan lihat semua keputusan yang harus dibuat oleh kompiler, dan di mana itu bisa dengan mudah mengacaukannya. Ini adalah detail di balik pernyataan berbahaya dari Wikipedia dalam pertanyaan Anda.

KeithS
sumber
1
Menarik tapi saya jauh dari yakin itulah yang dimaksud oleh artikel Wikipedia. Ini merujuk (melalui entri tanpa pemindai) laporan oleh Eelco Visser yang isinya pada pandangan pertama tidak kompatibel dengan penjelasan Anda.
Pemrogram
3
Terima kasih atas tanggapannya, tetapi tidak benar-benar mengatasi OP. Saya tidak setuju dengan asumsi di pos tentang apa tujuan parser tanpa pemindai dan bagaimana penerapannya. Ada banyak cara untuk mengimplementasikan parser tanpa pemindai dan posting ini tampaknya hanya berurusan dengan subset terbatas.