Alternatif yang jauh lebih portabel untuk bahasa pemrograman C.

Bagi Anda yang ingat "gratis", Oak pada dasarnya adalah versi yang lebih kuat dan tingkat tinggi dari proyek itu. Tujuan Oak adalah setinggi mungkin di frontend, tetapi setenang dan seterusnya mungkin di backend.
Saya lulusan sekolah menengah yang baru dicetak dan mahasiswa baru di perguruan tinggi mencari pekerjaan. Jika Anda menikmati proyek saya, pertimbangkan untuk mendukung saya dengan membelikan saya kopi!
Kunci dari portabilitas gila Oak adalah implementasi backend yang sangat kompak. Kode untuk backend Oak dapat dinyatakan di bawah 100 baris C. Implementasi sekecil itu hanya mungkin karena set instruksi kecil dari representasi perantara. IR Oak hanya terdiri dari 17 instruksi berbeda . Itu setara dengan Brainfuck!
Backend fungsi Oak dengan sangat sederhana. Setiap instruksi beroperasi pada pita memori . Rekaman ini pada dasarnya adalah array statis pelampung presisi ganda.
let x : num = 5.25 ; ... let p : & num = & x ; `beginning of heap`
| | |
v v v
[ 0 , 0 , 0 , 5.25 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 3 , 0 , 0 , 0 , 0 , 0 , 0 , ... ]
^
|
`current location of the stack pointer`Ketika suatu variabel didefinisikan dalam suatu fungsi, itu diberikan posisi statis relatif terhadap penunjuk dasar mesin virtual saat ini. Jadi, ketika suatu fungsi dipanggil, ruang untuk variabel fungsi dialokasikan pada tumpukan, dan pointer dasar bertambah untuk menggunakan ruang baru ini. Kemudian, kompiler hanya menggantikan variabel dengan alamatnya ditambahkan ke offset pointer dasar di sisa kode!
Selain itu, pita memori berfungsi sebagai tumpukan dan tumpukan . Setelah ruang untuk semua variabel program ditetapkan, memori yang digunakan untuk tumpukan dimulai. Tumpukan tumbuh dan menyusut dengan data di seluruh program: ketika dua angka dijumlahkan, misalnya, mereka muncul dari tumpukan dan diganti dengan hasilnya. Demikian pula, tumpukan tumbuh dan menyusut di seluruh program. Heap, bagaimanapun, digunakan untuk data yang dialokasikan secara dinamis : informasi dengan jejak memori yang tidak diketahui pada waktu kompilasi .
Sekarang setelah Anda memahami bagaimana backend Oak secara fundamental beroperasi, inilah set instruksi lengkap!
| Petunjuk | Efek samping |
|---|---|
push(n: f64); | Dorong angka ke tumpukan. |
add(); | Buka dua angka dari tumpukan, dan dorong jumlah mereka. |
subtract(); | Buka dua angka dari tumpukan. Kurangi yang pertama dari yang kedua, dan dorong hasilnya. |
multiply(); | Tembakan dua angka dari tumpukan, dan dorong produk mereka. |
divide(); | Buka dua angka dari tumpukan. Bagilah yang kedua dengan yang pertama, dan dorong hasilnya. |
sign(); | Buka nomor dari tumpukan. Jika lebih besar atau sama dengan nol, dorong 1 , jika tidak tekan -1 . |
allocate(); | Buka angka dari tumpukan, dan kembalikan pointer ke jumlah sel bebas di tumpukan. |
free(); | Buka angka dari tumpukan, dan pergi ke tempat nomor ini menunjuk dalam memori. Pop nomor lain dari tumpukan, dan membebaskan banyak sel di lokasi ini dalam memori. |
store(size: i32); | Buka angka dari tumpukan, dan pergi ke tempat nomor ini menunjuk dalam memori. Kemudian, nomor size pop dari tumpukan. Simpan angka -angka ini dalam urutan terbalik di lokasi ini dalam memori. |
load(size: i32); | Buka angka dari tumpukan, dan pergi ke tempat nomor ini menunjuk dalam memori. Kemudian, tekan size sel memori berturut -turut ke tumpukan. |
call(fn: i32); | Hubungi fungsi yang ditentukan pengguna oleh ID yang ditugaskan kompiler. |
call_foreign_fn(name: String); | Hubungi fungsi asing dengan namanya di Sumber. |
begin_while(); | Mulailah beberapa saat. Untuk setiap iterasi, keluarkan angka dari tumpukan. Jika nomornya tidak nol, lanjutkan loop. |
end_while(); | Tandai akhir beberapa saat loop. |
load_base_ptr(); | Muat penunjuk dasar dari bingkai tumpukan yang ditetapkan, yang selalu kurang dari atau sama dengan penunjuk tumpukan. Variabel disimpan relatif terhadap penunjuk dasar untuk setiap fungsi. Jadi, fungsi yang mendefinisikan x: num dan y: num , x mungkin disimpan di base_ptr + 1 , dan y mungkin disimpan di base_ptr + 2 . Ini memungkinkan fungsi untuk menyimpan variabel dalam memori secara dinamis dan sesuai kebutuhan, daripada menggunakan lokasi memori statis. |
establish_stack_frame(arg_size: i32, local_scope_size: i32); | Pop Off arg_size Jumlah sel dari tumpukan dan simpan. Kemudian, hubungi load_base_ptr untuk melanjutkan bingkai tumpukan induk saat fungsi ini berakhir. Dorong local_scope_size jumlah nol ke tumpukan untuk memberi ruang bagi variabel fungsi. Akhirnya, dorong sel argumen yang disimpan kembali ke tumpukan seperti yang awalnya dipesan. |
end_stack_frame(return_size: i32, local_scope_size: i32); | Pop off return_size jumlah sel dari tumpukan dan simpan. Kemudian, pop local_scope_size jumlah sel dari tumpukan untuk membuang memori bingkai tumpukan. Buka nilai dari tumpukan dan simpan di pointer dasar untuk melanjutkan bingkai tumpukan induk. Akhirnya, dorong sel nilai pengembalian yang disimpan kembali ke tumpukan seperti yang awalnya dipesan. |
Menggunakan hanya instruksi ini, Oak dapat menerapkan abstraksi tingkat yang lebih tinggi daripada yang dapat ditawarkan C !!! Itu mungkin tidak terdengar banyak, tapi sangat kuat untuk bahasa sekecil ini.
Sintaks Oak sangat terinspirasi oleh bahasa pemrograman karat.
Fungsi dinyatakan dengan kata kunci fn , dan secara sintaksis identik dengan fungsi karat, dengan pengecualian semantik return . Selain itu, tipe dan konstanta yang ditentukan pengguna dinyatakan masing -masing dengan kata kunci type dan const .
Mirip dengan atribut luar Rust, Oak memperkenalkan banyak bendera waktu kompilasi. Beberapa di antaranya ditunjukkan di bawah ini bersama dengan fitur kayu ek lainnya.

Jadi bagaimana tepatnya kompiler oak bekerja?
Ratakan struktur ke dalam fungsinya
putnumln(*bday.day) menjadi putnumln(*Date::day(&bday)) . Ini adalah proses yang cukup sederhana.Hitung ukuran jenis operasi setiap operasi
// `3` is the size of the structure on the stack
fn Date :: new ( month : 1 , day : 1 , year : 1 ) -> 3 {
month ; day ; year
}
// self is a pointer to an item of size `3`
fn Date :: day ( self : & 3 ) -> & 1 { self + 1 }
fn main ( ) -> 0 {
let bday : 3 = Date :: new ( 5 , 14 , 2002 ) ;
}Secara statis menghitung jejak memori program
Konversi Ekspresi dan Pernyataan Oak menjadi Instruksi IR yang setara
Ada banyak keadaan berbeda di mana panggilan metode valid. Metode selalu mengambil pointer ke struktur sebagai argumen . Namun, objek yang memanggil metode tidak diharuskan menjadi penunjuk . Misalnya, kode berikut ini valid: let bday: Date = Date::new(); bday.print(); . Variabel bday bukan pointer, namun metode .print() masih dapat digunakan. Inilah alasannya.
Ketika kompiler melihat panggilan metode yang rata, ia perlu menemukan cara untuk mengubah "ekspresi instan" menjadi pointer. Untuk variabel, ini mudah: cukup tambahkan referensi! Misalnya ekspresi yang sudah ada petunjuk, bahkan lebih mudah: jangan lakukan apa -apa! Namun, untuk jenis ekspresi lainnya, ini sedikit lebih bertele -tele. Kompiler menyelinap dalam variabel tersembunyi untuk menyimpan ekspresi, dan kemudian mengkompilasi panggilan metode lagi menggunakan variabel sebagai ekspresi instan. Cukup keren, kan?
Merakit instruksi IR untuk target
Target . Jika Anda menerapkan masing -masing instruksi IR untuk bahasa Anda menggunakan sifat Target , maka Oak dapat secara otomatis mengkompilasi sampai ke bahasa pemrograman atau perakitan baru Anda! Ya, semudah kedengarannya! Untuk mengizinkan pengguna membaca dokumentasi perpustakaan dan file tanpa akses ke Internet, Oak menyediakan sub -perintah doc . Ini memungkinkan penulis untuk menambahkan atribut dokumentasi ke kode mereka untuk membantu pengguna lain memahami kode atau API mereka tanpa harus menyaring sumber dan membaca komentar.
Berikut ini beberapa contoh kode.
# [ std ]
# [ header ( "This file tests Oak's doc subcommand." ) ]
# [ doc ( "This constant is a constant." ) ]
const CONSTANT = 3 ;
// No doc attribute
const TEST = CONSTANT + 5 ;
# [ doc ( "This structure represents a given date in time.
A Date object has three members:
|Member|Value|
|-|-|
|`month: num` | The month component of the date |
|`day: num` | The day component of the date |
|`year: num` | The year component of the date |" ) ]
struct Date {
let month : num , day : num , year : num ;
# [ doc ( "The constructor used to create a date." ) ]
fn new ( month : num , day : num , year : num ) -> Date {
return [ month , day , year ] ;
}
# [ doc ( "Print the date object to STDOUT" ) ]
fn print ( self : & Date ) {
putnum ( self ->month ) ; putchar ( '/' ) ;
putnum ( self ->day ) ; putchar ( '/' ) ;
putnumln ( self ->year ) ;
}
}
# [ doc ( "This function takes a number `n` and returns `n * n`, or `n` squared." ) ]
fn square ( n : num ) -> num {
return n * n
}
fn main ( ) {
let d = Date :: new ( 5 , 14 , 2002 ) ;
d . print ( ) ;
} Dan berikut adalah contoh penggunaan sub -perintah doc untuk mencetak dokumentasi yang diformat ke terminal.

Untuk mendapatkan pembangunan pengembangan saat ini, klon repositori dan instal.
git clone https://github.com/adam-mcdaniel/oakc
cd oakc
cargo install -f --path . Untuk mendapatkan rilis saat ini, instal dari crates.io.
# Also works for updating oakc
cargo install -f oakcKemudian, file kayu ek dapat dikompilasi dengan biner OAKC.
oak c examples/hello_world.ok -c
main.exeC Backend - Kompiler GCC apa pun yang mendukung C99
GO BACKEND - Golang 1.14 Compiler
Backend TypeScript - TypeScript 3.9 Compiler