
Pendahuluan
Salam kepada semua yang mampir untuk membaca artikel saya berikutnya.
Saya ulangi, saya menggambarkan penciptaan bahasa bahasa pemrograman berdasarkan karya sebelumnya, yang hasilnya
dijelaskan dalam posting ini .
Pada bagian pertama (tautan:
habr.com/post/435202 ) saya menjelaskan tahapan merancang dan menulis bahasa VM yang akan mengeksekusi aplikasi masa depan kita dalam bahasa masa depan kita.
Pada artikel ini, saya berencana untuk menggambarkan tahapan utama pembuatan bahasa pemrograman perantara yang akan dirangkai menjadi bytecode abstrak untuk eksekusi langsung pada VM kami.
Saya pikir tidak ada salahnya untuk segera memberikan tautan ke situs web proyek dan repositori-nya.
SitusRepositoriSaya harus segera mengatakan bahwa semua kode ditulis dalam FPC dan saya akan memberikan contohnya.
Jadi, kita memulai pencerahan kita.
Mengapa kami menyerahkan bahasa perantara?
Penting untuk dipahami bahwa mengonversi program dari bahasa tingkat tinggi langsung ke bytecode yang dapat dieksekusi, yang terdiri dari sekumpulan instruksi terbatas, sangat sepele sehingga lebih baik untuk menyederhanakannya dengan urutan besarnya dengan menambahkan bahasa perantara ke proyek. Adalah jauh lebih baik untuk menyederhanakan kode secara bertahap daripada segera menyajikan ekspresi matematika, struktur, dan kelas dengan satu set opcodes. Ngomong-ngomong, ini adalah cara kerja sebagian besar penerjemah dan kompiler pihak ketiga.
Dalam artikel saya sebelumnya, saya menulis tentang bagaimana mengimplementasikan VM bahasa. Sekarang kita perlu mengimplementasikan bahasa seperti assembler untuk itu dan fungsionalitas untuk menulis lebih lanjut penerjemah. Pada tahap ini, kami meletakkan dasar untuk proyek masa depan. Perlu dipahami bahwa semakin baik fondasi, semakin curam bangunan.
Kami mengambil langkah pertama untuk mewujudkan keajaiban ini
Sebagai permulaan, ada baiknya menetapkan tujuan. Apa yang sebenarnya akan kita tulis? Apa karakteristik yang harus dimiliki kode akhir dan apa yang harus dilakukan?
Saya dapat membuat daftar bagian fungsional utama yang bagian dari proyek ini harus terdiri:
- Assembler sederhana. Mengubah instruksi sederhana menjadi satu set opcode untuk VM.
- Implementasi dasar fungsional untuk mengimplementasikan variabel.
- Implementasi dasar fungsional untuk bekerja dengan konstanta.
- Fungsi untuk mendukung titik masuk ke metode dan menghitung alamat mereka pada tahap terjemahan.
- Mungkin beberapa roti lebih fungsional.
Ilustrasi di atas menunjukkan fragmen kode dalam bahasa perantara yang dikonversi menjadi kode untuk VM oleh penerjemah primitif, yang akan dibahas.
Jadi, tujuannya sudah ditetapkan, mari kita lanjutkan ke implementasi.
Menulis assembler sederhana
Kami bertanya pada diri sendiri apa itu assembler?
Sebenarnya, ini adalah program yang melakukan penggantian opcodes alih-alih deskripsi tekstualnya.
Pertimbangkan kode ini:
push 0 push 1 add peek 2 pop
Setelah memproses kode assembler, kami mendapatkan kode yang dapat dieksekusi untuk VM.
Kita melihat bahwa instruksinya bisa bersuku kata satu dan bersuku kata satu. Tidak ada instruksi yang lebih rumit untuk VM yang ditumpuk.
Kami membutuhkan kode yang dapat mengekstrak token dari string (kami memperhitungkan bahwa mungkin ada string di antara mereka).
Kami menulisnya:
function Tk(s: string; w: word): string; begin Result := ''; while (length(s) > 0) and (w > 0) do begin if s[1] = '"' then begin Delete(s, 1, 1); Result := copy(s, 1, pos('"', s) - 1); Delete(s, 1, pos('"', s)); s := trim(s); end else if Pos(' ', s) > 0 then begin Result := copy(s, 1, pos(' ', s) - 1); Delete(s, 1, pos(' ', s)); s := trim(s); end else begin Result := s; s := ''; end; Dec(w); end; end;
Ok, sekarang kita perlu mengimplementasikan sesuatu seperti konstruk kasus saklar untuk setiap pernyataan, dan assembler sederhana kita sudah siap.
Variabel
Ingatlah bahwa VM kami memiliki array pointer untuk mendukung variabel dan, karenanya, pengalamatan statis. Ini berarti bahwa fungsional untuk bekerja dengan variabel dapat direpresentasikan sebagai TStringList, di mana string adalah nama-nama variabel dan indeks mereka adalah alamat statis mereka. Harus dipahami bahwa duplikasi nama variabel dalam daftar ini tidak dapat diterima. Saya pikir Anda dapat membayangkan kode yang diperlukan dan / atau bahkan menulisnya sendiri.
Jika Anda ingin melihat implementasi yang telah selesai, maka Anda dipersilakan: /lang/u_variables.pas
Konstanta
Prinsip di sini sama dengan variabel, tetapi ada satu hal. Untuk mengoptimalkan, lebih baik mengikat bukan pada nama konstanta, tetapi pada nilainya. Yaitu setiap nilai konstan dapat memiliki TStringList, yang akan berfungsi untuk menyimpan nama-nama konstanta dengan nilai ini.
Untuk konstanta, Anda harus menentukan tipe data dan, karenanya, untuk menambahkannya ke bahasa, Anda harus menulis parser kecil.
Implementasi: /lang/u_consts.pas
Metode Titik Masuk
Untuk menerapkan pemblokiran kode, dukungan untuk desain yang berbeda, dll. dukungan untuk fungsi ini harus diimplementasikan di tingkat assembler.
Pertimbangkan contoh kode:
Summ: peek 0 pop peek 1 pop push 0 new peek 2 mov push 2 push 0 add jr
Di atas adalah contoh terjemahan dari metode Summ:
func Summ(a, b): return a + b end
Harus dipahami bahwa tidak ada opcode untuk titik masuk. Apa itu titik masuk ke metode Summ? Nomor utama ini adalah offset dari titik masuk opcode berikutnya. (offset opcode adalah jumlah opcode relatif terhadap awal bytecode abstrak yang dapat dieksekusi). Sekarang kita memiliki tugas - kita perlu menghitung offset ini pada tahap kompilasi dan, sebagai opsi, menyatakan konstanta Summ sebagai angka ini.
Kami menulis untuk ini penghitung bobot tertentu untuk setiap operator. Kami memiliki operator bersuku kata satu sederhana, misalnya "pop". Mereka menempati 1 byte. Ada yang lebih kompleks, misalnya, "push 123" - mereka menempati 5 byte, 1 untuk opcode dan 4 untuk tipe int unsigned.
Inti dari kode untuk menambahkan dukungan untuk assembler titik masuk:
- Kami memiliki penghitung, misalkan i = 0.
- Kami menjalankan kode, jika kami memiliki konstruksi tipe "push 123", kemudian tambahkan 5 untuk itu, jika opcode sederhana adalah 1. Jika kami memiliki titik masuk, kemudian lepaskan dari kode dan nyatakan konstanta yang sesuai dengan nilai penghitung dan nama titik masuk.
Fungsionalitas lainnya
Ini, misalnya, adalah konversi kode sederhana sebelum diproses.
Ringkasan
Kami telah menerapkan assembler kecil kami. Kami akan membutuhkannya untuk mengimplementasikan penerjemah yang lebih kompleks berdasarkan itu. Sekarang kita dapat menulis program kecil untuk VM kita. Dengan demikian, dalam artikel selanjutnya proses penulisan penerjemah yang lebih kompleks akan dijelaskan.
Terima kasih telah membaca sampai akhir jika Anda melakukannya.
Jika ada sesuatu yang tidak jelas bagi Anda, maka saya menunggu komentar Anda.