Hakikat Bahasa Pemrograman

Memahami bahasa pemrograman, spesifikasi bahasa pemrograman, compiler, dan interpreter

Ketika baru mengenal dunia pemrograman, salah satu hal yang seringkali saya bingungkan adalah apa perbedaan JavaScript dan ECMAScript. Banyak yang menjelaskan dengan kalimat seperti, “JavaScript adalah bahasa pemrograman, sedangkan ECMAScript adalah standar.” Tetapi bagi saya pada waktu itu penjelasan seperti ini membingungkan karena saya belum sepenuhnya memahami apa itu bahasa pemrograman. Hingga selanjutnya muncul pertanyaan-pertanyaan seperti, “Jadi ECMAScript itu JavaScript? Kenapa tidak disebut JavaScript sekalian? Atau berbeda?” “Apa maksudnya standar?” “Kenapa kalau ada fitur baru di JavaScript fiturnya dikaitkan dengan ECMAScript?” dan pertanyaan-pertanyaan sejenisnya.

Mungkin bagi sebagian pembaca pertanyaan-pertanyaan di atas adalah pertanyaan yang aneh dan bodoh, tetapi itulah pertanyaan-pertanyaan yang ada di kepala saya ketika baru mengenal dunia pemrograman.

Tulisan ini adalah percobaan saya untuk menuliskan pemikiran saya kembali setelah beberapa percobaan untuk membuat interpreter sendiri serta mendapatkan pemahaman yang saya rasa lebih baik terkait apa itu bahasa pemrograman.

Tentang Ekstensi dan Teks

Dulu saya memandang bahwa bahasa pemrograman harus ditulis menggunakan file dengan ekstensi spesial yang dapat diproses oleh semua komputer (somehow semua komputer tahu bagaimana cara memproses file-file itu), layaknya file dokumen banyak yang memiliki ekstensi .pdf, file Microsoft Word memiliki ekstensi .docx, sebagian file gambar memiliki ekstensi .jpeg, sebagian lainnya memiliki ekstensi .png. Demikian pula, ketika hendak memprogram JavaScript saya berpikir saya harus menuliskan perintah-perintahnya pada sebuah file berekstensi .js. Dan semua komputer tahu bagaimana memproses file berekstensi .js tersebut sehingga itu bisa disebut bahasa pemrograman. Begitu pikir saya.

Tetapi sekarang saya melihat bahwa tulisan .pdf, .docx, .jpeg, dan dot dot yang lain pada nama sebuah file hanyalah konvensi penamaan saja untuk menunjukkan konten seperti apakah yang ada di file tersebut dan program apakah yang bisa digunakan untuk membuka file tersebut agar kontennya dapat ditampilkan dengan baik.

Jika saya ingin, saya bisa saja membuat sebuah file berekstensi .pdf lalu menuliskan kode C di dalamnya, seperti berikut:

Atau saya juga bisa membuat sebuah file berekstensi .go lalu menuliskan kode JavaScript di dalamnya.

Bisa dilihat pada gambar di atas VSCode menampilkan galat expected 'package', found console karena saya menuliskan skrip JS di dalam file .go, tetapi ketika file tersebut dijalankan dengan Node.js tidak terjadi masalah apa-apa.

Hal-hal di atas sah-sah saja dilakukan. Node.js tidak peduli ekstensi file yang dijadikan sebagai input, selama isinya merupakan skrip JS, maka Node.js dapat memprosesnya dengan baik.

Ini menunjukkan bahwa bahasa pemrograman bukan tentang file dengan ekstensi tertentu, melainkan lebih berkaitan dengan isi dari file tersebut.

Sebagian compiler/interpreter tetap mengharuskan penamaan file dengan ekstensi tertentu, tetapi itu tidak relevan terhadap poin yang saya buat karena sebenarnya sama saja yang diproses tetap isi dari file tersebut.

Sebuah teks yang ditulis di dalam file berekstensi .cpp pun belum tentu dapat disebut sebagai kode C++. Kita baru bisa menyebut sebuah teks sebagai kode C++ jika teks tersebut disusun sedemikian rupa agar memenuhi aturan-aturan penulisan Bahasa Pemrograman C++. Sehingga kita baru bisa menyimpulkan bahwa sebuah teks itu merupakan kode bahasa pemrograman hanya dengan melihat bagaimana teks itu disusun, karena ekstensi file saja bisa menipu.

Jika kita perlu melihat bagaimana suatu teks itu disusun untuk dapat menyimpulkan apakah teks itu merupakan kode bahasa pemrograman tertentu atau bukan, maka bahasa pemrograman sebenarnya bukanlah teksnya itu sendiri, melainkan aturan yang dijadikan acuan untuk menulis teks tersebut.

Jadi bahasa pemrograman adalah aturannya, bukan teksnya.

Compiler dan Interpreter

Jika bahasa pemrograman hanyalah sekumpulan aturan, lalu siapa yang berhak mengimplementasikan aturan tersebut? Karena aturan yang tidak diimplementasikan ya hanya akan berakhir sebagai sekedar tulisan dan tidak akan pernah menghasilkan hal konkrit.

Di sinilah tugas dari compiler dan interpreter.

Compiler atau interpreter adalah program komputer biasa yang menerima teks biasa sebagai input, mengecek apakah teks tersebut memenuhi aturan bahasa pemrograman tertentu, lalu melakukan hal berikut:

  1. Compiler akan mengubah teks tersebut menjadi sekumpulan bit yang dapat dipahami oleh proses komputer, atau biasanya disebut file binary.

  2. Interpreter akan "menjalankan" skrip tersebut.

Sebagai contoh jika kita berbicara tentang compiler C++ dan mencocokkannya dengan deskripsi cara kerja compiler di atas, maka yang terjadi adalah sebagai berikut:

Sebagaimana dapat dilihat pada gambar di atas, compiler C++ mengimplementasikan aturan penulisan Bahasa Pemrograman C++ yang digambarkan pada tanda panah nomor 2.

Tapi siapa yang bertugas membuat compiler dan interpreter ini?

Tidak ada kewajiban pada pihak tertentu untuk membuat compiler dan interpreter bahasa pemrograman, termasuk para perusahaan pembuat komputer juga bukanlah pihak yang wajib membuat compiler dan interpreter. Siapapun bisa membuat compiler dan interpreter mereka sendiri. Yang mereka butuhkan hanyalah mengetahui aturan apa saja yang perlu diimplementasikan oleh compiler/interpreter tersebut. Tetapi biasanya compiler dan interpreter pertama untuk sebuah bahasa pemrograman dibuat oleh penggagas bahasa pemrogramannya itu sendiri.

Tapi lalu siapa yang berhak membuat bahasa pemrograman? Sama halnya dengan compiler dan interpreter, semua orang bisa membuat bahasa pemrograman mereka sendiri. Anda ingin membuat bahasa pemrograman yang hanya memperbolehkan penggunaan emoji 🙏 dan 👍? Bisa. Anda ingin membuat bahasa pemrograman dengan paradigma campur aduk dengan menggabungkan Java dan Haskell? Bisa. Bahkan jika Anda ingin membuat gim dan sistem operasi menggunakan bahasa pemrograman Anda sendiri pun juga bisa. Yang Anda perlukan hanyalah mendefinisikan aturan penulisan dan cara kerja bahasa pemrograman Anda, lalu membuat compiler/interpreter yang mengimplementasikan aturan-aturan tersebut.

Saya sendiri juga pernah mencoba membuat bahasa pemrograman sendiri melalui 2 proyek berikut:

Mungkin proyek saya sama sekali tidak keren, berikut adalah yang lebih keren, baru-baru ini ramai pembicaraan di lingkungan programmer di Twitter Indonesia terkait karya seseorang berupa emulator NES yang dibuat menggunakan bahasa pemrogramannya sendiri yang dinamai magelang, sebuah pencapaian yang impresif.

Standar Bahasa Pemrograman dan Banyaknya Compiler/Interpreter

Jika compiler/interpreter adalah program untuk mengimplementasikan aturan Bahasa Pemrograman. Lalu untuk apa sebagian bahasa pemrograman memiliki banyak compiler/interpreter? Sebagaimana C++ memiliki banyak compiler seperti Clang, GCC, MSVC, Nvidia nvcc, Intel C++, dan masih banyak lagi. Serta Python yang memiliki beberapa opsi interpreter seperti CPython, PyPy, dan IronPython?

Aturan untuk suatu bahasa pemrograman biasanya terdiri atas 2 bagian, yaitu:

  1. Aturan sintaksis, yang mengatur bagaimana cara menulis bahasa pemrograman tersebut dengan benar.

  2. Aturan semantik, yang mengatur dan mendefinisikan apa maksud dari suatu bagian kode, atau bagaimana suatu bagian kode itu berperilaku.

Aturan sintaksis lebih mudah diingat karena kita biasanya mendefinisikan sebuah bahasa pemrograman hanya dengan melihat sintaksnya. Selain itu, aturan sintaksis biasanya tidak terdapat banyak perbedaan antara satu compiler/interpreter dengan yang lainnya untuk satu bahasa yang sama.

Tetapi aturan semantik berbeda, satu potongan kode C++ berikut bisa memiliki arti yang berbeda tergantung compiler yang digunakan, karena ukuran long berbeda antara satu compiler dengan compiler lain.

#include <iostream>

auto main() -> int {
    int size_of_long = sizeof(long);
    std::cout << "Size of long: " 
              << size_of_long 
              << " bytes.\n";
    return 0;
}

Itu hanyalah salah satu contoh kecil bagaimana perilaku program bisa berbeda tergantung compiler yang digunakan.

Hal ini memberikan ruang untuk inovasi karena satu compiler/interpreter bisa mengimplementasikan aturan sebuah bahasa pemrograman sedemikian rupa sehingga compiler/interpreter tersebut memiliki keunggulan-keunggulan yang tidak dimiliki compiler/interpreter lainnya bahkan ketika memproses skrip yang sama, sebagai contoh:

  1. PyPy memiliki performa yang lebih baik ketimbang CPython yang merupakan interpreter default Python.

  2. JRuby membuat Ruby bisa berjalan di platform JVM sehingga programmer dapat memanfaatkan ekosistem Java yang masif, ini tidak mungkin dicapai menggunakan interpreter default Ruby (MRI).

  3. Hermes membuat waktu start-up aplikasi mobile berbasis JavaScript lebih cepat, sehingga lebih cocok dibandingkan interpreter lain seperti JavaScriptCore untuk digunakan pada pengembangan aplikasi cross-platform berbasis React Native.

  4. Zapcc mampu mengkompilasi kode C++ dengan lebih cepat dibandingkan compiler-compiler alternatifnya.

Dan masih banyak lagi contoh keunggulan satu compiler/interpreter dibandingkan compiler/interpreter lainnya.

Tetapi selain ruang inovasi, tersedianya lebih dari satu compiler/interpreter untuk suatu bahasa juga berpotensi menimbulkan perselisihan. Bisa jadi satu compiler C++ mengimplementasikan aturan bahwa tipe data char berukuran 1 byte, sedangkan compiler lainnya mengimplementasikan aturan bahwa tipe data char berukuran 32 byte (💀). Perbedaan seperti ini sesungguhnya juga menyediakan ruang inovasi, tetapi tetap perlu ada limitasi atau standar yang bisa dijadikan acuan oleh para pembuat compiler C++ agar perbedaan tersebut tidak menjadi terlalu ekstrim. Contoh ekstrim tersebut adalah misalnya satu compiler C++ mendukung fitur class tetapi compiler lain tidak mendukung penggunaan class, maka ini terlalu ekstrim dan sudah berada di ambang implementasi bahasa yang berbeda. Untuk menghindari hal ini, diperlukan spesifikasi untuk sebuah bahasa pemrograman.

Standar atau spesifikasi bahasa pemrograman adalah sekumpulan aturan yang sudah dikodifikasi dan disepakati bersama bahwa aturan tersebut adalah standar untuk bahasa pemrograman tersebut dan para pembuat compiler/interpreter dianjurkan untuk mematuhi standar tersebut. Meski pada prakteknya tidak semua compiler/interpreter 100% sesuai dengan standar bahasa pemrograman yang mereka implementasikan, tetapi setidaknya secara umum biasanya masih kompatibel dengan spesifikasi bahasa tersebut. Sebagai referensi, berikut adalah beberapa spesifikasi bahasa pemrograman yang dapat diakses dengan bebas:

  1. ECMAScript 2024, standar JavaScript keluaran terbaru per artikel ini ditulis.

  2. Standar Java 21.

  3. C11, standar C tahun 2011.

Kata kunci dari standar/spesifikasi bahasa pemrograman adalah sudah dikodifikasi dan disepakati. Hal ini penting karena aturan untuk suatu bahasa pemrograman tidak harus ditulis secara formal dan tidak harus disepakati jika memang tidak ingin dijadikan standar. Sebagai contoh, Go dan Rust adalah 2 bahasa yang belum memiliki standar, tetapi bukan berarti kedua bahasa tersebut tidak memiliki aturan, tentu saja punya, karena jika tidak maka keduanya tidak bisa menjadi bahasa pemrograman, hanya saja aturan kedua bahasa tersebut belum dikodifikasi secara formal dan belum disepakati sebagai standar untuk masing-masing bahasa. Sehingga jika ada siapapun yang hendak membuat compiler Go, mereka bisa saja menambahkan fitur class yang notabene jauh melenceng dibandingkan spesifikasi informal yang telah disediakan Tim Go, fitur yang disediakan gc (compiler default Go), dan Bahasa Go yang umum diketahui orang.

Akhir Kata

Bahasa pemrograman tidaklah semagis itu, ia hanyalah sekumpulan aturan yang mendefinisikan bagaimana menulis suatu teks dan apa arti tiap baris kode di dalam teks tersebut. Tidak ada satu otoritas tertentu yang berhak membuat/menghapus bahasa pemrograman. Setiap orang bisa membuat bahasa pemrograman mereka sendiri, membuat standar untuk bahasa pemrograman mereka sendiri, membuat compiler/interpreter untuk bahasa pemrograman mereka sendiri, serta membuat program-program lain menggunakan bahasa pemrograman mereka sendiri.

Selain itu, compiler/interpreter tidaklah semagis itu pula, ia hanya program biasa yang menerima teks sebagai input dan mengeluarkan luaran berupa file binary yang bisa dipahami oleh komputer yang hanya bisa berkomunikasi menggunakan 0 dan 1.