Membuat Bahasa JVM yang Dapat Digunakan: Gambaran Umum
Diterbitkan: 2022-03-11Ada beberapa kemungkinan alasan untuk membuat bahasa, beberapa di antaranya tidak langsung terlihat. Saya ingin menyajikannya bersama dengan pendekatan untuk membuat bahasa untuk Java Virtual Machine (JVM) menggunakan kembali alat yang ada sebanyak mungkin. Dengan cara ini kami akan mengurangi upaya pengembangan dan menyediakan rantai alat yang akrab bagi pengguna, membuatnya lebih mudah untuk mengadopsi bahasa pemrograman baru kami.
Dalam artikel ini, seri pertama, saya akan menyajikan tinjauan umum tentang strategi dan berbagai alat yang terlibat dalam pembuatan bahasa pemrograman kami sendiri untuk JVM. di artikel mendatang, kami akan membahas detail implementasinya.
Mengapa Membuat Bahasa JVM Anda?
Sudah ada jumlah bahasa pemrograman yang tidak terbatas. Jadi mengapa repot-repot membuat yang baru? Ada banyak kemungkinan jawaban untuk itu.
Pertama-tama, ada banyak jenis bahasa: apakah Anda ingin membuat bahasa pemrograman tujuan umum (GPL) atau khusus domain? Jenis pertama mencakup bahasa seperti Java atau Scala: bahasa yang dimaksudkan untuk menulis solusi yang cukup layak untuk sejumlah besar masalah. Domain Specific Languages (DSL) malah berfokus pada pemecahan masalah tertentu dengan sangat baik. Pikirkan HTML atau Lateks: Anda dapat menggambar di layar atau membuat dokumen di Java tetapi akan merepotkan, dengan DSL ini Anda dapat membuat dokumen dengan sangat mudah tetapi terbatas pada domain tertentu.
Jadi mungkin ada serangkaian masalah yang sering Anda kerjakan dan yang masuk akal untuk membuat DSL. Bahasa yang akan membuat Anda sangat produktif sambil memecahkan masalah yang sama berulang kali.
Mungkin sebaliknya Anda ingin membuat GPL karena Anda memiliki beberapa ide baru, misalnya untuk mewakili hubungan sebagai warga kelas satu atau mewakili konteks.
Terakhir, Anda mungkin ingin membuat bahasa baru karena menyenangkan, keren, dan karena Anda akan belajar banyak dalam prosesnya.
Faktanya adalah bahwa jika Anda menargetkan JVM, Anda dapat memperoleh bahasa yang dapat digunakan dengan sedikit usaha, itu karena:
- Anda hanya perlu membuat bytecode dan kode Anda akan tersedia di semua platform di mana ada JVM
- Anda akan dapat memanfaatkan semua perpustakaan dan kerangka kerja yang ada untuk JVM
Jadi biaya pengembangan bahasa sangat berkurang pada JVM dan masuk akal untuk membuat bahasa baru dalam skenario yang tidak ekonomis di luar JVM.
Apa yang Anda Butuhkan untuk Membuatnya Dapat Digunakan?
Ada beberapa alat yang benar-benar Anda perlukan untuk menggunakan bahasa Anda - pengurai dan kompiler (atau juru bahasa) termasuk di antara alat-alat ini. Namun, ini tidak cukup. Untuk membuat bahasa Anda benar-benar dapat digunakan dalam praktik, Anda perlu menyediakan banyak komponen lain dari rantai alat, mungkin berintegrasi dengan alat yang ada.
Idealnya Anda ingin dapat:
- Kelola referensi ke kode yang dikompilasi untuk JVM dari bahasa lain
- Edit file sumber di IDE favorit Anda dengan sorotan sintaks, identifikasi kesalahan, dan pelengkapan otomatis
- Anda ingin dapat mengkompilasi file menggunakan sistem build favorit Anda: maven, gradle, atau lainnya
- Anda ingin dapat menulis tes dan menjalankannya sebagai bagian dari solusi Continuous-Integration Anda
Jika Anda bisa melakukannya, mengadopsi bahasa Anda akan jauh lebih mudah.
Jadi bagaimana kita bisa mencapai itu? Di sisa pos kami memeriksa bagian-bagian berbeda yang kami butuhkan untuk memungkinkan ini.
Parsing dan Kompilasi
Hal pertama yang perlu Anda lakukan untuk mengubah file sumber Anda dalam suatu program adalah menguraikannya, memperoleh representasi Abstract-Syntax-Tree (AST) dari informasi yang terkandung dalam kode. Pada saat itu Anda perlu memvalidasi kode: apakah ada kesalahan sintaksis? Kesalahan semantik? Anda perlu menemukan semuanya dan melaporkannya kepada pengguna. Jika semuanya berjalan lancar, Anda masih perlu menyelesaikan simbol. Misalnya, apakah “Daftar” merujuk ke java.util.List atau java.awt.List ? Saat Anda memanggil metode yang kelebihan beban, mana yang Anda gunakan? Terakhir, Anda perlu membuat bytecode untuk program Anda.
Jadi, dari kode sumber ke bytecode yang dikompilasi ada tiga fase utama:
- Membangun AST
- Menganalisis dan mengubah AST
- Memproduksi bytecode dari AST
Mari kita lihat fase-fase tersebut secara detail.
Membangun AST : parsing adalah semacam masalah yang terpecahkan. Ada banyak kerangka kerja di luar sana tetapi saya sarankan Anda menggunakan ANTLR. Itu terkenal, terpelihara dengan baik dan memiliki beberapa fitur yang membuatnya lebih mudah untuk menentukan tata bahasa (ini menangani aturan yang kurang rekursif - Anda tidak perlu memahaminya tetapi bersyukurlah!).
Menganalisis dan mengubah AST : menulis sistem tipe, validasi, dan resolusi simbol bisa jadi menantang dan membutuhkan cukup banyak pekerjaan. Topik ini saja akan membutuhkan posting terpisah. Untuk saat ini pertimbangkan bahwa ini adalah bagian dari kompiler Anda di mana Anda akan menghabiskan sebagian besar upaya.
Memproduksi bytecode dari AST : fase terakhir ini sebenarnya tidak terlalu sulit. Anda seharusnya telah menyelesaikan simbol pada fase sebelumnya dan menyiapkan medan sehingga pada dasarnya Anda dapat menerjemahkan satu node dari AST yang diubah ke satu atau beberapa instruksi bytecode. Struktur kontrol mungkin memerlukan beberapa pekerjaan ekstra karena Anda akan menerjemahkan for-loop, switch, ifs, dan seterusnya dalam urutan lompatan bersyarat dan tanpa syarat (ya, di bawah bahasa Anda yang indah masih akan ada banyak gotos). Anda perlu mempelajari cara kerja JVM secara internal, tetapi implementasi sebenarnya tidak terlalu sulit.
Integrasi dengan Bahasa Lain
Ketika Anda akan memperoleh dominasi dunia untuk bahasa Anda, semua kode akan ditulis dengan menggunakannya secara eksklusif. Namun sebagai langkah perantara bahasa Anda mungkin akan digunakan bersama bahasa JVM lainnya. Mungkin seseorang akan mulai menulis beberapa kelas atau modul kecil dalam bahasa Anda di dalam proyek yang lebih besar. Masuk akal untuk berharap dapat mencampur beberapa bahasa JVM. Jadi, bagaimana pengaruhnya terhadap alat bahasa Anda?
Anda perlu mempertimbangkan dua skenario berbeda:
- Bahasa Anda dan yang lainnya hidup dalam modul yang dikompilasi secara terpisah
- Bahasa Anda dan yang lainnya hidup dalam modul yang sama dan dikompilasi bersama
Dalam skenario pertama, kode Anda hanya perlu menggunakan kode terkompilasi yang ditulis dalam bahasa lain. Misalnya beberapa dependensi seperti Guava atau modul dalam proyek yang sama dapat dikompilasi secara terpisah. Integrasi semacam ini memerlukan dua hal: pertama, Anda harus dapat menginterpretasikan file kelas yang dihasilkan oleh bahasa lain untuk menyelesaikan simbol kepada mereka dan menghasilkan bytecode untuk menjalankan kelas tersebut. Poin kedua adalah khusus untuk yang pertama: modul lain mungkin ingin menggunakan kembali kode yang ditulis dalam bahasa Anda setelah dikompilasi. Sekarang, biasanya itu tidak menjadi masalah karena Java dapat berinteraksi dengan sebagian besar file kelas. Namun Anda masih bisa mengatur untuk menulis file kelas yang valid untuk JVM tetapi tidak dapat dipanggil dari Java (misalnya karena Anda menggunakan pengidentifikasi yang tidak valid di Java).

Skenario kedua lebih rumit: misalkan Anda memiliki kelas A yang didefinisikan dalam kode Java dan kelas B yang ditulis dalam bahasa Anda. Misalkan dua kelas merujuk satu sama lain (misalnya A dapat memperluas B dan B dapat menerima A sebagai parameter untuk metode yang sama). Sekarang intinya adalah Java compiler tidak dapat memproses kode dalam bahasa Anda, jadi Anda harus menyediakannya file kelas untuk kelas B. Namun untuk mengkompilasi kelas B Anda perlu memasukkan referensi ke kelas A. Jadi yang perlu Anda lakukan adalah untuk memiliki semacam kompiler Java parsial, yang diberikan file sumber Java dapat menafsirkannya dan menghasilkan modelnya yang dapat Anda gunakan untuk mengkompilasi kelas B Anda. Perhatikan bahwa ini mengharuskan Anda untuk dapat mengurai kode Java (menggunakan sesuatu seperti JavaParser) dan memecahkan simbol. Jika Anda tidak tahu harus mulai dari mana, lihat Java-symbol-solver.
Alat: Gradle, Maven, Kerangka Uji, CI
Kabar baiknya adalah Anda dapat membuat fakta bahwa mereka menggunakan modul yang ditulis dalam bahasa Anda benar-benar transparan bagi pengguna dengan mengembangkan plugin untuk gradle atau maven. Anda dapat menginstruksikan sistem build untuk mengompilasi file dalam bahasa pemrograman Anda. Pengguna akan terus menjalankan mvn compile atau gradle assemble dan tidak melihat adanya perbedaan.
Berita buruknya adalah menulis plugin Maven tidak mudah: dokumentasinya sangat buruk, tidak dapat dipahami dan sebagian besar sudah ketinggalan zaman atau hanya salah . Ya, itu tidak terdengar menghibur. Saya belum menulis plugin gradle tetapi tampaknya jauh lebih mudah.
Perhatikan bahwa Anda juga harus mempertimbangkan bagaimana pengujian dapat dijalankan menggunakan sistem build. Untuk mendukung pengujian, Anda harus memikirkan kerangka kerja yang sangat mendasar untuk pengujian unit dan Anda harus mengintegrasikannya dengan sistem pembangunan, sehingga menjalankan pengujian maven mencari pengujian dalam bahasa Anda, mengompilasi dan menjalankannya dengan melaporkan hasilnya kepada pengguna.
Saran saya adalah melihat contoh yang tersedia: salah satunya adalah plugin Maven untuk bahasa pemrograman Turin.
Setelah Anda menerapkannya, setiap orang akan dapat dengan mudah mengkompilasi file sumber yang ditulis dalam bahasa Anda dan menggunakannya dalam layanan Continuous-Integration seperti Travis.
Plugin IDE
Plugin untuk IDE akan menjadi alat yang paling terlihat bagi pengguna Anda dan sesuatu yang akan sangat memengaruhi persepsi bahasa Anda. Plugin yang baik dapat membantu pengguna mempelajari bahasa dengan menyediakan pelengkapan otomatis yang cerdas, kesalahan kontekstual, dan refactoring yang disarankan.
Sekarang, strategi yang paling umum adalah memilih satu IDE (biasanya Eclipse atau IntelliJ IDEA) dan mengembangkan plugin khusus untuk itu. Ini mungkin bagian paling kompleks dari rantai alat Anda. Ini terjadi karena beberapa alasan: pertama-tama Anda tidak dapat menggunakan kembali secara wajar pekerjaan yang akan Anda habiskan untuk mengembangkan plugin Anda untuk satu IDE untuk yang lain. Eclipse Anda dan plugin IntelliJ Anda akan benar-benar terpisah. Poin kedua adalah bahwa pengembangan plugin IDE adalah sesuatu yang tidak umum, jadi tidak banyak dokumentasi dan komunitasnya kecil. Ini berarti Anda harus menghabiskan banyak waktu untuk mencari tahu sendiri. Saya pribadi mengembangkan plugin untuk Eclipse dan untuk IntelliJ IDEA. Pertanyaan saya di forum Eclipse tetap tidak terjawab selama berbulan-bulan atau bertahun-tahun. Di forum IntelliJ saya lebih beruntung, dan terkadang saya mendapat jawaban dari pengembang. Namun basis pengguna pengembang plugin lebih kecil dan API sangat bizantium. Bersiaplah untuk menderita.
Ada alternatif untuk semua ini, dan itu adalah menggunakan Xtext. Xtext adalah kerangka kerja untuk mengembangkan plugin untuk Eclipse, IntelliJ IDEA dan web. Itu telah lahir di Eclipse dan baru-baru ini diperluas untuk mendukung platform lain, jadi tidak banyak pengalaman tentang itu tetapi itu bisa menjadi alternatif yang layak untuk dipertimbangkan. Biarkan saya meluruskan ini: satu-satunya cara untuk mengembangkan plugin yang sangat bagus adalah dengan mengembangkannya menggunakan API asli dari setiap IDE. Namun dengan Xtext Anda dapat memiliki sesuatu yang cukup layak dengan sedikit usaha - Anda hanya memberikannya ke sintaks bahasa Anda dan Anda mendapatkan kesalahan/penyelesaian sintaks secara gratis. Namun, Anda harus menerapkan resolusi simbol dan bagian yang sulit, tetapi ini adalah titik awal yang sangat menarik; namun, bagian yang sulit adalah integrasi dengan perpustakaan khusus platform untuk memecahkan simbol Java jadi ini tidak akan menyelesaikan semua masalah Anda.
Kesimpulan
Ada banyak cara Anda bisa kehilangan calon pengguna yang menunjukkan minat pada bahasa Anda. Mengadopsi bahasa baru adalah sebuah tantangan karena membutuhkan mempelajarinya dan mengadaptasi kebiasaan perkembangan kita. Dengan mengurangi sebanyak mungkin gesekan dan memanfaatkan ekosistem yang sudah diketahui pengguna Anda, Anda dapat mencegah pengguna menyerah sebelum mereka belajar dan jatuh cinta dengan bahasa Anda.
Dalam skenario yang ideal, pengguna Anda dapat mengkloning proyek sederhana yang ditulis dalam bahasa Anda, dan membangunnya menggunakan alat standar (Maven atau Gradle) tanpa melihat perbedaan apa pun. Jika dia ingin mengedit proyek, dia bisa membukanya di editor favoritnya dan plugin akan membantu menunjukkan kepadanya kesalahan dan memberikan penyelesaian yang cerdas. Ini adalah skenario yang jauh berbeda daripada harus mencari cara untuk memanggil kompiler Anda dan mengedit file menggunakan notepad. Ekosistem di sekitar bahasa Anda benar-benar dapat membuat perbedaan, dan saat ini dapat dibangun dengan upaya yang masuk akal.
Saran saya adalah menjadi kreatif dalam bahasa Anda, tetapi tidak dalam alat Anda. Kurangi kesulitan awal yang harus dihadapi orang untuk mengadopsi bahasa Anda dengan menggunakan standar yang sudah dikenal.
Selamat mendesain bahasa!
Bacaan Lebih Lanjut di Blog Teknik Toptal:
- Bagaimana Pendekatan Menulis Penerjemah Dari Awal