14 KiB
saQut Compiler Toolbox — Yol Haritası ve Yapılacaklar
Felsefe: saQut bir derleyici değil, bir derleyici alet çantasıdır. Amacı en hızlı kodu üretmek, en güvenli sandbox'u kurmak veya en optimize binary'yi çıkarmak değildir. Amacı, derleyicinin kendisidir.
Her aşama (Lexer, Parser, Optimizer, Compiler) birbirinden bağımsız, kendi parametreleriyle yönlendirilebilen, gerektiğinde devre dışı bırakılabilen birer araç kutusu modülü olarak tasarlanır.
Geliştirici, isterse sadece token listesini alır, isterse AST'yi JSON olarak çeker, isterse sembol tablosunu inceler, isterse kodu çalıştırır. Bu, ileride LSP sunucusu yazmayı, IDE eklentileri geliştirmeyi ve kod hiyerarşisi görselleştirmeyi mümkün kılar.
Temel Prensipler
-
Her aşama bağımsız: Lexer → Tokenizer → Parser → Optimizer → IR → Backend. Her biri tek başına çalışabilir, kendi parametrelerini alır.
-
Her şey metadata: Her token ve AST düğümü; dosya adı, satır, sütun, karakter offset'i taşır. Debug ve hata mesajları bu veri üzerine kurulur.
-
AST bellek canavarı: Token'lar AST'de yaşar. Kaynak kodun neredeyse birebir izdüşümü AST üzerinde korunur. Hiçbir bilgi atılmaz.
-
Sembol tablosu aynı felsefede: Basit yapı, çok veri. Her sembolün tanımlandığı yer, tipi, kullanıldığı tüm noktalar, scope bilgisi.
-
Feature toggle: Her keyword, her operatör, her optimizasyon adımı açılıp kapatılabilir.
--disable-while,--skip-constant-folding. -
Çoklu çıktı formatı: Düz metin, JSON, (ileride) HTML/SVG. Tüm ara gösterimler dışa aktarılabilir.
-
IR merkezli backend: Tüm backend'ler (C transpile, QBE, LLVM, JIT) aynı IR'den beslenir. Yeni backend eklemek = yeni bir IR yürüteci yazmak.
Aşama 0: Metadata ve Konum Takibi
Hedef: Her token ve AST düğümü nereden geldiğini bilsin.
0.1 SourceFile Yöneticisi
SourceFilesınıfı: dosya adı, tam metin, satır başı offset'leriSourceLocationyapısı:{file, line, column, offset}offset → (line, column)dönüşümü (binary search ile O(log n))- Çoklu dosya desteği (import/include için temel)
0.2 Lexer'a Konum Ekleme
- Lexer her
nextChar()'da satır/sütun takibi yapsın getLocation()→ mevcut konumuSourceLocationolarak döndürsünINumberyapısınaSourceLocation startLoc, endLoceklensin
0.3 Token'lara Konum Ekleme
Tokentemel sınıfınaSourceLocation loceklensin- Her token oluşturulurken konumu kaydedilsin
start/endoffset'leriSourceLocationile değiştirilsin
0.4 AST Düğümlerine Konum Ekleme
ASTNodetemel sınıfınaSourceLocation loceklensin- Her düğüm oluşturulurken ilgili token'ın konumu kopyalansın
Aşama 1: Interpreter (CLI + Dosya)
Hedef:
saqutkomutu ile REPL veya dosya çalıştırma.
1.1 CLI Altyapısı
argc/argvile komut satırı argümanları./saqut→ REPL modu (etkileşimli)./saqut file.sqt→ dosyayı çalıştır./saqut --mode=parse file.sqt→ sadece AST üret./saqut --mode=tokens file.sqt→ sadece token listesi./saqut --mode=ir file.sqt→ sadece IR./saqut --mode=symbols file.sqt→ sembol tablosu./saqut --format=json file.sqt→ JSON çıktı
1.2 REPL Modu
>komut istemi- Her satır ayrı ayrı parse edilir
.astkomutu → son ifadenin AST'sini göster.tokenskomutu → son ifadenin token'larını göster.symbolskomutu → şu ana kadarki sembolleri göster.exit/.quit→ çıkış- Çok satırlı giriş (bloklar için)
1.3 Dosya Modu
- Kaynak dosyayı oku → pipeline'ı çalıştır
- Çıktıyı seçilen formatta bas
-o output.json→ dosyaya yaz
Aşama 2: AST'yi Bellek Canavarı Yapma
Hedef: AST, kaynak kodun tam bir izdüşümü olsun. Hiçbir bilgi kaybolmasın. Token'lar AST'de yaşasın.
2.1 Token Tutma
- AST düğümleri token'ları sahiplensin (unique_ptr veya shared_ptr)
LiteralNode,IdentifierNode→ ilgili token'ı doğrudan tutarBinaryExpressionNode→ operatör token'ını tutar- Her statement → ilgili keyword token'ını tutar (
iftoken'ı,fortoken'ı)
2.2 Kaynak Kod Parçası Erişimi
ASTNode::getSourceText()→ bu düğümün kaynak koddaki ham metniASTNode::getSourceRange()→ başlangıç/bitiş SourceLocation- Hata mesajlarında kaynak kod parçası gösterimi:
Hata: test.sqt:5:10: 'x' değişkeni tanımlı değil 4 | int main() { 5 | y = x + 1; | ^ 6 | }
2.3 AST Görselleştirme
--format=json→ tam AST'yi JSON olarak dök--format=dot→ Graphviz DOT formatı (görsel ağaç)--format=html→ Etkileşimli HTML ağaç görünümü (ileride)- JSON çıktısı her düğüm için:
{ "kind": "BinaryExpression", "operator": "PLUS", "location": { "file": "test.sqt", "line": 5, "column": 9 }, "sourceText": "x + 1", "left": { ... }, "right": { ... } }
Aşama 3: Sembol Tablosu
Hedef: AST kadar zengin, basit yapılı bir sembol tablosu. Her sembolün tüm kullanım noktaları, tip bilgisi, scope'u kayıtlı.
3.1 Symbol Sınıfı
Symbolyapısı:name: sembol adıkind: variable, function, parameter, type, ...type: tip bilgisi (şimdilik string, ileride Type sistemi)definitionLoc: tanımlandığı SourceLocationreferences: kullanıldığı tüm SourceLocation'ların listesiscope: ait olduğu scope (global, function, block)metadata: opsiyonel anahtar-değer çiftleri
3.2 SymbolTable Sınıfı
- İç içe scope desteği (global → function → block)
enterScope()/exitScope()define(name, kind, type, location)→ yeni sembol ekleresolve(name)→ en yakın scope'tan başlayarak araaddReference(name, location)→ kullanım noktası eklegetAllSymbols()→ tüm scope'lardaki tüm semboller (düz liste)- JSON'a serialize edilebilme
3.3 Sembol Toplama (AST Walker)
SymbolCollectorsınıfı: AST'yi dolaşır, sembolleri toplar- Değişken tanımlarını yakala →
define() - Değişken kullanımlarını yakala →
addReference() - Fonksiyon tanımlarını yakala
- Hata durumları: tanımsız değişken, çift tanım, tip uyuşmazlığı
Aşama 4: Feature Toggle Sistemi
Hedef: Derleyicinin her özelliği açılıp kapatılabilir olsun.
4.1 CompilerConfig Yapısı
CompilerConfigstruct'ı:struct CompilerConfig { // Dil özellikleri bool enableWhile = true; bool enableFor = true; bool enableDoWhile = true; bool enableSwitch = true; bool enableClass = false; // henüz yok bool enableInterface = false; bool enableEnum = false; // Operatörler bool enableTernary = true; bool enablePostfix = true; bool enableUnary = true; // Optimizasyon adımları bool optConstantFolding = false; bool optDeadCodeElim = false; bool optUnusedVariable = false; // Çıktı std::string outputFormat = "text"; // text, json std::string mode = "run"; // tokens, ast, ir, symbols, run std::string inputFile; std::string outputFile; };
4.2 Keyword Kontrolü
- Tokenizer, config'te kapalı keyword'leri identifier olarak tanısın
- Parser, kapalı keyword'ler için hata değil, görmezden gelsin
--disable-while→whileartık bir keyword değil
4.3 Optimizasyon Kontrolü
- Her optimizasyon adımı bir
OptimizationPasssoyut sınıfı OptimizationManager: config'e göre pas'ları çalıştırır--skip-constant-folding→ o adım atlanır--opt-all→ tüm optimizasyonlar aktif--opt-none→ hiçbiri aktif değil
Aşama 5: Çoklu Backend Altyapısı (IR Merkezli)
Hedef: Tüm backend'ler aynı IR'den beslenir. Yeni backend eklemek = yeni bir
IRRunneryazmak.
5.1 IR'yi Güçlendirme
- Kontrol akışı:
br,jmp,cmp,br_eq,br_lt,br_gt - Fonksiyon:
call,ret,param - Bellek:
load,store,alloca - Tip bilgisi: her IR komutu tipleri taşısın
- Debug bilgisi: kaynak satır eşlemesi
IRModulesınıfı: fonksiyon listesi, global değişkenler, IR komutları
5.2 Backend Arayüzü
IBackendsoyut sınıfı:class IBackend { public: virtual bool initialize(CompilerConfig& config) = 0; virtual bool compile(IRModule& ir) = 0; virtual bool run() = 0; virtual std::string getOutput() = 0; virtual ~IBackend() = default; };
5.3 C Transpile Backend (Aşama 1)
- IR → C kaynak kodu dönüşümü
- Değişken tanımları, if/for/while, fonksiyonlar
- GCC/Clang ile derleme ve çalıştırma
- Üretilen C kodu okunabilir ve debug edilebilir olmalı
5.4 Interpreter Backend (Aşama 1)
- IR'yi doğrudan yürüten sanal makine
- Stack tabanlı veya register tabanlı
- Adım adım çalıştırma (step) desteği
- Değişken değerlerini gösterme
5.5 QBE Backend (Aşama 2)
- QBE C API'si ile entegrasyon
- IR → QBE IR dönüşümü
- Native binary üretimi
5.6 LLVM Backend (Aşama 3, opsiyonel)
- LLVM C API ile entegrasyon
- IR → LLVM IR dönüşümü
- Agresif optimizasyonlar, çok platformlu kod üretimi
Aşama 6: Hata Sistemi
Hedef: Sadece hata mesajı değil, açıklama. Kullanıcıya neyi yanlış yaptığını ve nasıl düzelteceğini söyle.
6.1 Diagnostic Sistemi
Diagnosticyapısı:{level, location, message, hint, sourceLine}- Seviyeler: error, warning, note, hint
- Birden fazla diagnostic toplama (ilk hatada durma)
DiagnosticEngine: diagnostic'leri toplar, formatlar, basar
6.2 Hata Mesajı Formatı
- Kaynak satır gösterimi (^^^^ işaretleme)
- Renkli çıktı (opsiyonel,
--color) - JSON formatında da alınabilir
- Hata kodları (örn: E0001, W0002)
6.3 Zengin Hata Açıklamaları
- Tanımsız değişken → "x tanımlı değil. Bunu mu demek istediniz: xy?"
- Tip uyuşmazlığı → "int bekleniyor ama float verilmiş. Cast ekleyin."
- Eksik noktalı virgül → "; eksik. Bu satırın sonuna ekleyin."
- Sembol tablosu kullanarak bağlamlı öneriler
Aşama 7: LSP Sunucusu (Uzun Vade)
Hedef: VS Code ve diğer IDE'ler için tam dil desteği. Tüm metadata ve çoklu çıktı formatları LSP'yi kolaylaştırır.
7.1 LSP Protokolü Temelleri
initialize,shutdown,exittextDocument/didOpen,didChange,didClose- JSON-RPC 2.0 üzerinden iletişim
7.2 Dil Özellikleri
- Syntax highlighting: Token listesini LSP'e bildir
- Diagnostics: Hata/uyarıları anlık bildir
- Go to definition: Sembol tablosundan tanım konumuna git
- Find references: Sembolün tüm kullanım noktalarını bul
- Hover: İmleç üstündeki sembol hakkında bilgi göster
- Code completion: Bağlama göre öneriler (sembol tablosundan)
- Document symbols: Dosyadaki tüm sembolleri listele (AST'den)
- Code lens: Fonksiyon başına reference sayısı
7.3 Kod Hiyerarşisi Görselleştirme
--format=htmlile etkileşimli AST ağacı- AST'yi JSON olarak alıp web UI'da görselleştirme
- Sembol tablosunu grafik olarak gösterme
- Fonksiyon çağrı grafiği (call graph)
Aşama 8: Test ve Kalite
8.1 Birim Testleri
- Lexer testleri: her sayı formatı, her operatör, string
- Tokenizer testleri: keyword'ler, delimiter'lar, yorumlar
- Parser testleri: her statement tipi, iç içe ifadeler, hata durumları
- IR testleri: her opcode, doğru register tahsisi
- Sembol tablosu testleri: tanımlama, çözümleme, scope
8.2 Anlık Görüntü (Snapshot) Testleri
source.sqt→output.jsonkarşılaştırması- Her commit'te AST/IR/sembol çıktısı değişmemeli (regresyon)
8.3 Benchmark
- Büyük dosya parse süresi
- Tokenizer throughput (token/saniye)
- Bellek kullanımı
Öncelik Sıralaması
Şu an (Aşama 0) ──▶ Metadata ve konum takibi
│
Haftaya (Aşama 1) ──▶ CLI + REPL + dosya modu
│
2 hafta (Aşama 2) ──▶ Bellek canavarı AST + JSON çıktı
│
3 hafta (Aşama 3) ──▶ Sembol tablosu
│
4 hafta (Aşama 4) ──▶ Feature toggle + CompilerConfig
│
5 hafta (Aşama 5) ──▶ IR güçlendirme + C transpile backend
│
6 hafta (Aşama 6) ──▶ Diagnostic + zengin hata mesajları
│
... (Aşama 7-8) ──▶ LSP, testler, benchmark
Anlık Görevler (Bu Hafta)
Bugün / Yarın
SourceLocationyapısını tanımla (src/core/location.hpp)SourceFilesınıfını yaz (offset → satır/sütun dönüşümü)- Lexer'a satır/sütun takibi ekle
INumberyerineSourceLocationkullanan yeni yapı
Bu Hafta
- Token'lara
SourceLocationekle - AST düğümlerine
SourceLocationekle - Token'ları AST'nin sahiplenmesi (unique_ptr)
- CLI modülü: argc/argv, temel komutlar
Mimari Notlar
Neden Bellek Canavarı?
Çünkü amaç bilgiyi korumak. Derleyiciler genelde parse ettikten sonra kaynak kod bilgisini atar. Biz atmayacağız. Bu sayede:
- Hata mesajlarında kaynak kodu gösterebiliriz
- IDE'de "go to definition" yapabiliriz
- Kodu yeniden oluşturabiliriz (pretty printer)
- Dönüşümleri (refactoring) güvenle yapabiliriz
Neden Feature Toggle?
Çünkü bu bir toolbox. Kullanıcı dili özelleştirebilmeli:
- Eğitim amaçlı: önce sadece if/else, sonra while ekle
- Deney amaçlı: "while olmadan program yazabilir misin?"
- Güvenlik: belirli yapıları yasakla (eval, system call)
- DSL: kendi alt-dilini oluştur
Neden IR Merkezli?
IR, tüm backend'lerin ortak dilidir. Yeni bir backend eklemek istediğimizde parser'ı, lexer'ı, optimizer'ı değiştirmemize gerek kalmaz. Sadece IR → Hedef dönüşümü yazarız. Bu, LLVM dışında QBE, Cranelift, hatta kendi JIT'imizi eklemeyi mümkün kılar.