evilfactorylabsarchive / blog

blog by evilfactory team
https://blog.evilfactory.id
Creative Commons Attribution Share Alike 4.0 International
5 stars 0 forks source link

Memahami Compiler #9

Open faultables opened 5 years ago

faultables commented 5 years ago

Tulisan ini terinspirasi dari tulisan Tom Dale yang berjudul Compilers are the New Frameworks, yang mana diakhir paragraf, Tom Dale menulis:

So here’s my advice for anyone who wants to make a dent in the future of web development: > time to learn how compilers work.

Karena sedang senggang, mari kita coba memahami bagaimana Compiler bekerja.

Apa itu compiler?

Compiler adalah sebuah program yang menerjemahkan sebuah kode yang ditulis menggunakan sebuah bahasa program menjadi bahasa program lainnya.

–– https://en.wikipedia.org/wiki/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:

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:

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:

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:

Input (contoh):

Garis 100 2 red
Bulat 150 150 80 pink

Output (contoh):

<svg>
  <rect width="300" height="2" fill="red"></rect>
  <circle cx="150" cy="150" r="80" fill="pink"></circle>
</svg>

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.

Dan ya, bahasa Indonesia.

Token

Hanya Shape, Number, dan String untuk ini. Misal:

Bulat 150 150 80 pink

Contoh hasil tokenisasi:

[
   {
      "type": "Shape",
      "value": "Bulat"
   },
   {
      "type": "Number",
      "value": "150"
   },
   {
      "type": "Number",
      "value": "150"
   },
   {
      "type": "Number",
      "value": "80"
   },
   {
      "type": "String",
      "value": "pink"
   }
]

Parsing

Dan ini contoh syntax tree nya setelah diparsing:

{
   "body": [
      {
         "type": "CallExpression",
         "name": "Bulat",
         "arguments": [
            {
               "type": "Number",
               "value": 150
            },
            {
               "type": "Number",
               "value": 150
            },
            {
               "type": "Number",
               "value": 80
            },
            {
               "type": "String",
               "value": "pink"
            }
         ]
      }
   ]
}

Transform

Dan ini contoh syntax tree nya setelah di transform berdasarkan syntax tree sebelumnya

{
   "type": "svg",
   "body": [
      {
         "type": "circle",
         "props": {
            "cx": 150,
            "cy": 150,
            "r": 80,
            "fill": "pink"
         }
      }
   ]
}

Generate

Lalu, dari hasil terakhir syntax tree tersebut, beginilah hasil akhirnya setelah digenerate:

<svg>
  <circle cx="150" cy="150" r="80" fill="pink"></circle>
</svg>

Fase

  1. input => tokenizer => tokens
  2. tokens => parser => ast
  3. ast => transformer => newAst
  4. newAst => generator => output

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.

Sek sahur dulu

faultables commented 5 years ago

Lah kagak dilanjut si anjir