Selama beberapa tahun saya mencoba mengembangkan bahasa pemrograman saya. Saya ingin membuat menurut saya bahasa yang paling sederhana, berfungsi penuh dan nyaman.
Dalam artikel ini saya ingin menyoroti tahap utama pekerjaan saya dan mulai dengan menjelaskan konsep bahasa yang dibuat dan implementasi pertamanya di mana saya saat ini bekerja.
Saya akan katakan sebelumnya bahwa saya menulis seluruh proyek dalam Free Pascal, karena Program di dalamnya dapat dirakit untuk sejumlah besar platform, dan kompiler itu sendiri menghasilkan binari yang sangat optimal (saya mengumpulkan semua komponen proyek dengan bendera O2).
Bahasa runtime
Hal pertama yang saya bicarakan adalah mesin virtual yang harus saya tulis untuk menjalankan aplikasi masa depan dalam bahasa saya. Saya memutuskan untuk mengimplementasikan arsitektur stack, mungkin, karena itu cara termudah. Saya tidak menemukan satu artikel pun yang normal tentang cara melakukan ini dalam bahasa Rusia, jadi setelah membaca materi berbahasa Inggris, saya duduk untuk mendesain dan menulis sepeda saya. Selanjutnya saya akan memberikan ide-ide dan perkembangan "maju" saya dalam masalah ini.
Penumpukan implementasi
Jelas, VM berada di kepala tumpukan. Dalam implementasi saya, ini bekerja dalam blok. Intinya, ini adalah array sederhana pointer dan variabel untuk menyimpan indeks bagian atas tumpukan.
Ketika diinisialisasi, array 256 elemen dibuat. Jika lebih banyak pointer dilemparkan ke tumpukan, maka ukurannya meningkat dengan 256 elemen berikutnya. Dengan demikian, saat mengeluarkan item dari tumpukan, ukurannya dapat disesuaikan.
VM menggunakan beberapa tumpukan:
- Tumpukan utama.
- Stack untuk menyimpan poin kembali.
- Tumpukan pengumpul sampah.
- Penangan tumpukan blok coba / tangkap / akhirnya.
Konstanta dan variabel
Dengan ini, semuanya sederhana. Konstanta diproses dalam potongan kode kecil yang terpisah dan tersedia di aplikasi di masa depan di alamat statis. Variabel adalah array pointer dengan ukuran tertentu, akses ke selnya dilakukan oleh indeks - yaitu alamat statis. Variabel dapat ditempatkan di bagian atas tumpukan atau dibaca dari sana. Sebenarnya, karena variabel kita pada dasarnya menyimpan pointer ke nilai dalam memori VM, kemudian bekerja dengan pointer implisit yang mendominasi dalam bahasa.
Pengumpul sampah
Dalam VM saya ini semi-otomatis. Yaitu pengembang memutuskan kapan akan memanggil pengumpul sampah. Ini tidak bekerja sesuai dengan penghitung pointer biasa, seperti dalam Python, Perl, Ruby, Lua, dll yang sama Diimplementasikan melalui sistem penanda. Yaitu ketika dipahami bahwa suatu variabel diberi nilai sementara, sebuah penunjuk ke nilai ini ditambahkan ke tumpukan pengumpul sampah. Di masa depan, kolektor dengan cepat menelusuri daftar petunjuk yang sudah disiapkan.
Menangani blok mencoba / menangkap / akhirnya
Seperti dalam bahasa modern lainnya, penanganan pengecualian adalah komponen penting dari itu. Kernel VM dibungkus dalam blok try..catch, yang dapat kembali ke eksekusi kode setelah mendapatkan pengecualian dengan meletakkan sedikit informasi tentangnya di stack. Dalam kode aplikasi, Anda dapat menentukan coba / tangkap / akhirnya blok kode, yang menunjukkan titik masuk untuk ditangkap (penangan pengecualian) dan akhirnya / akhir (ujung blok).
Multithreading
Ini didukung di tingkat VM. Ini sederhana dan nyaman digunakan. Ini bekerja tanpa sistem interupsi, sehingga kode harus berjalan di beberapa utas beberapa kali lebih cepat, masing-masing.
Pustaka Eksternal untuk VM
Tidak ada cara untuk melakukannya tanpanya. VM mendukung impor, seperti halnya diterapkan dalam bahasa lain. Anda dapat menulis sebagian kode dalam Mash dan sebagian kode dalam bahasa asli, lalu menautkannya bersama.
Penerjemah dari bahasa tingkat tinggi Mash ke bytecode untuk VM
Bahasa menengah
Untuk dengan cepat menulis penerjemah dari bahasa yang rumit ke kode VM, saya pertama-tama mengembangkan bahasa perantara. Ternyata pemandangan menakutkan seperti assembler, yang tidak ada rasa khusus untuk dipertimbangkan di sini. Saya hanya bisa mengatakan bahwa pada level ini penerjemah memproses sebagian besar konstanta, variabel, menghitung alamat statis dan alamat titik masuknya.
Arsitektur Penerjemah
Saya memilih bukan arsitektur terbaik untuk implementasi. Penerjemah tidak membangun pohon kode, sebagaimana layaknya penerjemah lainnya. Dia melihat awal konstruksi. Yaitu jika potongan kode yang diuraikan seperti βwhile <condition>:β, maka jelas bahwa ini adalah konstruksi sementara dari loop dan perlu diproses sebagai konstruksi loop sementara. Sesuatu seperti sakelar yang rumit.
Berkat solusi arsitektur seperti itu, penerjemahnya tidak terlalu cepat. Namun, kesederhanaan perbaikannya telah meningkat secara signifikan. Saya menambahkan desain yang diperlukan lebih cepat daripada kopi saya bisa dingin. Dukungan penuh untuk OOP dilaksanakan dalam waktu kurang dari seminggu.
Optimasi kode
Di sini, tentu saja, itu bisa direalisasikan lebih baik (dan itu akan terwujud, tetapi nanti, saat tangan menjangkau). Sejauh ini, pengoptimal hanya tahu cara memotong kode, konstanta, dan impor yang tidak digunakan dari perakitan. Juga, beberapa konstanta dengan nilai yang sama digantikan oleh satu. Itu saja.
Bahasa Mash
Konsep dasar bahasa
Gagasan utamanya adalah mengembangkan bahasa yang paling fungsional dan sederhana. Saya percaya bahwa pembangunan mengatasi tugasnya dengan keras.
Blok Kode, Prosedur, dan Fungsi
Semua konstruksi dalam bahasa dibuka dengan titik dua
: dan ditutup dengan operator
akhir .
Prosedur dan fungsi masing-masing dinyatakan sebagai proc dan func. Argumen tercantum dalam tanda kurung. Sama seperti kebanyakan bahasa lainnya.
Pernyataan kembali dapat mengembalikan nilai dari suatu fungsi, pernyataan
istirahat memungkinkan Anda untuk keluar dari prosedur / fungsi (jika di luar loop).
Contoh kode:
...
func summ(a, b):
return a + b
end
proc main():
println(summ(inputln(), inputln()))
end
- : for..end, while..end, until..end
- : if..[else..]end, switch..[case..end..][else..]end
- : proc <>():β¦ end, func <>():β¦ end
- Label & goto: <>:, jump <>
- Enum .
, var .
:
a ?= 10
b ?= a + 20
var a = 10, b = a + 20
.
. Mash - . .. , , ( .. ), ().
, .
:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
proc main():
x ?= new MyClass(10, 20)
println(x->Summ())
x->Free()
end
: 30.
:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
class MyNewClass(MyClass):
func Summ
end
func MyNewClass::Summ():
return ($a + $b) * 2
end
proc main():
x ?= new MyNewClass(10, 20)
println(x->Summ())
x->Free()
end
: 60.
? !:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
class MyNewClass(MyClass):
func Summ
end
func MyNewClass::Summ():
return ($a + $b) * 2
end
proc main():
x ?= new MyClass(10, 20)
x->Summ ?= MyNewClass::Summ
println(x->Summ())
x->Free()
end
: 60.
:
uses <bf>
uses <crt>
class MyClass:
var a, b
end
proc main():
x ?= new MyClass
println(BoolToStr(x->type == MyClass))
x->rem()
println(BoolToStr(typeof(3.14) == typeReal))
end
: true, true.
?= .
= .
. .
@<> β .
?<> β .
@= β .
:
uses <bf>
uses <crt>
proc main():
var a = 10, b
b ?= @a
PrintLn(b)
b ?= ?b
PrintLn(b)
b++
PrintLn(a)
InputLn()
end
: - , 10, 11.
Try..[catch..][finally..]end
:
uses <bf>
uses <crt>
proc main():
println("Start")
try:
println("Trying to do something...")
a ?= 10 / 0
catch:
println(getError())
finally:
println("Finally")
end
println("End")
inputln()
end
GraalVM & Truffle. JIT , . , JIT GraalVM LLVM.
.
GitHub, , .