saqut-compiler/docs/adr-frontend-analiz.md

683 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# saQut Derleyici — Frontend & Semantic Analiz Karar Kaydı (ADR-006 …)
> Bu belge, `docs/fikirler.md`'deki ADR-001…005'in **devamıdır**. Orada backend
> stratejisi, parser mimarisi, header-only tercihi, token sistemi ve IR tasarımı
> kararlaştırılmıştı. Bu belge ise **frontend'in tamamlanması** — symbol table,
> semantic analiz ve optimizasyon framework'ü — etrafında alınan kararları,
> **neden** alındıklarını, elenen alternatifleri ve gelecekteki sonuçlarını kaydeder.
>
> Bu kararlar bir tasarım oturumunda (kullanıcı + asistan) tartışılarak alındı.
> Tartışmanın tam akışı için bkz. `docs/transkript-frontend-tasarim.md`.
> Uygulama planı için bkz. `docs/roadmap-frontend.md`.
>
> ⚠️ **Yapılan vs planlanan:** Bu belgedeki ADR-006…019 **tasarım kararlarıdır**;
> tarif edilen makine (sembol tablosu, semantik analiz, tip sistemi, diagnostic,
> optimizasyon, IR+VM) **henüz kodlanmamıştır.** Bugün çalışan: lexer, tokenizer,
> Pratt parser, AST, AST'nin JSON serileştirmesi, CLI iskeleti, konum takibi ve
> basit aritmetiği düşüren minimal bir IR deneyi. Hiçbir ADR, var olmayan bir
> mekanizmayı varmış gibi anlatmaz.
---
## ADR-006: Çok-Aşamalı (Multi-Pass) Frontend Mimarisi
### Bağlam
Derleyici tek bir monolitik geçişle çalışmaz; lexing, parsing, analiz,
optimizasyon, kod üretimi gibi **birbirinden bağımsız aşamalardan** oluşur.
saQut'un "alet çantası" (toolbox) felsefesi gereği bu aşamaların her biri:
- bağımsız çalışabilmeli,
- net bir girdi/çıktı sözleşmesine sahip olmalı,
- gerektiğinde çoğaltılabilmeli (projenin amacına sadık kalarak).
CLI komutları (`tokens`, `ast`, `symbols`, …) zaten bu aşama-modülü
yapısının dışa vurumudur — her komut bir aşamanın çıktısını gösterir.
### Değerlendirilen Yaklaşımlar
#### Tek geçişli (single-pass) parser+analiz
- **+** Basit, hızlı.
- **** Forward reference (ileri başvuru) imkânsızlaşır; her şey tanımdan
önce bilinmek zorunda kalır.
- **** Analiz ve syntax iç içe girer, test edilemez, bakımı zor.
#### Çok geçişli (multi-pass) — net aşamalar
- **+** Her aşama tek bir iş yapar, ayrı ayrı test edilir.
- **+** Forward reference mümkün olur.
- **+** Aşamalar incelenebilir (`saqut ast`, `saqut symbols` …).
- **** Daha fazla kod ve veri yapısı; aşamalar arası sözleşme tasarımı gerekir.
### Karar
**Çok-aşamalı frontend.** Aşamalar şu üç katmana ayrılır (klasik derleyici
mimarisi):
```
FRONTEND MIDDLE-END BACKEND
lexer → token → optimizasyon IR lowering →
parser → AST → (opsiyonel, iteratif, bytecode VM (birincil)
symbol table → toggle'lı, ortak + ileride C transpile
semantic analiz gösterim üstünde) (makine kodu = uzak
(annotated AST) gelecek; ADR-015)
```
- **Pass 1 (Syntax):** token → ham AST. (Büyük ölçüde mevcut.)
- **Pass 2 (Symbol):** AST → SymbolTable (scope'lu, iki-geçişli — bkz. ADR-011).
- **Pass 3 (Semantic / "ASTyi derinleştir"):** symbol table + AST kullanılarak
her node zenginleştirilir (tip, symbol bağı, erişilebilirlik, reference sayısı).
"Parser ve symbol hikayesini bitirmek" = **frontend'i bitirmek.** Optimizasyon
ayrı bir katmandır (middle-end), backend'ler bu ortak çıktıdan beslenir.
**Neden bu katmanlama?** Birden çok backend (birincil: IR+bytecode VM, ADR-015;
ileride: C transpile; çok uzak: makine kodu) hedeflendiği için, ortak işler
(analiz, optimizasyon) **bir kez** ortak katmanda yapılmalı; yoksa her backend
aynı optimizasyonu yeniden yazar.
---
## ADR-007: Analiz (Annotation) ile Optimizasyon (Transformation) Ayrımı
### Bağlam
`docs/fikirler.md` ve `docs/todo.md`'nin temel prensibi: **"AST bellek canavarı.
Hiçbir bilgi atılmaz."** Aynı zamanda constant folding (`1+2` → `3`), dead code
elimination gibi optimizasyonlar isteniyor. Bu ikisi doğrudan çelişir gibi
görünür: optimizasyon AST'yi bozarsa, kaynak kodun izdüşümü kaybolur ve
`saqut ast` artık kullanıcının yazdığını değil, optimize edilmiş hali gösterir.
Ayrıca kritik bir kullanıcı gereksinimi belirlendi: **kullanıcı, AST'nin veya
sembol tablosunun optimizasyondan önceki ve sonraki halini ayrı ayrı görebilmeli.**
### İki Kavram
- **Analiz (annotation) = programın gerçekleri.** "Bu node sabit, değeri 3",
"bu kod erişilemez", "bu ifadenin tipi int", "bu değişken 2 kez kullanıldı".
Bunlar **değişiklik değil, tespittir.** Backend'den bağımsızdır.
- **Optimizasyon (transformation) = ağacı/IR'ı gerçekten değiştirmek.**
`1+2`'yi `3` ile değiştirmek, ölü kodu silmek.
### Karar
**İki kavram net ayrılır:**
1. **Analiz, orijinal AST'nin üstüne yerinde işaretleme yapar** (node'lara tip,
symbol bağı, erişilebilirlik, constness ekler). Ağacı **bozmaz**, zenginleştirir.
Orijinal AST hâlâ kaynak kodun tam izdüşümüdür.
2. **Optimizasyon dönüşümü, ağacın bir KOPYASI (klon) üzerinde yapılır.**
Orijinal analizli AST = "öncesi"; klon + dönüştürülmüş AST = "sonrası".
Ağaç klonlamak ucuz ve basittir, yalnızca `--optimized` istendiğinde yapılır.
**Sonuç:** Hem "bellek canavarı" felsefesi korunur (orijinal AST her şeyi tutar),
hem optimizasyon yapılır, hem de öncesi/sonrası ayrı ayrı incelenebilir.
```
saqut ast file.sqt → ham + annotate edilmiş AST (1+2 burada durur)
saqut ast file.sqt --optimized → klon, folding uygulanmış (3 var)
```
### Güncelleme — Klon maliyeti yük taşır (load-bearing)
İlk metin "ağaç klonlamak ucuz ve basittir" diyordu; bu **klon maliyetini hafife
alıyor** ve bir **tutarlılık (coherence) problemini** atlıyordu. Düzeltme:
`ASTNode::clone()` "belki gerekir" değil, **merkezi ve spesifiye edilmesi
zorunlu** bir bileşendir; tüm öncesi/sonrası hikâyesi ona dayanır (bkz. roadmap
Faz 4'te clone() yükseltildi).
**Klonlanırken karar verilmesi gereken iki nokta (açıkça belgele):**
1. **Parent pointer'lar yeniden bağlanmalı.** Klon node'larının `parent`'ı
orijinali değil, klonu göstermeli; yoksa yapısal doğrulama ve dönüşümler
yanlış ağaçta gezinir.
2. **`IdentifierNode → Symbol` bağları: paylaş mı, yeniden eşle mi?**
- **Paylaş** (klon ve orijinal aynı sembol tablosuna işaret eder): ucuz, ama
klonu optimize etmek orijinalin **referans sayımlarını bozar** (DCE klonda
bir kullanımı silince orijinalin Symbol ref-count'u da düşer).
- **Yeniden eşle** (klona ait bir sembol tablosu kopyası): doğru, ama ucuz
değil.
- **Karar:** `--optimized` istendiğinde sembol tablosu da **klonlanır ve
yeniden eşlenir** (remap). Doğruluk, ucuzluğa tercih edilir; klon zaten
yalnızca optimizasyon istendiğinde üretilir, sıcak yol değildir. "Ucuz"
iddiası kaldırıldı.
Bu, ADR-013'teki "ref-count Symbol'da yaşar" kararıyla tutarlıdır: ref-count
Symbol'da olduğu için, klonun kendi Symbol'larına sahip olması şarttır.
---
## ADR-008: Optimizasyon Konumu — AST mı, IR mı?
### Bağlam
"Optimizasyonu IR/derleme zamanında mı yapmalıyız, yoksa AST aşamasında mı?"
sorusu tartışıldı. İki uç yaklaşım var:
- **AST seviyesi:** kaynak-seviyesi, dile yakın, incelenebilir.
- **IR seviyesi:** açık kontrol-akış grafiği (CFG), dataflow analizi için uygun
(LLVM modeli).
### Karar
**Hibrit, optimizasyon türüne göre bölünür:**
- **Kaynak-seviyesi, ağaç-yerel optimizasyonlar** (constant folding, ölü kod
işaretleme, unused variable) → **AST'de** yapılır. Çünkü:
1. Dil JS gibi basit; ağır optimizasyona ihtiyaç yok.
2. Backend-bağımsız → bytecode VM ve ileride C transpile birden faydalanır.
3. İncelenebilir kalır (`saqut ast --optimized`) — projenin varlık sebebi.
- **CFG/dataflow gerektiren optimizasyonlar** ("bir kez atanıp bir kez
kullanılan değişken" = copy propagation, common subexpression elimination,
loop optimizasyonları) → **IR'de** yapılır, IR olgunlaşınca ertelenir.
Çünkü bunlar açık kontrol akışı ister, ağaçta yapmak işkencedir.
**Neden backend'e bırakmıyoruz?** 3 backend varsa, optimizasyon backend'e
konulursa 3 kez yazılır. Ortak katmanda (middle-end) bir kez yazılır.
---
## ADR-009: Optimizasyon Pass Yönetimi — Sabit Sayı Değil, Fixpoint
### Bağlam
Optimizasyon adımları birbirini tetikler: constant folding yeni ölü kod doğurur,
dead code elimination yeni kullanılmayan değişken doğurur. Tek geçişte zincirleme
fırsatlar kaçırılır. "5 pass mı, 10 pass mı çalıştıralım?" sorusu yanlış kurgu.
> **Not:** Buradaki "pass", ADR-006'daki derleyici aşamalarından (lexing/parsing
> gibi makro-aşamalar) farklıdır. Burada "pass" = tek bir optimizasyon adımının
> AST üzerindeki bir gezisidir.
### Karar
**Fixpoint döngüsü.** Önceden belirlenmiş sayıda değil; bir pass havuzu,
**hiçbir pass değişiklik yapmayana kadar** döngüde çalışır. Belki 2 tur sürer,
belki 7 — kodun kendisi belirler.
- Her **tur** bir öncekinden daha az iş yapar (giderek azalan değişiklik), ta ki
sıfır değişiklikle stabilize olana kadar.
- Pass'ler `CompilerConfig` ile tek tek açılıp kapatılabilir.
- `OptimizationManager` pass listesini tutar, sırayı ve fixpoint döngüsünü yönetir.
> Düzeltme notu: "Her pass bir öncekinden daha kolay" sezgisi yanlıştır. Doğrusu:
> her **tur** daha az değişiklik yapar. Analiz pass'leri (symbol table, type check)
> "kolaylaşmaz"; onlar bir kez çalışır.
### Güncelleme — Sonlanma değişmezi (termination invariant)
Fixpoint döngüsünün **sonlanacağı garanti edilmeli**. İki seçenekten en az biri
zorunludur:
1. **Monotonluk:** havuzdaki tüm pass'ler **monoton** olmalı — yalnızca
küçültür/sadeleştirir, asla büyütmez. Constant folding ve dead code
elimination bugün monotondur, dolayısıyla fixpoint sonlanır.
2. **Sert iterasyon tavanı (cap):** bir üst sınır (örn. `maxFixpointRounds`).
**Neden gerekli:** ileride **büyüten** pass'ler (inlining, loop unrolling)
eklenirse, naif fixpoint **salınabilir** (A büyütür, B küçültür, sonsuz döngü).
Büyüten bir pass eklendiği an, monotonluk bozulur ve **iterasyon tavanı zorunlu
hale gelir.** Bu değişmez şimdiden yazıya geçirildi ki ileride unutulmasın.
### Güncelleme — "Analiz bir kez çalışır" çelişkisinin çözümü
ADR-013 "analiz bir kez çalışır" diyor; ama folding **erişilebilirliği**
(`if(false)`) ve **referans sayımlarını** değiştirir, DCE de tam bunlara
dayanır. Eğer analiz gerçekten yalnızca bir kez çalışırsa, fixpoint'in ikinci
turundaki DCE **bayat (stale) veriyle** çalışır ve zincirleme fırsatları kaçırır.
**Çözüm — iki analiz sınıfını ayır:**
- **Kaynağa-bağlı analiz** (her ifadenin tipi, sembol bağları): kaynak değişmediği
sürece sabittir → **bir kez** çalışır, klona taşınır.
- **Türetilmiş/akışa-bağlı analiz** (erişilebilirlik `isReachable`, referans
sayıları): bir dönüşüm bunları geçersizleştirir → **fixpoint döngüsünün her
turunda, klon üzerinde yeniden hesaplanır.**
Yani "analiz bir kez çalışır" ifadesi yalnızca **kaynağa-bağlı** analiz için
geçerlidir; akışa-bağlı analiz tur başına tazelenir. ADR-013 buna göre okunmalı.
---
## ADR-010: Tip Sistemi Tasarımı
### Bağlam
Dil **tipli** olacak. Şu anda `varType`/`returnType` AST'de yalnızca
`std::string`. Tip kontrolünü string karşılaştırmasıyla yazmak kırılgandır ve
`int[]`, `struct Point`, fonksiyon tipi gelince baştan yazmayı gerektirir.
### Alınan Kararlar
**Minimal ama genişletilebilir `Type` sınıfı** (`src/core/type.hpp`):
- `kind`: `Primitive / Array / Struct / Function / Error`.
- Primitifler: `int, float, double, char, string, bool, void`.
- `Array` → eleman tipi (boyut tipin parçası DEĞİL — bkz. aşağı).
- `Function` → dönüş tipi + parametre tipleri.
- İleride `Pointer`, `Generic` eklenebilir.
**`Error` tipi şart.** Tip hatası olduğunda node'a `Error` atanır; böylece
ardışık sahte hatalar üretilmez (tek hata, tek mesaj).
**Gizli (implicit) dönüşüm YOK.** `int → float` otomatik olmaz; her şey açık.
- **Tek istisna:** sabit ifadelerde (constant folding) — `int a = 5 / 2;``2`.
Sabitler üzerinde küçük analiz/hesap yapılır.
**Tip çıkarımı (auto/var) YOK.** Her şey açıkça tiplenir. `auto` keyword'ü
yok sayılır. Sebep: basitlik, öngörülebilirlik, kafa karışıklığını önlemek.
**Array tip temsili: `int[]` (boyut tipte yok).** `int[]` sadece "int dizisi";
boyut tip eşitliğine girmez (JS gibi). Tip kontrolü basit kalır.
**Neden genişletilebilir?** "Bu dilin geleceğini bilmiyoruz; beklenenden popüler
de olabilir, yıllarca repolarda tozlanabilir de." Temel sağlam ve büyümeye açık
olmalı.
### Güncelleme — Sayısal literal tipleme kuralı
"Gizli dönüşüm yok + tip çıkarımı yok" altında `float x = 1;` ifadesi
**tanımsızdı**. Bu açıkça karara bağlanmalı, çünkü tip denetleyicisini (Faz 3)
doğrudan yönlendirir.
**Değerlendirilen iki kural:**
- **(a) Literal her zaman `int`:** `1` daima `int`'tir. `float x = 1;` bir tip
hatasıdır; `float x = 1.0;` yazmak zorunludur. En katı, en öngörülebilir; ama
rahatsız edici ve "gizli dönüşüm yok" ilkesini literallere kadar gereksiz yere
zorlar.
- **(b) Tamsayı literali bağlama-göre tiplenir (context-typed / polymorphic):**
tipsiz bir tamsayı sabiti, beklenen tip ona **kayıpsız** sığıyorsa o tipe
uyarlanır. `float x = 1;` çalışır (`1` → `1.0`); `int y = 1.5;` ise hata
(kayıp olur).
**Karar:****(b) Bağlama-göre tiplenen tamsayı literalleri.**
- Gerekçe: bu bir **değişken-değer dönüşümü değil, bir derleme-zamanı sabitinin
uygun tipte yorumlanmasıdır** — tam olarak ADR-010'un zaten tanıdığı "sabit
istisnası" (`int a = 5/2 → 2`) ruhuyla aynı kapıya çıkar. Çalışma zamanı
`int` değişkenini `float`'a gizlice çevirmek hâlâ **yasaktır**; istisna
yalnızca **literal/sabit** içindir.
- Kural net: *değişken→değişken* gizli dönüşüm yok; *literal→beklenen tip*
kayıpsızsa serbest. `float x = anInt;` hata; `float x = 1;` serbest.
---
## ADR-011: Scope ve Forward Reference Kuralları
### Bağlam
Dil "Java gibi forward reference, C gibi syntax, başta OOP yok" olarak
tasarlandı (JS yalnızca **syntax basitliği** örneği olarak verildi; JS'in kötü
yanları — null/undefined ikiliği, var hoisting — **alınmıyor**).
### "Hoisting" nedir?
Bir tanımın, yazıldığı satırdan **önce de** görünür olması (scope'un tepesine
"kaldırılmış" gibi).
### Karar
**Asimetrik kurallar (tam olarak Java'nın davranışı):**
- **Üst seviye (global): tam forward reference (hoisting var).** Fonksiyonlar,
global değişkenler, struct'lar sırasından bağımsız her yerde görünür.
```
int main() { return kare(5); } // kare aşağıda ama görünür → OK
int kare(int n) { return n * n; }
```
**Neden güvenli?** `main`'in gövdesi tanımlandığı anda çalışmaz; çağrılınca
çalışır, o ana kadar `kare` zaten vardır. Tanımların çalışma sırası yoktur.
- **Lokal (fonksiyon içi): declare-before-use (hoisting YOK).**
```
int main() {
int x = y + 1; // HATA: y henüz tanımlı değil
int y = 5;
}
```
**Neden?** Lokal değişkenin bir çalışma sırası ve değeri vardır; tanımdan önce
kullanmak, var olmayan/değeri olmayan bir şeyi kullanmaktır. Local hoisting
olsaydı isim olur ama değeri çöp/undefined olurdu (JS `var` derdi) — kaçınılan
durum.
**Asimetri tutarsızlık değildir:** global tanımlar yerinde çalışmaz (forward ref
güvenli), lokal değişkenlerin sırası ve değeri vardır (declare-before-use güvenli).
Bu, Java/C#'ın da davranışıdır.
**Duplicate kesinlikle yasak.** Aynı scope'ta aynı isimli iki
değişken/fonksiyon tanımlanamaz → diagnostic. (Overloading yok.)
**Shadowing serbest.** İç scope, dış scope'u gölgeleyebilir (hata değil).
**Scope oluşturan node'lar:** `Program` (global), `FunctionDecl` (parametreler),
`Block`, `for`/`while` (init değişkeni döngüye ait; döngü dışında görünmez).
Her katman bir namespace tutar; değişken bulunamazsa bir üst katmanda aranır.
### Symbol Table'ın İki Geçişi
**Sadece üst seviyede iki geçiş gerekir:**
- **Geçiş 1:** tüm üst-seviye tanımları (fonksiyon imzaları, struct isim+alanları,
global değişkenler) global scope'a hoist et.
- **Geçiş 2:** gövdelere in; lokal'leri declare-before-use ile topla, her
`Identifier`'ı çöz, reference ekle.
- **Fonksiyon içi tek geçiş yeter** (lokal'de forward ref yok). "Öncesi/sonrası"
derdi yalnızca global'ler içindir, onu da Geçiş 1 çözer (global'ler en baştan
tamamen doludur).
### Güncelleme — Global "tam forward reference" çok genişti: üç-parçalı kural
İlk metin "global = her zaman forward-reference güvenli" diyordu; bu **fazla
geniş**. Global bir değişkenin **başlatıcısının (initializer) bir çalışma
sırası vardır** (tıpkı lokaller gibi). Düzeltme — üç ayrı kural:
1. **Global fonksiyonlar / struct'lar → tam hoisting.** Tanım anında çalışmazlar,
sıradan bağımsız her yerde görünür. (Güvenli; değişmedi.)
2. **Global değişken isimleri → hoist edilir.** İsim her yerde görünür.
3. **Global değişken başlatıcıları → değer sırasına tabidir** (lokaller gibi) →
**declare-before-use** VEYA bir **definite-assignment (kesin-atama) analizi**
gerektirir.
**Neden:** `int a = b; int b = 5;` global scope'ta, isim-hoisting'e güvenilirse,
`a`'ya **sessizce çöp değer** verir — kaçınmaya çalıştığımız tam o JS `var`
durumu. Java da aynı sebeple bunu kısıtlar. Karar: global başlatıcılar için de
**declare-before-use** uygulanır (en basit, definite-assignment'a gerek
bırakmaz). Yani isim görünür ama **kendinden önceki** bir global başlatıcıda
kullanılabilir.
### Güncelleme — Döngüsel / karşılıklı-özyinelemeli struct tespiti
Pointer olmadığı için tüm struct iç içeliği **değer (by-value)** ile olur →
herhangi bir kapsama döngüsü sonsuz boyut demektir:
```
struct A { B b } // A, B'yi değer olarak içerir
struct B { A a } // B, A'yı değer olarak içerir → sonsuz boyut
```
Bu **derleme hatası olmak zorunda** ve hata kataloğunda **eksikti**. Eklendi:
**`E010` — özyinelemeli/döngüsel struct tanımı.** Symbol toplama sonrası bir
**topolojik / kapsama-döngüsü kontrolü** çalışır (struct'ları düğüm, "alan
olarak içerir" kenarını çevrim arayan bir DFS ile). Çevrim bulunursa `E010`.
(Karşılaştır: `struct A { B b }` + `struct B { int x }` geçerlidir; yalnızca
**çevrim** yasaktır. Pointer olsaydı çevrim mümkün olurdu — ama pointer yok.)
---
## ADR-012: ExpressionNode / StatementNode Ara Tabanları
### Bağlam
Şu anda tüm AST node'ları doğrudan `ASTNode`'dan türüyor; "ifade" (değer üreten)
ve "deyim" (iş yapan ama değer olmayan) ayrımı yok. Tipli bir dilde yalnızca
**ifadelerin** tipi vardır: `5 + 3` → int; `if (...) {...}` → tipi yok.
`resolvedType` alanını nereye koyacağımız bir tasarım kararı. Seçenekler:
- (a) `ASTNode` tabanına koy → her node'da olur, `if`/`while`'da boşa durur.
- (b) `ExpressionNode`/`StatementNode` ara tabanları → alanlar doğru yere oturur.
- (c) Yan-tablo `map<ASTNode*, Annotation>` → AST temiz ama dolaylı/karmaşık.
### Karar
**(b) İki ara taban eklenir:**
- `ExpressionNode : ASTNode``resolvedType`, `isConstant`, `foldedValue`.
- `StatementNode : ASTNode``isReachable` (ölü kod analizi için).
```
ASTNode
├─ ExpressionNode (resolvedType, isConstant, foldedValue)
│ ├─ LiteralNode / BinaryExpressionNode / IdentifierNode / CallExpressionNode …
└─ StatementNode (isReachable)
├─ IfStatementNode / WhileStatementNode / ReturnStatementNode / BlockNode …
```
**Kazanımlar:**
1. `resolvedType` yalnızca tip taşıyabilen node'larda olur.
2. Parser/analiz "burası ifade olmalı" diyebilir (örn. `if` koşulu bir
`ExpressionNode` olmalı, fonksiyon argümanı `ExpressionNode` olmalı).
**Önemli:** Bu karar, "her şey AST'de" felsefesini bozmaz (bkz. ADR-013); yalnızca
analiz alanlarını doğru node sınıflarına dağıtır. Node cpp dosyaları zaten boştu;
bu tabanlar onları doldururken ekleniyor. Maliyeti şimdi düşük, sonra yüksek
olurdu.
---
## ADR-013: Analiz Verisi Nerede Yaşar — Her Şey AST'de
### Bağlam
İki model: (1) her şey AST node'larının üstünde; (2) AST temiz, analiz sonuçları
ayrı yan-tablolarda.
- **Her şey AST'de:** tek doğruluk kaynağı, gezinmesi kolay (`node->type`),
kullanıcının zihinsel modeli, boş node class'larını doldurur. Ancak öncesi/
sonrası için ağacı klonlamak gerekir.
- **Temiz AST + yan-tablolar:** AST sade kalır, çoklu bağımsız analiz mümkün;
ancak dolaylılık ve karmaşıklık artar, "node class'larını doldur" isteğine ters.
### Karar
**Her şey AST node'larının üstünde** (kullanıcının modeli):
- **Analiz (tip, constness, erişilebilirlik) = node'lara yerinde işaretlenir.**
- **Optimizasyon dönüşümü = ağacın klonunda yapılır** (ADR-007), böylece
öncesi/sonrası korunur.
**Önemli ayrım — "kaç kez kullanıldı" bilgisi node'da değil, Symbol'da:**
- `IdentifierNode` → işaret ettiği `Symbol`'a pointer tutar.
- `Symbol` → o değişkenin tüm referanslarının listesini + sayısını tutar.
- `ExpressionNode` → kendi sonuç tipini, sabit olup olmadığını tutar.
Sebep: kullanım sayısı **değişkene** aittir, tek bir kullanım node'una değil.
> **Pointer notu:** Burada ve genel olarak derleyici **içinde** pointer serbestçe
> kullanılır (Symbol bağları, parent pointer'lar vb.). Kullanıcıya sunulan
> **dilde** pointer syntax'ı (`*`, `&`) yoktur — bkz. ADR-014.
---
## ADR-014: Dil Kapsamı ve Özellik Kararları
### Karar — Başlangıç Dili (v0)
| Özellik | Karar | Not |
|---|---|---|
| Pointer (kullanıcı syntax'ı `*`/`&`) | ❌ Yok | Ama derleyici/runtime **içeride** pointer'ı sonuna kadar kullanır |
| Tuple / Generic (`<T,U>`) | ❌ Yok | |
| Class / OOP / kalıtım | ❌ Yok (başta) | `class` keyword'ü yok sayılır |
| Closure | ❌ Yok | Bkz. ADR-019 (bellek bağımlılığı) |
| Struct | ✅ Var | `struct A { B bVar }` olur (B başka yerde tanımlı); **çevrim yasak → `E010`** |
| `interface` | ⏸️ Ertelendi | Reddedilmedi; v0 değil — gerekçe aşağıda + ADR-018 |
| Array | ✅ `int[]` | Dinamik yönde; runtime bellek modeli ertelendi |
| Fonksiyonlar | ✅ Tipli | Dönüş + parametre tipleri zorunlu |
| `auto` / tip çıkarımı | ❌ Yok | Her şey açık tipli |
| Gizli int↔float dönüşümü | ❌ Yok | Sadece sabit/literal folding'de istisna (ADR-010) |
### Dinamik Array'in Bellek Yükümlülüğü (Gelecek Notu)
`int[]` büyüyebilen array = heap + bir yönetim stratejisi gerektirir. Bu gerçek
bir yükümlülüktür ama **frontend'i bloklamaz** ve kolay yolu vardır:
- **Frontend:** yalnızca "bu int dizisi" bilgisini ister; bellek modelinden habersiz.
- **IR + bytecode VM (ilk çalıştırma modeli, ADR-015):** bellek host (C++)
heap'idir; array'ler host tarafında `std::vector` benzeri bir yapıyla tutulur.
VM, array işlemleri için **host fonksiyonlarına** (FFI seam, ADR-016) çağrı
yapar. v0 için özel allocator gerekmez.
- **C transpile backend (ileride, ikinci backend):** `int[]` → C'de
`struct {int* data; size_t len, cap;}`, `malloc/realloc/free` ile yönetilir.
- **Yönetim stratejisi (ne zaman free):** scope-tabanlı ownership (array'i tutan
değişken scope'tan çıkınca free). GC gerekmez — **neden gerekmediği aşağıda
gerekçelendirildi.**
### Güncelleme — Scope-tabanlı bellek artık GEREKÇELİ (bağımlılığı belgele)
Önceki kaygı ("scope çıkışında free, aliasing/escape altında bozulur") kilitli
dil kimliğiyle **lehte çözüldü:**
> prosedürel + value semantics + kullanıcı pointer'ı yok + closure yok + kaçan
> referans yok → array/string'ler tanımlandıkları scope'tan **kaçamaz** →
> scope-tabanlı ownership (scope çıkışında free) **gerçekten çalışır, GC
> gerekmez.**
**Bağımlılık açıkça yazılır:** scope-tabanlı bellek, *yalnızca* no-pointer /
value-semantics seçimi sayesinde geçerlidir. `interface` değerleri veya kaçan
referanslar eklenirse bu sorun **yeniden açılır**. (Bu, `interface`'i ertelemenin
ikinci sebebidir — bkz. ADR-018, ADR-019.)
---
## ADR-015: Çalıştırma Modeli — IR + Bytecode VM (Makine-Kodu JIT Kapsam Dışı)
### Bağlam
Daha önceki belge/konuşmalarda çalıştırma için "JIT" terimi geçiyordu. Hangi
çalıştırma modeli? Üç uç var: tree-walker, bytecode VM, gerçek makine-kodu JIT.
### Değerlendirilen Yaklaşımlar
- **Tree-walker (AST'yi doğrudan gez-çalıştır):** en basit, ama **çok yavaş**;
her çalıştırmada ağaç gezilir.
- **Makine-kodu JIT** (register allocation, ABI/çağırma sözleşmeleri,
çalıştırılabilir `mmap` bellek): en hızlı; ama **tek faydası ham hızdır**, ki
burada öncelik değil. Determinizmi ve incelenebilirliği zorlaştırır, devasa
mühendislik yükü getirir.
- **IR + bytecode VM** (kendi IR'imize derle, yorumlayıcı döngü ile çalıştır):
determinizm ve incelenebilirliği **doğrudan** sağlar; tree-walker'dan hızlı;
bellek host heap'iyle kolay.
### Karar
**IR + bytecode VM.** saQut kendi IR'sine derler ve bir yorumlayıcı döngüyle
çalıştırır.
-**Makine-kodu JIT kapsam dışıdır** (terminoloji düzeltmesi: "JIT" demeyi
bırak). Öncelikler **determinizm + incelenebilirlik** (toolbox), ham hız değil.
- **Bellek kolaydır:** host (C++) heap'i; özel runtime allocator yok (v0).
- **C'ye transpile, geçerli bir İKİNCİ backend olarak ileride kalır** (frontend
backend-bağımsız, ADR-006).
- İleride makine kodu **gerçekten** istenirse: elle code generator yazmak yerine
**libgccjit / LLVM'e bağlan** (ADR-001'deki QBE/custom değerlendirmeleri o gün
için geçerli). Bu **çok uzak gelecektir.**
---
## ADR-016: FFI Seam — Host Fonksiyon Çağırma Deliği
### Bağlam
`print` bile bir "dış dünya" çağrısıdır; VM tek başına ekrana yazamaz, host'tan
bir fonksiyon çağırmalıdır. Bu ihtiyaç ya **kaza eseri** tek bir özel-durum
olarak gömülür, ya da **kasıtlı bir mekanizma** olarak tasarlanır.
### Karar
**IR/runtime tasarımına bilinçli bir FFI seam konur:** "host fonksiyonu çağır"
için tek, genel bir IR mekanizması (örn. `callhost <id>, args...`).
- `print` bu seam'in **ilk müşterisidir**, özel-durum değil.
- İleride tüm "batteries" (bkz. ADR-017) bu sınır üzerinden gelir: sıkıştırma/
kripto C kütüphaneleri buraya bağlanır.
- **Neden şimdi:** seam'i sonradan eklemek IR ve VM'i baştan değiştirmeyi
gerektirir; deliği bir kez doğru açmak ucuzdur. Mekanizmayı **şimdi** doğru
tasarla, içini sonra doldur.
---
## ADR-017: Batteries / Stdlib — Sınır Problemi (Ertelendi)
### Bağlam
Gerçek bir genel sürüm pil ile gelmeli (sıralama, sıkıştırma, kripto,
JSON/XML/HTML, ileride runtime/donanım, ses/görüntü/video). JSON/string
ergonomisi olmayan bir dil benimsenmez — bu doğru. Ama korku: pilleri çekirdeğe
gömmek **monolit** yaratır.
### Karar
**Pil = sınır (boundary) problemi, "zlib'i yeniden yaz" problemi değil.**
- Çekirdek: **küçük bir gerçek builtin kümesi** (`print`, temel zorunlular) +
**gerisi kütüphane/FFI.**
- **JSON/XML/HTML ayrıştırıcıları saQut'ta yazılabilir** (string + struct +
fonksiyon + kontrol akışı yeter) — ilk "gerçek program" demoları.
- **Sıkıştırma/kripto:** denenmiş C kütüphanelerine **FFI** ile bağlan. **Kripto
asla elle yazılmaz.**
- **Bugüne tek yansıması:** FFI seam'i (ADR-016) bırak. Gerisi **v0 kapsamı
dışıdır.** Sınır bir kez çizilir, piller üstünde sonsuza dek birikir.
---
## ADR-018: `interface` Ertelemesi (Reddedilmedi)
### Bağlam
Kullanıcı struct'ın yanında `interface` de istedi (crypto, compression, custom
data types, JSON, string için). `interface` alınmalı mı, ne zaman?
### Karar
**Şimdi `struct`, `interface` ise ertelenir (reddedilmez).**
**Neden ertelendi:** `interface`, `struct`'tan kategorik olarak ağırdır.
- Struct yalnızca alan yerleşimidir (field layout).
- Interface "bu metotları sağlayan herhangi bir tip" demektir → çağrı yerinde
somut tip **bilinmez****dinamik dispatch** → vtable / fat pointer (içsel
pointer, izinli) → ve bir interface değeri **herhangi bir tipi tutabilir**, bu
da **kaçma/yaşam-süresi (escape/lifetime) problemini yeniden açar** (ADR-019).
- Go bunu fat pointer + GC ile çözer; saQut **GC istemiyor.**
Kullanıcının saydığı her şey (crypto, compression, custom data, JSON, string)
**yalnızca struct + fonksiyonla** yapılabilir (C bunu kanıtlar). Dolayısıyla:
şimdi struct'ı al, interface'i ertele.
**Metot-çağrı şekeri** (`list.push(5)` → `push(list, 5)`) ileride **parser
seviyesinde, sıfır semantik maliyetli** bir desugaring olarak eklenebilir —
şimdi değil.
---
## ADR-019: Frontend ↔ Runtime Sorumluluk Ayrımı
### Bağlam
"Hangi CPU çekirdeği, hangi cihaz, ne zaman tetiklenir, hangi çıktı formatı"
gibi sorular nereye ait? Frontend'e mi, runtime'a mı?
### Karar
**Net ayrım, frontend'i runtime kaygılarıyla yükleme:**
- **Frontend:** **yapı ve anlam** — tip, scope, dataflow. (Bu yol haritasının
konusu.)
- **Runtime/backend:** çekirdek/cihaz/tetikleme/çıktı formatı.
**Neden:** bu ayrım, kullanıcının önem verdiği **modülerliği korur**; frontend
backend-bağımsız kalır (ADR-006), böylece IR+VM ve ileride C-transpile aynı
frontend'den beslenir. Ayrıca **value-semantics + no-escape** kararının (kaçan
referans/closure yok) bağımlılığı buradadır: bu sayede scope-tabanlı bellek
çalışır (ADR-014). Closure veya interface değerleri eklemek bu ayrımı ve bellek
modelini birlikte zorlar — ikisi de bu yüzden ertelendi.
---
## Kararların Özet Tablosu
| ADR | Konu | Karar |
|---|---|---|
| 006 | Frontend mimarisi | Çok-aşamalı; frontend/middle-end/backend katmanları |
| 007 | Analiz vs optimizasyon | Analiz yerinde işaretler; optimizasyon klonda dönüştürür; `clone()` merkezi, sembol tablosu remap edilir |
| 008 | Optimizasyon konumu | Basitler AST'de, dataflow gerektirenler IR'de |
| 009 | Pass yönetimi | Fixpoint döngüsü, toggle'lı; monotonluk/iterasyon-tavanı değişmezi; akışa-bağlı analiz tur başına tazelenir |
| 010 | Tip sistemi | Minimal+genişletilebilir Type; gizli dönüşüm yok; Error tipi; tamsayı literali bağlama-göre tiplenir |
| 011 | Scope/forward ref | Global'de forward ref (fonksiyon/struct), ama global başlatıcı declare-before-use; lokal declare-before-use; döngüsel struct → `E010` |
| 012 | Node hiyerarşisi | ExpressionNode / StatementNode ara tabanları |
| 013 | Analiz verisi yeri | Her şey AST'de; ref-count Symbol'da |
| 014 | Dil kapsamı | Pointer/class/generic/closure yok; struct+array+tipli fonksiyon var; scope-tabanlı bellek gerekçeli |
| 015 | Çalıştırma modeli | IR + bytecode VM; makine-kodu JIT kapsam dışı |
| 016 | FFI seam | Kasıtlı "host fonksiyonu çağır" mekanizması; `print` ilk müşteri |
| 017 | Batteries/stdlib | Sınır problemi; küçük builtin + FFI/kütüphane; ertelendi |
| 018 | `interface` | Ertelendi (reddedilmedi); struct+fonksiyon yeter |
| 019 | Frontend↔runtime | Frontend yapı+anlam; çekirdek/cihaz/çıktı runtime'a ait |