Bagaimana bahasa pemrograman mendefinisikan dan menyimpan fungsi / metode? Saya membuat bahasa pemrograman yang ditafsirkan dalam Ruby, dan saya mencoba mencari cara untuk mengimplementasikan deklarasi fungsi.
Ide pertama saya adalah menyimpan konten deklarasi di peta. Misalnya, jika saya melakukan sesuatu seperti
def a() {
callSomething();
x += 5;
}
Lalu saya akan menambahkan entri ke peta saya:
{
'a' => 'callSomething(); x += 5;'
}
Masalahnya adalah ini akan menjadi rekursif, karena saya harus memanggil parse
metode saya pada string, yang kemudian akan memanggil parse
lagi ketika bertemu doSomething
, dan kemudian saya akan kehabisan ruang stack pada akhirnya.
Jadi, bagaimana bahasa yang ditafsirkan menangani ini?
programming-languages
language-design
functions
methods
Gagang pintu
sumber
sumber
Jawaban:
Apakah saya benar dengan menganggap bahwa fungsi "parse" Anda tidak hanya mem-parsing kode tetapi juga menjalankannya pada saat yang sama? Jika Anda ingin melakukannya dengan cara itu, alih-alih menyimpan konten suatu fungsi di peta Anda, simpan lokasi fungsi tersebut.
Tapi ada cara yang lebih baik. Dibutuhkan sedikit usaha di muka, tetapi menghasilkan hasil yang jauh lebih baik seiring meningkatnya kompleksitas: gunakan Pohon Sintaksis Abstrak.
Ide dasarnya adalah bahwa Anda hanya menguraikan kode sekali, selamanya. Kemudian Anda memiliki satu set tipe data yang mewakili operasi dan nilai, dan Anda membuat pohon dari mereka, seperti:
menjadi:
(Ini hanya representasi teks dari struktur AST hipotetis. Pohon yang sebenarnya mungkin tidak akan dalam bentuk teks.) Bagaimanapun, Anda mengurai kode Anda menjadi AST, dan kemudian Anda menjalankan penerjemah melalui AST secara langsung, atau gunakan pass kedua ("pembuatan kode") untuk mengubah AST menjadi beberapa bentuk output.
Dalam hal bahasa Anda, apa yang mungkin Anda lakukan adalah memiliki peta yang memetakan nama fungsi untuk fungsi AST, alih-alih nama fungsi ke string fungsi.
sumber
(((((((((((((((( x )))))))))))))))))
. Pada kenyataannya, tumpukan bisa jauh lebih besar, dan kompleksitas tata bahasa kode nyata sangat terbatas. Tentu saja jika kode itu harus dapat dibaca manusia.Anda seharusnya tidak menelepon parse setelah melihat
callSomething()
(saya kira Anda maksudkancallSomething
daripadadoSomething
). Perbedaan antaraa
dancallSomething
adalah bahwa yang satu adalah definisi metode sedangkan yang lainnya adalah pemanggilan metode.Saat Anda melihat definisi baru, Anda ingin melakukan pemeriksaan terkait untuk memastikan Anda dapat menambahkan definisi itu, jadi:
Dengan asumsi lulus pemeriksaan ini, Anda dapat menambahkannya ke peta Anda dan mulai memeriksa konten metode itu.
Ketika Anda menemukan panggilan metode seperti
callSomething()
, Anda harus melakukan pemeriksaan berikut:callSomething
ada di peta Anda?Jika Anda menemukan itu
callSomething()
baik-baik saja, maka pada titik ini apa yang ingin Anda lakukan benar-benar tergantung pada bagaimana Anda ingin mendekatinya. Sebenarnya, setelah Anda tahu bahwa panggilan seperti itu tidak masalah pada saat ini, Anda hanya bisa menyimpan nama metode dan argumen tanpa masuk ke perincian lebih lanjut. Ketika Anda menjalankan program Anda, Anda akan memanggil metode dengan argumen yang harus Anda miliki saat runtime.Jika Anda ingin melangkah lebih jauh, Anda dapat menyimpan tidak hanya string tetapi tautan ke metode aktual. Ini akan lebih efisien, tetapi jika Anda harus mengelola memori, ini bisa membingungkan. Saya akan merekomendasikan Anda hanya memegang string pada awalnya. Nanti Anda bisa mencoba mengoptimalkan.
Perhatikan bahwa ini semua dengan anggapan bahwa Anda telah lexxed program Anda, yang berarti Anda telah mengenali semua token dalam program Anda dan tahu apa itu . Itu bukan untuk mengatakan Anda tahu apakah mereka masuk akal bersama, yang merupakan fase parsing. Jika Anda belum tahu apa token itu, saya sarankan Anda fokus dulu untuk mendapatkan informasi itu terlebih dahulu.
Saya harap itu membantu! Selamat Datang di Programmer SE!
sumber
Membaca posting Anda, saya perhatikan dua pertanyaan dalam pertanyaan Anda. Yang paling penting adalah bagaimana mengurai. Ada banyak jenis parser (misalnya parser keturunan Recursive , LR Parsers , Packrat Parsers ) dan generator parser (mis. GNU bison , ANTLR ) yang dapat Anda gunakan untuk menelusuri program tekstual "secara rekursif" dengan tata bahasa (eksplisit atau implisit).
Pertanyaan kedua adalah tentang format penyimpanan untuk berbagai fungsi. Ketika Anda tidak melakukan terjemahan diarahkan-sintaks , Anda membuat representasi perantara dari program Anda, yang dapat berupa pohon sintaksis abstrak , atau beberapa bahasa perantara kustom, untuk melakukan pemrosesan lebih lanjut dengan itu (mengkompilasi, mengubah, mengeksekusi, menulis di file, dll).
sumber
Dari sudut pandang generik, definisi fungsi sedikit lebih dari label, atau bookmark, dalam kode. Kebanyakan operator loop, scope, dan conditional lainnya serupa; mereka berdiri untuk perintah "lompat" atau "goto" dasar di tingkat abstraksi yang lebih rendah. Panggilan fungsi pada dasarnya bermuara pada perintah komputer tingkat rendah berikut:
Pernyataan "kembali" atau serupa akan melakukan hal berikut:
Fungsi, oleh karena itu, hanyalah abstraksi dalam spesifikasi bahasa tingkat tinggi, yang memungkinkan manusia untuk mengatur kode dengan cara yang lebih terpelihara dan intuitif. Ketika dikompilasi menjadi bahasa assembly atau intermediate (JIL, MSIL, ILX), dan pasti ketika diterjemahkan sebagai kode mesin, hampir semua abstraksi semacam itu hilang.
sumber