Ders 06 / 27
Yapılandırma Dili
Kendi küçük yapılandırma dilimiz tanımlanır ve ayrıştırıcısı yazılır; dilin taşıdığı bildirim, ifade ve tip yapıları ayrı ayrı sayılır. Tip bildirimi olmayan kayıtta on bir kusurun kaçının ancak uygulamada göründüğü, tip eklenince kaçının ayrıştırma anında yakalandığı ve aynı hedefin üç yazımında satır sayısıyla okuma sıçramasının ters yönde hareket ettiği ölçülür.
İçindekiler
Bir önceki konu sapmayı sayılabilir kıldı; ölçülmeden kalan tek şey kaydın kendisiydi. Bu ders kendi
küçük yapılandırma dilimizi tanımlar, node ile bir ayrıştırıcı yazar ve üç şeyi ölçer:
dilin taşıdığı bildirim, ifade ve tip yapıları; tipsiz kayıt ile tipli kaydın aynı kusurları nerede
yakaladığı; aynı hedefin üç yazımının bedeli. Ölçüm bölgesel ölçüm ağının batı ortamı
üzerindedir ve kurgudur; dil, ayrıştırıcı ve hedef sistem node ile kurulmuş bir modeldir.
BS1. Kurgu kayıt batı ortamının iki kaynağını taşır: bir makine kümesi ve bir kuyruk. BS2. Tip bildirimi dilin yapısıdır, hedef sistemin değil; uygulamadan önce koşar. BS3. Tip denetimi ifadenin sonucunu hesaplamaz, yalnız sabit yazılmış değeri sınar. BS4. Hedef sistem modeli kayıttaki değeri olduğu gibi yazar, yalnız sayı beklenen yerde sayı olmayanı reddeder. BS5. Hedefe yazılan değer doğru kayıttakinden farklıysa ve hiçbir aşama bildirmiyorsa kusur sessiz sayılır. BS6. Sıçrama, bir alanın değerini bulmak için izlenen blok başvurusu sayısıdır.
Dilin Üç Yapısı
// dil.mjs — dilimizin sozcuk ayiricisi ve ayristiricisi. const TUR = ["dize", "sayi", "ad", "imce", "yorum", "bilinmeyen"]; const IM = /"([^"]*)"|(-?\d+)|([a-z_][a-z0-9_]*)|([{}=,.()])|(#[^\n]*)|(\S)/g; export function sozcukle(k) { const t = []; for (const m of k.matchAll(IM)) { const i = m.slice(1).findIndex((x) => x !== undefined); if (i === 5) throw new Error(`cozulemeyen im "${m[0]}"`); if (i !== 4) t.push({ tur: TUR[i], deger: m[i + 1] }); } return t; } export function ayristir(kaynak) { const t = sozcukle(kaynak); let i = 0; const bak = () => t[i], al = () => t[i++]; const bekle = (d) => { const x = al(); if (!x || x.deger !== d) throw new Error(`"${d}" bekleniyordu, "${x ? x.deger : "son"}" bulundu`); }; const govde = () => { const h = {}; // blok govdesi ve harita ayni yol while (bak().deger !== "}") { const a = al().deger; bekle("="); h[a] = ifade(); if (bak().deger === ",") al(); } al(); return h; }; function ifade() { const x = al(); if (!x) throw new Error("ifade bitmeden kayit bitti"); if (x.tur === "sayi") return { d: "sayi", deger: Number(x.deger) }; if (x.tur === "dize") return { d: "dize", deger: x.deger }; if (x.deger === "{") return { d: "harita", alanlar: govde() }; if (x.tur !== "ad") throw new Error(`beklenmeyen "${x.deger}"`); if (bak() && bak().deger === "(") { al(); const arg = []; while (bak().deger !== ")") { arg.push(ifade()); if (bak().deger === ",") al(); } al(); return { d: "cagri", ad: x.deger, arg }; } const yol = [x.deger]; while (bak() && bak().deger === ".") { al(); yol.push(al().deger); } return { d: "yol", yol }; } const b = []; while (i < t.length) { const tur = al().deger, etiket = []; while (bak() && bak().tur !== "imce") etiket.push(al().deger); bekle("{"); b.push({ tur, etiket, alanlar: govde(), anahtar: tur === "kaynak" ? etiket.join(".") : tur }); } return b; }
// kayit.mjs — bati ortaminin kurgu kaydi. export const ORTAM = `ortam "bati" { bolge = "bati" toplayici = 6 esik_ms = 900 saklama = 30 }`; export const GOVDE = ` kaynak makine "toplayici" { ad = birlestir(ortam.bolge, "-toplayici") sayi = ortam.toplayici cekirdek = 4 bellek_gb = 8 etiket = { gorev = "sayac-okuma", bolge = ortam.bolge } } kaynak kuyruk "sayac" { ad = birlestir(ortam.bolge, "-sayac-kuyrugu") bolme = carp(ortam.toplayici, 2) saklama_gn = ortam.saklama bicim = "ikili" esik_ms = ortam.esik_ms }`; export const TIP = ` tip ortam { bolge = dize toplayici = sayi esik_ms = sayi saklama = sayi } tip makine { ad = dize sayi = aralik(1, 64) cekirdek = aralik(1, 64) bellek_gb = aralik(1, 256) etiket = harita } tip kuyruk { ad = dize bolme = aralik(1, 128) saklama_gn = aralik(1, 365) bicim = kume("ikili", "metin") esik_ms = sayi }`;
Dilde tek bildirim biçimi vardır: tür sözcüğü, etiketler, ayraç içinde alan atamaları. Sağ taraf sabit, başka bir bloğun alanına başvuru, bir çağrı ya da bir harita olabilir; işleç yoktur, çarpma da bir çağrıdır. Ölçüyü taşıyan en küçük biçim budur. Tip bildirimi de aynı söz dizimiyle yazılır.
Tipsiz Kayıt, Tipli Kayıt
// denet.mjs — ad cozumu, tip denetimi, uygulama modeli. import { ayristir } from "./dil.mjs"; const ISLEV = { birlestir: (...a) => a.join(""), carp: (a, b) => a * b, sec: (h, k) => h[k] }; const alt = (e) => e.d === "cagri" ? e.arg : e.d === "harita" ? Object.values(e.alanlar) : []; export const yaz = (g, ...s) => console.log(s.map((x) => String(x).padEnd(g)).join("").trimEnd()); export const dugum = (e) => [e, ...alt(e).flatMap(dugum)]; export const derin = (e) => 1 + Math.max(0, ...alt(e).map(derin)); export const sabit = (e) => e.d === "sayi" || e.d === "dize"; export const cins = (b) => b.tur === "kaynak" ? b.etiket[0] : b.tur; export const dizinle = (b) => new Map(b.map((x) => [x.anahtar, x])); export const semaCikar = (m) => Object.fromEntries(ayristir(m).map((b) => [b.etiket[0], Object.fromEntries(Object.entries(b.alanlar).map(([a, e]) => [a, e.d === "yol" ? { taban: e.yol[0] } : e.ad === "aralik" ? { taban: "sayi", aralik: e.arg.map((x) => x.deger) } : { taban: "dize", kume: e.arg.map((x) => x.deger) }]))])); export function coz(yol, dizin) { for (let n = yol.length - 1; n >= 1; n--) { const b = dizin.get(yol.slice(0, n).join(".")), a = yol.slice(n).join("."); if (!b) continue; if (!(a in b.alanlar)) throw new Error(`${b.anahtar} icinde ${a} yok`); return { blok: b, alan: a }; } throw new Error(`${yol.join(".")} bir bloga denk gelmiyor`); } export function tipDenet(bloklar, sema) { // yalniz tipli kayitta kosar const h = []; for (const b of bloklar) { const t = sema[cins(b)]; for (const a of Object.keys(t)) if (!(a in b.alanlar)) h.push(`${b.anahtar}.${a} eksik`); for (const [a, e] of Object.entries(b.alanlar)) { const k = t[a], y = `${b.anahtar}.${a}`; if (!k) h.push(`${y} tipsiz`); else if (!sabit(e)) continue; // hesaplanan deger kayittan okunamaz else if (e.d !== k.taban) h.push(`${y} ${k.taban} degil`); else if (k.aralik && (e.deger < k.aralik[0] || e.deger > k.aralik[1])) h.push(`${y} aralik disi`); else if (k.kume && !k.kume.includes(e.deger)) h.push(`${y} kume disi`); } } return h; } export function degerle(e, d) { if (sabit(e)) return e.deger; if (e.d === "harita") return Object.fromEntries(Object.entries(e.alanlar).map(([a, v]) => [a, degerle(v, d)])); if (e.d === "cagri") return ISLEV[e.ad](...e.arg.map((x) => degerle(x, d))); const c = coz(e.yol, d); return degerle(c.blok.alanlar[c.alan], d); } export function uygula(bloklar, sema) { // hedef sistem modeli const d = dizinle(bloklar); return Object.fromEntries(bloklar.filter((x) => x.tur === "kaynak").flatMap((b) => Object.entries(b.alanlar).map(([a, e]) => { const v = degerle(e, d), y = `${b.anahtar}.${a}`; if (sema[cins(b)][a]?.taban === "sayi" && !Number.isFinite(v)) throw new Error(`${y} sayi degil`); return [y, typeof v === "object" ? JSON.stringify(v) : v]; }))); }
// hata.mjs — uc yapi ve on bir kusur sayilir. import { ayristir } from "./dil.mjs"; import { ORTAM, GOVDE, TIP } from "./kayit.mjs"; import { dugum, sabit, dizinle, coz, tipDenet, uygula, semaCikar, yaz } from "./denet.mjs"; const SEMA = semaCikar(TIP), KAYIT = ORTAM + GOVDE; const bloklar = ayristir(KAYIT), TABAN = uygula(bloklar, SEMA); const atama = bloklar.flatMap((x) => Object.values(x.alanlar)); const ifade = atama.flatMap(dugum), tipAlan = Object.values(SEMA).flatMap(Object.values); const say = (t) => `${ifade.filter((e) => e.d === t).length} ${t}`; console.log(`bildirim ${bloklar.length} blok, ${atama.length} atama, ${atama.filter((e) => !sabit(e)).length} hesaplanan`); console.log(`ifade ${ifade.length} dugum = ` + ["sayi", "dize", "yol", "cagri", "harita"].map(say).join(" + ")); console.log(`tip ${Object.keys(SEMA).length} cins, ${tipAlan.length} alan bildirimi, ` + `${tipAlan.filter((k) => k.aralik || k.kume).length} kisitli\n`); const boz = (alan, yeni) => yeni === null // tek satirlik kusur ? KAYIT.replace(new RegExp(`\n *${alan} *=[^\n]*`), "") : KAYIT.replace(new RegExp(`(\n *)${alan}( *=)[^\n]*`), yeni[0] === "@" ? `$1${yeni.slice(1)}` : `$1${alan}$2 ${yeni}`); const KUSUR = [ ["tur uyusmazligi", "cekirdek", '"4"'], ["alan eksik", "bellek_gb", null], ["alan adi yanlis", "saklama_gn", "@saklama = 30"], ["kume disi deger", "bicim", '"ham"'], ["aralik disi sabit", "cekirdek", "128"], ["basvuru yazimi", "sayi", "ortam.toplayic"], ["olmayan alan", "esik_ms", "makine.toplayici.cekirdekler"], ["soz dizimi", "bicim", 'ikili"'], ["yanlis alana basvuru", "sayi", "ortam.esik_ms"], ["hesaplanan asim", "saklama_gn", "carp(ortam.saklama, 20)"], ["hesaplanan sifir", "esik_ms", "carp(ortam.esik_ms, 0)"], ]; function asama(metin, tipVar) { // kusur hangi asamada goruluyor let b; try { b = ayristir(metin); const d = dizinle(b); for (const x of b) for (const e of Object.values(x.alanlar)) dugum(e).filter((y) => y.d === "yol").forEach((y) => coz(y.yol, d)); } catch (e) { return ["ayristirma", e.message]; } if (tipVar) { const t = tipDenet(b, SEMA); if (t.length) return ["tip", t[0]]; } let g; try { g = uygula(b, SEMA); } catch (e) { return ["uygulama: hata", e.message]; } const f = Object.keys(TABAN).filter((k) => TABAN[k] !== g[k]); return f.length ? ["uygulama: sessiz", `${f[0]} ${TABAN[f[0]]} yerine ${g[f[0]]}`] : ["gecti", "-"]; } console.log(`taban kayit ${asama(KAYIT, true)[0]}, hedefe yazilan ${Object.keys(TABAN).length} alan`); yaz(22, "kusur", "tipsiz kayit", "tipli kayit", "tipli kayitta uygulamadaki iz"); const sayac = { A: {}, B: {} }; for (const [ad, alan, yeni] of KUSUR) { const m = boz(alan, yeni), [a] = asama(m, false), [b, iz] = asama(m, true); sayac.A[a] = (sayac.A[a] || 0) + 1; sayac.B[b] = (sayac.B[b] || 0) + 1; yaz(22, ad, a, b, b[0] === "u" ? iz : ""); } console.log(); yaz(22, "asama", "tipsiz", "tipli"); for (const a of ["ayristirma", "tip", "uygulama: hata", "uygulama: sessiz"]) yaz(22, a, sayac.A[a] || 0, sayac.B[a] || 0);
bildirim 3 blok, 14 atama, 7 hesaplanan ifade 22 dugum = 6 sayi + 5 dize + 7 yol + 3 cagri + 1 harita tip 3 cins, 14 alan bildirimi, 6 kisitli taban kayit gecti, hedefe yazilan 10 alan kusur tipsiz kayit tipli kayit tipli kayitta uygulamadaki iz tur uyusmazligi uygulama: hata tip alan eksik uygulama: sessiz tip alan adi yanlis uygulama: sessiz tip kume disi deger uygulama: sessiz tip aralik disi sabit uygulama: sessiz tip basvuru yazimi ayristirma ayristirma olmayan alan ayristirma ayristirma soz dizimi ayristirma ayristirma yanlis alana basvuru uygulama: sessiz uygulama: sessiz makine.toplayici.sayi 6 yerine 900 hesaplanan asim uygulama: sessiz uygulama: sessiz kuyruk.sayac.saklama_gn 30 yerine 600 hesaplanan sifir uygulama: sessiz uygulama: sessiz kuyruk.sayac.esik_ms 900 yerine 0 asama tipsiz tipli ayristirma 3 3 tip 0 5 uygulama: hata 1 0 uygulama: sessiz 7 3
Üç blok on dört atama taşır; yedisi hesaplanan ifadedir, yirmi iki ifade düğümünün on biri sabittir. Tip tarafı üç cins için on dört alan bildirimi yazar — aynı cinsten on kaynak yazılsaydı aynı on dört bildirim yüz kırk atamayı denetlerdi. Tipin bedeli cins başına ödenir, kaynak başına değil.
Üç kusur tipten bağımsız görünür: bozuk söz dizimi, hiçbir bloğa denk gelmeyen başvuru, var olan bloğun olmayan alanı; bunlar için ad çözümü yeter. Kalan sekiz kusur tipsiz kayıtta uygulamaya iner ve yedisi hata vermeden hedefe farklı bir değer yazar. Tip bildirimi beşini ayrıştırma anına çeker; uygulamaya inen kusur üçe, sessiz kusur üçe iner. Kazanç mesafedir: ayrıştırmada yakalanan kusur hiçbir kaynağa dokunmaz.
Sona kalan üçünde değer sabit değil ifadedir: altı yerine dokuz yüz makine, otuz gün yerine altı yüz gün, dokuz yüz milisaniyelik eşik yerine sıfır. Tip üçünün de tabanını doğru bulur, çünkü hepsi sayıdır; aralığı sınayamaz, çünkü değer kayıt okunurken henüz yoktur. Kaydın kapsamadığı şey budur.
İfade Gücü ile Okunabilirlik
// bicem.mjs — ayni hedefin uc yazimi olculur. import { ayristir } from "./dil.mjs"; import { GOVDE, TIP } from "./kayit.mjs"; import { semaCikar, dugum, derin, sabit, coz, dizinle, cins, uygula, yaz } from "./denet.mjs"; const SEMA = semaCikar(TIP), O = ["bati", "dogu", "merkez"], ADET = { bati: 6, dogu: 6, merkez: 4 }; const ORTA = GOVDE.split("\n}\n")[0] + "\n}"; // kayittaki makine blogu, oldugu gibi const ortaOrtam = (o) => `ortam "${o}" { bolge = "${o}" toplayici = ${ADET[o]} }`; const sikiOrtam = (o) => `ortam "${o}" { bolge = "${o}" }`; const DUZ = ` kaynak makine "toplayici" { ad = "bati-toplayici" sayi = 6 cekirdek = 4 bellek_gb = 8 etiket = { gorev = "sayac-okuma", bolge = "bati" } }`; const SIKI = ` yerel "olcek" { taban = 2 kat = { bati = 3, dogu = 3, merkez = 2 } cekirdek = { bati = 4, dogu = 4, merkez = 4 } } kaynak makine "toplayici" { ad = birlestir(ortam.bolge, "-toplayici") sayi = carp(yerel.taban, sec(yerel.kat, ortam.bolge)) cekirdek = sec(yerel.cekirdek, ortam.bolge) bellek_gb = carp(yerel.taban, carp(sec(yerel.cekirdek, ortam.bolge), 1)) etiket = { gorev = "sayac-okuma", bolge = ortam.bolge } }`; const satir = (m) => m.split("\n").filter((s) => s.trim()).length; const sicrama = (e, d) => dugum(e).filter((x) => x.d === "yol") .reduce((t, x) => { const c = coz(x.yol, d); return t + 1 + sicrama(c.blok.alanlar[c.alan], d); }, 0); const es = (m) => JSON.stringify(uygula(ayristir(m), SEMA)); const olc = (ad, metinler, sat, per) => { const a = metinler.flatMap((m) => { const b = ayristir(m), d = dizinle(b); return b.filter((x) => x.tur === "kaynak").flatMap((k) => Object.entries(k.alanlar).map(([n, e]) => [derin(e), sicrama(e, d), (SEMA[cins(k)][n].aralik || SEMA[cins(k)][n].kume) && !sabit(e) ? 1 : 0])); }); const en = (i) => Math.max(...a.map((x) => x[i])); yaz(14, ad, sat, per, en(0), en(1), a.reduce((s, x) => s + x[2], 0)); }; console.log(`uc yazim ayni hedefi mi uretiyor: ${es(DUZ) === es(ortaOrtam("bati") + ORTA) && O.every((o) => es(sikiOrtam(o) + SIKI) === es(ortaOrtam(o) + ORTA)) ? "evet" : "hayir"}\n`); yaz(14, "yazim", "satir", "ortam basina", "en derin", "sicrama", "tip goremez"); olc("duz", [DUZ], satir(DUZ) * 3, satir(DUZ)); olc("orta", O.map((o) => ortaOrtam(o) + ORTA), satir(ORTA) + 3, 1); olc("siki", O.map((o) => sikiOrtam(o) + SIKI), satir(SIKI) + 3, 1);
uc yazim ayni hedefi mi uretiyor: evet yazim satir ortam basina en derin sicrama tip goremez duz 21 7 2 0 0 orta 10 1 2 1 3 siki 15 1 4 3 9
Üç yazım üç ortamda aynı on beş alanı aynı değerlerle üretir. Düz yazımda sıçrama yoktur ve tipin göremediği alan sıfırdır; bedeli üç ortam için yirmi bir satır ve her yeni ortam için yedi satırdır: on iki ortamda seksen dört.
Orta yazım aynı hedefi on satıra indirir ve yeni ortamın maliyetini bire düşürür; on iki ortamda on dokuz satır. Karşılığı bir sıçrama ve tipin göremediği üç alandır. Sıkı yazım ters yöne gider: satır sayısı on beşe çıkar, çünkü haritaları taşıyan ek blok da yazılmıştır; en derin ifade dörde, sıçrama üçe, tipin göremediği alan dokuza çıkar — on beş alanın dokuzu kayıt okunurken sınanamaz. İfade gücü tekrarı azaltır, satırı azaltmaz, denetlenebilirliği kesin olarak azaltır.
Özet
- Kurgu kayıt 3 blok, 14 atama, 22 ifade düğümü taşıdı; 7 atama hesaplanandı ve tip 3 cins için 14 alan bildirimi yazdı.
- Tipsiz kayıtta 11 kusurun 3’ü ayrıştırmada görüldü, 8’i uygulamaya indi; 7’si hata vermeden hedefe farklı değer yazdı.
- Tip bildirimi o 8’in 5’ini ayrıştırma anına çekti; uygulamaya inen kusur 3’e, sessiz kusur 3’e indi.
- Kalan 3 kusurun değeri ifadeydi; 6 yerine 900, 30 gün yerine 600 gün yazıldı, 900 ms eşik 0 oldu.
- Üç yazım 21, 10 ve 15 satır tuttu; sıçrama 0, 1 ve 3, tipin göremediği alan 0, 3 ve 9 oldu; yeni ortamın maliyeti 7 satıra karşı 1 satır.
Sonraki Adım
Buradaki hedef sistem beş satırlık bir modeldi. Gerçekte bir kaynağın yaratılması, okunması, güncellenmesi ve silinmesi ayrı davranışlardır ve bunları kayda bağlayan katmanın adı sağlayıcıdır. Bir sonraki ders sağlayıcıyı bir arayüz olarak modeller: hangi alan değişince kaynak yerinde güncellenemeyip yeniden oluşturuluyor, sağlayıcı sürümü değiştiğinde kaç alanın varsayılanı kayıyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.