
Pendahuluan
Saya menyambut Anda, pengembang membaca yang tertarik dalam bahasa apa pun di mana saya mengorientasikan artikel ini dan yang dukungan dan pendapatnya saya nilai.
Sebagai permulaan, menurut tradisi yang ada, saya akan memberikan tautan ke artikel sebelumnya:
Bagian 1: menulis bahasa VMBagian 2: presentasi program antaraUntuk membentuk di kepala Anda pemahaman lengkap tentang apa yang kami tulis dalam artikel ini, Anda harus membiasakan diri dengan bagian-bagian sebelumnya sebelumnya.
Juga, saya harus segera memposting tautan ke sebuah artikel tentang proyek yang saya tulis sebelumnya dan berdasarkan semua pembekalan ini:
Clack syudy . Mungkin ada baiknya berkenalan dengannya hal pertama.
Dan sedikit tentang proyek:
→
Situs proyek kecil→
GitHub repositoriBaiklah, saya juga akan mengatakan bahwa semuanya ditulis dalam Object Pascal, yaitu dalam FPC.
Jadi mari kita mulai.
Prinsip operasi kebanyakan penerjemah
Pertama-tama, perlu dipahami bahwa saya tidak bisa menulis apa pun yang berharga tanpa terlebih dahulu membiasakan diri dengan banyak materi teoretis dan sejumlah patung. Saya akan menjelaskan hal utama dalam beberapa kata.
Tugas penerjemah adalah pertama-tama menyiapkan kode untuk analisis (misalnya, untuk menghapus komentar darinya) dan memecah kode menjadi token (token adalah set karakter minimum yang berarti untuk bahasa tersebut).
Selanjutnya, dengan menganalisis dan mentransformasikan, Anda perlu mengurai kode menjadi representasi perantara tertentu dan kemudian mengumpulkan aplikasi yang siap untuk dieksekusi atau ... Apa yang perlu dikumpulkan secara umum.
Ya, saya tidak benar-benar mengatakan apa-apa dengan banyak teks ini, namun - sekarang tugas dibagi menjadi beberapa subtugas.
Mari kita lewati bagaimana kode disiapkan untuk eksekusi, karena terlalu membosankan untuk menggambarkan suatu proses. Misalkan kita memiliki satu set token yang siap untuk dianalisis.
Analisis kode
Anda mungkin pernah mendengar tentang membangun pohon kode dan analisisnya, atau bahkan lebih banyak hal muskil. Seperti biasa - ini tidak lebih dari mengacaukan istilah-istilah mengerikan yang sederhana. Dengan analisis kode, maksud saya serangkaian tindakan yang lebih sederhana. Tugasnya adalah memeriksa daftar token dan mem-parsing kode, masing-masing konstruksinya.
Sebagai aturan, dalam bahasa imperatif, kode sudah disajikan dalam bentuk semacam pohon dari struktur.
Anda harus mengakui bahwa tidak dapat diterima untuk memulai siklus "A" di dalam tubuh siklus "B", dan akhiri di luar tubuh siklus "B". Kode adalah struktur yang terdiri dari serangkaian konstruksi.
Dan apa yang dimiliki masing-masing desain? Itu benar - awal dan akhir (dan mungkin sesuatu yang lain di tengah, tetapi bukan intinya).
Oleh karena itu, kode parsing dapat dibuat dengan sekali jalan, benar-benar tanpa membangun pohon.
Untuk melakukan ini, Anda memerlukan loop yang akan dijalankan melalui kode dan switch-case besar yang akan melakukan analisis dan analisis kode utama.
Yaitu kita jalankan melalui token, kita memiliki token (misalnya, biarlah ...) "jika" - Aku benar-benar ragu bahwa token semacam itu dapat berada dalam kode begitu saja -> ini adalah awal dari if..then [.. else] .. end construction!
Kami menganalisis semua token berikutnya, masing-masing, untuk pembangunan kondisi dalam bahasa kami.
Sedikit tentang kesalahan kode
Pada tahap analisis struktur dan berjalan di sepanjang kode, lebih baik untuk tidak mencetak kesalahan pemrosesan. Ini adalah fungsi penerjemah yang berguna. Jika kesalahan terjadi selama analisis struktur, maka itu logis - struktur tidak dibangun dengan benar dan Anda harus memberi tahu pengembang.
Sekarang tentang Mash. Bagaimana bahasa diurai?
Di atas, saya menggambarkan konsep umum alat penerjemah. Sekarang saatnya berbicara tentang pekerjaan saya.
Bahkan, penerjemahnya ternyata sangat mirip dengan yang dijelaskan di atas. Tetapi bagi saya itu tidak memecah kode menjadi sekelompok token untuk analisis lebih lanjut.
Sebelum memulai penguraian, kode disajikan dalam bentuk yang lebih indah. Komentar dihapus dan semua konstruksi digabungkan menjadi garis panjang jika dijelaskan dalam beberapa baris.
Jadi, dalam setiap baris yang diambil secara terpisah ada konstruksi bahasa atau bagiannya. Ini keren, sekarang kita dapat menguraikan setiap baris dalam case switch besar kita, daripada mencari konstruksi ini di set token. Juga, keuntungan di sini adalah bahwa garis memiliki akhir dan lebih mudah untuk menentukan kesalahan dalam konstruksi dengan pendekatan ini.
Dengan demikian, analisis struktur individu terjadi dengan metode yang terpisah, yang mengembalikan representasi menengah dari kode struktur atau bagian-bagiannya.
P.S. Dalam artikel sebelumnya, saya menggambarkan konstruksi penerjemah dari bahasa perantara ke bytecode untuk VM. Sebenarnya - bahasa perantara ini adalah representasi perantara.
Harus dipahami bahwa struktur dapat terdiri dari beberapa struktur yang lebih sederhana. Karena Karena kami menganalisis setiap struktur dengan metode terpisah, kami dapat dengan mudah memanggil mereka dari satu sama lain ketika menganalisis setiap struktur.

Pemanasan dijalankan pada kode
Untuk memulainya, penerjemah harus dengan cepat membiasakan diri dengan kode, memeriksa dan memperhatikan beberapa desain.
Pada titik ini, Anda dapat menangani variabel global, menggunakan konstruk, serta impor, prosedur & fungsi, dan konstruk OOP.
Lebih baik untuk menghasilkan tampilan antara ke beberapa objek untuk penyimpanan, sehingga
rekatkan kode untuk variabel global setelah inisialisasi, tetapi sebelum memulai main ().
Kode untuk konstruksi OOP dapat dimasukkan di akhir.
Desain Canggih
Ok, kami menemukan desain yang sederhana. Sekarang saatnya untuk yang sulit. Saya tidak berpikir bahwa Anda berhasil melupakan contoh dengan dua siklus. Seperti kita ketahui, struktur biasanya datang dalam bentuk sejenis pohon. Ini berarti bahwa kita dapat mengurai struktur kompleks menggunakan tumpukan.
Apa hubungannya tumpukan itu? Apalagi
Pertama, kami jelaskan kelas yang akan kami dorong ke tumpukan. Saat mem-parsing konstruksi yang kompleks, kita dapat menghasilkan representasi perantara untuk awal dan akhir dari blok ini, misalnya, kita menguraikan for, sementara, sampai loop, jika membangun, metode, dan memang semuanya dalam bahasa Mash.
Kelas ini membutuhkan bidang untuk menyimpan representasi perantara, informasi meta (untuk beberapa konstruksi variabel) dan, tentu saja, untuk menyimpan tipe blok.
Saya hanya akan memberikan seluruh kode, karena tidak banyak:
unit u_prep_codeblock; {$mode objfpc}{$H+} interface uses Classes, SysUtils; type TBlockEntryType = (btProc, btFunc, btIf, btFor, btWhile, btUntil, btTry, btClass, btSwitch, btCase); TCodeBlock = class(TObject) public bType: TBlockEntryType; mName, bMeta, bMCode, bEndCode: string; constructor Create(bt: TBlockEntryType; MT, MC, EC: string); end; implementation constructor TCodeBlock.Create(bt: TBlockEntryType; MT, MC, EC: string); begin inherited Create; bType := bt; bMeta := MT; bMCode := MC; bEndCode := EC; end; end.
Nah, stack adalah TList sederhana, menciptakan kembali roda di sini benar-benar bodoh.
Jadi, parsing konstruksinya, misalkan loop yang sama terlihat seperti ini:
function ParseWhile(s: string; varmgr: TVarManager): string; var WhileNum, ExprCode: string; begin Delete(s, 1, 5);
Tentang ekspresi matematika
Anda mungkin tidak memperhatikan hal ini, tetapi ekspresi matematika / logis juga merupakan kode terstruktur.
Saya menerapkan analisis mereka dengan cara ditumpuk. Pertama, semua elemen individu dari ekspresi didorong ke stack, kemudian dalam beberapa melewati kode untuk representasi perantara dihasilkan.
Beberapa kali - karena ada operasi matematika prioritas, seperti perkalian.
Saya tidak mengerti maksudnya di sini, karena banyak dan membosankan.
P.S. /lang/u_prep_expressions.pas - ini dia sepenuhnya dan sepenuhnya terbuka untuk ulasan Anda.
Ringkasan
Jadi, kami telah menerapkan penerjemah yang dapat mengonversi ... Misalnya, ini adalah kodenya:
proc PrintArr(arr): for(i ?= 0; i < len(arr); i++): PrintLn("arr[", i, "] = ", arr[i]) end end proc main(): var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] PrintArr(arr) InputLn() end
Apa yang kurang dalam bahasa kita? Benar, dukung OOP. Kita akan membicarakan ini di artikel saya berikutnya.
Terima kasih telah membaca sampai akhir jika Anda melakukannya.
Jika ada sesuatu yang tidak jelas bagi Anda, maka saya menunggu komentar Anda. Atau pertanyaan di
forum , ya ... Ya, saya kadang-kadang mengeceknya.
Dan sekarang sebuah polling kecil (sehingga saya melihatnya dan menikmati pentingnya artikel saya):