Pada dasarnya, program adalah kumpulan-kumpulan instruksi, kan? Sesimple instruksi: "Tampilkan tulisan hello world" ke layar. Itu adalah sebuah program.
Dalam membuat instruksi, kita harus tau "siapa" target yang dimaksud. Bila gue ngasih instruksi ke lu: "beliin gue kopi di Indiemaret", lo, sebagai target, harus tau apa itu:
Verba: "beliin"
Pronomina: "gue"
Nomina: "kopi"
Afiks: "di"
Adverbia: "Indiemaret"
Apa yang terjadi kalau lo gak tau, misal: "apa itu beliin?", "apa itu kopi?", "apa itu gue?", dan lain sebagainya? Ya bingung.
Disinilah kerjaan nya compiler. Untuk menterjemahkan perintah lo tersebut. Singkatnya, ada 2 fase utama dari compiler, yakni Lexical Analysis (tokenisasi) dan Syntactic Analysis (parsing). Yang akan kita bahas lebih dalam
Tokenisasi
Ingat contoh kita tentang instruksi membeli kopi? Proses tokenisasi tersebut adalah ya mengurai instruksi tersebut. Seperti: "Oh beliin tuh verba, oh kopi tuh nomina, dsb". Ambil contoh kode JavaScript seperti ini:
console.log('Hello World')
Apakah "JavaScript engine" mengetahui maksud dari instruksi tersebut? Yang pada dasarnya adalah instruksi untuk menampilkan kata "Hello World" ke layar? Tentu tidak tau. Agar si engine paham, dia perlu mengurai kata-kata tersebut, yang singkatnya menjadi seperti ini:
Setelah kita mendapatkan token-token nya, waktunya untuk membuatnya menjadi "pohon" alias ke fase Syntactic Analysis (parsing).
Parsing
Lihat hasil dari proses tokenisasi diatas? Apa maknanya? Enggak tau kan? Bayangkan bila kembali ke contoh tentang beliin kopi, apa yang terjadi dibenak kalian:
Apakah gue harus beliin indomaret di kopi?
Apakah gue harus di kopi beliin indomaret ?
Atau gimana?
Disinilah tugasnya syntactic analysis. Dia akan membuat "pohon" sintaks, sehingga tau mana yang bentuknya "statement", "ekspresi", dsb. Dari kode contoh JavaScript tersebut, akan membuat pohon sintaks seperti berikut:
Atau versi yang lebih mudah dibayangkan, misalnya seperti ini:
Untuk merepresentasikan kode seperti ini:
function foo (x) {
if (x > 10) {
var a = 2
return a * x
}
return x + 10
}
Correct me if I'm wrong since I don't see the source code because I cannot access this page 🤷♂️.
Image are taken from that link via Google Image.
Ok well kita sudah mengerti sedikit gambaran tentang compiler. Pada dasarnya compiler pun terbagi 2 bagian: frontend dan backend. Dan yang kita bahas diatas adalah bagian frontend nya saja.
Untuk mencoba memahami apa yang telah kita pelajari tersebut, mari kita membuat compiler sederhana kita. Yang artinya: Kita akan membuat "bahasa program" yang nantinya akan diterjemahkan ke "bahasa program" lagi, yakni: Ehm, JavaScript.
Bahasa yang akan kita buat
Sebagai Frontend developer, kita sering berhadapan dengan "media" (gambar khususnya): Png, jpg, gif, dsb. Ada satu sebuah media gambar yang sampai hari ini susah banget gue kuasai: SVG.
SVG pada dasarnya adalah sebuah format gambar (bertipe vector) yang dibuat berdasarkan syntax XML.
Mengapa sangat sulit (bagi gue) dalam memahami SVG? Karena syntax nya yang susah banget diinget. Misal untuk membuat garis, kita bisa membuatnya dengan syntax xml (ehm, html) seperti ini:
<svg>
<rect width='300' height='2' />
</svg>
Di JavaScript, jadinya seperti ini:
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
const line = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
line.setAttribute('width', 300)
line.setAttribute('height', 2)
svg.appendChild(line)
// Test it
document.body.appendChild(svg)
Wow, gampang ya ternyata. Tapi terlihat imperatif. Sedangkan gue inginnya deklaratif! Mungkin kita bisa membuatnya terlihat deklaratif dengan membuat sebuah function, mungkin seperti ini:
function createSvg({ type, ...props }) {
const svg = document.createElementNS('http://www.w3.org/2000/svg', type)
for (let prop in props) {
svg.setAttribute(prop, props[prop])
}
return svg
}
const svg = createSvg({ type: 'svg' })
const line = createSvg({ type: 'rect', width: 300, height: 2 })
svg.appendChild(line)
// Test it
document.body.appendChild(svg)
Wah bahkan kita mendapatkan hasil yang sama! Bedanya dibuat dengan cara declarative-ish: tell how it will looks like, not how to make it looks like.
The Problem
Terlihat sekilas function tersebut tidak ada masalah (semoga saja! Plus, tidak ada validasi yang gue lakukan, kan?) lalu dimana masalahnya?
That function is lives in runtime.
Yang artinya, bila kalian memanggil function tersebut di file index.html, ada harga yang harus dibayar memanggil fungsi tersebut. Memanggil pun tidak hanya sebatas memanggil, kan? Ada beberapa sub-operasi lainnya (misal seperti validasi, looping, proses setAttribute, dsb).
Oke-oke mungkin ah ini cepet kok, paling sekitaran 0.759ms (yoi I measure it). Tapi bagaimana bila ada 10 svg yang ingin kita buat? Daaan, bagaimana bila ternyata proses tersebut bisa dilakukan di build time? Jadi, yaa browser langsung nerima "svg" nya, tanpa perlu melakukan "proses pembuatan svg nya secara declarative-ish" tersebut?
Lo gak perlu buat compiler, riz! Tinggal pakai "compiler" yang sudah ada (Babel, Buble, whatever), dan buat plugin macro nya!
Yoi. Tapi ini kan untuk pembelajaran, ya? Namanya juga coba-coba yakan? Mari kita buat compiler sederhananya.
Desain
Gue ingin membuat SVG dengan cara seperti ini, misal membuat garis dengan panjang 100px dan tinggi 2px, dan berwarna merah, maka seperti ini:
Sintaks diatas (input) terinspirasi dari Elm, sebuah bahasa program "fungsional" untuk membuat sebuah web app.
Fungsional
Mengapa memilih seperti Elm? Karena gue suka dengan konsep "fungsional" tersebut. Segala sesuatu adalah sebuah fungsi, fungsi yang murni. Kalau lo ingin membuat garis dengan panjang 100px dan tinggi 2px, dan berwarna merah, maka itulah yang bakal lo dapet. No side effect.
Intuitif
Lihat kode diatas yang contoh, apakah make sense? Well, sebenarnya itu diambil dari "rumus" dasar menghitung luas persegi panjang: Luas = panjang x lebar. Kita sudah biasa mengingat "panjang", "kali", "lebar". Berarti: "Panjang dulu", "baru lebar".
Jika masih bingung perbedaan antara "panjang" dan "lebar", berikut gambar dari Wikipedia yang menurut gue bisa mempermudah pemahaman:
Juga, gue ingin menggunakan bahasa sehari-hari. Kalau gue ingin garis, ya garis. Kalau kotak, ya kotak. Gak peduli meskipun sama-sama ber-tipe rect, karena yang gue tau garis ya cuma ada 2 titik aja.
Oke ini hanya untuk pembelajaran ya, tentu bentuk svg tidak hanya sebatas rect dan circle, ada ellipse, polyline, ehm line, polygon, dan yang paling dasar (sekaligus kompleks): path.
Tulisan ini terinspirasi dari tulisan Tom Dale yang berjudul Compilers are the New Frameworks, yang mana diakhir paragraf, Tom Dale menulis:
Karena sedang senggang, mari kita coba memahami bagaimana Compiler bekerja.
Apa itu compiler?
Terlalu abstrak? Mari kita analogikan!
Pada dasarnya, program adalah kumpulan-kumpulan instruksi, kan? Sesimple instruksi: "Tampilkan tulisan hello world" ke layar. Itu adalah sebuah program.
Dalam membuat instruksi, kita harus tau "siapa" target yang dimaksud. Bila gue ngasih instruksi ke lu: "beliin gue kopi di Indiemaret", lo, sebagai target, harus tau apa itu:
Apa yang terjadi kalau lo gak tau, misal: "apa itu beliin?", "apa itu kopi?", "apa itu gue?", dan lain sebagainya? Ya bingung.
Disinilah kerjaan nya compiler. Untuk menterjemahkan perintah lo tersebut. Singkatnya, ada 2 fase utama dari compiler, yakni Lexical Analysis (tokenisasi) dan Syntactic Analysis (parsing). Yang akan kita bahas lebih dalam
Tokenisasi
Ingat contoh kita tentang instruksi membeli kopi? Proses tokenisasi tersebut adalah ya mengurai instruksi tersebut. Seperti: "Oh beliin tuh verba, oh kopi tuh nomina, dsb". Ambil contoh kode JavaScript seperti ini:
Apakah "JavaScript engine" mengetahui maksud dari instruksi tersebut? Yang pada dasarnya adalah instruksi untuk menampilkan kata "Hello World" ke layar? Tentu tidak tau. Agar si engine paham, dia perlu mengurai kata-kata tersebut, yang singkatnya menjadi seperti ini:
Click Me
```json [ { "type": "Identifier", "value": "console" }, { "type": "Punctuator", "value": "." }, { "type": "Identifier", "value": "log" }, { "type": "Punctuator", "value": "(" }, { "type": "String", "value": "'Hello world'" }, { "type": "Punctuator", "value": ")" } ] ``` Contoh diambil dari [Esprima](http://esprima.org/demo/parse.html?code=console.log('Hello%20world')).Setelah kita mendapatkan token-token nya, waktunya untuk membuatnya menjadi "pohon" alias ke fase Syntactic Analysis (parsing).
Parsing
Lihat hasil dari proses tokenisasi diatas? Apa maknanya? Enggak tau kan? Bayangkan bila kembali ke contoh tentang beliin kopi, apa yang terjadi dibenak kalian:
Atau gimana?
Disinilah tugasnya syntactic analysis. Dia akan membuat "pohon" sintaks, sehingga tau mana yang bentuknya "statement", "ekspresi", dsb. Dari kode contoh JavaScript tersebut, akan membuat pohon sintaks seperti berikut:
Click Me
```json { "type": "Program", "body": [ { "type": "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "MemberExpression", "computed": false, "object": { "type": "Identifier", "name": "console" }, "property": { "type": "Identifier", "name": "log" } }, "arguments": [ { "type": "Literal", "value": "Hello world", "raw": "'Hello world'" } ] } } ], "sourceType": "script" } ``` Contoh diambil dari [Esprima](http://esprima.org/demo/parse.html?code=console.log('Hello%20world')).Atau versi yang lebih mudah dibayangkan, misalnya seperti ini:
Untuk merepresentasikan kode seperti ini:
Correct me if I'm wrong since I don't see the source code because I cannot access this page 🤷♂️.
Image are taken from that link via Google Image.
Ok well kita sudah mengerti sedikit gambaran tentang compiler. Pada dasarnya compiler pun terbagi 2 bagian: frontend dan backend. Dan yang kita bahas diatas adalah bagian frontend nya saja.
Untuk mencoba memahami apa yang telah kita pelajari tersebut, mari kita membuat compiler sederhana kita. Yang artinya: Kita akan membuat "bahasa program" yang nantinya akan diterjemahkan ke "bahasa program" lagi, yakni: Ehm, JavaScript.
Bahasa yang akan kita buat
Sebagai Frontend developer, kita sering berhadapan dengan "media" (gambar khususnya): Png, jpg, gif, dsb. Ada satu sebuah media gambar yang sampai hari ini susah banget gue kuasai: SVG.
SVG pada dasarnya adalah sebuah format gambar (bertipe vector) yang dibuat berdasarkan syntax XML.
Mengapa sangat sulit (bagi gue) dalam memahami SVG? Karena syntax nya yang susah banget diinget. Misal untuk membuat garis, kita bisa membuatnya dengan syntax xml (ehm, html) seperti ini:
Di JavaScript, jadinya seperti ini:
Wow, gampang ya ternyata. Tapi terlihat imperatif. Sedangkan gue inginnya deklaratif! Mungkin kita bisa membuatnya terlihat deklaratif dengan membuat sebuah
function
, mungkin seperti ini:Wah bahkan kita mendapatkan hasil yang sama! Bedanya dibuat dengan cara declarative-ish: tell how it will looks like, not how to make it looks like.
The Problem
Terlihat sekilas function tersebut tidak ada masalah (semoga saja! Plus, tidak ada validasi yang gue lakukan, kan?) lalu dimana masalahnya?
That function is lives in runtime.
Yang artinya, bila kalian memanggil function tersebut di file
index.html
, ada harga yang harus dibayar memanggil fungsi tersebut. Memanggil pun tidak hanya sebatas memanggil, kan? Ada beberapa sub-operasi lainnya (misal seperti validasi, looping, prosessetAttribute
, dsb).Oke-oke mungkin ah ini cepet kok, paling sekitaran 0.759ms (yoi I measure it). Tapi bagaimana bila ada 10 svg yang ingin kita buat? Daaan, bagaimana bila ternyata proses tersebut bisa dilakukan di build time? Jadi, yaa browser langsung nerima "svg" nya, tanpa perlu melakukan "proses pembuatan svg nya secara declarative-ish" tersebut?
Yoi. Tapi ini kan untuk pembelajaran, ya? Namanya juga coba-coba yakan? Mari kita buat compiler sederhananya.
Desain
Gue ingin membuat SVG dengan cara seperti ini, misal membuat garis dengan panjang 100px dan tinggi 2px, dan berwarna merah, maka seperti ini:
Input (contoh):
Output (contoh):
Sintaks diatas (input) terinspirasi dari Elm, sebuah bahasa program "fungsional" untuk membuat sebuah web app.
Fungsional
Mengapa memilih seperti Elm? Karena gue suka dengan konsep "fungsional" tersebut. Segala sesuatu adalah sebuah fungsi, fungsi yang murni. Kalau lo ingin membuat garis dengan panjang 100px dan tinggi 2px, dan berwarna merah, maka itulah yang bakal lo dapet. No side effect.
Intuitif
Lihat kode diatas yang contoh, apakah make sense? Well, sebenarnya itu diambil dari "rumus" dasar menghitung luas persegi panjang: Luas = panjang x lebar. Kita sudah biasa mengingat "panjang", "kali", "lebar". Berarti: "Panjang dulu", "baru lebar".
Jika masih bingung perbedaan antara "panjang" dan "lebar", berikut gambar dari Wikipedia yang menurut gue bisa mempermudah pemahaman:
Juga, gue ingin menggunakan bahasa sehari-hari. Kalau gue ingin garis, ya
garis
. Kalau kotak, yakotak
. Gak peduli meskipun sama-sama ber-tiperect
, karena yang gue tau garis ya cuma ada 2 titik aja.Dan ya, bahasa Indonesia.
Token
Hanya
Shape
,Number
, danString
untuk ini. Misal:Contoh hasil tokenisasi:
Parsing
Dan ini contoh syntax tree nya setelah diparsing:
Transform
Dan ini contoh syntax tree nya setelah di transform berdasarkan syntax tree sebelumnya
Generate
Lalu, dari hasil terakhir syntax tree tersebut, beginilah hasil akhirnya setelah digenerate:
Fase
Oke ini hanya untuk pembelajaran ya, tentu bentuk svg tidak hanya sebatas
rect
dancircle
, adaellipse
,polyline
, ehmline
,polygon
, dan yang paling dasar (sekaligus kompleks):path
.