# saQut Derleyici — Mimari Fikirler ve Karar Kaydı (ADR) > Bu belge, saQut derleyicisinin backend stratejisi, mimari kararları ve > gelecek yol haritası hakkında kapsamlı analizleri içerir. > Her kararın **neden** alındığı, alternatiflerin neden elendiği ve > gelecekte hangi koşullarda tekrar değerlendirileceği belirtilmiştir. --- ## ADR-001: Backend Stratejisi ### Bağlam saQut derleyicisi şu anda: - **Lexer**: Karakter seviyesinde tarama (src/lexer/lexer.hpp) - **Tokenizer**: Token üretimi, 6 token tipi, yorum satırı desteği (src/tokenizer/tokenizer.hpp) - **Parser**: Pratt parser ile ifade ayrıştırma + recursive descent ile statement ayrıştırma (src/parser/parser.hpp) - **AST**: Program, FunctionDecl, Block, değişken tanımlama, if/for/while/do-while/return, expression node'ları (src/parser/ast.hpp) - **IR**: Sadece temel matematik opcode'ları (mathadd/sub/mul/div) ve declare (src/ir/ir.hpp) Henüz çalışan bir backend yok. Kod üretimi (code generation) aşaması boş. ### Değerlendirilen Seçenekler #### 1. LLVM (Low Level Virtual Machine) **Nedir**: Derleyici altyapısı. C/C++/Rust/Swift gibi dillerin kullandığı endüstri standardı. **Artıları**: - Agresif optimizasyonlar (loop unrolling, inlining, vectorization, LTO) - Çok platformlu kod üretimi (x86, ARM, RISC-V, WebAssembly, GPU) - JIT ve AOT (Ahead-of-Time) derleme desteği - Olgun hata ayıklama bilgisi üretimi (DWARF, PDB) - Geniş araç zinciri (llc, opt, lld, clang) - GC (Garbage Collection) desteği için statepoint mekanizması **Eksileri**: - **Bağımlılık boyutu**: LLVM kütüphaneleri ~1GB+ disk alanı kaplar - **Derleme hızı**: LLVM'nin kendi derlenmesi dakikalar alır, link zamanı yavaştır - **Öğrenme eğrisi**: LLVM IR karmaşıktır, C++ API'si ağırdır - **Hata ayıklama zorluğu**: LLVM IR seviyesinde hata bulmak zordur - **Hafif projeler için aşırı**: saQut gibi deneysel bir derleyici için "sineği top ile vurmak" olur - **Build sistemi karmaşası**: LLVM'nin kendi build sistemi CMake ile entegre olur, proje yapısını domine eder **Karar**: ❌ Şimdilik kullanılmamalı. Deneysel aşamada çok ağır. Dil olgunlaştığında ve optimizasyon ihtiyacı somutlaştığında tekrar değerlendirilebilir. --- #### 2. GNU Lightning (JIT) **Nedir**: Anında makine kodu üreten hafif kütüphane. Register tabanlı, hedef mimariye göre kod üretir. **Artıları**: - Çok hafif (birkaç yüz KB) - Anında kod üretimi ve çalıştırma (JIT) - x86, ARM, MIPS, PowerPC gibi mimarilere kod üretebilir - Kod üretimi hızlıdır (optimizasyon yapmaz, direkt çeviri) - C API'si basit ve temiz **Eksileri**: - **Optimizasyon yok**: Constant folding, dead code elimination gibi temel optimizasyonlar bile yok - **Bakım durumu belirsiz**: Proje uzun süredir aktif geliştirilmiyor - **Sınırlı tip desteği**: Karmaşık veri tipleri ve struct'lar için manuel işlem gerekir - **Hata toleransı düşük**: Yanlış register kullanımı sessizce yanlış kod üretir - **Portability sorunları**: Her platformda aynı performansı vermez - **GC ve exception handling desteği yok** **Karar**: ⚠️ Prototip aşamasında kullanılabilir ancak üretim için uygun değil. --- #### 3. Sıfırdan Custom Backend (Go yaklaşımı) **Nedir**: Go dilinin yaptığı gibi, kendi kod üreticini yazmak. **Go'nun yaklaşımı**: - Go başlangıçta Plan 9 assembler'dan kendi assembler'ına geçti - Kendi register allocator, instruction selector ve optimizer'ını yazdı - Sonuç: LLVM bağımlılığı yok, hızlı derleme, tam kontrol - Go 1.21+ ile PGO (Profile-Guided Optimization) bile eklendi **Artıları**: - **Tam kontrol**: Her şeyi istediğin gibi tasarlayabilirsin - **Bağımlılık yok**: Dış kütüphane gerektirmez - **Hızlı derleme**: Optimizasyon seviyesini sen belirlersin - **Dil ile entegrasyon**: saQut diline özel optimizasyonlar yapabilirsin - **Öğrenme değeri**: Derleyicinin her katmanını anlarsın **Eksileri**: - **Çok iş**: Register allocation, instruction selection, calling convention, stack frame yönetimi, peephole optimization... hepsini sıfırdan yazmak aylar sürer - **Platform bağımlılığı**: Her hedef mimari için ayrı kod üretici gerekir - **Optimizasyon kalitesi**: LLVM seviyesinde optimizasyon yapmak yıllar alır - **Bakım yükü**: Tüm backend hataları senin sorumluluğunda **Karar**: ✅ **Önerilen uzun vadeli strateji**. Aşamalı olarak inşa edilmeli: 1. Aşama: C koduna transpile et (hızlı prototip, hemen çalışır) 2. Aşama: Basit bir register allocator + x86-64 kod üretici 3. Aşama: Orta seviye optimizasyonlar ekle 4. Aşama: ARM64 desteği ekle --- #### 4. QBE (Quick Backend) **Nedir**: LLVM'den 10 kat daha hızlı, hafif bir derleyici backend'i. cproc, harecc gibi C derleyicileri tarafından kullanılır. **Artıları**: - LLVM'den çok daha hafif (birkaç MB) - Hızlı kod üretimi (LLVM'den ~10x) - Makul optimizasyonlar (register allocation, copy propagation, memory folding) - x86-64 ve ARM64 desteği - Basit SSA-tabanlı IR **Eksileri**: - C'de yazılmış, FFI gerektirir - Optimizasyonlar LLVM kadar agresif değil - Dokümantasyon İngilizce, küçük topluluk - 32-bit ve RISC-V desteği deneysel - Hata ayıklama bilgisi (DWARF) desteği yok **Karar**: ✅ **Orta vadede en iyi seçenek**. Custom backend yazılana kadar QBE ideal bir ara çözüm. --- #### 5. Cranelift (WebAssembly odaklı) **Nedir**: Bytecode Alliance tarafından geliştirilen, Rust'ta yazılmış JIT/AOT derleyici backend'i. Wasmtime'ın JIT motoru. **Artıları**: - Hızlı JIT derlemesi - x86-64, ARM64, RISC-V64 desteği - Güvenlik odaklı (memory safety, sandboxing) - Modern mimari (SSA, e-graphs) **Eksileri**: - Rust'ta yazılmış, C++ projesine entegrasyon zor - WebAssembly odaklı, native diller için ikincil öncelik - Dokümantasyon sınırlı, hızlı değişiyor - Optimizasyonlar LLVM kadar agresif değil **Karar**: ❌ saQut gibi C++ tabanlı bir proje için uygun değil. --- #### 6. C Koduna Transpile Etme **Nedir**: AST'yi doğrudan C kaynak koduna çevirip GCC/Clang ile derlemek. **Artıları**: - **En hızlı prototip yolu**: Hemen çalışan bir sistem - GCC/Clang optimizasyonlarından bedava faydalanma - Hata ayıklama kolay (üretilen C kodunu okuyabilirsin) - Her platformda çalışır (C derleyicisi olan her yerde) **Eksileri**: - İki aşamalı derleme (yavaş) - saQut'a özgü optimizasyonlar kaybolabilir - Debug bilgisi orijinal kaynak koda değil, üretilen C koduna işaret eder - Dil özellikleri C'nin sınırları içinde kalır - Hata mesajları C derleyicisinden gelir, anlaşılması zor **Karar**: ✅ **Birinci aşama için ideal**. Hemen çalışan bir sistem kurup, sonra native backend'e geçiş yapılabilir. --- ### Nihai Karar ve Yol Haritası ``` ┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ Aşama 1 │────▶│ Aşama 2 │────▶│ Aşama 3 │ │ C Transpile│ │ QBE Backend │ │ Custom Backend │ │ (hemen) │ │ (orta vade) │ │ (uzun vade) │ └─────────────┘ └──────────────────┘ └─────────────────┘ 1-2 hafta 2-4 hafta 2-6 ay ``` **Aşama 1 — C Transpile**: Hemen başlanabilir. Mevcut AST ve IR'yi C koduna çevirip GCC ile derlemek. Bu sayede: - Dilin semantiği test edilebilir - Gerçek programlar çalıştırılabilir - Backend baskısı olmadan dil geliştirmeye devam edilebilir **Aşama 2 — QBE**: Dil yeterince olgunlaştığında, QBE ile native kod üretimi: - C derleyicisi bağımlılığı kalkar - Derleme hızı artar - Temel optimizasyonlar QBE tarafından yapılır **Aşama 3 — Custom Backend**: Dil tamamen stabilize olduğunda: - Tam kontrol - saQut'a özgü optimizasyonlar - Minimum bağımlılık --- ## ADR-002: Parser Mimarisi — Neden Pratt? ### Bağlam C/C++/Java gibi diller genellikle **recursive descent** veya **LALR(1) parser** (yacc/bison) ile ifade ayrıştırması yapar. saQut için hangi yaklaşım seçilmeli? ### Değerlendirilen Seçenekler #### Recursive Descent (elle yazılmış) - **+** Basit, okunabilir, hata mesajları kontrol edilebilir - **+** Java/C# benzeri dillerde yaygın - **−** Operatör önceliğini yönetmek için çok sayıda fonksiyon gerekir (parseAddExpr, parseMulExpr, parseUnaryExpr...) - **−** Yeni operatör eklemek zor #### Pratt Parser (Top-Down Operator Precedence) - **+** Operatör önceliğini merkezi bir tabloda yönetir - **+** Yeni operatör eklemek tek satır (tabloya ekle + NUD/LED yaz) - **+** Kod tekrarı yok, single source of truth - **+** Hem prefix hem infix hem postfix operatörleri aynı çerçevede işler - **−** Recursive descent kadar yaygın bilinmez - **−** İlk bakışta anlaşılması zor gelebilir #### LALR(1) / Parser Generator - **+** Gramer tanımı net - **−** Hata mesajları anlaşılmaz - **−** Shift/reduce conflict'leri ile uğraşmak zaman kaybı - **−** Generated code okunamaz, debug zor ### Karar ✅ **Pratt Parser** seçildi çünkü: 1. saQut'un operatör seti geniş ve büyüyebilir (ileride `|>`, `?.`, `??` gibi özel operatörler eklenebilir) 2. Operatör önceliğini merkezi bir tabloda (TokenPrecedence) yönetmek, kod tekrarını önler 3. Hem ifadeler hem prefix/postfix operatörler aynı çerçevede işlenir 4. Recursive descent'in statement tarafı için kullanılması, Pratt'in ifade tarafı için kullanılması hibrit bir yaklaşım sunar (en iyi iki dünya) --- ## ADR-003: Neden Header-Only? ### Bağlam saQut derleyicisi tüm `.hpp` dosyalarında hem tanım (declaration) hem gerçekleme (implementation) içerir. Geleneksel C++ projelerinde `.hpp` + `.cpp` ayrımı yapılır. ### Değerlendirme **Header-only avantajları**: - Tek dosya = tek gerçeklik. Tanım ve gerçekleme arasında senkronizasyon sorunu olmaz - `inline` anahtar kelimesi ile ODR (One Definition Rule) ihlali önlenir - Derleme süreci basit: tek bir `.cpp` dosyası (main.cpp) her şeyi include eder - Dağıtım kolay: Tüm derleyici tek bir header koleksiyonu **Header-only dezavantajları**: - Tüm kod her yerde görünür (ama zaten açık kaynak) - Büyük projelerde derleme süresi uzayabilir - Circular dependency riski (ama include guard'lar ile yönetiliyor) ### Karar ✅ **Header-only** devam ediyor. saQut şu anda küçük bir proje ve bu yaklaşım: 1. Kodun anlaşılmasını kolaylaştırır (dosyalar arası atlama yok) 2. Build sistemini basitleştirir 3. Hızlı iterasyon sağlar Gelecekte proje çok büyürse (100K+ satır), `.hpp` + `.cpp` ayrımına geçilebilir. --- ## ADR-004: Token Sistemi — Neden Polymorphic Token Sınıfları? ### Bağlam Tokenizer farklı token tipleri için farklı veri alanlarına ihtiyaç duyar: - NumberToken: `isFloat`, `hasEpsilon`, `base` - StringToken: `context`, `size` - IdentifierToken: `context`, `size` İki yaklaşım var: 1. **Tagged union**: Tek bir Token struct'ı, içinde `union` veya `std::variant` 2. **Class hierarchy**: Base Token sınıfı, her tip için alt sınıf ### Karar ✅ **Class hierarchy** seçildi çünkü: 1. C++'ta doğal ve yaygın bir pattern 2. Yeni token tipi eklemek kolay (yeni sınıf türet) 3. Tip güvenliği: `dynamic_cast` veya `gettype()` string karşılaştırması ile tip kontrolü 4. Bellek yönetimi açık: heap'te `new` ile oluşturulup pointer olarak saklanıyor ⚠️ **Bilinen sorun**: `ParserToken` yapısı eskiden `Token token` (değer kopyası) tutuyordu, bu object slicing'e neden oluyordu (alt sınıf verileri kayboluyordu). `commit 40579ca` ile `Token* token` pointer'a geçildi. --- ## ADR-005: IR Tasarımı ### Bağlam Mevcut IR (src/ir/ir.hpp) sadece 5 opcode içeriyor: `declare`, `mathadd`, `mathsub`, `mathmul`, `mathdiv`. Bu bir "virtual register" IR'si — her işlem yeni bir sanal register'a yazılır. ### Mevcut Durum ``` OPCode: declare, mathadd, mathsub, mathmul, mathdiv Param: {isRegister: bool, value: variant} IROpData: {op: OPCode, targetReg: int, arg1-3: Param} ``` ### Eksikler (TODO) - [ ] Kontrol akışı: `branch`, `jump`, `compare` - [ ] Fonksiyon çağrısı: `call`, `ret` - [ ] Bellek: `load`, `store`, `alloc` - [ ] Tip bilgisi: IR opcode'ları tipleri taşımıyor - [ ] Debug bilgisi: Kaynak satır eşlemesi yok ### Gelecek Yön IR'nin iki katmanlı olması planlanıyor: - **HeavyIR**: Debug bilgisi, tip bilgisi, değişken isimleri içeren zengin IR (interpreter/debug için) - **LightIR**: Sadece çalıştırma için gerekli minimum IR (JIT/compiler için) --- ## Performans Karşılaştırması: JIT vs AOT | Kriter | JIT (Lightning/Custom) | AOT (LLVM/QBE/Custom) | Transpile (C) | |---|---|---|---| | İlk derleme hızı | ⚡ Çok hızlı (mikrosaniye) | 🐢 Yavaş (saniye) | 🐢 Orta | | Çalışma hızı | 🐢 Optimizasyonsuz | ⚡ Yüksek optimizasyon | ⚡ GCC/Clang seviyesi | | Bellek kullanımı | ✅ Düşük | ⚠️ Yüksek (LLVM) | ✅ Derleme anında yok | | Debug kolaylığı | ⚠️ Makine kodu seviyesi | ✅ Kaynak eşlemesi var | ⚠️ C kodu üzerinden | | Platform bağımsızlığı | ⚠️ Her mimariye özel | ✅ LLVM her yerde | ✅ C her yerde | | Geliştirme süresi | ⚡ Kısa (Lightning ile) | 🐢 Uzun (LLVM öğrenme) | ⚡ En kısa | ### Sonuç **Prototip için**: C transpile > QBE > JIT **Üretim için**: Custom backend > QBE > LLVM **Dinamik kod (REPL) için**: JIT (Lightning veya custom) --- ## Gelecek Özellikler (Roadmap) ### Kısa Vade (1-4 hafta) - [ ] C koduna transpile (Aşama 1 backend) - [ ] Tip kontrolü (symbol table) - [ ] Fonksiyon parametreleri - [ ] else-if zincirleri - [ ] Mantıksal operatörler (&&, ||) kısa devre değerlendirmesi ### Orta Vade (1-3 ay) - [ ] QBE backend entegrasyonu - [ ] Array/dizi desteği - [ ] Struct/record tipleri - [ ] Import/include sistemi - [ ] Hata mesajlarında kaynak satır gösterimi - [ ] Basit optimizasyonlar (constant folding, dead code elimination) ### Uzun Vade (3-12 ay) - [ ] Custom native backend - [ ] Interpreter modu (REPL) - [ ] Debugger desteği (DWARF) - [ ] Package yöneticisi - [ ] LSP sunucusu (IDE desteği) - [ ] Kendi kendini derleyebilme (self-hosting) --- ## Mimari Prensipler 1. **Tek sorumluluk**: Her dosya/class tek bir iş yapar - Lexer: Karakter → sayı/konum - Tokenizer: Lexer → Token - Parser: Token → AST - IR Generator: AST → IR - (Gelecek) Code Generator: IR → Makine kodu / C kodu 2. **Bağımlılık yönü**: Tek yönlü ``` Lexer ← Tokenizer ← ParserToken ← AST ← Parser ← IR ``` 3. **Test edilebilirlik**: Her katman bağımsız test edilebilir - Lexer: `scan("42")` → `INumber{42, base=10}` - Tokenizer: `scan("1+2")` → `[NumberToken, OperatorToken, NumberToken]` - Parser: `parse(tokens)` → `ASTNode*` - IR: `parse(ast)` → `vector` 4. **Hata toleransı**: Parser mümkün olduğunca ilerlemeye çalışır, ilk hatada durmaz (ileride panic mode eklenecek) 5. **Kademeli geliştirme**: Her aşamada çalışan bir sistem. "Big bang" entegrasyon yok.