Mufid's Code blog

Lembar Kerja dalam Tiga Puluh Baris Javascript

| Comments

Spreadsheet

Saya menjelajahi kembali beberapa tautan yang ada di penanda saya. Saya menemukan tautan yang sangat menarik beberapa tahun lampau: membuat lembar kerja dalam 30 baris di Javascript. Woh!

Baiklah, untuk membuat cerita lebih pendek, saya melihat kembali dimana keajaiban baris kode tersebut. Anda dapat melihat kode sumber aslinya di Codepen (via HN). Pertanyaan saya ada dua:

  • Bagaimana menyelesaikan formula di sana?
  • Bagaimana dependency resolution antar sel di lembar kerja tersebut?

Mari kita bahas yang pertama. Ini menjadi hal yang menarik bagi saya karena penyelesaian formula berarti Anda membuat DSL sendiri, menerjemahkannya, dan mengeksekusinya. DSL yang digunakan oleh semua program lembar kerja serupa. DSL yang digunakan juga dekat dengan bahasa pemrograman pada umumnya. Ya, menguraikan formula menjadi hal yang dimengerti dan dieksekusi oleh mesin bukanlah hal yang sederhana.

Ternyata, program Javascript ini tidak memiliki DSL sendiri. Alih-alih, program ini menggunakan perintah eval sederhana untuk menguraikan dan mengeksekusi formulanya. Yup, tentu saja jika Anda memasukkan perintah Javascript yang aneh-aneh —– bukan bentuk formula lembar kerja pada umumnya —– Anda akan menemukan sedikit kejanggalan. Anda dapat memberikan perintah alert pada perintahnya dan alert akan bena-benar muncul. Tentu saja bukan suatu hal yang akan Anda lakukan pada mesin produksi, tetapi ini sudah sangat baik sebagai sebuah PoC.

Mari kita bahas yang kedua: bagaimana dependency resolution dilakukan? Anggap saya memiliki sel A1 berisi =B2 * 5 dan B2 yang berisi =100*C2 dan C2 yang berisi 99? Bagaimana penyelesaian keterkaitan formula yang berbeda sel? Ah, “keajaiban” bahasa yang interpret di sini.

Anda dapat melihat di kode tersebut: ada perintah yang membuat property. Lihat pada bagian ini:

var getter = function() {
    var value = localStorage[elm.id] || "";
    if (value.charAt(0) == "=") {
        with (DATA) return eval(value.substring(1));
    } else { return isNaN(parseFloat(value)) ? value : parseFloat(value); }
};
Object.defineProperty(DATA, elm.id, {get:getter});
Object.defineProperty(DATA, elm.id.toLowerCase(), {get:getter});

Ini adalah dokumentasi dari MDN:

Object.defineProperty(obj, prop, descriptor)

Parameters

  • obj
    The object on which to define the property.
  • prop
    The name of the property to be defined or modified.
  • descriptor
    The descriptor for the property being defined or modified.

Jadi, pada dasarnya, eval akan dilakukan dengan property di dalam object bernama DATA. Property-nya sendiri bersifat dinamis, di dalam fungsi. Properti baru benar-benar didapatkan nilainya apa ketika dieksekusi. Jadi, sebenarnya yang melakukan dependency resolution adalah eval itu sendiri. Program ini tidak benar-benar membuat dependency graph seperti yang saya bayangkan sebelumnya.

Sebagai contoh, ada formula =A9*Math.cos(B2). Yang terjadi adalah Javascript eval akan mengevaluasi A9 * Math.cos(B2) ke dalam sel tersebut. Math.cos adalah fungsi bawaan Javascript sehingga tidak perlu dipertanyakan. Bagaimana dengan A9 dan B2? Dua variabel tersebut adalah property dalam objek DATA yang dapat diakses tanpa mengetikkan DATA berkat idiom with. Apa nilai A9 dan B2? Belum tahu. Akan tetapi, karena A9 dan B2 itu sendiri merupakan property yang memiliki getter sendiri, maka kemudian eval akan mengeksekusi DATA.A9 dan DATA.B2 — yang mana di dalam property tersebut akan melakukan eval lagi jika diawali dengan =.

Bagaimana jika ada circular reference? Ini diakali dengan try-catch sederhana berikut:

INPUTS.forEach(function(elm) { try { elm.value = DATA[elm.id]; } catch(e) {} });

Jika terdapat circular reference, call-stack akan menjadi sangat panjang. Bayangkan B2 menggunakan nilai dari A1 dan A1 menggunakan nilai dari B2. Akan terjadi sebuah pemanggilan fungsi getter terhadap dua sel berbeda yang tidak ada habisnya. Ketika call stack sangat panjang, interpreter akan menghasilkan exception, yang ditangkap, dan diabaikan.

Saya rubah kode asli agar menangkap exception dan mencetaknya. Beriktu hasilnya:

Recursion

Comments