
Tahun 2019 telah tiba, liburan Tahun Baru akan segera berakhir. Saatnya untuk mulai mengingat byte, perintah, variabel, loop ...
Sesuatu yang sudah saya lupakan dengan liburan ini. Harus ingat bersama!
Hari ini kami akan membuat juru bahasa untuk mesin byte kami. Ini adalah artikel ketiga, bagian pertama ada di sini:
bagian 1 ,
bagian 2 .
Selamat Tahun Baru untuk semua orang, dan selamat datang di cut!
Untuk mulai dengan, saya akan menjawab pertanyaan dari
fpauk . Pertanyaan-pertanyaan ini benar sekali. Sekarang arsitektur mesin byte ini sedemikian rupa sehingga kami bekerja dengan alamat prosesor langsung. Tetapi dalam bytecode alamat-alamat ini tidak, mereka terbentuk setelah dimulainya sistem. Setelah sistem dimulai, kita dapat membuat pointer apa saja, dan kode ini akan berfungsi dengan baik pada platform apa pun. Misalnya, alamat variabel atau array dapat diperoleh dengan perintah var0. Perintah ini akan bekerja pada platform apa pun dan akan mengembalikan alamat yang benar khusus untuk platform ini. Maka Anda dapat bekerja dengan alamat ini sesuka Anda.
Tapi tetap saja,
fpauk benar. Alamat tidak dapat disimpan dalam bytecode. Ternyata kita dapat menulis kode platform-independen, tetapi untuk ini kita perlu melakukan beberapa upaya. Secara khusus, pastikan alamat tidak ada dalam bytecode. Dan mereka bisa masuk, misalnya, jika Anda menyimpan kode yang dikompilasi ke file. Itu akan berisi data, dan itu bisa alamat. Misalnya, nilai-nilai variabel di sini, konteks, dan lainnya.
Untuk menghilangkan masalah seperti itu, Anda perlu membuat alamat virtual. Mengatasi prosesor x86 cukup kuat, dan dalam banyak kasus bahkan tidak akan menambah perintah tambahan. Tapi tetap saja, saya akan melanjutkan dalam arsitektur saat ini, dengan alamat absolut. Dan kemudian, ketika kita sampai pada tes, akan mungkin untuk mengulang alamat menjadi yang virtual, dan melihat bagaimana ini akan mempengaruhi kinerja. Ini menarik.
Lakukan pemanasan
Dan sekarang sedikit latihan. Mari kita membuat bagian lain dari perintah byte yang kecil namun bermanfaat. Ini akan menjadi perintah nip, emit, 1+, +!, -!, Count, kata-kata kerja dengan return stack r>,> r, r @, string literal ("), dan kata-kata konstan 1, 2, 3, 4, 8. Jangan lupa untuk memasukkannya ke dalam tabel perintah.
Berikut adalah kode untuk perintah-perintah inib_nip = 0x39 bcmd_nip: pop rax mov [rsp], rax jmp _next b_emit = 0x81 bcmd_emit: pop rax mov rsi, offset emit_buf # mov [rsi], al mov rax, 1 # № 1 - sys_write mov rdi, 1 # № 1 - stdout mov rdx, 1 # push r8 syscall # pop r8 jmp _next b_wp = 0x18 bcmd_wp: incq [rsp] jmp _next b_setp = 0x48 bcmd_setp: pop rcx pop rax add [rcx], rax jmp _next b_setm = 0x49 bcmd_setm: pop rcx pop rax sub [rcx], rax jmp _next b_2r = 0x60 bcmd_2r: pop rax sub rbp, 8 mov [rbp], rax jmp _next b_r2 = 0x61 bcmd_r2: push [rbp] add rbp, 8 jmp _next b_rget = 0x62 bcmd_rget: push [rbp] jmp _next b_str = 0x82 bcmd_str: movzx rax, byte ptr [r8] lea r8, [r8 + rax + 1] jmp _next b_count = 0x84 bcmd_count: pop rcx movzx rax, byte ptr [rcx] inc rcx push rcx push rax jmp _next b_num1 = 0x03 bcmd_num1: push 1 jmp _next b_num2 = 0x04 bcmd_num2: push 2 jmp _next b_num3 = 0x05 bcmd_num3: push 3 jmp _next b_num4 = 0x06 bcmd_num4: push 4 jmp _next b_num8 = 0x07 bcmd_num8: push 8 jmp _next
Perintah nip menghapus kata di bawah bagian atas tumpukan. Itu sama dengan perintah swap drop. Ini kadang-kadang bisa membantu.
Perintah emit mendorong satu karakter dari stack. Ini menggunakan sistem panggilan nomor 1 yang sama, karakter menempatkan dalam buffer dengan panjang 1.
Perintah count sangat sederhana - dibutuhkan alamat baris dengan penghitung dari tumpukan dan mengubahnya menjadi dua nilai - alamat baris tanpa penghitung dan panjangnya.
Perintah b_2r, b_r2, b_rget adalah kata Fort r>,> r, r @. Yang pertama mengambil kata dari tumpukan kembali dan menempatkannya di tumpukan aritmatika. Yang kedua melakukan operasi yang berlawanan. Yang ketiga menyalin kata dari tumpukan kembali, menempatkannya di aritmatika, tumpukan kembali tidak berubah.
Perintah b_setp dan b_setm adalah kata +! dan -! .. Mereka mengambil nilai dan alamat dari tumpukan, dan memodifikasi kata di alamat yang ditentukan, menambah atau menghapus nilai dari tumpukan.
Perintah b_str memiliki parameter panjang acak - garis dengan penghitung. Baris ini berada dalam bytecode setelah byte perintah, dan perintah hanya mendorong alamat baris ini ke stack. Sebenarnya, ini adalah string literal.
Sisa tim, saya pikir, tidak perlu komentar.
Kami juga akan membuat perintah untuk mencetak string konstan (. "). Kami akan menerapkannya sebagai titik masuk untuk mengetik, sebagai berikut:
b_strp = 0x83 bcmd_strp: movsx rax, byte ptr [r8] inc r8 push rax push r8 add r8, rax b_type = 0x80 bcmd_type: mov rax, 1 # № 1 - sys_write mov rdi, 1 # № 1 - stdout pop rdx # pop rsi # push r8 syscall # pop r8 jmp _next
Perintah ini terstruktur mirip dengan b_str. Hanya saja dia tidak meletakkan apa pun di tumpukan. Baris yang terletak di belakang perintah ini sebagai parameter hanya ditampilkan kepada pengguna.
Pemanasan telah berakhir, saatnya telah tiba untuk sesuatu yang lebih serius. Mari kita berurusan dengan generator kata dan perintah var lainnya.
Kata-kata generator
Ingat variabel. Kita tahu bagaimana mereka diatur pada level bytecode (perintah var0). Untuk membuat variabel baru, benteng menggunakan konstruksi berikut:
variable < >
Setelah melakukan urutan ini, kata baru <nama variabel> dibuat. Eksekusi kata baru ini mendorong alamat pada stack untuk menyimpan nilai variabel. Ada juga konstanta di benteng, mereka diciptakan seperti ini:
<> constant < >
Setelah membuat konstanta, eksekusi kata <nama konstan> ditempatkan pada tumpukan <nilai>.
Jadi, variabel kata dan konstanta kata adalah kata-kata penghasil. Mereka dirancang untuk membuat kata-kata baru. Di benteng, kata-kata seperti itu dijelaskan menggunakan create ... does> construct.
Variabel dan konstanta dapat didefinisikan sebagai berikut:
: variable create 0 , does> ; : constant create , does> @ ;
Apa artinya semua ini?
Kata create, ketika dieksekusi, membuat kata baru dengan nama yang akan diambil ketika dieksekusi dari input stream. Setelah pembuatan, urutan kata dieksekusi sebelum kata tersebut>. Tetapi pada saat eksekusi kata ini, apa yang ditulis sesudahnya> dieksekusi. Pada saat yang sama, alamat data sudah ada di tumpukan (seperti yang mereka katakan di benteng, "bidang data").
Jadi, ketika membuat variabel, urutan "0" dijalankan - ini adalah pemesanan kata mesin dengan nol pengisian. Dan ketika kata yang dibuat dieksekusi, tidak ada yang dilakukan (setelah melakukan> tidak ada apa-apa). Alamat memori tempat nilai disimpan tetap berada di tumpukan.
Dalam definisi konstanta, sebuah kata dengan nilai yang mengisi stack dicadangkan. Ketika kata yang dibuat dieksekusi, "@" dieksekusi, yang mengambil nilai pada alamat yang ditentukan.
Sekarang mari kita pikirkan bagaimana kata yang kita buat bisa diatur. Ini mendorong alamat data ke stack (seperti var0), dan kemudian mentransfer kontrol ke alamat tertentu, bytecode. Perintah var0 segera kembali. Tetapi dalam hal ini, kita tidak perlu kembali, tetapi pada kenyataannya, sebuah transisi.
Sekali lagi saya akan merumuskan apa yang perlu dilakukan:
- letakkan alamat data di stack
- lompat ke sepotong kode setelah>
Ternyata, Anda hanya perlu mentransfer kontrol ke alamat bytecode lain, tetapi pertama-tama letakkan alamat byte berikutnya (R8) pada stack.
Ini hampir merupakan perintah cabang! Dan di sini dia tidak sendirian. Sudah memiliki branch8 dan branch16. Kami akan memberi nama perintah var8 dan var16 baru, dan biarkan ini hanya menjadi titik masuk ke perintah cabang. Kami menghemat transisi ke tim transisi :) Jadi, akan seperti ini:
b_var8 = 0x29 bcmd_var8: push r8 b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_var16 = 0x30 bcmd_var16: push r8 b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next
Dalam cara yang baik, perintah var32 akan tetap berfungsi, dan juga var64. Kami tidak memiliki transisi yang begitu lama, karena transisi yang biasa tidak begitu lama. Tetapi untuk perintah var, ini adalah kasus yang sangat realistis. Tetapi untuk saat ini, kami tidak akan melakukan perintah ini. Kami akan melakukannya nanti, jika perlu.
Dengan kata-kata generator diurutkan. Sekarang giliran untuk memutuskan kamus.
Kosakata
Biasanya, ketika mereka berbicara secara sederhana tentang kamus benteng, itu disajikan dalam bentuk daftar entri kamus searah. Faktanya, semuanya sedikit lebih rumit, karena benteng mendukung banyak kamus. Padahal, mereka adalah pohon. Pencarian kata dalam pohon seperti itu dimulai dengan "sheet" - ini adalah kata terakhir dalam kamus saat ini. Kamus saat ini ditentukan oleh variabel konteks, dan alamat kata terakhir ada di kata kamus. Variabel lain digunakan untuk mengelola kamus - kamus ini mendefinisikan kamus tempat kata-kata baru akan ditambahkan. Dengan demikian, satu kamus dapat diinstal untuk pencarian, dan satu lagi untuk memasukkan kata-kata baru.
Untuk kasus sederhana kami, adalah mungkin untuk tidak melakukan dukungan banyak kamus, tetapi saya memutuskan untuk tidak menyederhanakan apa pun. Bahkan, untuk memahami kode byte, mesin byte, tidak perlu mengetahui apa yang dijelaskan dalam bagian ini. Karena itu, yang tidak tertarik, Anda dapat melewati bagian ini. Nah, siapa yang ingin tahu detailnya - silakan!
Awalnya, ada kamus dasar yang dinamai sebagainya. Ini berarti bahwa ada kata seperti itu - sebagainya. Kata ini juga disebut "kamus", ada beberapa kebingungan. Oleh karena itu, ketika menyangkut suatu kata, saya akan menyebutnya kata kamus.
Kamus baru dibuat menggunakan konstruksi ini:
vocabulary < >
Ini menciptakan kata dengan nama <dibuat nama kamus>. Saat dijalankan, kata ini akan menetapkan kamus yang dibuat sebagai kamus awal untuk pencarian.
Bahkan, dalam kata kamus ada tautan ke artikel terakhir kamus ini, yang dengannya pencarian dimulai. Dan pada saat eksekusi, kata kamus ini menulis tautan ke bidang datanya dalam variabel konteks.
Nanti akan dimungkinkan untuk membuat kata kosakata, yang di benteng, dalam implementasi saat ini, dijelaskan secara sederhana:
: vocabulary create context @ , does> context ! ;
Jadi, ciptakan kata maju. Kami akan menggunakan perintah var8. Bytecode "konteks!" tempatkan tepat setelah bidang data:
forth: .byte b_var8 .byte does_voc - . - 1 .quad 0 # <-- . , - . does_voc: .byte b_call8 .byte context - . - 1 .byte b_set .byte b_exit
Sekarang kembali ke membuat kamus itu sendiri.
Secara umum, di benteng, deskripsi kata dalam memori disebut "entri kamus". Dalam istilah biasa, saya akan mengatakan bahwa ada judul artikel dan kode-kodenya. Tapi semuanya tidak biasa di benteng, di sana disebut "bidang nama", "bidang komunikasi", "bidang kode" dan "bidang data". Saya akan mencoba memberi tahu Anda apa artinya semua ini dalam istilah tradisional.
Bidang nama adalah nama kata, "baris dengan penghitung." Seperti dalam pascal lama - byte panjang string, kemudian string. Bidang tautan adalah tautan ke artikel sebelumnya. Sebelumnya, hanya ada alamat, tetapi kami akan memiliki kode platform-independen, dan ini akan menjadi offset. Bidang kode, secara tradisional di benteng, adalah kode mesin (ketika implementasinya adalah pada jalur langsung), untuk kata-kata di luar kernel ada panggilan _call. Kami hanya akan memiliki bytecode. Dan bidang data untuk kata-kata yang berisi data - misalnya, untuk variabel atau konstanta. Omong-omong, kata kamus juga merujuk padanya.
Untuk kompiler, kita masih membutuhkan flag. Biasanya benteng hanya membutuhkan satu flag - segera, dan itu ditempatkan dalam byte yang panjang (terkadang ada yang lain - tersembunyi). Tapi ini untuk kode dijahit langsung, di mana kontrol prosesor ditransfer ketika dipanggil ke bidang kode. Dan kami memiliki kata-kata yang berbeda - bytecode dan kode mesin, dan setidaknya dua, atau bahkan tiga, bendera diperlukan.
Berapa banyak yang dibutuhkan untuk bidang komunikasi? Pada awalnya, saya ingin menggunakan 16 bit. Ini adalah tautan ke kata sebelumnya, dan kata itu pasti kurang dari 64 Kb. Tetapi kemudian saya ingat bahwa kata itu dapat berisi data dengan ukuran hampir berapa saja. Dan selain itu, di hadapan beberapa kamus, tautannya bisa melalui banyak kata. Ternyata dalam kebanyakan kasus, 8 bit sudah cukup, tetapi bisa ada 16 dan 32. Dan bahkan 64 bit, jika ada data lebih dari 4 GB. Baiklah, mari buat dukungan untuk semua opsi. Opsi mana yang digunakan - taruh di flag. Ternyata setidaknya 4 flag: atribut langsung, atribut kata inti, dan 2 bit per varian bidang komunikasi yang digunakan. Penting untuk menggunakan byte terpisah untuk flag, tidak dengan cara lain.
Kami mendefinisikan bendera sebagai berikut:
f_code = 0x80 f_immediate = 0x60
Bendera f_code akan untuk kata-kata kernel yang ditulis dalam assembler, bendera f_immediate akan berguna untuk kompilator, tentang hal itu di artikel berikutnya. Dan dua bit paling tidak signifikan akan menentukan panjang bidang komunikasi (1, 2, 4 atau 8 byte).
Jadi, judul artikel akan seperti ini:
- bendera (1 byte)
- bidang komunikasi (1-8 byte)
- nama panjang byte
- nama (1-255 byte)
Sampai saat ini, saya belum menggunakan kemampuan assembler "makro". Dan sekarang kita membutuhkannya. Inilah cara saya mendapatkan makro dengan item nama untuk membentuk judul kata:
.macro item name, flags = 0 link = . - p_item 9: .if link >= -256/2 && link < 256/2 .byte \flags .byte link .elseif link >= -256*256/2 && link < 256*256/2 .byte \flags | 1 .word . - p_item .elseif link >= -256*256*256*256/2 && link < 256*256*256*256/2 .byte \flags | 2 .int . - p_item .elseif link >= -256*256*256*256*256*256*256*256/2 && link < 256*256*256*256*256*256*256*256/2 .byte \flags | 3 .quad . - p_item .endif p_item = 9b .byte 9f - . - 1 .ascii "\name" 9: .endm
Makro ini menggunakan nilai p_item - ini adalah alamat entri kamus sebelumnya. Nilai ini pada akhirnya diperbarui untuk penggunaan di masa mendatang: p_item = 9b. Di sini 9b adalah label, bukan angka, jangan bingung :) Makro memiliki dua parameter - nama kata dan bendera (opsional). Di awal makro, offset ke kata sebelumnya dihitung. Kemudian, tergantung pada ukuran offset, bendera dan bidang komunikasi dari ukuran yang diinginkan dikompilasi. Kemudian byte dari panjang nama dan nama itu sendiri.
Tentukan sebelum kata pertama p_item sebagai berikut:
p_item = .
Intinya adalah alamat kompilasi saat ini di assembler. Sebagai hasil dari definisi ini, kata pertama akan merujuk pada dirinya sendiri (bidang komunikasi akan 0). Ini adalah tanda berakhirnya kamus.
Ngomong-ngomong, apa yang akan ada di bidang kode kata-kata kernel? Minimal, Anda harus menyimpan kode perintah di suatu tempat. Saya memutuskan untuk menempuh jalan yang paling sederhana. Untuk kata-kata kernel juga akan ada bytecode. Untuk sebagian besar tim, ini hanya perintah byte, diikuti oleh b_exit. Jadi, untuk penerjemah, bendera f_code tidak perlu dianalisis, dan perintah untuk itu tidak akan berbeda dengan cara apa pun. Anda hanya perlu memanggil bytecode untuk semua orang.
Ada keuntungan lain untuk opsi ini. Untuk perintah dengan parameter, Anda dapat menentukan parameter aman. Misalnya, jika Anda menjalankan perintah lit dalam implementasi Fort dengan kode dijahit langsung, sistem akan macet. Dan di sini akan ditulis di sana, misalnya, menyala 0, dan urutan ini hanya akan menempatkan 0 pada tumpukan. Bahkan untuk cabang bisa dilakukan dengan aman!
.byte branch8 .byte 0f - . 0: .byte b_exit
Dengan panggilan seperti itu akan ada beberapa overhead, tetapi untuk penerjemah mereka tidak akan signifikan. Dan kompiler akan menganalisis bendera, dan mengkompilasi kode yang benar dan cepat.
Kata pertama tentu saja akan menjadi kata "sebagainya" - kosakata dasar yang kita buat. Di sini, cukup gunakan perintah var berguna dengan tautan ke kode setelah>. Saya sudah mengutip kode ini di bagian sebelumnya, tetapi saya akan mengulanginya lagi, dengan judul:
p_item = . item forth .byte b_var8 .byte does_voc - . - 1 .quad 0 does_voc: .byte b_call8 .byte context - . - 1 .byte b_set .byte b_exit
Dan kami akan segera membuat variabel konteks dan, kami membutuhkan mereka untuk mencari kata-kata:
item .byte b_var0 .quad 0 item context context: .byte b_var0 .quad 0
Dan sekarang, Anda harus bersabar dan menulis judul untuk setiap kata yang kami tulis di assembler dengan bendera f_code:
item 0, f_code .byte b_num0 .byte b_exit item 1, f_code .byte b_num1 .byte b_exit ... item 1-, f_code .byte b_wm .byte b_exit item 1+, f_code .byte b_wp .byte b_exit item +, f_code .byte b_add .byte b_exit item -, f_code .byte b_sub .byte b_exit item *, f_code .byte b_mul .byte b_exit
Dan seterusnya ...
Dengan tim yang ditulis dalam bytecode bahkan lebih mudah. Cukup menambahkan hanya heading sebelum bytecode, seperti kata sebagainya, misalnya:
item hold hold: .byte b_call8 .byte holdpoint - . - 1 # holdpoint ...
Untuk perintah dengan parameter, kami akan membuat parameter aman. Sebagai contoh, biarkan perintah lite mengembalikan nomor Pi, jika seseorang memanggil mereka secara interaktif, akan ada easter seperti itu :)
item lit8, f_code .byte b_lit8 .byte 31 .byte b_exit item lit16, f_code .byte b_lit16 .word 31415 .byte b_exit item lit32, f_code .byte b_lit32 .int 31415926 .byte b_exit item lit64, f_code .byte b_lit64 .quad 31415926535 .byte b_exit
Kata terakhir dalam daftar akan membuat kata selamat tinggal secara simbolis. Tetapi kita masih perlu menginisialisasi alamat kata ini di bidang data sebagainya. Untuk mendapatkan alamat kata ini, gunakan perintah var0:
last_item: .byte b_var0 item bye, f_code .byte b_bye
Dalam desain ini, jika kita memanggil alamat last_item dalam bytecode, kita akan mendapatkan alamat kata bye. Untuk menuliskannya pada bidang data kata maju, jalankan maju, dan alamat yang diinginkan akan berada dalam konteks. Dengan demikian, kode inisialisasi sistem akan seperti ini:
forth last_item context @ !
Dan sekarang mari kita lanjutkan langsung ke penerjemah. Pertama, kita perlu bekerja dengan buffer input dan mengekstrak kata-kata darinya. Biarkan saya mengingatkan Anda bahwa penerjemah di benteng sangat sederhana. Dia mengekstrak kata-kata dari buffer input secara berurutan, mencoba menemukannya. Jika kata itu ditemukan, penerjemah meluncurkannya untuk dieksekusi.
Input buffer dan ekstraksi kata
Sejujurnya, saya tidak ingin menghabiskan banyak waktu mempelajari standar benteng. Tapi tetap saya akan mencoba membuatnya sedekat mungkin dengan mereka, terutama dari ingatan. Jika ahli benteng akan melihat perbedaan yang kuat di sini - tulis, saya akan memperbaikinya.
Benteng memiliki tiga variabel untuk bekerja dengan buffer: tib, #tib dan> in. Variabel tib mendorong alamat buffer input pada tumpukan. Variabel #tib mendorong jumlah karakter yang ada di buffer ke tumpukan. Dan variabel> dalam berisi offset di buffer input, di luar teks mentah itu berada. Tentukan variabel-variabel ini.
item tib .byte b_var0 v_tib: .quad 0 item #tib .byte b_var0 v_ntib: .quad 0 item >in .byte b_var0 v_in: .quad 0
Selanjutnya kita membuat kata blword. Kata ini, menggunakan variabel yang ditentukan, mendapatkan kata berikutnya dari aliran input. Spasi digunakan sebagai pembatas dan semua karakter dengan kode kurang dari spasi. Kata ini akan di assembler. Setelah debugging, ternyata seperti ini:
b_blword = 0xF0 bcmd_blword: mov rsi, v_tib # mov rdx, rsi # RDX mov rax, v_in # mov rcx, v_ntib # add rsi, rax # RSI - sub rcx, rax # jz 3f word2: lodsb # AL RSI cmp al, ' ' ja 1f # ( ) dec rcx jnz word2 # 3: sub rsi, rdx mov v_in, rsi push rcx jmp _next 1: lea rdi, [rsi - 1] # RDI = RSI - 1 ( ) dec rcx word3: lodsb cmp al, ' ' jbe 2f dec rcx jnz word3 2: mov rax, rsi sub rsi, rdx # ( ) mov v_in, rsi sub rax, rdi dec rax jz word1 push rdi # word1: push rax # jmp _next
Kata ini mirip dengan kata standar, tetapi, tidak seperti itu, memperhitungkan semua pembatas dan tidak menyalin kata ke buffer. Ini mengembalikan hanya dua nilai pada stack - address dan length. Jika kata tidak dapat diambil, kembali 0. Waktunya telah tiba untuk mulai menulis juru bahasa.
Pencarian Kata dan Juru Bahasa
Untuk memulai, mari kita buat kata interpretasikan. Kata ini memilih kata baru dari buffer menggunakan blworld, mencarinya di kamus, dan mengeksekusinya. Dan itu berulang sampai buffer habis. Kami masih belum memiliki kemampuan untuk mencari kata, jadi kami akan menulis sebuah rintisan uji coba yang hanya akan mencetak kata dari buffer menggunakan tipe. Ini akan memberi kita kesempatan untuk memeriksa dan men-debug blworld:
# : interpret begin blword dup while type repeat drop ; item interpret 1: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_type .byte b_branch8 .byte 1b - . 0: .byte b_drop .byte b_exit
Sekarang buat kata berhenti. Biasanya mereka melakukan ini ketika menerapkan sistem benteng: mereka menggunakan kata berhenti atau batal untuk masuk ke mode penerjemah. Kata berhenti menyiram tumpukan dan memulai loop input dan interpretasi buffer tanpa akhir. Bersama kami itu hanya akan menjadi panggilan interpretasi. Kode untuk kata ini terdiri dari dua bagian. Bagian pertama akan di assembler, bagian kedua akan di bytecode. Bagian pertama:
b_quit = 0xF1 bcmd_quit: lea r8, quit mov sp, init_stack mov bp, init_rstack jmp _next
Bagian kedua:
quit: .byte b_call16 .word interpret - . - 2 .byte b_bye
Seperti biasa, kode assembler terletak di bagian .text, kode byte ada di bagian .data.
Dan akhirnya, ubah bytecode awal. Hanya akan ada inisialisasi kamus, mengatur penyangga pada baris mulai, dan panggilan berhenti.
# forth last_item context @ ! start_code tib ! < > #tib ! quit start: .byte b_call16 .word forth - . - 2 .byte b_call16 .word last_item - . - 2 .byte b_call16 .word context - . - 2 .byte b_get .byte b_set .byte b_call8 .byte start_code - . - 1 .byte b_call16 .word tib - . - 2 .byte b_set .byte b_lit16 .world 1f - 0f .byte b_call16 .word ntib - . - 2 .byte b_set .byte b_quit start_code: .byte b_var0 0: .ascii "word1 word2 word3" 1:
Kompilasi, tautkan, jalankan!
$ as forth.s -o forth.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth word1word2wordBye!
Agak seperti bubur, tapi inilah yang seharusnya hasilnya. Kami menghasilkan tanpa pembatas. By the way, letakkan feed line sebelum membeli untuk masa depan, ini tidak ada salahnya.
Tentu saja, saya harus mengutak-atik debugging. Selain "Segmentasi fault (core dumped)" yang telah disebutkan sebelumnya, terkadang hasil yang menarik diperoleh. Sebagai contoh, ini:
$ ./forth word1word2word3forth)%60Acurrent(context(%600lit8lit16zlit32v%5E%DF%80lit64v%5E%DF%80call8call16call32branch8branch16qbranch8qbranch16exit1-+!-%22*#/$mod%25/mod&abs'dup0drop1swap2rot3-rot4over5pick6roll7depth8@@!Ac@Bc!Cw@Dw!Ei@Fi!G0=P0%3CQ0%3ER=S%3CT%3EU%3C=V%3E=Wvar8)var160base(holdbuf(Qholdpoint(hold@0U110ACp@&20T0!?!%3CgF!A0@RF!5%220'%DE%A61Q-%DD%80:tib(%7F%60(%3Ein(%20%20%20%20%20%20%20interpret01('byeSegmentation%20fault%20(core%20dumped)
Ini sepertinya hanya seluruh kamus kami dalam bentuk biner dengan teks dipotong menjadi pembatas :) Itu terjadi ketika saya lupa "dec rcx" sebelum word3 pada perintah b_blword.
Kami dapat memilih kata-kata dari input stream, ada kamus. Sekarang Anda perlu menerapkan pencarian kamus dan meluncurkan kata-kata untuk dieksekusi. Ini akan membutuhkan kata-kata find, cfa, dan eksekusi.
Kata find akan mengambil alamat kata dan panjangnya dari tumpukan. Kata ini akan dikembalikan oleh alamat entri kamus atau 0 jika tidak ditemukan.
Kata cfa di alamat artikel akan menghitung alamat bytecode yang dapat dieksekusi.
Dan kata execute akan mengeksekusi bytecode.
Mari kita mulai dengan find. Dalam standar benteng, dibutuhkan satu alamat - garis dengan penghitung. Tetapi saya tidak ingin sekali lagi menyalin string ke buffer, jadi saya akan menyimpang sedikit dari standar. Kata find akan mengambil dua parameter pada stack - alamat dan panjang string (pada kenyataannya, itu mengembalikan kata blword). Setelah debugging, kata ini mengambil bentuk berikut:
b_find = 0xF2 bcmd_find: pop rbx # pop r9 # mov rdx, v_context mov rdx, [rdx] # # find0: mov al, [rdx] # and al, 3 # - , , or al, al jz find_l8 cmp al, 1 jz find_l16 cmp al, 2 jz find_l32 mov r10, [rdx + 1] # 64 lea rsi, [rdx + 9] # jmp find1 find_l32: movsx r10, dword ptr [rdx + 1] # 32 lea rsi, [rdx + 5] # jmp find1 find_l16: movsx r10, word ptr [rdx + 1] # 16 lea rsi, [rdx + 3] # jmp find1 find_l8: movsx r10, byte ptr [rdx + 1] # 8 lea rsi, [rdx + 2] # find1: movzx rax, byte ptr [rsi] # cmp rax, rbx jz find2 # find3: or r10, r10 jz find_notfound # , add rdx, r10 # jmp find0 # , find2: inc rsi mov rdi, r9 mov rcx, rax repz cmpsb jnz find3 # push rdx jmp _next find_notfound: push r10 jmp _next
, . interpret, type «find .»:
# : interpret begin blword dup while find . repeat drop ; item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_find .byte b_call16 .word dot - . - 2 .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit
, , «0 1- dup + .».
!
$ ld forth.o -o forth $ ./forth 6297733 6297898 6298375 Bye!
, . ( ). cfa. , , find:
b_cfa = 0xF3 bcmd_cfa: pop rdx # mov al, [rdx] # and al, 3 # - , , or al, al jz cfa_l8 cmp al, 1 jz cfa_l16 cmp al, 2 jz cfa_l32 lea rsi, [rdx + 9] # (64 ) jmp cfa1 find_l32: lea rsi, [rdx + 5] # (32 ) jmp cfa1 find_l16: lea rsi, [rdx + 3] # (16 ) jmp cfa1 find_l8: lea rsi, [rdx + 2] # (8 ) xor rax, rax lodsb add rsi, rax push rsi jmp _next
, , execute, :
b_execute = 0xF4 bcmd_execute: sub rbp, 8 mov [rbp], r8 # pop r8 # - jmp _next
interpret !
# : interpret begin blword dup while find cfa execute repeat drop ; item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_find .byte b_cfa .byte b_execute .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit
:
$ as forth.s -o forth.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth -2 Bye!
, !
(), 0 1, , -2 :)
, - . , — 0, 1, 2, 3, 4 8 ( ). , «number?». , find, . «number?» — . , 1. , : 0.
, :
b_number = 0xF5 bcmd_number: pop rcx # pop rsi # xor rax, rax # xor rbx, rbx # mov r9, v_base # xor r10, r10 # or rcx, rcx jz num_false mov bl, [rsi] cmp bl, '+' jnz 1f inc rsi dec rcx jz num_false jmp num0 1: cmp bl, '-' jnz num0 mov r10, 1 inc rsi dec rcx jz num_false num0: mov bl, [rsi] cmp bl, '0' ja num_false cmp bl, '9' jae num_09 cmp bl, 'A' ja num_false cmp bl, 'Z' jae num_AZ cmp bl, 'a' ja num_false sub bl, 'a' - 10 jmp num_check num_AZ: sub bl, 'A' - 10 jmp num_check num_09: sub bl, '0' num_check: cmp rbx, r9 jge num_false add rax, rbx mul r9 inc rsi dec rcx jnz num0 or r10, r10 push rax push 1 jmp _next num_false: xor rcx, rcx push rcx jmp _next
interpret. , :
# : interpret # begin # blword dup # while # over over find dup # if -rot drop drop cfa execute else number? drop then # repeat # drop ; item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over .byte b_find .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_numberq .byte b_drop 2: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit last_item: .byte b_var0 item bye, f_code .byte b_bye
! - , -, «» -… , … GDB, … — ! , .
!
… , :)
, : — «s.». , interpret. , ,
. :
# : .s depth dup . c": emit do dup while dup pick . 1- again drop ; item .s # 11 22 33 prstack: .byte b_depth # 11 22 33 3 .byte b_dup # 11 22 33 3 3 .byte b_lit8 .byte '(' .byte b_emit .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_strp # 11 22 33 3 .byte 3 .ascii "): " 1: .byte b_dup # 11 22 33 3 3 .byte b_qnbranch8 # 11 22 33 3 .byte 2f - . .byte b_dup # 11 22 33 3 3 .byte b_pick # 11 22 33 3 11 .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_wm # 11 22 33 2 .byte b_branch8 .byte 1b - . 2: .byte b_drop # 11 22 33 .byte b_exit
, . , , . , . «» !
:
.macro prs new_line = 1 .byte b_call16 .word prstack - . - 2 .if \new_line > 0 .byte b_lit8, '\n' .byte b_emit .endif .endm
, :
item interpret interpret: .byte b_blword prs .byte b_dup prs .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over ......
, :
$ ./forth (2 ): 6297664 1 (3 ): 6297664 1 1 (3 ): 2 6297666 1 (4 ): 2 6297666 1 1 (4 ): 2 3 6297668 1 (5 ): 2 3 6297668 1 1 (3 ): 6 6297670 2 (4 ): 6 6297670 2 2 (4 ): 6 6297670 6297673 1 (5 ): 6 6297670 6297673 1 1 6297670 (2 ): 6 0 (3 ): 6 0 0 Bye!
. :)
, :
.macro pr string .byte b_strp .byte 9f - 8f 8: .ascii "\n\string" 9: .endm
, :
item interpret interpret: .byte b_blword pr blworld prs .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over prs .byte b_find pr find prs .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa pr execute prs .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_numberq pr numberq prs .byte b_drop 2: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit
:
$ ./forth blworld(2 ): 6297664 2 (4 ): 6297664 2 6297664 2 find(3 ): 6297664 2 0 numberq(2 ): 6297664 0 blworld(3 ): 6297664 6297667 2 (5 ): 6297664 6297667 2 6297667 2 find(4 ): 6297664 6297667 2 0 numberq(3 ): 6297664 6297667 0 blworld(4 ): 6297664 6297667 6297670 1 (6 ): 6297664 6297667 6297670 1 6297670 1 find(5 ): 6297664 6297667 6297670 1 6297958 execute(3 ): 6297664 6297667 6297962 blworld(3 ): 39660590749888 6297672 1 (5 ): 39660590749888 6297672 1 6297672 1 find(4 ): 39660590749888 6297672 1 6298496 execute(2 ): 39660590749888 6298500 39660590749888 blworld(1 ): 0 Bye!
«20 30 * .».
… , , …
, , , - .
, , . , , , . ".s".
. , - . , .
interpret, : . , interpret :
item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over .byte b_find .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_drop .byte b_over, b_over .byte b_numberq # , .byte b_qbranch8, 3f - . # 0, , 3 .byte b_type # .byte b_strp # .byte 19 # .ascii " : word not found!\n" .byte b_quit # 3: .byte b_nip, b_nip # , ( b_over, b_over) 2: # .byte b_depth # .byte b_zlt # , 0 ( 0<) .byte b_qnbranch8, interpret_ok - . # , , .byte b_strp # .byte 14 .ascii "\nstack fault!\n" .byte b_quit # interpret_ok: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit
, , quit , . , , «» . .
— .
. expect, — . . span. . .
.data item span span: .byte b_var0 v_span: .quad 0 .text b_expect = 0x88 bcmd_expect: mov rax, 0 # № 1 - sys_read mov rdi, 0 # № 1 - stdout pop rdx # pop rsi # push r8 syscall # pop r8 mov rbx, rax or rax, rax jge 1f xor rbx, rbx 1: mov v_span, rbx jmp _next
. 256 .
.
inbuf_size = 256 inbuf: .byte b_var0 .space inbuf_size
quit, -. tib inbuf, expect, span #tib. >in , interpret. . — ( !). ( quit):
# forth last_item context @ ! quit start: .byte b_call16 .word forth - . - 2 .byte b_call16 .word last_item - . - 2 .byte b_call16 .word context - . - 2 .byte b_get .byte b_set .byte b_quit inbuf: .byte b_var0 .space inbuf_size # begin inbuf dup tib ! inbuf_size expect span @ #tib ! 0 >in ! interpret again quit: .byte b_strp, 1 .ascii "\n" .byte b_call16 .word prstack - . - 2 .byte b_strp .byte 2 .ascii "> " .byte b_call16 .word inbuf - . - 2 .byte b_dup .byte b_call16 .word tib - . - 2 .byte b_set .byte b_lit16 .word inbuf_size .byte b_expect .byte b_call16 .word span - . - 2 .byte b_get .byte b_call16 .word ntib - . - 2 .byte b_set .byte b_num0 .byte b_call16 .word bin - . - 2 .byte b_set .byte b_call16 .word interpret - . - 2 .byte b_branch8, quit - .
:
$ ./forth ( 0 ): > 60 ( 1 ): 60 > 60 24 ( 3 ): 60 60 24 > rot ( 3 ): 60 24 60 > -rot ( 3 ): 60 60 24 > swap ( 3 ): 60 24 60 > * * . 86400 ( 0 ): > 200 30 /mod ( 2 ): 20 6 > bye Bye! $
, ">" — . — . , . , .
Ringkasan
. — «» «» :)
— . — , , ">". ( 76 ). , — , , , .
( 1300 ) .intel_syntax noprefix stack_size = 1024 f_code = 0x80 f_immediate = 0x60 .macro item name, flags = 0 link = p_item - . 9: .if link >= -256/2 && link < 256/2 .byte \flags .byte link .elseif link >= -256*256/2 && link < 256*256/2 .byte \flags | 1 .word link .elseif link >= -256*256*256*256/2 && link < 256*256*256*256/2 .byte \flags | 2 .int link .elseif link >= -256*256*256*256*256*256*256*256/2 && link < 256*256*256*256*256*256*256*256/2 .byte \flags | 3 .quad link .endif p_item = 9b .byte 9f - . - 1 .ascii "\name" 9: .endm .section .data init_stack: .quad 0 init_rstack: .quad 0 emit_buf: .byte 0 inbuf_size = 256 msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte # len msg_bye: .ascii "\nBye!\n" msg_bye_len = . - msg_bye bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_num1, bcmd_num2, bcmd_num3, bcmd_num4, bcmd_num8 # 0x00 .quad bcmd_lit8, bcmd_lit16, bcmd_lit32, bcmd_lit64, bcmd_call8, bcmd_call16, bcmd_call32, bcmd_bad .quad bcmd_branch8, bcmd_branch16, bcmd_qbranch8, bcmd_qbranch16, bcmd_qnbranch8, bcmd_qnbranch16,bcmd_bad, bcmd_exit # 0x10 .quad bcmd_wp, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_wm, bcmd_add, bcmd_sub, bcmd_mul, bcmd_div, bcmd_mod, bcmd_divmod, bcmd_abs # 0x20 .quad bcmd_var0, bcmd_var8, bcmd_var16, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_dup, bcmd_drop, bcmd_swap, bcmd_rot, bcmd_mrot, bcmd_over, bcmd_pick, bcmd_roll # 0x30 .quad bcmd_depth, bcmd_nip, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_get, bcmd_set, bcmd_get8, bcmd_set8, bcmd_get16, bcmd_set16, bcmd_get32, bcmd_set32 # 0x40 .quad bcmd_setp, bcmd_setm, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_zeq, bcmd_zlt, bcmd_zgt, bcmd_eq, bcmd_lt, bcmd_gt, bcmd_lteq, bcmd_gteq # 0x50 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_2r, bcmd_r2, bcmd_rget, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x60 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_type, bcmd_emit, bcmd_str, bcmd_strp, bcmd_count, bcmd_bad, bcmd_bad, bcmd_bad # 0x80 .quad bcmd_expect, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x90 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_blword, bcmd_quit, bcmd_find, bcmd_cfa, bcmd_execute, bcmd_numberq, bcmd_bad, bcmd_bad # 0xF0 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # forth last_item context @ ! quit start: .byte b_call16 .word forth - . - 2 .byte b_call16 .word last_item - . - 2 .byte b_call16 .word context - . - 2 .byte b_get .byte b_set .byte b_quit inbuf: .byte b_var0 .space inbuf_size # begin inbuf dup tib ! inbuf_size expect span @ #tib ! 0 >in ! interpret again quit: .byte b_strp, 1 .ascii "\n" .byte b_call16 .word prstack - . - 2 .byte b_strp .byte 2 .ascii "> " .byte b_call16 .word inbuf - . - 2 .byte b_dup .byte b_call16 .word tib - . - 2 .byte b_set .byte b_lit16 .word inbuf_size .byte b_expect .byte b_call16 .word span - . - 2 .byte b_get .byte b_call16 .word ntib - . - 2 .byte b_set .byte b_num0 .byte b_call16 .word bin - . - 2 .byte b_set .byte b_call16 .word interpret - . - 2 .byte b_branch8, quit - . p_item = . item forth forth: .byte b_var8 .byte does_voc - . .quad 0 does_voc: .byte b_call8 .byte context - . - 1 .byte b_set .byte b_exit item current .byte b_var0 .quad 0 item context context: .byte b_var0 v_context: .quad 0 item 0, f_code .byte b_num0 .byte b_exit item 1, f_code .byte b_num1 .byte b_exit item 2, f_code .byte b_num2 .byte b_exit item 3, f_code .byte b_num3 .byte b_exit item 4, f_code .byte b_num4 .byte b_exit item 8, f_code .byte b_num8 .byte b_exit item lit8, f_code .byte b_lit8 .byte 31 .byte b_exit item lit16, f_code .byte b_lit16 .word 31415 .byte b_exit item lit32, f_code .byte b_lit32 .int 31415926 .byte b_exit item lit64, f_code .byte b_lit64 .quad 31415926 .byte b_exit item call8, f_code .byte b_call8 .byte 0f - . - 1 0: .byte b_exit item call16, f_code .byte b_call16 .word 0f - . - 2 0: .byte b_exit item call32, f_code .byte b_call32 .int 0f - . - 4 0: .byte b_exit item branch8, f_code .byte b_branch8 .byte 0f - . 0: .byte b_exit item branch16, f_code .byte b_branch16 .word 0f - . 0: .byte b_exit item qbranch8, f_code .byte b_qbranch8 .byte 0f - . 0: .byte b_exit item qbranch16, f_code .byte b_qbranch16 .word 0f - . 0: .byte b_exit item exit, f_code .byte b_exit item 1-, f_code .byte b_wm .byte b_exit item 1+, f_code .byte b_wp .byte b_exit item +, f_code .byte b_add .byte b_exit item -, f_code .byte b_sub .byte b_exit item *, f_code .byte b_mul .byte b_exit item /, f_code .byte b_div .byte b_exit item mod, f_code .byte b_mod .byte b_exit item /mod, f_code .byte b_divmod .byte b_exit item abs, f_code .byte b_abs .byte b_exit item dup, f_code .byte b_dup .byte b_exit item drop, f_code .byte b_drop .byte b_exit item swap, f_code .byte b_swap .byte b_exit item rot, f_code .byte b_rot .byte b_exit item -rot, f_code .byte b_mrot .byte b_exit item over, f_code .byte b_over .byte b_exit item pick, f_code .byte b_pick .byte b_exit item roll, f_code .byte b_roll .byte b_exit item depth, f_code .byte b_depth .byte b_exit item @, f_code .byte b_get .byte b_exit item !, f_code .byte b_set .byte b_exit item c@, f_code .byte b_get8 .byte b_exit item c!, f_code .byte b_set8 .byte b_exit item w@, f_code .byte b_get16 .byte b_exit item w!, f_code .byte b_set16 .byte b_exit item i@, f_code .byte b_get32 .byte b_exit item i!, f_code .byte b_set32 .byte b_exit item +!, f_code .byte b_setp .byte b_exit item -!, f_code .byte b_setm .byte b_exit item >r, f_code .byte b_2r .byte b_exit item r>, f_code .byte b_r2 .byte b_exit item r@, f_code .byte b_rget .byte b_exit item "0=", f_code .byte b_zeq .byte b_exit item 0<, f_code .byte b_zlt .byte b_exit item 0>, f_code .byte b_zgt .byte b_exit item "=", f_code .byte b_eq .byte b_exit item <, f_code .byte b_lt .byte b_exit item >, f_code .byte b_gt .byte b_exit item "<=", f_code .byte b_lteq .byte b_exit item ">=", f_code .byte b_gteq .byte b_exit item type, f_code .byte b_type .byte b_exit item expect, f_code .byte b_expect .byte b_exit item emit, f_code .byte b_emit .byte b_exit item count, f_code .byte b_count .byte b_exit item "(\")", f_code .byte b_str .byte b_exit item "(.\")", f_code .byte b_strp .byte b_exit item var8, f_code .byte b_var8 .byte 0f - . 0: .byte b_exit item var16, f_code .byte b_var16 .word 0f - . 0: .byte b_exit item base base: .byte b_var0 v_base: .quad 10 holdbuf_len = 70 item holdbuf holdbuf: .byte b_var0 .space holdbuf_len item holdpoint holdpoint: .byte b_var0 .quad 0 item span span: .byte b_var0 v_span: .quad 0 # : hold holdpoint @ 1- dup holdbuf > if drop drop else dup holdpoint ! c! then ; item hold hold: .byte b_call8 .byte holdpoint - . - 1 # holdpoint .byte b_get # @ .byte b_wm # 1- .byte b_dup # dup .byte b_call8 .byte holdbuf - . - 1 # holdbuf .byte b_gt # > .byte b_qbranch8 # if .byte 0f - . .byte b_drop # drop .byte b_drop # drop .byte b_branch8 # ( then) .byte 1f - . 0: .byte b_dup # dup .byte b_call8 .byte holdpoint - . - 1 # holdpoint .byte b_set # ! .byte b_set8 # c! 1: .byte b_exit # ; # : # base /mod swap dup 10 < if c" 0 + else 10 - c" A + then hold ; item # conv: .byte b_call16 .word base - . - 2 # base .byte b_get # @ .byte b_divmod # /mod .byte b_swap # swap .byte b_dup # dup .byte b_lit8 .byte 10 # 10 .byte b_lt # < .byte b_qnbranch8 # if .byte 0f - . .byte b_lit8 .byte '0' # c" 0 .byte b_add # + .byte b_branch8 # else .byte 1f - . 0: .byte b_lit8 .byte '?' # c" A .byte b_add # + 1: .byte b_call16 .word hold - . - 2 # hold .byte b_exit # ; # : <# holdbuf 70 + holdpoint ! ; item <# conv_start: .byte b_call16 .word holdbuf - . - 2 .byte b_lit8 .byte holdbuf_len .byte b_add .byte b_call16 .word holdpoint - . - 2 .byte b_set .byte b_exit # : #s do # dup 0=until ; item #s conv_s: .byte b_call8 .byte conv - . - 1 .byte b_dup .byte b_qbranch8 .byte conv_s - . .byte b_exit # : #> holdpoint @ holdbuf 70 + over - ; item #> conv_end: .byte b_call16 .word holdpoint - . - 2 .byte b_get .byte b_call16 .word holdbuf - . - 2 .byte b_lit8 .byte holdbuf_len .byte b_add .byte b_over .byte b_sub .byte b_exit item . dot: .byte b_dup .byte b_abs .byte b_call8 .byte conv_start - . - 1 .byte b_lit8 .byte ' ' .byte b_call16 .word hold - . - 2 .byte b_call8 .byte conv_s - . - 1 .byte b_drop .byte b_zlt .byte b_qnbranch8 .byte 1f - . .byte b_lit8 .byte '-' .byte b_call16 .word hold - . - 2 1: .byte b_call8 .byte conv_end - . - 1 .byte b_type .byte b_exit item tib tib: .byte b_var0 v_tib: .quad 0 item #tib ntib: .byte b_var0 v_ntib: .quad 0 item >in bin: .byte b_var0 v_in: .quad 0 # : .s depth dup . c": emit do dup while dup pick . 1- again drop ; item .s # 11 22 33 prstack: .byte b_depth # 11 22 33 3 .byte b_dup # 11 22 33 3 3 .byte b_strp .byte 2 .ascii "( " .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_strp # 11 22 33 3 .byte 3 .ascii "): " .byte b_dup, b_zlt .byte b_qnbranch8, 1f - . .byte b_strp .byte 14 .ascii "\nStack fault!\n" .byte b_quit 1: .byte b_dup # 11 22 33 3 3 .byte b_qnbranch8 # 11 22 33 3 .byte 2f - . .byte b_dup # 11 22 33 3 3 .byte b_pick # 11 22 33 3 11 .byte b_call16 # 11 22 33 3 .word dot - . - 2 .byte b_wm # 11 22 33 2 .byte b_branch8 .byte 1b - . 2: .byte b_drop # 11 22 33 .byte b_exit .macro prs new_line = 1 .byte b_call16 .word prstack - . - 2 .if \new_line > 0 .byte b_lit8, '\n' .byte b_emit .endif .endm .macro pr string .byte b_strp .byte 9f - 8f 8: .ascii "\n\string" 9: .endm item interpret interpret: .byte b_blword .byte b_dup .byte b_qnbranch8 .byte 0f - . .byte b_over .byte b_over .byte b_find .byte b_dup .byte b_qnbranch8 .byte 1f - . .byte b_mrot .byte b_drop .byte b_drop .byte b_cfa .byte b_execute .byte b_branch8 .byte 2f - . 1: .byte b_drop .byte b_over, b_over .byte b_numberq # , .byte b_qbranch8, 3f - . # 0, , 3 .byte b_type # .byte b_strp # .byte 19 # .ascii " : word not found!\n" .byte b_quit # 3: .byte b_nip, b_nip # , ( b_over, b_over) 2: # .byte b_depth # .byte b_zlt # , 0 ( 0<) .byte b_qnbranch8, interpret_ok - . # , , .byte b_strp # .byte 14 .ascii "\nstack fault!\n" .byte b_quit # interpret_ok: .byte b_branch8 .byte interpret - . 0: .byte b_drop .byte b_exit last_item: .byte b_var0 item bye, f_code .byte b_bye .section .text .global _start # _start: mov rbp, rsp sub rbp, stack_size lea r8, start mov init_stack, rsp mov init_rstack, rbp jmp _next b_var0 = 0x28 bcmd_var0: push r8 b_exit = 0x17 bcmd_exit: mov r8, [rbp] add rbp, 8 _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] b_num0 = 0x02 bcmd_num0: push 0 jmp _next b_num1 = 0x03 bcmd_num1: push 1 jmp _next b_num2 = 0x04 bcmd_num2: push 2 jmp _next b_num3 = 0x05 bcmd_num3: push 3 jmp _next b_num4 = 0x06 bcmd_num4: push 4 jmp _next b_num8 = 0x07 bcmd_num8: push 8 jmp _next b_lit8 = 0x08 bcmd_lit8: movsx rax, byte ptr [r8] inc r8 push rax jmp _next b_lit16 = 0x09 bcmd_lit16: movsx rax, word ptr [r8] add r8, 2 push rax jmp _next b_call8 = 0x0C bcmd_call8: movsx rax, byte ptr [r8] sub rbp, 8 inc r8 mov [rbp], r8 add r8, rax jmp _next b_call16 = 0x0D bcmd_call16: movsx rax, word ptr [r8] sub rbp, 8 add r8, 2 mov [rbp], r8 add r8, rax jmp _next b_call32 = 0x0E bcmd_call32: movsx rax, dword ptr [r8] sub rbp, 8 add r8, 4 mov [rbp], r8 add r8, rax jmp _next b_lit32 = 0x0A bcmd_lit32: movsx rax, dword ptr [r8] add r8, 4 push rax jmp _next b_lit64 = 0x0B bcmd_lit64: mov rax, [r8] add r8, 8 push rax jmp _next b_dup = 0x30 bcmd_dup: push [rsp] jmp _next b_wm = 0x20 bcmd_wm: decq [rsp] jmp _next b_wp = 0x18 bcmd_wp: incq [rsp] jmp _next b_add = 0x21 bcmd_add: pop rax add [rsp], rax jmp _next b_sub = 0x22 bcmd_sub: pop rax sub [rsp], rax jmp _next b_mul = 0x23 bcmd_mul: pop rax pop rbx imul rbx push rax jmp _next b_div = 0x24 bcmd_div: pop rbx pop rax cqo idiv rbx push rax jmp _next b_mod = 0x25 bcmd_mod: pop rbx pop rax cqo idiv rbx push rdx jmp _next b_divmod = 0x26 bcmd_divmod: pop rbx pop rax cqo idiv rbx push rdx push rax jmp _next b_abs = 0x27 bcmd_abs: mov rax, [rsp] or rax, rax jge _next neg rax mov [rsp], rax jmp _next b_drop = 0x31 bcmd_drop: add rsp, 8 jmp _next b_swap = 0x32 bcmd_swap: pop rax pop rbx push rax push rbx jmp _next b_rot = 0x33 bcmd_rot: pop rax pop rbx pop rcx push rbx push rax push rcx jmp _next b_mrot = 0x34 bcmd_mrot: pop rcx pop rbx pop rax push rcx push rax push rbx jmp _next b_over = 0x35 bcmd_over: push [rsp + 8] jmp _next b_pick = 0x36 bcmd_pick: pop rcx push [rsp + 8*rcx] jmp _next b_roll = 0x37 bcmd_roll: pop rcx mov rbx, [rsp + 8*rcx] roll1: mov rax, [rsp + 8*rcx - 8] mov [rsp + 8*rcx], rax dec rcx jnz roll1 push rbx jmp _next b_depth = 0x38 bcmd_depth: mov rax, init_stack sub rax, rsp sar rax, 3 push rax jmp _next b_nip = 0x39 bcmd_nip: pop rax mov [rsp], rax jmp _next b_get = 0x40 bcmd_get: pop rcx push [rcx] jmp _next b_set = 0x41 bcmd_set: pop rcx pop rax mov [rcx], rax jmp _next b_get8 = 0x42 bcmd_get8: pop rcx movsx rax, byte ptr [rcx] push rax jmp _next b_set8 = 0x43 bcmd_set8: pop rcx pop rax mov [rcx], al jmp _next b_get16 = 0x44 bcmd_get16: pop rcx movsx rax, word ptr [rcx] push rax jmp _next b_set16 = 0x45 bcmd_set16: pop rcx pop rax mov [rcx], ax jmp _next b_get32 = 0x46 bcmd_get32: pop rcx movsx rax, dword ptr [rcx] push rax jmp _next b_set32 = 0x47 bcmd_set32: pop rcx pop rax mov [rcx], eax jmp _next b_setp = 0x48 bcmd_setp: pop rcx pop rax add [rcx], rax jmp _next b_setm = 0x49 bcmd_setm: pop rcx pop rax sub [rcx], rax jmp _next b_2r = 0x60 bcmd_2r: pop rax sub rbp, 8 mov [rbp], rax jmp _next b_r2 = 0x61 bcmd_r2: push [rbp] add rbp, 8 jmp _next b_rget = 0x62 bcmd_rget: push [rbp] jmp _next # 0= b_zeq = 0x50 bcmd_zeq: pop rax or rax, rax jnz rfalse rtrue: push -1 jmp _next rfalse: push 0 jmp _next # 0< b_zlt = 0x51 bcmd_zlt: pop rax or rax, rax jl rtrue push 0 jmp _next # 0> b_zgt = 0x52 bcmd_zgt: pop rax or rax, rax jg rtrue push 0 jmp _next # = b_eq = 0x53 bcmd_eq: pop rbx pop rax cmp rax, rbx jz rtrue push 0 jmp _next # < b_lt = 0x54 bcmd_lt: pop rbx pop rax cmp rax, rbx jl rtrue push 0 jmp _next # > b_gt = 0x55 bcmd_gt: pop rbx pop rax cmp rax, rbx jg rtrue push 0 jmp _next # <= b_lteq = 0x56 bcmd_lteq: pop rbx pop rax cmp rax, rbx jle rtrue push 0 jmp _next # >= b_gteq = 0x57 bcmd_gteq: pop rbx pop rax cmp rax, rbx jge rtrue push 0 jmp _next b_var8 = 0x29 bcmd_var8: push r8 b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_var16 = 0x30 bcmd_var16: push r8 b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next b_qbranch8 = 0x12 bcmd_qbranch8: pop rax or rax, rax jnz bcmd_branch8 inc r8 jmp _next b_qbranch16 = 0x13 bcmd_qbranch16: pop rax or rax, rax jnz bcmd_branch16 add r8, 2 jmp _next b_qnbranch8 = 0x14 bcmd_qnbranch8: pop rax or rax, rax jz bcmd_branch8 inc r8 jmp _next b_qnbranch16 = 0x15 bcmd_qnbranch16:pop rax or rax, rax jz bcmd_branch16 add r8, 2 jmp _next b_bad = 0x00 bcmd_bad: mov rax, 1 # 1 - sys_write mov rdi, 1 # 1 stdout mov rsi, offset msg_bad_byte # mov rdx, msg_bad_byte_len # syscall # mov rax, 60 # 1 - sys_exit mov rbx, 1 # 1 syscall # b_bye = 0x01 bcmd_bye: mov rax, 1 # 1 - sys_write mov rdi, 1 # 1 stdout mov rsi, offset msg_bye # mov rdx, msg_bye_len # syscall # mov rax, 60 # 60 - sys_exit mov rdi, 0 # 0 syscall # b_strp = 0x83 bcmd_strp: movsx rax, byte ptr [r8] inc r8 push r8 add r8, rax push rax b_type = 0x80 bcmd_type: mov rax, 1 # 1 - sys_write mov rdi, 1 # 1 - stdout pop rdx # pop rsi # push r8 syscall # pop r8 jmp _next b_expect = 0x88 bcmd_expect: mov rax, 0 # 1 - sys_read mov rdi, 0 # 1 - stdout pop rdx # pop rsi # push r8 syscall # pop r8 mov rbx, rax or rax, rax jge 1f xor rbx, rbx 1: mov v_span, rbx jmp _next b_str = 0x82 bcmd_str: movzx rax, byte ptr [r8] lea r8, [r8 + rax + 1] jmp _next b_count = 0x84 bcmd_count: pop rcx movzx rax, byte ptr [rcx] inc rcx push rcx push rax jmp _next b_emit = 0x81 bcmd_emit: pop rax mov rsi, offset emit_buf # mov [rsi], al mov rax, 1 # 1 - sys_write mov rdi, 1 # 1 - stdout mov rdx, 1 # push r8 syscall # pop r8 jmp _next b_blword = 0xF0 bcmd_blword: mov rsi, v_tib # mov rdx, rsi # RDX mov rax, v_in # mov rcx, v_ntib # mov rbx, rcx add rsi, rax # RSI - sub rcx, rax # jz 3f word2: lodsb # AL RSI cmp al, ' ' ja 1f # ( ) dec rcx jnz word2 # 3: sub rsi, rdx mov v_in, rsi push rcx jmp _next 1: lea rdi, [rsi - 1] # RDI = RSI - 1 ( ) dec rcx jz word9 word3: lodsb cmp al, ' ' jbe 2f dec rcx jnz word3 word9: inc rsi 2: mov rax, rsi sub rsi, rdx # ( ) cmp rsi, rbx jle 4f mov rsi, rbx 4: mov v_in, rsi sub rax, rdi dec rax jz word1 push rdi # word1: push rax # jmp _next b_quit = 0xF1 bcmd_quit: lea r8, quit mov rsp, init_stack mov rbp, init_rstack jmp _next b_find = 0xF2 bcmd_find: pop rbx # pop r9 # mov rdx, v_context mov rdx, [rdx] # # find0: mov al, [rdx] # and al, 3 # - , , or al, al jz find_l8 cmp al, 1 jz find_l16 cmp al, 2 jz find_l32 mov r10, [rdx + 1] # 64 lea rsi, [rdx + 9] # jmp find1 find_l32: movsx r10, dword ptr [rdx + 1] # 32 lea rsi, [rdx + 5] # jmp find1 find_l16: movsx r10, word ptr [rdx + 1] # 16 lea rsi, [rdx + 3] # jmp find1 find_l8: movsx r10, byte ptr [rdx + 1] # 8 lea rsi, [rdx + 2] # find1: movzx rax, byte ptr [rsi] # cmp rax, rbx jz find2 # find3: or r10, r10 jz find_notfound # , add rdx, r10 # jmp find0 # , find2: inc rsi mov rdi, r9 mov rcx, rax repz cmpsb jnz find3 # push rdx jmp _next find_notfound: push r10 jmp _next b_cfa = 0xF3 bcmd_cfa: pop rdx # mov al, [rdx] # and al, 3 # - , , or al, al jz cfa_l8 cmp al, 1 jz cfa_l16 cmp al, 2 jz cfa_l32 lea rsi, [rdx + 9] # (64 ) jmp cfa1 cfa_l32: lea rsi, [rdx + 5] # (32 ) jmp cfa1 cfa_l16: lea rsi, [rdx + 3] # (16 ) jmp cfa1 cfa_l8: lea rsi, [rdx + 2] # (8 ) cfa1: xor rax, rax lodsb add rsi, rax push rsi jmp _next b_execute = 0xF4 bcmd_execute: sub rbp, 8 mov [rbp], r8 # pop r8 # - jmp _next b_numberq = 0xF5 bcmd_numberq: pop rcx # pop rsi # xor rax, rax # xor rbx, rbx # mov r9, v_base # xor r10, r10 # or rcx, rcx jz num_false mov bl, [rsi] cmp bl, '+' jnz 1f inc rsi dec rcx jz num_false jmp num0 1: cmp bl, '-' jnz num0 mov r10, 1 inc rsi dec rcx jz num_false num0: mov bl, [rsi] cmp bl, '0' jb num_false cmp bl, '9' jbe num_09 cmp bl, 'A' jb num_false cmp bl, 'Z' jbe num_AZ cmp bl, 'a' jb num_false cmp bl, 'z' ja num_false sub bl, 'a' - 10 jmp num_check num_AZ: sub bl, 'A' - 10 jmp num_check num_09: sub bl, '0' num_check: cmp rbx, r9 jge num_false mul r9 add rax, rbx inc rsi dec rcx jnz num0 or r10, r10 push rax push 1 jmp _next num_false: xor rcx, rcx push rcx jmp _next
, .
:
https://github.com/hal9000cc/forth64, bin Linux x64 . Linux, .
Windows — WSL (Windows Subsystem for Linux). , . , 5. , , «» PowerShell. , , .
— Windows :) , , .
! .
, , . , - , - . , -.
:
- ( ) - ( 4)