Ders 23 / 25
Derleyici Tabanlı Yaklaşımlar
Bağımlılıkların derleme zamanında çözülmesi; şablonu güncelleme koduna çeviren küçük bir dönüştürücü, üretilen kodun okunması, çözümlemenin sağlamlık sınırı ve derleme adımının bedeli.
İçindekiler
Önceki iki aile de defterini çalışma zamanında tutuyordu: biri ağacı karşılaştırarak, diğeri abonelikleri kaydederek. İkisinin de indirilen kodda bir karşılığı vardı.
Oysa hangi ifadenin hangi değere bağlı olduğu, çoğu durumda kaynak metne bakarak anlaşılabilir. Bir şablonda bir metin düğümünün hangi değişkeni okuduğu yazılıdır. Bunu kod çalışmadan önce çıkarabilen bir araç, çalışma zamanına yalnızca “şu değer değişirse şu yeri yaz” biçiminde doğrudan kod bırakabilir.
Bağımlılık Kaynak Metinde Yazılıdır
Bilgisayarlar Nasıl Çalışır kursundaki Derleme Aşamaları dersinde kurulan zincir burada küçük ölçekte tekrarlanır: sözcüksel çözümleme, ağaç kurma, çözümleme ve kod üretimi. Girdi bir şablon, çıktı çalıştırılabilir güncelleme kodudur.
Ayrım şuradadır: önceki iki ailede çerçeve, uygulamanın verisini yorumlar; bu ailede araç, uygulamanın kaynak metnini yorumlar. Yorumlama bir kez, derleme sırasında yapılır ve sonucu bir daha değişmez.
Aşağıdaki dönüştürücü bunu en yalın biçimde kurar. Şablonu durağan parçalara ve ifade yuvalarına böler, her ifadenin okuduğu durum alanlarını bulur ve alan başına yalnızca o alandan etkilenen yuvaları yazan bir işlev üretir.
// derleyici.mjs — sablonu derleme zamaninda cozup guncelleme kodu ureten donusturucu const DURUM_ALANLARI = ["ad", "deger", "damga", "esikAsildi"]; const SABLON = `<tr class="satir {esikAsildi ? 'uyari' : 'olagan'}"> <th scope="row">{ad}</th> <td class="deger">{deger.toFixed(1)} °C</td> <td class="damga">{esikAsildi ? damga : '—'}</td> </tr>`; // 1) Ayristirma: sablon, duragan parcalara ve ifade yuvalarina bolunur. function ayristir(sablon) { const duragan = []; const ifadeler = []; let son = 0; for (const eslesme of sablon.matchAll(/\{([^{}]*)\}/g)) { duragan.push(sablon.slice(son, eslesme.index)); ifadeler.push(eslesme[1].trim()); son = eslesme.index + eslesme[0].length; } duragan.push(sablon.slice(son)); return { duragan, ifadeler }; } // Dizgi degismezlerini ayirmadan hicbir cozumleme dogru olamaz. const dizgisiz = (ifade) => ifade.replace(/'[^']*'|"[^"]*"/g, (d) => " ".repeat(d.length)); // 2) Cozumleme: her ifadenin okudugu durum alanlari bulunur. function cozumle(ifade) { const g = dizgisiz(ifade); const dinamik = /\[[^\]]*\]/.test(g); // durum[degisken] gibi erisim const adlar = DURUM_ALANLARI.filter((a) => new RegExp(`(?<![.\\w$])${a}\\b`).test(g)); return { adlar, dinamik }; } // 3) Kod uretimi: ifadedeki durum adlari `d.` ile nitelenir. function nitele(ifade) { const g = dizgisiz(ifade); let sonuc = ""; let son = 0; const eslesmeler = []; for (const a of DURUM_ALANLARI) for (const e of g.matchAll(new RegExp(`(?<![.\\w$])${a}\\b`, "g"))) eslesmeler.push({ konum: e.index, uzunluk: a.length }); eslesmeler.sort((x, y) => x.konum - y.konum); for (const e of eslesmeler) { sonuc += ifade.slice(son, e.konum) + "d." + ifade.slice(e.konum, e.konum + e.uzunluk); son = e.konum + e.uzunluk; } return sonuc + ifade.slice(son); } function derle(sablon) { const { duragan, ifadeler } = ayristir(sablon); const cozum = ifadeler.map(cozumle); const govdeler = ifadeler.map((ifade, i) => `y[${i}] = String(${nitele(ifade)});`); const etkilenen = {}; for (const alan of DURUM_ALANLARI) etkilenen[alan] = cozum.map((c, i) => (c.dinamik || c.adlar.includes(alan) ? i : -1)).filter((i) => i >= 0); const kaynak = [ `const DURAGAN = ${JSON.stringify(duragan)};`, `const yuvaYaz = [`, ...govdeler.map((g, i) => ` (d, y) => { ${g} }, // yuva ${i}`), `];`, `const kur = (d) => { const y = []; for (const f of yuvaYaz) f(d, y); return y; };`, ...DURUM_ALANLARI.map((a) => `const ${a}Degisti = (d, y) => { ${etkilenen[a].map((i) => `yuvaYaz[${i}](d, y);`).join(" ")} return ${etkilenen[a].length}; };`), `const metin = (y) => DURAGAN.reduce((m, s, i) => m + s + (y[i] ?? ""), "");`, ].join("\n"); // Uretilen kodu calistirilabilir hale getir. const yuvaYaz = govdeler.map((g) => new Function("d", "y", g)); const modul = { kur: (d) => { const y = []; for (const f of yuvaYaz) f(d, y); return y; }, metin: (y) => duragan.reduce((m, s, i) => m + s + (y[i] ?? ""), ""), }; for (const a of DURUM_ALANLARI) modul[`${a}Degisti`] = (d, y) => { for (const i of etkilenen[a]) yuvaYaz[i](d, y); return etkilenen[a].length; }; return { kaynak, duragan, ifadeler, cozum, etkilenen, modul }; } const derlenmis = derle(SABLON); console.log("--- üretilen kod ---"); console.log(derlenmis.kaynak); console.log("\n--- ifade / bağımlılık tablosu ---"); derlenmis.ifadeler.forEach((ifade, i) => console.log(` yuva ${i}: ${ifade.padEnd(32)} → ${JSON.stringify(derlenmis.cozum[i].adlar)}`)); console.log(" alan → etkilenen yuvalar:", JSON.stringify(derlenmis.etkilenen)); const m = derlenmis.modul; const durum = { ad: "sensör-120", deger: 42.5, damga: "06:00", esikAsildi: true }; const yuvalar = m.kur(durum); console.log("\nilk çizim:"); console.log(m.metin(yuvalar)); durum.deger = 43.1; console.log(`\ndeğer değişti → yeniden değerlendirilen ifade: ${m.degerDegisti(durum, yuvalar)}`); console.log(m.metin(yuvalar)); durum.esikAsildi = false; console.log(`\neşik durumu değişti → yeniden değerlendirilen ifade: ${m.esikAsildiDegisti(durum, yuvalar)}`); console.log(m.metin(yuvalar)); console.log("\ndeğişen alan derlenmiş çözümleme yapmayan çalışma zamanı"); for (const alan of DURUM_ALANLARI) console.log(`${alan.padEnd(16)} ${String(derlenmis.etkilenen[alan].length).padStart(9)} ${String(derlenmis.ifadeler.length).padStart(34)}`); // 4) Cozumlemenin siniri: dinamik erisim butun alanlara bagimlilik uretir. const belirsiz = derle(`<td>{deger[ad]}</td>`); console.log("\ndinamik erişimli şablon:"); console.log(" ifade:", belirsiz.ifadeler[0], "| dinamik:", belirsiz.cozum[0].dinamik); console.log(" alan → etkilenen yuvalar:", JSON.stringify(belirsiz.etkilenen));
--- üretilen kod ---
const DURAGAN = ["<tr class=\"satir ","\">\n <th scope=\"row\">","</th>\n <td class=\"deger\">"," °C</td>\n <td class=\"damga\">","</td>\n</tr>"];
const yuvaYaz = [
(d, y) => { y[0] = String(d.esikAsildi ? 'uyari' : 'olagan'); }, // yuva 0
(d, y) => { y[1] = String(d.ad); }, // yuva 1
(d, y) => { y[2] = String(d.deger.toFixed(1)); }, // yuva 2
(d, y) => { y[3] = String(d.esikAsildi ? d.damga : '—'); }, // yuva 3
];
const kur = (d) => { const y = []; for (const f of yuvaYaz) f(d, y); return y; };
const adDegisti = (d, y) => { yuvaYaz[1](d, y); return 1; };
const degerDegisti = (d, y) => { yuvaYaz[2](d, y); return 1; };
const damgaDegisti = (d, y) => { yuvaYaz[3](d, y); return 1; };
const esikAsildiDegisti = (d, y) => { yuvaYaz[0](d, y); yuvaYaz[3](d, y); return 2; };
const metin = (y) => DURAGAN.reduce((m, s, i) => m + s + (y[i] ?? ""), "");
--- ifade / bağımlılık tablosu ---
yuva 0: esikAsildi ? 'uyari' : 'olagan' → ["esikAsildi"]
yuva 1: ad → ["ad"]
yuva 2: deger.toFixed(1) → ["deger"]
yuva 3: esikAsildi ? damga : '—' → ["damga","esikAsildi"]
alan → etkilenen yuvalar: {"ad":[1],"deger":[2],"damga":[3],"esikAsildi":[0,3]}
ilk çizim:
<tr class="satir uyari">
<th scope="row">sensör-120</th>
<td class="deger">42.5 °C</td>
<td class="damga">06:00</td>
</tr>
değer değişti → yeniden değerlendirilen ifade: 1
<tr class="satir uyari">
<th scope="row">sensör-120</th>
<td class="deger">43.1 °C</td>
<td class="damga">06:00</td>
</tr>
eşik durumu değişti → yeniden değerlendirilen ifade: 2
<tr class="satir olagan">
<th scope="row">sensör-120</th>
<td class="deger">43.1 °C</td>
<td class="damga">—</td>
</tr>
değişen alan derlenmiş çözümleme yapmayan çalışma zamanı
ad 1 4
deger 1 4
damga 1 4
esikAsildi 2 4
dinamik erişimli şablon:
ifade: deger[ad] | dinamik: true
alan → etkilenen yuvalar: {"ad":[0],"deger":[0],"damga":[0],"esikAsildi":[0]}
Üretilen Kodun Okunması
Çıktının ilk bölümü, çalışma zamanına inen şeyin ne olduğunu gösteriyor: dört yuva yazma işlevi, alan başına bir güncelleme işlevi ve bir metin birleştirici. Fark alma algoritması yok, abone kümesi yok, ağaç temsili yok. Kod, hangi yerin hangi değişiklikte yazılacağını doğrudan söylüyor.
esikAsildiDegisti iki yuvayı birden yazıyor, çünkü eşik durumu hem satırın sınıf adında
hem de damga hücresinde okunuyor. Bu bilgi bağımlılık tablosundan geliyor ve çalışma
zamanında hiçbir hesaba dayanmıyor.
damgaDegisti yalnızca üçüncü yuvayı yazıyor. Damga değiştiğinde satırın sınıf adı yeniden
hesaplanmıyor — çünkü sınıf adı damgayı okumuyor. Çözümleme yapmayan bir çalışma zamanı
hangi alan değişirse değişsin dört ifadeyi de yeniden değerlendirirdi; tablo bu farkı
gösteriyor.
Durağan parçaların da ayrılmış olması ikinci bir kazançtır. <th scope="row"> metni bir
kez üretilir ve bir daha hiçbir güncellemede dokunulmaz. Sanal ağaç ailesinde bu düğüm her
karşılaştırmada gezilir; burada karşılaştırma diye bir adım yoktur.
Çözümlemenin Sağlamlık Sınırı
Son bölüm yaklaşımın kırılma noktasını gösteriyor. deger[ad] ifadesinde okunan alan
kaynak metinden belli değildir; hangi anahtarın kullanılacağı çalışma zamanında belli olur.
Çözümleyici bu durumda muhafazakâr davranmak zorundadır: ifadeyi bütün alanlara bağımlı
sayar, böylece hangi alan değişirse değişsin yuva yeniden yazılır.
Bu, derleyici tabanlı yaklaşımın genel kuralıdır. Çıkarım sağlam olmalıdır: bir bağımlılığı kaçırmak, güncellenmeyen bir görünüm demektir ve bu bir başarım sorunu değil, doğruluk sorunudur. Fazladan bağımlılık eklemek yalnızca gereksiz iş üretir. Belirsizlik karşısında araç her zaman fazlayı seçer.
Sonuç, dilin bir alt kümesine yönelmedir. Şablon dilleri, ifade sözdizimini bilerek kısıtlar: sınırlı ifade biçimleri, sınırlı erişim yolları, açıkça bildirilen durum. Kısıt bir eksiklik değil, çıkarımın çalışabilmesinin koşuludur. Aynı fikir Düzen Sistemleri kursundaki ön işlemcilerde de vardır — girdi dili ne kadar dar olursa, araç o kadar çok şey söyleyebilir.
Çözümleme aynı zamanda kullanılmayan yolları da görür. Bir durum alanı hiçbir ifadede okunmuyorsa, o alan için üretilen güncelleme işlevi boş kalır; kullanılmayan bileşenler ve dallar paketten tümüyle çıkarılabilir. Derleme Aşamaları dersindeki ölü kod eleme, burada uygulama düzeyine taşınır.
Derleme Adımının Bedeli
Kazançların karşılığı, çalıştırılan kodun yazılan kod olmamasıdır.
Hata ayıklama bunun ilk sonucudur. Kesme noktası konan satır ile çalışan satır ayrıdır; aradaki bağı kaynak haritası kurar. Tarayıcı ve Web Platformu kursundaki Kaynak Hata Ayıklama dersinde tanıtılan kaynak haritası bu ailede isteğe bağlı bir kolaylık değil, zorunlu bir parçadır. Eşleme eksik ya da bozuksa, hata iletisi anlaşılmaz bir konumu gösterir.
İkinci bedel araç zinciri bağımlılığıdır. Kod tarayıcıda doğrudan çalışmaz; bir derleme adımından geçmek zorundadır. Geliştirme sırasında bu adımın hızlı olması gerekir, yoksa her değişiklikte beklenir. Sürüm yükseltmelerinde de derleyici ile yazılan kaynak metin arasında uyum gözetilir: aracın sürümü, uygulamanın kaynak metninin bir parçası hâline gelir.
Üçüncü bedel öngörülebilirliktir. Üretilen kod okunabilir olsa bile, yazılan kodun ne üreteceği her zaman apaçık değildir. Bir ifadeyi bir yardımcı işleve taşımak, çözümleyicinin bağımlılığı görmesini engelleyip muhafazakâr moda düşürebilir; sonuç, hiçbir hata vermeden artan bir iş miktarıdır. Bu ailede başarım sorunlarının tanısı, üretilen kodun okunmasını gerektirir.
Ailenin Profili
Güncelleme birimi, derleme zamanında belirlenmiş tek bir yazma işlemidir. Çalışma zamanı kodu, uygulamanın karmaşıklığıyla değil, kullanılan özelliklerle orantılıdır; genel amaçlı bir güncelleme düzeneği taşınmaz.
Karşılığında bir derleme adımı, bir kaynak haritası zinciri ve girdi dili üzerinde bir kısıt gelir. Çözümleme sağlam kalmak zorunda olduğu için dinamik yapılar muhafazakâr bağımlılık üretir; dilin ifade gücü ile çıkarımın keskinliği arasında doğrudan bir ödünleşim vardır.
Bu aile ile önceki iki aile arasındaki sınır kesin değildir. Bağımlılığı çalışma zamanında izleyen çerçeveler de şablonlarını derler; sanal ağaç tutan çerçeveler de derleme zamanında değişmez alt ağaçları işaretleyip karşılaştırmadan çıkarabilir. Ayrım, kararın nerede verildiğinin ağırlığıdır.
Özet
- Bu ailede hangi ifadenin hangi değere bağlı olduğu, kod çalışmadan önce kaynak metinden çıkarılır ve sonuç doğrudan güncelleme kodu olarak üretilir.
- Üretilen kodda fark alma algoritması, abone kümesi ve ağaç temsili yoktur; alan başına hangi yuvaların yazılacağı sabittir.
- Durağan parçalar bir kez üretilir ve hiçbir güncellemede gezilmez.
- Çıkarım sağlam olmak zorundadır: dinamik erişim gibi belirsiz durumlarda araç muhafazakâr davranıp bütün alanlara bağımlılık üretir.
- Kısıtlı bir girdi dili, keskin çıkarımın koşuludur; kullanılmayan yolların paketten çıkarılması da aynı çözümlemeden gelir.
- Bedel, çalıştırılan kodun yazılan kod olmamasıdır: kaynak haritası zorunlu bir parçaya, aracın sürümü kaynak metnin bir bağımlılığına dönüşür.
Sonraki Adım
Üç aile de aynı soruya yanıt veriyordu: durum değiştiğinde belgenin neresi yazılacak. Bir uygulamanın bileşen katmanının üstünde ise başka sorular var — adresin hangi görünüme karşılık geldiği, formun doğrulama kurallarını nereden aldığı, bir servisin bileşene nasıl ulaştığı. Şimdiye kadar ele alınan ailelerde bu soruların yanıtı çerçevenin dışındadır ve her ekip kendi birleşimini kurar. Bir başka yaklaşım, bu yanıtları çerçevenin kendisiyle birlikte vermektir. Sonraki ders, yönlendirmeyi, form modelini ve bağımlılık çözümünü tek bir sözleşmede toplayan çerçeveleri ele alır ve bütünleşikliğin karar yüzeyini nasıl küçülttüğünü, buna karşılık neyi bağladığını sayarak gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.