Saya bekerja melalui algoritma Shunting-yard , seperti yang dijelaskan oleh wikipedia.
Deskripsi algoritma ketika berhadapan dengan operator adalah sebagai berikut:
Jika token adalah operator, o1, maka:
sementara ada token operator, o2, di bagian atas tumpukan operator, dan keduanya
o1 is left-associative and its precedence is less than or equal to that of o2, or o1 is right associative, and has precedence less than that of o2,
kemudian pop o2 dari stack operator, ke antrian output;
dorong o1 ke tumpukan operator.
Namun, mereka memberikan contoh berikut:
Memasukkan:
sin max 2 3 / 3 * 3.1415
Ketika algoritma mengenai /
token, deskripsi tentang apa yang harus terjadi adalah sebagai berikut:
Token | Action | Output (in RPN) | Operator Stack
...
/ | Pop token to output | 2 3 max | / sin
...
Mereka bermunculan fungsi tanda max
dari stack
dan menempatkan ke dalam queue
. Menurut algoritme mereka, ini sepertinya berarti token fungsi keduanya operator, dan memiliki prioritas lebih rendah dari token /
operator.
Tidak ada penjelasan apakah ini kasusnya atau tidak. Jadi, untuk Shunting-yard
algoritma, apa yang diutamakan dari suatu fungsi? Apakah fungsinya benar atau kiri asosiatif? Atau apakah wikipedia hanya tidak lengkap / tidak akurat?
sumber
sin( max( 2 3) / 3 * 3.1415)
Ada dua kasus berbeda yang perlu dipertimbangkan, tergantung pada sintaksis bahasa Anda. Jika bahasa Anda menggunakan tanda kurung untuk menunjukkan aplikasi fungsi (misalnya
f(2+1)
) maka prioritas tidak relevan. Fungsi harus didorong ke tumpukan dan muncul setelah (untuk contoh di atas, hasilnya adalah2 1 + f
). Sebagai alternatif, Anda dapat memperlakukan fungsi sebagai nilai dan mengeluarkannya segera, dan mengeluarkan operasi pemanggilan fungsi setelah tanda kurung tutup (yang seharusnya diperlakukan sama dengan tanda kurung lainnya), misalnyaf 2 1 + $
, di mana$
operasi pemanggilan fungsi.Jika bahasa Anda, bagaimanapun, tidak menggunakan tanda kurung untuk menunjukkan pemanggilan fungsi, tetapi sebaliknya menempatkan argumen langsung setelah fungsi tanpa tanda baca khusus (misalnya
f 2 + 1
), seperti yang terjadi pada contoh Wikipedia, maka hal-hal sedikit lebih rumit. Perhatikan bahwa ungkapan yang baru saja saya berikan adalah contoh yang ambigu: apakah f diterapkan pada 2 dan 1 ditambahkan ke hasilnya, atau apakah kita menambahkan 2 dan 1 bersamaan dan kemudian memanggil f dengan hasilnya?Sekali lagi, ada dua pendekatan. Anda cukup mendorong fungsi ke tumpukan operator ketika Anda menjumpainya dan menetapkannya prioritas apa pun yang Anda inginkan. Ini adalah pendekatan yang paling sederhana, dan tampaknya apa yang telah dilakukan oleh contoh yang dikutip. Namun, ada masalah praktis. Pertama, bagaimana Anda mengidentifikasi suatu fungsi? Jika Anda memiliki set yang terbatas itu mudah, tetapi jika Anda memiliki fungsi yang ditentukan pengguna, ini berarti parser Anda perlu memberi umpan balik ke lingkungan Anda, yang dapat menjadi berantakan dengan cepat. Dan bagaimana Anda menangani fungsi dengan banyak argumen?
Perasaan saya adalah bahwa untuk gaya sintaksis ini, menggunakan fungsi sebagai nilai yang lebih handier oleh operator aplikasi fungsi jauh lebih masuk akal. Kemudian, Anda bisa menyuntikkan operator aplikasi setiap kali Anda membaca nilai dan hal terakhir yang Anda baca juga merupakan nilai, sehingga Anda tidak perlu cara khusus untuk mengetahui pengidentifikasi mana yang berfungsi. Anda juga dapat bekerja dengan ekspresi yang mengembalikan fungsi (yang sulit atau tidak mungkin dengan gaya fungsi-sebagai-operasi). Dan ini berarti Anda dapat menggunakan currying untuk menangani beberapa fungsi argumen, yang merupakan penyederhanaan besar untuk mencoba menanganinya secara langsung.
Satu-satunya hal yang perlu Anda putuskan adalah apa yang menjadi prioritas aplikasi fungsi. Pilihannya terserah Anda, tetapi dalam setiap bahasa yang saya gunakan yang berfungsi seperti ini, ia telah menjadi operator yang paling kuat dalam bahasa tersebut, dan telah diasosiasikan dengan benar. (Satu-satunya variasi yang menarik adalah Haskell, yang selain memiliki versi pengikatan yang sangat jelas, juga memiliki sinonim dengan simbol
$
yang merupakan operator pengikat yang paling lemah dalam bahasa tersebut, yang memungkinkan ekspresi sepertif 2 + 1
menerapkan f ke 2 danf $ 2 + 1
untuk menerapkan ke seluruh sisa ekspresi)sumber
Saya menerapkan "fungsi di halaman shunting" yang diminta setelah membaca pemikiran asli Dijkstra (Halaman 7-11 dalam makalah kompiler Algol 60, https://ir.cwi.nl/pub/9251 ), dan membutuhkan solusi yang kuat, saya lakukan hal berikut:
parsing:
Infix-to-postfix (halaman shunting):
Berfungsi sempurna dalam pengujian yang kuat dan skenario yang rumit. Dalam aplikasi saya (expander argumen yang berisi ekspresi baris perintah), saya mendukung fungsi multi argumen dan tanda "," tanda untuk memisahkan mereka, dan ini mengalir melalui seluruh proses.
Contohnya terlihat seperti "sqrt (3 ^ 2 + 4 ^ 2)" yang menjadi "3 2 ^ 4 2 ^ + sqrt" dan akhirnya "5" yang merupakan argumen program. Itu bignum, jadi "" binomial (64, 32) / gcd (binomial (64, 32), binomial (63, 31)) "==> hal-hal besar ==>" 2 "sangat membantu." 123456 ^ 789 " adalah 40.173 digit dan waktunya menunjukkan "evaluasi = 0,000390 detik" di MacBookPro saya, begitu cepat.
Saya juga menggunakan ini untuk memperluas data dalam tabel, dan menemukan itu berguna. Bagaimanapun, ini tip saya tentang cara saya dengan hati-hati menangani panggilan fungsi, banyak argumen, dan bersarang dalam konteks halaman shunting-halaman Dijkstra. Lakukan saja hari ini dari pemikiran mandiri. Tidak tahu apakah mungkin ada cara yang lebih baik.
sumber