Bahasa pemrograman baru Mash

Selama beberapa tahun saya mencoba mengembangkan bahasa pemrograman saya sendiri. Menurut pendapat saya, saya ingin membuat bahasa yang paling sederhana, berfungsi penuh, dan nyaman.

Pada artikel ini saya ingin menyoroti tahapan utama pekerjaan saya dan, sebagai permulaan, menjelaskan konsep bahasa yang dibuat dan implementasi pertamanya, yang sedang saya kerjakan.

Izinkan saya mengatakan sebelumnya bahwa saya menulis seluruh proyek dalam Free Pascal, karena... program di dalamnya dapat dirakit untuk sejumlah besar platform, dan kompilernya sendiri menghasilkan biner yang sangat optimal (saya mengumpulkan semua komponen proyek dengan flag O2).

Waktu proses bahasa

Pertama-tama, ada baiknya membicarakan mesin virtual yang harus saya tulis untuk menjalankan aplikasi masa depan dalam bahasa saya. Saya memutuskan untuk mengimplementasikan arsitektur tumpukan, mungkin karena ini adalah cara termudah. Saya tidak menemukan satu pun artikel normal tentang cara melakukan ini dalam bahasa Rusia, jadi setelah membaca materi berbahasa Inggris, saya mulai merancang dan menulis sepeda saya sendiri. Selanjutnya saya akan memaparkan ide-ide β€œmaju” dan perkembangan saya dalam hal ini.

Implementasi tumpukan

Jelasnya, di bagian atas VM adalah tumpukan. Dalam implementasi saya ini berfungsi dalam blok. Pada dasarnya ini adalah array sederhana dari pointer dan variabel untuk menyimpan indeks bagian atas tumpukan.
Saat diinisialisasi, array yang terdiri dari 256 elemen dibuat. Jika lebih banyak pointer yang dimasukkan ke tumpukan, ukurannya bertambah 256 elemen berikutnya. Oleh karena itu, saat mengeluarkan elemen dari tumpukan, ukurannya disesuaikan.

VM menggunakan beberapa tumpukan:

  1. Tumpukan utama.
  2. Tumpukan untuk menyimpan titik pengembalian.
  3. Tumpukan pengumpul sampah.
  4. Coba/tangkap/akhirnya blokir tumpukan pengendali.

Konstanta dan Variabel

Yang ini sederhana. Konstanta ditangani dalam potongan kecil kode yang terpisah dan tersedia dalam aplikasi masa depan melalui alamat statis. Variabel adalah larik penunjuk dengan ukuran tertentu, akses ke selnya dilakukan berdasarkan indeks - mis. alamat statis. Variabel dapat didorong ke atas tumpukan atau dibaca dari sana. Sebenarnya karena Meskipun variabel kami pada dasarnya menyimpan pointer ke nilai dalam memori VM, bahasanya didominasi oleh bekerja dengan pointer implisit.

Pemulung

Di VM saya ini semi-otomatis. Itu. pengembang sendiri yang memutuskan kapan harus memanggil pemulung. Ini tidak berfungsi menggunakan penghitung penunjuk biasa, seperti pada Python, Perl, Ruby, Lua, dll. Ini diimplementasikan melalui sistem penanda. Itu. ketika suatu variabel dimaksudkan untuk diberi nilai sementara, penunjuk ke nilai ini ditambahkan ke tumpukan pengumpul sampah. Di masa depan, kolektor dengan cepat menelusuri daftar petunjuk yang sudah disiapkan.

Menangani blok coba/tangkap/akhirnya

Seperti dalam bahasa modern lainnya, penanganan pengecualian merupakan komponen penting. Inti VM dibungkus dalam blok try..catch, yang dapat kembali ke eksekusi kode setelah menangkap pengecualian dengan memasukkan beberapa informasi tentangnya ke dalam tumpukan. Dalam kode aplikasi, Anda dapat menentukan blok kode coba/tangkap/akhirnya, menentukan titik masuk pada tangkapan (penanganan pengecualian) dan akhirnya/akhir (akhir blok).

Multithread

Ini didukung di tingkat VM. Sederhana dan nyaman digunakan. Ia bekerja tanpa sistem interupsi, sehingga kode harus dieksekusi di beberapa thread beberapa kali lebih cepat.

Pustaka eksternal untuk VM

Tidak ada cara untuk melakukannya tanpa ini. VM mendukung impor, serupa dengan penerapannya dalam bahasa lain. Anda dapat menulis sebagian kode dalam bahasa Mash dan sebagian kode dalam bahasa asli, lalu menautkannya menjadi satu.

Penerjemah dari bahasa Mash tingkat tinggi ke bytecode untuk VM

Bahasa perantara

Untuk menulis penerjemah dengan cepat dari bahasa yang kompleks ke dalam kode VM, pertama-tama saya mengembangkan bahasa perantara. Hasilnya adalah tontonan mengerikan seperti assembler, yang tidak ada gunanya mempertimbangkannya di sini. Saya hanya akan mengatakan bahwa pada tingkat ini penerjemah memproses sebagian besar konstanta dan variabel, menghitung alamat statisnya dan alamat titik masuk.

Arsitektur penerjemah

Saya tidak memilih arsitektur terbaik untuk implementasi. Penerjemah tidak membuat pohon kode seperti yang dilakukan penerjemah lainnya. Dia melihat bagian awal struktur. Itu. jika potongan kode yang diurai tampak seperti β€œsementara <kondisi>:”, maka jelas bahwa ini adalah konstruksi perulangan while dan perlu diproses sebagai konstruksi perulangan while. Sesuatu seperti saklar yang rumit.

Berkat solusi arsitektur ini, penerjemahnya menjadi tidak terlalu cepat. Namun, kemudahan modifikasinya meningkat secara signifikan. Saya menambahkan struktur yang diperlukan lebih cepat daripada waktu dinginnya kopi saya. Dukungan OOP penuh diterapkan dalam waktu kurang dari seminggu.

Pengoptimalan kode

Di sini, tentu saja, hal ini dapat diimplementasikan dengan lebih baik (dan akan diimplementasikan, namun nanti, segera setelah ada yang melakukannya). Sejauh ini, pengoptimal hanya mengetahui cara memotong kode, konstanta, dan impor yang tidak digunakan dari rakitan. Selain itu, beberapa konstanta dengan nilai yang sama diganti dengan satu konstanta. Itu saja.

bahasa tumbuk

Konsep dasar bahasa

Ide utamanya adalah mengembangkan bahasa yang paling fungsional dan sederhana. Saya pikir pembangunan mengatasi tugasnya dengan baik.

Blok kode, prosedur dan fungsi

Semua konstruksi dalam bahasa dibuka dengan titik dua. : dan ditutup oleh operator akhir.

Prosedur dan fungsi masing-masing dideklarasikan sebagai proc dan func. Argumennya tercantum dalam tanda kurung. Semuanya seperti kebanyakan bahasa lainnya.

Operator kembali Anda dapat mengembalikan nilai dari suatu fungsi, operator istirahat memungkinkan Anda keluar dari prosedur/fungsi (jika berada di luar loop).

Π΅Ρ€ ΠΎΠ΄Π°:

...

func summ(a, b):
  return a + b
end

proc main():
  println(summ(inputln(), inputln()))
end

Desain yang Didukung

  • Loop: untuk..akhir, sementara..akhir, sampai..akhir
  • Ketentuan: if..[else..]end, switch..[case..end..][else..]end
  • Metode: proc <nama>():... akhir, fungsi <nama>():... akhir
  • Label & buka: <nama>:, lompat <nama>
  • Enum enumerasi dan array konstan.

Variabel

Penerjemah dapat menentukannya secara otomatis, atau jika pengembang menulis var sebelum mendefinisikannya.

Contoh kode:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Variabel global dan lokal didukung.

OOP

Nah, kita sampai pada topik yang paling enak. Mash mendukung semua paradigma pemrograman berorientasi objek. Itu. kelas, pewarisan, polimorfisme (termasuk dinamis), refleksi otomatis dinamis dan introspeksi (penuh).

Tanpa basa-basi lagi, lebih baik berikan saja contoh kodenya.

Kelas sederhana dan bekerja dengannya:

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

Akan menghasilkan: 30.

Warisan dan polimorfisme:

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

Akan menghasilkan: 60.

Bagaimana dengan polimorfisme dinamis? Ya, ini refleksi!:

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

Akan menghasilkan: 60.

Sekarang mari kita luangkan waktu sejenak untuk introspeksi nilai dan kelas sederhana:

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

Akan menghasilkan: benar, benar.

Tentang operator penugasan dan petunjuk eksplisit

Operator ?= digunakan untuk menetapkan variabel sebagai penunjuk ke suatu nilai di memori.
Operator = mengubah nilai dalam memori menggunakan pointer dari suatu variabel.
Dan sekarang sedikit tentang petunjuk eksplisit. Saya menambahkannya ke bahasa sehingga ada.
@<variable> β€” mengambil pointer eksplisit ke suatu variabel.
?<variable> β€” mendapatkan variabel dengan pointer.
@= β€” memberikan nilai ke variabel dengan penunjuk eksplisit ke variabel tersebut.

Π΅Ρ€ ΠΎΠ΄Π°:

uses <bf>
uses <crt>

proc main():
  var a = 10, b
  b ?= @a
  PrintLn(b)
  b ?= ?b
  PrintLn(b)
  b++
  PrintLn(a)
  InputLn()
end

Akan menghasilkan: beberapa angka, 10, 11.

Coba..[tangkap..][akhirnya..]akhiri

Π΅Ρ€ ΠΎΠ΄Π°:

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

ΠŸΠ»Π°Π½Ρ‹ Π½Π° Π±ΡƒΠ΄ΡƒΡ‰Π΅Π΅

Saya terus mencari dan melihat GraalVM & Truffle. Lingkungan runtime saya tidak memiliki kompiler JIT, jadi dalam hal kinerja saat ini hanya bersaing dengan Python. Saya berharap dapat mengimplementasikan kompilasi JIT berdasarkan GraalVM atau LLVM.

gudang

Anda dapat bermain-main dengan perkembangannya dan mengikuti proyeknya sendiri.

Situs web
Repositori di GitHub

Terima kasih telah membaca sampai akhir jika Anda melakukannya.

Sumber: www.habr.com

Tambah komentar