diff --git a/compile.sh b/compile.sh index 457a9d1..4b8f9e1 100755 --- a/compile.sh +++ b/compile.sh @@ -1,8 +1,33 @@ #!/bin/bash -# saQut Compiler — build script -# Derleme: g++ src/main.cpp -Isrc -o saqut +# ============================================================================ +# saQut Compiler — Derleme Betiği +# ============================================================================ +# +# AMAÇ: Projeyi tek komutla derlemek. +# +# KULLANIM: +# ./compile.sh → saqut binary'sini üret +# ./saqut → derleyiciyi çalıştır +# +# DERLEME SÜRECİ: +# Tek bir .cpp dosyası (src/main.cpp) tüm header-only kütüphaneleri +# include eder. Harici bağımlılık yoktur. +# +# g++ parametreleri: +# -Isrc : include path (header'lar src/ altında) +# -std=c++17 : C++17 standardı (std::variant, constexpr, vb.) +# -Wall -Wextra : Tüm uyarıları aç +# -O0 -g : Optimizasyon kapalı, debug sembolleri açık +# -o saqut : Çıktı binary adı +# +# GELECEK: +# - Makefile veya CMakeLists.txt ile daha esnek build +# - Release modu: -O2 -DNDEBUG +# - Test modu: ayrı bir test binary'si +# +# ============================================================================ -set -e +set -e # Hata durumunda dur echo "=== saQut Compiler Build ===" diff --git a/fikirler.md b/fikirler.md new file mode 100644 index 0000000..f667d67 --- /dev/null +++ b/fikirler.md @@ -0,0 +1,385 @@ +# 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. diff --git a/saqut b/saqut index 49c73e8..9ed7f68 100755 Binary files a/saqut and b/saqut differ diff --git a/source.sqt b/source.sqt index 2c3ffcd..f96b3bd 100644 --- a/source.sqt +++ b/source.sqt @@ -1 +1,10 @@ -1 / (74 - 63 + !1) - 74 * 2 / -0.7e+10 +int main() { + int x = 0; + while (x < 5) { + x = x + 1; + } + do { + x = x - 1; + } while (x > 0); + return x; +} diff --git a/src/ir/ir.hpp b/src/ir/ir.hpp index d369221..7881deb 100644 --- a/src/ir/ir.hpp +++ b/src/ir/ir.hpp @@ -1,3 +1,54 @@ +// ============================================================================ +// saQut Compiler — Intermediate Representation (Ara Gösterim) +// ============================================================================ +// +// DİZİN: src/ir/ir.hpp +// KATMAN: Katman 4 — AST'yi alır, IR üretir +// BAĞIMLI: AST (src/parser/ast.hpp), dolaylı olarak Tokenizer ve Parser +// KULLANAN: main.cpp (debug çıktısı), gelecekte Code Generator +// +// AMAÇ: +// Zengin AST'yi, çalıştırılabilir düşük seviyeli komutlara (IR) dönüştürür. +// IR, bir "virtual register machine" (sanal kayıtçı makinesi) modelidir. +// +// IR MODELİ: +// Her işlem (instruction) şu bileşenlerden oluşur: +// - opcode: İşlem kodu (mathadd, mathsub, mathmul, mathdiv, declare) +// - targetReg: Sonucun yazılacağı sanal register numarası +// - arg1-arg3: İşlem parametreleri (register veya sabit değer) +// +// Sanal register'lar sınırsızdır (gerçek register tahsisi sonraki aşamada). +// Bu yaklaşım, register allocation'ı ayrı bir probleme dönüştürür. +// +// ADR-005: IR Tasarımı +// İki katmanlı IR planlanıyor: +// - LightIR: Sadece çalıştırma için minimum bilgi (JIT/compiler) +// - HeavyIR: Debug bilgisi, tip bilgisi, değişken isimleri (interpreter/debug) +// +// Mevcut IR, LightIR'in embriyonik halidir. +// +// MEVCUT DURUM: +// Desteklenen AST düğümleri: +// ✅ BinaryExpression (sadece +, -, *, /) +// ✅ Literal (NumberToken) +// ✅ Program, FunctionDecl, Block (çocukları dolaşır) +// ✅ ExpressionStatement, VariableDecl, ReturnStatement +// ✅ IfStatement, WhileStatement, ForStatement, DoWhileStatement +// ❌ Kontrol akışı (branch/jump/compare) — TODO +// ❌ Fonksiyon çağrısı (call/ret) — TODO +// ❌ Mantıksal/kıyaslama operatörleri — TODO +// +// BİLİNEN SINIRLAMALAR (TODO): +// TODO: Kontrol akışı opcode'ları: br, jmp, cmp, br_eq, br_lt, vb. +// TODO: Fonksiyon çağrısı: call, ret, param +// TODO: Bellek: load, store, alloca +// TODO: Tip bilgisi: IR opcode'ları tipleri taşımıyor +// TODO: Float/int ayrımı düzgün değil (processNumber void* döndürüyor) +// TODO: String, bool, null literal'ları işlenmiyor +// TODO: Identifier (değişken okuma) işlenmiyor +// +// ============================================================================ + #ifndef SAQUT_IR #define SAQUT_IR @@ -5,41 +56,95 @@ #include #include "parser/ast.hpp" -// ============================================================ -// IR opcodes -// ============================================================ - +// ============================================================================ +// OPCode — İşlem Kodları +// ============================================================================ +// +// Sanal makinenin komut seti. Her komut bir veya daha fazla sanal register +// üzerinde işlem yapar. +// +// mathadd/mathsub/mathmul/mathdiv: arg1 ve arg2'yi işle, targetReg'e yaz +// declare: bir sabit değeri (literal) targetReg'e yükle +// +// TODO: cmp, br, jmp, call, ret, load, store, alloca eklenecek +// enum class OPCode { - mathadd, - mathsub, - mathdiv, - mathmul, - declare + mathadd, // targetReg = arg1 + arg2 + mathsub, // targetReg = arg1 - arg2 + mathdiv, // targetReg = arg1 / arg2 + mathmul, // targetReg = arg1 * arg2 + declare // targetReg = literal değer (arg1) }; +// ============================================================================ +// Param — İşlem Parametresi +// ============================================================================ +// +// Bir IR komutunun girdisi. İki tür olabilir: +// isRegister=true → value bir register numarasıdır (int) +// isRegister=false → value bir sabit değerdir (int veya float) +// +// std::variant kullanımı: Derleme zamanı tip güvenliği sağlar. +// C union'dan farkı: Hangi tipin aktif olduğunu bilir, yanlış erişimi engeller. +// struct Param { - bool isRegister; - std::variant value; + bool isRegister; // true: register referansı, false: sabit değer + std::variant value; // Değer (register numarası veya sabit) }; +// ============================================================================ +// IROpData — Tek Bir IR Komutu +// ============================================================================ +// +// Sanal makinenin bir instruction'ı. 3 adrese kadar (3-address code) destekler. +// Çoğu işlem 2 parametre kullanır (binary ops), declare 1 parametre kullanır. +// struct IROpData { - OPCode op; - int targetReg; - Param arg1; - Param arg2; - Param arg3; + OPCode op; // İşlem kodu + int targetReg; // Sonuç register'ı (sanal, sınırsız) + Param arg1; // Birinci parametre + Param arg2; // İkinci parametre + Param arg3; // Üçüncü parametre (ileride kullanım için) }; +// ============================================================================ +// Identifier — Sanal Register Yöneticisi +// ============================================================================ +// +// Sınırsız sanal register tahsisi. Her yeni değer için monoton artan bir +// numara verir. Gerçek register tahsisi (register allocation) daha sonra +// yapılacak — bu aşamada sadece unique ID üretir. +// +// last: Şu ana kadar tahsis edilmiş en yüksek register numarası. +// ++identifier.last → yeni register numarası. +// struct Identifier { - int last = 0; + int last = 0; // Son tahsis edilen register numarası }; -// ============================================================ -// CodeGenerator: AST → IR -// ============================================================ - +// ============================================================================ +// CodeGenerator — AST → IR Dönüştürücü +// ============================================================================ +// +// AST ağacını dolaşır (tree walk) ve her düğüm için karşılık gelen IR +// komutlarını üretir. Ziyaretçi deseni (visitor pattern) benzeri bir +// yaklaşım kullanır: parse() metodu ASTKind enum'ına göre dispatch eder. +// +// AKIŞ: +// 1. parse(rootAST) çağrılır +// 2. rootAST->kind'e göre uygun parse* metodu seçilir +// 3. Alt düğümler recursive olarak işlenir +// 4. Her BinaryExpression/Literal için IR komutu eklenir +// 5. Sonuç: IROpDatas vektörü doldurulur +// class CodeGenerator { private: + // ---------------------------------------------------------------------- + // processNumber: NumberToken'dan C++ native sayı üret. + // + // Neden void*? Hem int hem float döndürebilmek için. + // TODO: std::variant ile değiştir. + // ---------------------------------------------------------------------- void* processNumber(NumberToken* num, const std::string& rawStr) { if (num->isFloat || num->hasEpsilon) return new float(std::strtof(rawStr.c_str(), nullptr)); @@ -47,9 +152,16 @@ private: } public: - Identifier identifier; - std::vector IROpDatas; + Identifier identifier; // Sanal register yöneticisi + std::vector IROpDatas; // Üretilen IR komutları + // ------------------------------------------------------------------ + // parse: Ana dispatch fonksiyonu. AST düğüm tipine göre yönlendirir. + // + // BUG FIX (commit 40579ca): null giriş kontrolü eklendi. + // BinaryExpression'da Left null olabilir (unary operatörlerde). + // parse(nullptr) segfault'a neden oluyordu. + // ------------------------------------------------------------------ int parse(ASTNode* ast) { if (!ast) return 0; switch (ast->kind) { @@ -73,46 +185,46 @@ public: return parseIf((IfStatementNode*)ast); case ASTKind::WhileStatement: return parseWhile((WhileStatementNode*)ast); + case ASTKind::ForStatement: + return parseFor((ForStatementNode*)ast); + case ASTKind::DoWhileStatement: + return parseDoWhile((DoWhileStatementNode*)ast); default: return 0; } } + // --- Yapısal düğümler: çocukları dolaş --- + int parseProgram(ProgramNode* prog) { - for (auto* child : prog->getChildren()) - parse(child); + for (auto* child : prog->getChildren()) parse(child); return 0; } int parseFunctionDecl(FunctionDeclNode* fn) { - for (auto* child : fn->getChildren()) - parse(child); + for (auto* child : fn->getChildren()) parse(child); return 0; } int parseBlock(BlockNode* block) { - for (auto* child : block->getChildren()) - parse(child); + for (auto* child : block->getChildren()) parse(child); return 0; } int parseReturn(ReturnStatementNode* ret) { - if (ret->value) - return parse(ret->value); + if (ret->value) return parse(ret->value); return 0; } int parseVariableDecl(VariableDeclNode* vd) { - if (vd->initExpr) - return parse(vd->initExpr); + if (vd->initExpr) return parse(vd->initExpr); return 0; } int parseIf(IfStatementNode* ifn) { parse(ifn->condition); parse(ifn->thenBranch); - if (ifn->elseBranch) - parse(ifn->elseBranch); + if (ifn->elseBranch) parse(ifn->elseBranch); return 0; } @@ -122,6 +234,29 @@ public: return 0; } + int parseFor(ForStatementNode* fs) { + if (fs->init) parse(fs->init); + if (fs->condition) parse(fs->condition); + if (fs->update) parse(fs->update); + parse(fs->body); + return 0; + } + + int parseDoWhile(DoWhileStatementNode* dw) { + parse(dw->body); + if (dw->condition) parse(dw->condition); + return 0; + } + + // ------------------------------------------------------------------ + // parseBinaryExpr: İkili işlem ifadesini IR'ye dönüştür. + // + // Sadece +, -, *, / operatörleri desteklenir. + // Diğer operatörler (karşılaştırma, mantıksal) şimdilik es geçilir. + // + // BUG FIX (commit 438bc0e): Left veya Right null olabilir (unary prefix). + // parse(nullptr) çağrısı segfault yapıyordu. Ternary ile koruma eklendi. + // ------------------------------------------------------------------ int parseBinaryExpr(BinaryExpressionNode* bin) { OPCode op; switch (bin->Operator) { @@ -129,7 +264,7 @@ public: case TokenType::PLUS: op = OPCode::mathadd; break; case TokenType::MINUS: op = OPCode::mathsub; break; case TokenType::SLASH: op = OPCode::mathdiv; break; - default: return 0; + default: return 0; // Desteklenmeyen operatör } int left = bin->Left ? parse(bin->Left) : 0; @@ -145,6 +280,16 @@ public: return identifier.last; } + // ------------------------------------------------------------------ + // parseLiteral: Sayısal literal'ı IR'ye dönüştür. + // + // BUG FIX (commit 40579ca): &lit->parserToken.token → lit->parserToken.token + // ParserToken artık Token* tutuyor, & gereksiz (ve hatalı). + // + // BİLİNEN SORUN: Sadece NumberToken destekleniyor. + // StringToken, KeywordToken (true/false/null) için cast hatalı. + // TODO: Token tipine göre dispatch ekle. + // ------------------------------------------------------------------ int parseLiteral(LiteralNode* lit) { NumberToken* num = (NumberToken*)lit->parserToken.token; @@ -171,4 +316,4 @@ public: } }; -#endif +#endif // SAQUT_IR diff --git a/src/lexer/lexer.hpp b/src/lexer/lexer.hpp index dadb815..b8f4b5b 100644 --- a/src/lexer/lexer.hpp +++ b/src/lexer/lexer.hpp @@ -1,3 +1,62 @@ +// ============================================================================ +// saQut Compiler — Lexer (Karakter Seviyesinde Tarayıcı) +// ============================================================================ +// +// DİZİN: src/lexer/lexer.hpp +// KATMAN: Katman 1 — Derleyici pipeline'ının ilk aşaması +// BAĞIMLI: Yok (sadece standart kütüphane) +// KULLANAN: Tokenizer (src/tokenizer/tokenizer.hpp) +// +// AMAÇ: +// Ham kaynak kodu (std::string) karakter karakter tarayarak: +// 1. Karakter konumunu takip eder (offset) +// 2. Backtracking (geri alma) desteği ile desen eşleme yapar +// 3. Sayısal literal'ları okur ve sınıflandırır (decimal, hex, binary, octal, float) +// 4. Boşluk karakterlerini atlar +// 5. Satır/sütun bilgisi sağlar (hata mesajları için temel) +// +// ADR-006: Neden Kendi Lexer'ımız? +// - std::istringstream veya regex kullanmak yerine, tam kontrol sağlayan +// sıfırdan bir lexer yazdık. +// - Backtracking: offsetMap ile konum yığını tutar, denenen bir eşleşme +// başarısız olursa geri alınabilir. Bu özellik std::istream'de yoktur. +// - Performans: Sanal fonksiyon çağrısı yok, her şey inline. +// - Hata ayıklama: Her karakter okuması kontrol edilebilir. +// +// TASARIM KARARLARI: +// 1. offsetMap (std::vector): İç içe backtracking için yığın. +// beginPosition() → yığına mevcut konumu ekler +// acceptPosition() → yığındaki son konumu kalıcı yapar +// rejectPosition() → yığındaki son konumu atar (geri al) +// Bu sayede "dene, başarısız olursa geri al" patterni çalışır. +// +// 2. getchar() iki overload: +// - getchar(): mevcut konumdaki karakteri okur +// - getchar(int offset): mevcut konum + offset'teki karakteri okur +// İkincisi özellikle keyword kontrolünde önemlidir: +// "do" kelimesini gördükten sonra, bunun "double"ın başlangıcı olmadığını +// kontrol etmek için keyword sonrası karaktere bakılır. +// +// 3. isEnd(): offset >= size ile kontrol. offset her zaman [0, size] aralığında. +// size konumunda EOF (end of file) anlamına gelir. +// +// 4. readNumeric(): C/C++/Java sayı formatlarını destekler: +// - Decimal: 42, -3, +7 +// - Hex: 0xFF, 0X1A +// - Binary: 0b1010, 0B1100 +// - Octal: 0777 (0 ile başlayan ve 8-9 içermeyen) +// - Float: 3.14, .5, 1e10, 2.5E-3 +// - Negatif/Pozitif: -42, +3 (baştaki işaret) +// +// BİLİNEN SINIRLAMALAR (TODO): +// TODO: Satır/sütun takibi eklenmeli (şu anda sadece offset var) +// TODO: Unicode/UTF-8 desteği (şu anda sadece ASCII) +// TODO: ' char literal'ı okunamıyor +// TODO: Sayısal alt çizgi (_) ayracı: 1_000_000 formatı +// TODO: Binary floating point: 0b1.1p10 formatı (C99 hexfloat) +// +// ============================================================================ + #ifndef SAQUT_LEXER #define SAQUT_LEXER @@ -5,83 +64,172 @@ #include #include +// ============================================================================ +// INumber — Ara Sayısal Veri Yapısı +// ============================================================================ +// +// Lexer'ın readNumeric() fonksiyonu tarafından döndürülür. +// Tokenizer bu yapıyı NumberToken'a dönüştürür. +// +// Neden ayrı bir struct? Lexer katmanı Token sınıflarından haberdar değil. +// Bağımlılık yönü: Lexer ← Tokenizer. Lexer hiçbir üst katmanı include etmez. +// +// ALANLAR: +// start, end : Kaynak koddaki başlangıç/bitiş konumları (offset) +// token : Sayının ham string hali (örn: "0xFF", "3.14", "1e10") +// isFloat : Ondalıklı sayı mı? (nokta veya epsilon içeriyor mu) +// hasEpsilon : Bilimsel gösterim mi? (e/E içeriyor mu) +// base : Sayı tabanı: 2, 8, 10, veya 16 +// - 0x/0X ile başlarsa 16 +// - 0b/0B ile başlarsa 2 +// - 0 ile başlayıp 8-9 içermiyorsa 8 +// - diğer her şey 10 +// positive : Pozitif mi? (başında - işareti yoksa true) +// struct INumber { - int start = 0; - int end = 0; - std::string token; - bool isFloat = false; - bool hasEpsilon = false; - int base = 10; - bool positive = true; + int start = 0; // Kaynak koddaki başlangıç offset'i + int end = 0; // Kaynak koddaki bitiş offset'i + std::string token; // Sayının ham metni (örn: "42", "0xFF", "3.14e-2") + bool isFloat = false; // true ise float/double literal + bool hasEpsilon = false; // true ise bilimsel gösterim (örn: 1e10) + int base = 10; // Sayı tabanı: 2, 8, 10, 16 + bool positive = true; // false ise sayı negatif }; +// ============================================================================ +// Lexer — Karakter Seviyesinde Tarayıcı +// ============================================================================ +// +// Derleyici pipeline'ının en alt katmanı. Ham string üzerinde çalışır. +// Üst katmanlara (Tokenizer) karakter okuma ve konum yönetimi hizmeti sunar. +// +// DURUM DEĞİŞKENLERİ: +// input : Taranan kaynak kodun tamamı (string kopyası, değişmez) +// size : input.length() önbelleği (performans: her seferinde hesaplamaz) +// offset : Mevcut okuma konumu. 0 = ilk karakter, size = EOF +// offsetMap : Backtracking yığını. İç içe beginPosition/acceptPosition/rejectPosition +// +// PERFORMANS NOTU: +// Tüm metotlar inline tanımlanmıştır. Sanal fonksiyon çağrısı yoktur. +// offset değişiklikleri O(1)'dir. +// include() metodu O(n) karakter karşılaştırması yapar (n = kelime uzunluğu). +// class Lexer { public: - std::string input; - int size = 0; - int offset = 0; - std::vector offsetMap; + // --- Ham Veri --- + std::string input; // Kaynak kodun tamamı + int size = 0; // input.length() önbelleği + int offset = 0; // Mevcut okuma konumu (0 = başlangıç) + std::vector offsetMap; // Backtracking yığını - // --- Position tracking --- - void beginPosition(); - int getLastPosition(); - void acceptPosition(); - void setLastPosition(int n); - void rejectPosition(); + // --- Pozisyon Yönetimi (Backtracking API) --- + // + // Kullanım örneği: + // lexer.beginPosition(); // konumu kaydet + // if (lexer.include("for", false)) // dene (false = eşleşse de geri al) + // lexer.acceptPosition(); // başarılı → kalıcı yap + // else + // lexer.rejectPosition(); // başarısız → geri al - // --- Reading --- - bool isEnd(); - int* positionRange(); - std::string getPositionRange(); + void beginPosition(); // Şu anki konumu yığına kaydet + int getLastPosition(); // Yığındaki son konumu döndür + void acceptPosition(); // Yığındaki son konumu kalıcı yap (apply) + void setLastPosition(int n); // Yığındaki son konumu n olarak değiştir + void rejectPosition(); // Yığındaki son konumu at (discard) + + // --- Dosya Sonu ve Pozisyon Sorgulama --- + bool isEnd(); // offset >= size ise true (EOF) + int* positionRange(); // [start, end] offset aralığı (tahsis eder, silinmeli!) + std::string getPositionRange(); // Pozisyon aralığındaki metni döndür + + // --- Desen Eşleme --- + // include(): Belirtilen kelime mevcut konumda başlıyor mu? + // accept=true (varsayılan): eşleşirse konum ilerletilir + // accept=false: eşleşse bile konum geri alınır (keyword kontrolü için) + // Örnek: include("for", false) → "for" ile başlıyor mu? konumu değiştirme. bool include(std::string word, bool accept = true); - int getOffset(); - int setOffset(int n); - char getchar(int additionalOffset); - char getchar(); - void nextChar(); - void toChar(int n); + // --- Konum Okuma/Yazma --- + int getOffset(); // Mevcut offset'i döndür + int setOffset(int n); // Offset'i n olarak ayarla, yeni değeri döndür - void setText(std::string input); - void skipWhiteSpace(); - bool isNumeric(); - INumber readNumeric(); + // --- Karakter Okuma --- + char getchar(int additionalOffset); // offset + ek'teki karakteri oku + char getchar(); // Mevcut offset'teki karakteri oku + void nextChar(); // offset'i 1 ilerlet (EOF kontrolü yapar) + void toChar(int n); // offset'i n kadar ilerlet + + // --- Üst Seviye İşlemler --- + void setText(std::string input); // Yeni kaynak kodu yükle + void skipWhiteSpace(); // Boşluk/sekme/satırsonu karakterlerini atla + bool isNumeric(); // Mevcut karakter 0-9 aralığında mı? + INumber readNumeric(); // Sayı literal'ı oku ve INumber olarak döndür }; -// ============================================================ -// Implementation -// ============================================================ +// ============================================================================ +// GERÇEKLEME (Implementation) +// ============================================================================ +// Tüm metotlar inline olarak aşağıda tanımlanmıştır. +// Derleme modeli: header-only. main.cpp bu dosyayı include eder. +// ============================================================================ -void Lexer::beginPosition() { +// -------------------------------------------------------------------------- +// beginPosition: Mevcut offset'i yığına kaydet. +// İç içe çağrılabilir: 3 kere beginPosition → 3 elemanlı yığın. +// -------------------------------------------------------------------------- +inline void Lexer::beginPosition() { offsetMap.push_back(getLastPosition()); } -int Lexer::getLastPosition() { +// -------------------------------------------------------------------------- +// getLastPosition: Yığının tepesindeki konumu döndür. +// Yığın boşsa mevcut offset'i döndür (başlangıç durumu). +// -------------------------------------------------------------------------- +inline int Lexer::getLastPosition() { if (offsetMap.empty()) return offset; return offsetMap.back(); } -void Lexer::acceptPosition() { +// -------------------------------------------------------------------------- +// acceptPosition: Yığındaki son geçici konumu kalıcı yap. +// Örnek: offsetMap=[5,10], offset=15 → offsetMap.back()=10 olur. +// Bu sayede include() denemesi başarılı olduğunda konum ilerletilmiş olur. +// -------------------------------------------------------------------------- +inline void Lexer::acceptPosition() { int t = offsetMap.back(); setLastPosition(t); } -void Lexer::setLastPosition(int n) { +// -------------------------------------------------------------------------- +// setLastPosition: Yığının tepesini veya offset'i değiştir. +// -------------------------------------------------------------------------- +inline void Lexer::setLastPosition(int n) { if (offsetMap.empty()) offset = n; else offsetMap.back() = n; } -bool Lexer::isEnd() { +// -------------------------------------------------------------------------- +// isEnd: Dosya sonuna gelindi mi? offset >= size. +// -------------------------------------------------------------------------- +inline bool Lexer::isEnd() { return size <= getOffset(); } -void Lexer::rejectPosition() { +// -------------------------------------------------------------------------- +// rejectPosition: Yığındaki son konumu at. Başarısız include() denemesi sonrası. +// -------------------------------------------------------------------------- +inline void Lexer::rejectPosition() { offsetMap.pop_back(); } -int* Lexer::positionRange() { +// -------------------------------------------------------------------------- +// positionRange: Yığındaki en dış ve en iç konumu [start, end] olarak döndür. +// UYARI: new int[2] ile heap'te tahsis eder. Çağıran sorumludur. +// TODO: std::pair veya yapı kullanarak tahsisi kaldır. +// -------------------------------------------------------------------------- +inline int* Lexer::positionRange() { int len = offsetMap.size(); if (len == 0) return new int[2]{0, offset}; @@ -90,7 +238,10 @@ int* Lexer::positionRange() { return new int[2]{offsetMap[len - 2], offsetMap[len - 1]}; } -std::string Lexer::getPositionRange() { +// -------------------------------------------------------------------------- +// getPositionRange: positionRange() aralığındaki metni string olarak döndür. +// -------------------------------------------------------------------------- +inline std::string Lexer::getPositionRange() { int* a = positionRange(); std::string mem; for (int i = a[0]; i < a[1]; i++) @@ -98,7 +249,25 @@ std::string Lexer::getPositionRange() { return mem; } -bool Lexer::include(std::string word, bool accept) { +// -------------------------------------------------------------------------- +// include: Belirtilen kelime mevcut konumda başlıyor mu? +// +// Algoritma: +// 1. beginPosition() ile konumu kaydet +// 2. Kelimenin her karakterini sırayla karşılaştır +// 3. Eşleşmezse veya EOF olursa → rejectPosition() ve false dön +// 4. Tüm karakterler eşleşirse: +// - accept=true ise → acceptPosition() (konum kalıcı ilerler) +// - accept=false ise → rejectPosition() (konum eski haline döner) +// 5. true dön +// +// Neden accept parametresi var? +// Tokenizer scope() fonksiyonu, keyword'leri kontrol ederken accept=false +// kullanır. Çünkü bir keyword eşleşmesi, aynı zamanda daha uzun bir +// keyword'ün parçası olabilir (örn: "do", "double"ın başlangıcı). +// Eğer include("do", true) kullanılırsa, konum ilerler ve geri alınamaz. +// -------------------------------------------------------------------------- +inline bool Lexer::include(std::string word, bool accept) { beginPosition(); for (size_t i = 0; i < word.size(); i++) { if (isEnd()) { @@ -118,16 +287,25 @@ bool Lexer::include(std::string word, bool accept) { return true; } -int Lexer::getOffset() { +// -------------------------------------------------------------------------- +// getOffset / setOffset: Konum erişimcileri. +// -------------------------------------------------------------------------- +inline int Lexer::getOffset() { return getLastPosition(); } -int Lexer::setOffset(int n) { +inline int Lexer::setOffset(int n) { setLastPosition(n); return getLastPosition(); } -char Lexer::getchar(int additionalOffset) { +// -------------------------------------------------------------------------- +// getchar(additionalOffset): offset + ek kadar ilerideki karakteri oku. +// Sınır kontrolü yapar: target >= size ise '\0' döndürür ve hata mesajı basar. +// Bu metot özellikle keyword sınır kontrolünde kullanılır: +// "do" eşleşti, sıradaki karakter 'u' ise bu "double" olabilir → keyword değil +// -------------------------------------------------------------------------- +inline char Lexer::getchar(int additionalOffset) { int target = getOffset() + additionalOffset; if (target >= size) { std::cerr << "Lexer hatası: sınır aşımı\n"; @@ -136,7 +314,7 @@ char Lexer::getchar(int additionalOffset) { return input.at(target); } -char Lexer::getchar() { +inline char Lexer::getchar() { int target = getOffset(); if (target >= size) { std::cerr << "Lexer hatası: sınır aşımı\n"; @@ -145,29 +323,40 @@ char Lexer::getchar() { return input.at(target); } -void Lexer::nextChar() { +// -------------------------------------------------------------------------- +// nextChar / toChar: Konum ilerletme. +// EOF kontrolü yapar — dosya sonundaysa ilerlemez. +// -------------------------------------------------------------------------- +inline void Lexer::nextChar() { if (!isEnd()) setOffset(getOffset() + 1); } -void Lexer::toChar(int n) { +inline void Lexer::toChar(int n) { if (!isEnd()) setOffset(getOffset() + n); } -void Lexer::setText(std::string text) { +// -------------------------------------------------------------------------- +// setText: Yeni kaynak kodu yükle. input ve size'ı günceller. +// -------------------------------------------------------------------------- +inline void Lexer::setText(std::string text) { input = text; size = text.length(); } -void Lexer::skipWhiteSpace() { +// -------------------------------------------------------------------------- +// skipWhiteSpace: Boşluk, sekme, satırsonu, satırbaşı karakterlerini atla. +// Yorum satırlarını atlamaz — bu Tokenizer'ın işi. +// -------------------------------------------------------------------------- +inline void Lexer::skipWhiteSpace() { while (!isEnd()) { switch (getchar()) { - case '\r': - case '\n': - case '\b': - case '\t': - case ' ': + case '\r': // carriage return (Windows satırsonu \r\n) + case '\n': // line feed (Unix satırsonu) + case '\b': // backspace + case '\t': // tab + case ' ': // boşluk nextChar(); break; default: @@ -176,15 +365,45 @@ void Lexer::skipWhiteSpace() { } } -bool Lexer::isNumeric() { +// -------------------------------------------------------------------------- +// isNumeric: Mevcut karakter bir rakam mı? (0-9) +// Pointer aritmetiği veya ASCII tablosu karşılaştırması yerine basit aralık +// kontrolü. Performans: O(1), branchless (modern derleyiciler optimize eder). +// -------------------------------------------------------------------------- +inline bool Lexer::isNumeric() { char c = getchar(); return (c >= '0' && c <= '9'); } -INumber Lexer::readNumeric() { +// -------------------------------------------------------------------------- +// readNumeric: Tam bir sayı literal'ı oku. +// +// Desteklenen formatlar (öncelik sırasıyla): +// 1. İşaret: -42, +3 (baştaki isteğe bağlı işaret) +// 2. 0x/0X: Hex (0xFF, 0X1A) +// 3. 0b/0B: Binary (0b1010) +// 4. 0 ile başlayan: Octal (0777) veya Float (0.5) +// 5. Ondalık: 42, 3.14 +// 6. Bilimsel: 1e10, 2.5E-3, 1.0e+5 +// +// Algoritma: +// 1. İsteğe bağlı işareti oku (+ veya -) +// 2. İlk karakter '0' ise → özel durum (hex/bin/octal/float kontrolü) +// 3. Ana döngü: rakamları, hex harflerini (a-f/A-F), nokta (.), epsilon (e/E) oku +// 4. Her karakterde taban uygunluğunu kontrol et (örn: octal'da 8-9 geçersiz) +// 5. İlk karakter '0' değilse → doğrudan decimal +// +// Özel durum: "0" takip eden karakter yoksa → tek haneli sayı, base=10. +// "0xFF" → hex, "0b10" → binary, "077" → octal, "0.5" → float. +// +// TODO: Hex float desteği (0x1.ffp10) — C99 standardı +// TODO: Sayısal ayraç: 1_000_000 — C++14/Java 7 +// -------------------------------------------------------------------------- +inline INumber Lexer::readNumeric() { INumber num; num.start = getLastPosition(); + // --- Adım 1: İsteğe bağlı işaret --- if (getchar() == '-') { nextChar(); num.positive = false; @@ -195,23 +414,24 @@ INumber Lexer::readNumeric() { num.positive = true; } + // --- Adım 2: İlk karakter '0' ise özel format kontrolü --- bool nextDot = false; if (getchar() == '0') { num.token.push_back('0'); nextChar(); char c = getchar(); switch (c) { - case 'x': case 'X': + case 'x': case 'X': // Hex: 0xFF, 0X1A num.token.push_back(c); num.base = 16; nextChar(); break; - case 'b': case 'B': + case 'b': case 'B': // Binary: 0b1010 num.token.push_back(c); num.base = 2; nextChar(); break; - case '.': + case '.': // Float: 0.5, 0.0 num.token.push_back(c); num.base = 10; nextDot = true; @@ -220,11 +440,15 @@ INumber Lexer::readNumeric() { break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': - // Octal: continue reading in the main loop + // Octal: 0777 — sonraki karakter octal rakam ise devam et num.base = 8; break; default: - // Just "0" — stop here + // Sadece "0" — takip eden karakter rakam değil. + // Hemen dön: base=10 (varsayılan). + // BUG FIX (commit 438bc0e): Eskiden bu dalda sıradaki karakter + // token'a ekleniyor ve base=8 yapılıyordu. Bu, "0;" durumunda + // ';' karakterinin sayıya eklenmesine neden oluyordu. num.end = getLastPosition(); return num; } @@ -232,6 +456,15 @@ INumber Lexer::readNumeric() { num.base = 10; } + // --- Adım 3: Ana okuma döngüsü --- + // Bu döngü, geçerli tabana uygun tüm karakterleri okur. + // Her karakter tipi için taban uygunluğu kontrol edilir: + // - 0-1: tüm tabanlar + // - 2-7: base >= 8 + // - 8-9: base >= 10 + // - a-f/A-F: base >= 16 + // - . (nokta): sadece ondalık, sadece bir kere + // - e/E: sadece ondalık ve hex (hex'te epsilon yok, direkt okunur) while (!isEnd()) { char c = getchar(); switch (c) { @@ -267,6 +500,8 @@ INumber Lexer::readNumeric() { } break; case '.': + // Nokta: Sadece bir kere izin verilir. + // .5 gibi başıboş noktalı sayılar için "0." öneki eklenir. if (!nextDot) { if (num.token.empty()) num.token += "0."; @@ -275,11 +510,15 @@ INumber Lexer::readNumeric() { nextDot = true; num.isFloat = true; } else { + // İkinci nokta → sayı bitti num.end = getLastPosition(); return num; } break; case 'e': case 'E': + // Epsilon (bilimsel gösterim): + // - Hex tabanda: epsilon DEĞİL, hex hanesi olarak okunur. + // - Decimal tabanda: 1e10, 2.5E-3 formatı. if (num.base == 16) { num.token.push_back(c); break; @@ -289,10 +528,12 @@ INumber Lexer::readNumeric() { num.token.push_back(c); nextChar(); c = getchar(); + // İsteğe bağlı işaret: e+10, E-3 if (c == '+' || c == '-') { num.token.push_back(c); nextChar(); } + // Epsilon sonrası rakamları oku while (!isEnd()) { c = getchar(); if (c >= '0' && c <= '9') { @@ -308,6 +549,7 @@ INumber Lexer::readNumeric() { num.end = getLastPosition(); return num; default: + // Tanınmayan karakter → sayı bitti num.end = getLastPosition(); return num; } @@ -317,4 +559,4 @@ INumber Lexer::readNumeric() { return num; } -#endif +#endif // SAQUT_LEXER diff --git a/src/main.cpp b/src/main.cpp index eb7eefc..2a6d5e0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,30 @@ +// ============================================================================ +// saQut Compiler — Giriş Noktası (main) +// ============================================================================ +// +// DİZİN: src/main.cpp +// KATMAN: En üst — tüm alt katmanları birleştirir +// BAĞIMLI: Tokenizer, Parser, IR (ve dolaylı olarak Lexer, AST, Token) +// +// AMAÇ: +// Derleyici pipeline'ını başlatır: +// 1. source.sqt dosyasını oku +// 2. Lexing + Tokenizing +// 3. Parsing (AST üretimi) +// 4. IR üretimi +// 5. Sonuçları konsola yazdır (debug modu) +// +// KULLANIM: +// ./saqut → source.sqt dosyasını derler +// echo "1+2" > source.sqt && ./saqut → hızlı test +// +// GELECEK: +// - Komut satırı argümanları: ./saqut file.sqt -o output +// - Mod seçimi: ./saqut --mode=parse|ir|compile|run +// - Birden fazla dosya: ./saqut file1.sqt file2.sqt +// +// ============================================================================ + #include #include #include @@ -7,7 +34,12 @@ #include "ir/ir.hpp" int main() { - // Read source file + // ------------------------------------------------------------------ + // 1. Kaynak dosyayı oku + // ------------------------------------------------------------------ + // Şimdilik sabit dosya adı: source.sqt. + // TODO: argc/argv ile dosya adı al. + // ------------------------------------------------------------------ std::ifstream file("source.sqt", std::ios::in | std::ios::binary); if (!file.is_open()) { std::cerr << "Hata: source.sqt dosyası açılamadı\n"; @@ -22,7 +54,12 @@ int main() { std::cout << "=== saQut Compiler ===\n"; std::cout << "Kaynak kod:\n" << source << "\n\n"; - // Lexing → Tokenizing + // ------------------------------------------------------------------ + // 2. Lexing → Tokenizing + // ------------------------------------------------------------------ + // Tokenizer, Lexer'ı içerir. scan() tüm pipeline'ı çalıştırır. + // Token'lar heap'te new ile oluşturulur, iş bitince silinmeli. + // ------------------------------------------------------------------ Tokenizer tokenizer; auto tokens = tokenizer.scan(source); @@ -32,16 +69,27 @@ int main() { } std::cout << "\n"; - // Parsing → AST + // ------------------------------------------------------------------ + // 3. Parsing → AST + // ------------------------------------------------------------------ + // Parser, token listesini alır, AST üretir. + // parse() artık parseProgram()'ı çağırır — birden fazla deklarasyon + // veya statement içeren tam programları ayrıştırabilir. + // ------------------------------------------------------------------ Parser parser; ASTNode* ast = parser.parse(tokens); if (ast) { std::cout << "AST:\n"; - ast->log(0); + ast->log(0); // Ağacı girintili olarak yazdır std::cout << "\n"; - // IR generation + // ------------------------------------------------------------------ + // 4. IR Üretimi + // ------------------------------------------------------------------ + // CodeGenerator AST'yi dolaşır, sanal register makine komutları üretir. + // Şu anda sadece matematik işlemleri ve literal'lar destekleniyor. + // ------------------------------------------------------------------ CodeGenerator cg; cg.parse(ast); std::cout << "IR (" << cg.IROpDatas.size() << " komut):\n"; @@ -55,11 +103,17 @@ int main() { case OPCode::mathdiv: std::cout << "div"; break; case OPCode::declare: std::cout << "literal"; break; } + // arg1.value.index(): 0=int, 1=float std::cout << " (" << op.arg1.value.index() << ")\n"; } } - // Cleanup + // ------------------------------------------------------------------ + // 5. Temizlik + // ------------------------------------------------------------------ + // Token'lar heap'te oluşturuldu, manuel silinmeli. + // TODO: std::unique_ptr ile otomatik bellek yönetimi. + // ------------------------------------------------------------------ for (auto* t : tokens) delete t; return 0; diff --git a/src/parser/ast.hpp b/src/parser/ast.hpp index e34490b..a86746b 100644 --- a/src/parser/ast.hpp +++ b/src/parser/ast.hpp @@ -1,3 +1,59 @@ +// ============================================================================ +// saQut Compiler — Soyut Sözdizim Ağacı (AST) +// ============================================================================ +// +// DİZİN: src/parser/ast.hpp +// KATMAN: Katman 3 — Parser'ın ürettiği, IR'nin tükettiği +// BAĞIMLI: Token (src/parser/token.hpp), Tools (src/tools.hpp) +// KULLANAN: Parser (src/parser/parser.hpp), IR (src/ir/ir.hpp) +// +// AMAÇ: +// Kaynak kodun hiyerarşik, anlamsal gösterimi. Her dil yapısı (ifade, +// deyim, fonksiyon) bir AST düğümü ile temsil edilir. +// +// AST DÜĞÜM HİYERARŞİSİ: +// ASTNode (soyut taban) +// ├── ProgramNode : Kök düğüm, tüm üst seviye deklarasyonları tutar +// ├── FunctionDeclNode : Fonksiyon tanımı (int main() { ... }) +// ├── BlockNode : { ... } bloğu, statement listesi +// ├── VariableDeclNode : Değişken tanımı (int x = 10;) +// ├── IfStatementNode : if/else +// ├── WhileStatementNode : while döngüsü +// ├── ForStatementNode : for döngüsü +// ├── DoWhileStatementNode : do-while döngüsü +// ├── ReturnStatementNode : return [ifade] +// ├── BreakStatementNode : break +// ├── ContinueStatementNode : continue +// ├── ExpressionStatementNode: ifade + ; (bir statement olarak) +// ├── BinaryExpressionNode : İkili işlem (a + b, a * b) +// ├── LiteralNode : Sabit değer (42, "hello", true) +// ├── IdentifierNode : Değişken/fonksiyon ismi +// └── PostfixNode : Son ek işlem (a++, a--) +// +// TASARIM KARARLARI: +// 1. ASTKind enum: Her düğüm tipi için bir enum değeri. +// RTTI (dynamic_cast) yerine manuel tip kontrolü sağlar. +// Daha hızlı ve hata ayıklaması kolay. +// +// 2. parent pointer: Her düğüm ebeveynini bilir. +// Yukarı doğru gezinme (ör: bir döngü içinde break'in hedefini bulma). +// +// 3. children vektörü (protected): Sadece addChild() ile ekleme. +// ProgramNode, FunctionDeclNode, BlockNode gibi liste tutan düğümler +// bu vektörü kullanır. İkili işlem gibi sabit sayıda çocuğu olan +// düğümler kendi üye değişkenlerini kullanır (Left, Right). +// +// 4. log() metodu: Her düğüm kendi alt ağacını girintili olarak yazdırır. +// Debug ve test için. Gerçek kod üretimi için kullanılmaz. +// +// BİLİNEN SINIRLAMALAR (TODO): +// TODO: Bellek yönetimi: AST düğümleri heap'te new ile oluşturuluyor, +// silme sorumluluğu yok (sızıntı). unique_ptr veya arena allocator. +// TODO: Ziyaretçi deseni (Visitor pattern) eklenerek log() ve IR +// üretimi ayrı sınıflara taşınabilir. +// +// ============================================================================ + #ifndef SAQUT_AST #define SAQUT_AST @@ -6,43 +62,58 @@ #include "parser/token.hpp" #include "tools.hpp" -// ============================================================ -// AST Node types -// ============================================================ - +// ============================================================================ +// ASTKind — AST Düğüm Tipi Enum'u +// ============================================================================ +// +// Her AST düğüm sınıfı, constructor'ında kendi kind değerini atar. +// CodeGenerator (IR) ve diğer AST işlemcileri, düğümün tipini bu enum +// üzerinden belirler. +// +// İsimlendirme: Düğüm sınıf adları "Node" ile biter, enum değerleri bitmez. +// Örn: sınıf=IfStatementNode, enum=IfStatement +// enum class ASTKind { - Program, - FunctionDecl, - Block, - VariableDecl, - BinaryExpression, - UnaryExpression, - Literal, - Identifier, - Postfix, - IfStatement, - ForStatement, - WhileStatement, - DoWhileStatement, - ReturnStatement, - BreakStatement, - ContinueStatement, - ExpressionStatement, + Program, // Kök düğüm + FunctionDecl, // Fonksiyon tanımı + Block, // { } bloğu + VariableDecl, // Değişken tanımı + BinaryExpression, // İkili işlem (a + b) + UnaryExpression, // Tekli işlem (-a, !a) — ileride kullanılacak + Literal, // Sabit değer + Identifier, // İsim referansı + Postfix, // Son ek (a++) + IfStatement, // if/else + ForStatement, // for + WhileStatement, // while + DoWhileStatement, // do-while + ReturnStatement, // return + BreakStatement, // break + ContinueStatement, // continue + ExpressionStatement, // ifade + ; }; -// ============================================================ -// Base AST Node -// ============================================================ - +// ============================================================================ +// ASTNode — Soyut Temel Sınıf +// ============================================================================ +// +// Tüm AST düğümlerinin ortak atası. Minimum arayüz: +// - kind: Düğüm tipi (ASTKind enum) +// - parent: Ebeveyn düğüm (kök için nullptr) +// - addChild() / getChildren(): Çocuk yönetimi +// - log(): Debug çıktısı (virtual, her alt sınıf override eder) +// class ASTNode { public: - ASTKind kind; - ASTNode* parent = nullptr; + ASTKind kind; // Düğüm tipi (alt sınıf constructor'ında atanır) + ASTNode* parent = nullptr; // Ebeveyn düğüm (kök = nullptr) virtual void log(int indent = 0) { + (void)indent; // Kullanılmayan parametre uyarısını sustur std::cout << "\n"; } + // Çocuk ekleme. Otomatik olarak parent pointer'ı ayarlar. void addChild(ASTNode* child) { children.push_back(child); child->parent = this; @@ -53,13 +124,16 @@ public: virtual ~ASTNode() = default; protected: - std::vector children; + std::vector children; // Alt düğümler (liste tipi düğümler için) }; -// ============================================================ -// Program (root) -// ============================================================ - +// ============================================================================ +// ProgramNode — Kök Düğüm +// ============================================================================ +// +// Her saQut programı tek bir ProgramNode ile başlar. +// Çocukları: FunctionDeclNode, VariableDeclNode (global), ExpressionStatement. +// class ProgramNode : public ASTNode { public: ProgramNode() { kind = ASTKind::Program; } @@ -71,14 +145,21 @@ public: } }; -// ============================================================ -// Function declaration -// ============================================================ - +// ============================================================================ +// FunctionDeclNode — Fonksiyon Tanımı +// ============================================================================ +// +// Örnek: int main() { ... } +// returnType: "int", "void", "float", ... +// name: "main", "calculate", ... +// children: gövde (genellikle tek bir BlockNode) +// +// TODO: Parametre listesi (şu anda boş) +// class FunctionDeclNode : public ASTNode { public: - std::string name; - std::string returnType; + std::string name; // Fonksiyon adı + std::string returnType; // Dönüş tipi (string olarak, ileride tip sistemi) FunctionDeclNode() { kind = ASTKind::FunctionDecl; } @@ -90,10 +171,13 @@ public: } }; -// ============================================================ -// Block { ... } -// ============================================================ - +// ============================================================================ +// BlockNode — Blok { ... } +// ============================================================================ +// +// Bir dizi statement'i gruplar. Kendi scope (kapsam) alanı oluşturur. +// Örnek: { int x = 1; x = x + 2; } +// class BlockNode : public ASTNode { public: BlockNode() { kind = ASTKind::Block; } @@ -105,15 +189,20 @@ public: } }; -// ============================================================ -// Variable declaration: type name [= expr] -// ============================================================ - +// ============================================================================ +// VariableDeclNode — Değişken Tanımı +// ============================================================================ +// +// Örnek: int x = 10; +// varType: "int", "float", "bool", ... +// name: "x", "counter", ... +// initExpr: Başlangıç değeri (nullptr = tanımsız, örn: int x;) +// class VariableDeclNode : public ASTNode { public: - std::string varType; - std::string name; - ASTNode* initExpr = nullptr; + std::string varType; // Değişken tipi + std::string name; // Değişken adı + ASTNode* initExpr = nullptr; // Başlangıç ifadesi (opsiyonel) VariableDeclNode() { kind = ASTKind::VariableDecl; } @@ -129,19 +218,32 @@ public: } }; -// ============================================================ -// Expression nodes -// ============================================================ - +// ============================================================================ +// BinaryExpressionNode — İkili İşlem (a OP b) +// ============================================================================ +// +// İki operandlı tüm işlemler: a + b, a * b, a == b, a && b, ... +// Unary prefix operatörler de burada temsil edilir (Left = nullptr). +// +// Operator: İşlem tipi (PLUS, MINUS, STAR, EQUAL_EQUAL, ...) +// Left: Sol operand (unary prefix'te nullptr) +// Right: Sağ operand (her zaman dolu) +// +// NEDEN AYRI BİR UnaryExpressionNode YOK? +// Pratt parser'da unary ve binary operatörler aynı akışta işlenir. +// Left'in null olması unary olduğunu belirtir. Bu, kod tekrarını önler. +// İleride AST işlemcisi Left'e bakarak unary/binary ayrımı yapabilir. +// class BinaryExpressionNode : public ASTNode { public: - TokenType Operator; - ASTNode* Left = nullptr; - ASTNode* Right = nullptr; + TokenType Operator; // İşlem tipi + ASTNode* Left = nullptr; // Sol operand + ASTNode* Right = nullptr; // Sağ operand BinaryExpressionNode() { kind = ASTKind::BinaryExpression; } void log(int indent = 0) override { + // Operatörün enum ismini ve sembolünü göster auto it = OPERATOR_MAP_STRREV.find(Operator); std::string sym = (it != OPERATOR_MAP_STRREV.end()) ? std::string(it->second) : "?"; std::string val; @@ -150,15 +252,24 @@ public: std::cout << padRight("", indent) << "BinaryExpr " << sym << " (" << val << ")\n"; + // Önce sağ, sonra sol yazdır — ağaç görselleştirmesi için if (Right) Right->log(indent + 2); if (Left) Left->log(indent + 2); } }; +// ============================================================================ +// LiteralNode — Sabit Değer +// ============================================================================ +// +// Kaynak kodda doğrudan yazılan değerler: 42, "hello", true, false, null. +// lexerToken: Orijinal Token (NumberToken ise isFloat/base bilgisi) +// parserToken: Parser'ın atadığı tip bilgisi +// class LiteralNode : public ASTNode { public: - Token* lexerToken = nullptr; - ParserToken parserToken; + Token* lexerToken = nullptr; // Tokenizer'dan gelen orijinal token + ParserToken parserToken; // Parser tarafından zenginleştirilmiş token LiteralNode() { kind = ASTKind::Literal; } @@ -168,6 +279,13 @@ public: } }; +// ============================================================================ +// IdentifierNode — Tanımlayıcı Referansı +// ============================================================================ +// +// Değişken, fonksiyon, veya tip ismi. Örn: x, myVar, calculate. +// İleride symbol table ile çözümlenecek (bu değişken nerede tanımlı?). +// class IdentifierNode : public ASTNode { public: Token* lexerToken = nullptr; @@ -181,10 +299,18 @@ public: } }; +// ============================================================================ +// PostfixNode — Son Ek İşlem (a++, a--) +// ============================================================================ +// +// Operand'dan SONRA gelen operatör. Şu anda sadece ++ ve --. +// operand: İşlem yapılan ifade (genellikle IdentifierNode) +// Operator: PLUS_PLUS veya MINUS_MINUS +// class PostfixNode : public ASTNode { public: - ASTNode* operand = nullptr; - TokenType Operator; + ASTNode* operand = nullptr; // İşlem yapılan ifade + TokenType Operator; // PLUS_PLUS veya MINUS_MINUS PostfixNode() { kind = ASTKind::Postfix; } @@ -201,15 +327,19 @@ public: } }; -// ============================================================ -// Statement nodes -// ============================================================ - +// ============================================================================ +// IfStatementNode — if / else +// ============================================================================ +// +// condition: Koşul ifadesi (parantez içindeki) +// thenBranch: if gövdesi (BlockNode veya tek statement) +// elseBranch: else gövdesi (opsiyonel, nullptr = else yok) +// class IfStatementNode : public ASTNode { public: - ASTNode* condition = nullptr; - ASTNode* thenBranch = nullptr; // BlockNode or single statement - ASTNode* elseBranch = nullptr; // optional + ASTNode* condition = nullptr; // Koşul + ASTNode* thenBranch = nullptr; // if gövdesi + ASTNode* elseBranch = nullptr; // else gövdesi (opsiyonel) IfStatementNode() { kind = ASTKind::IfStatement; } @@ -226,10 +356,16 @@ public: } }; +// ============================================================================ +// WhileStatementNode — while Döngüsü +// ============================================================================ +// +// while (condition) body +// class WhileStatementNode : public ASTNode { public: - ASTNode* condition = nullptr; - ASTNode* body = nullptr; + ASTNode* condition = nullptr; // Döngü koşulu + ASTNode* body = nullptr; // Döngü gövdesi WhileStatementNode() { kind = ASTKind::WhileStatement; } @@ -242,12 +378,23 @@ public: } }; +// ============================================================================ +// ForStatementNode — for Döngüsü +// ============================================================================ +// +// for (init; condition; update) body +// +// init: Başlangıç (VariableDeclNode veya ExpressionStatementNode) +// condition: Devam koşulu (nullptr = sonsuz döngü) +// update: Her adımda çalışan ifade +// body: Döngü gövdesi +// class ForStatementNode : public ASTNode { public: - ASTNode* init = nullptr; // VariableDecl or ExpressionStatement - ASTNode* condition = nullptr; // expression - ASTNode* update = nullptr; // expression - ASTNode* body = nullptr; + ASTNode* init = nullptr; // Başlangıç + ASTNode* condition = nullptr; // Koşul + ASTNode* update = nullptr; // Güncelleme + ASTNode* body = nullptr; // Gövde ForStatementNode() { kind = ASTKind::ForStatement; } @@ -270,6 +417,12 @@ public: } }; +// ============================================================================ +// DoWhileStatementNode — do-while Döngüsü +// ============================================================================ +// +// do body while (condition); +// class DoWhileStatementNode : public ASTNode { public: ASTNode* condition = nullptr; @@ -286,9 +439,16 @@ public: } }; +// ============================================================================ +// ReturnStatementNode — return [ifade] +// ============================================================================ +// +// value = nullptr ise "return;" (void fonksiyonda) +// value dolu ise "return expr;" +// class ReturnStatementNode : public ASTNode { public: - ASTNode* value = nullptr; // optional + ASTNode* value = nullptr; // Dönüş değeri (opsiyonel) ReturnStatementNode() { kind = ASTKind::ReturnStatement; } @@ -303,6 +463,12 @@ public: } }; +// ============================================================================ +// BreakStatementNode — break +// ============================================================================ +// +// En yakın döngüden veya switch'ten çıkar. +// class BreakStatementNode : public ASTNode { public: BreakStatementNode() { kind = ASTKind::BreakStatement; } @@ -311,6 +477,12 @@ public: } }; +// ============================================================================ +// ContinueStatementNode — continue +// ============================================================================ +// +// En yakın döngünün başına atlar. +// class ContinueStatementNode : public ASTNode { public: ContinueStatementNode() { kind = ASTKind::ContinueStatement; } @@ -319,9 +491,16 @@ public: } }; +// ============================================================================ +// ExpressionStatementNode — İfadeyi Statement Olarak Sarma +// ============================================================================ +// +// Bir ifadeyi (expression) statement bağlamında kullanmak için sarar. +// Örn: x = 5; → ExpressionStatementNode( BinaryExpressionNode(x, =, 5) ) +// class ExpressionStatementNode : public ASTNode { public: - ASTNode* expression = nullptr; + ASTNode* expression = nullptr; // İç ifade ExpressionStatementNode() { kind = ASTKind::ExpressionStatement; } @@ -331,4 +510,4 @@ public: } }; -#endif +#endif // SAQUT_AST diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index 1412af8..afabedf 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -1,3 +1,75 @@ +// ============================================================================ +// saQut Compiler — Parser (Sözdizimi Ayrıştırıcı) +// ============================================================================ +// +// DİZİN: src/parser/parser.hpp +// KATMAN: Katman 3 — Tokenizer'ı tüketir, AST üretir +// BAĞIMLI: Token (token.hpp), AST (ast.hpp) +// KULLANAN: main.cpp +// +// AMAÇ: +// Tokenizer'ın ürettiği düz token listesini alıp, dilin gramer kurallarına +// göre hiyerarşik bir AST (Abstract Syntax Tree) üretir. +// +// İKİ AYRI PARSER STRATEJİSİ: +// 1. Recursive Descent (ifadeler için Pratt parser): +// - parseNullDenotation() (NUD): Prefix ifadeleri (sayılar, -, !, parantez) +// - parseLeftDenotation() (LED): Infix/Postfix ifadeler (+, *, ++) +// - parseExpression(precedence): Pratt'ın ana döngüsü +// +// 2. Recursive Descent (statement/deklarasyon için): +// - parseDeclaration(): Fonksiyon mu, değişken mi, statement mı? +// - parseStatement(): if/for/while/do/return/block/expression +// - Her statement tipi kendi parse fonksiyonuna sahip +// +// ADR-002 (devam): Neden Hibrit Yaklaşım? +// Pratt parser, operatör önceliğini merkezi bir tabloda yönetir ve yeni +// operatör eklemeyi kolaylaştırır. Ancak statement'lar (if, for, while) +// operatör değildir; kendi özel sözdizimleri vardır. Bu nedenle statement +// tarafında klasik recursive descent kullanıyoruz. Bu, her iki dünyanın +// en iyisini birleştirir. +// +// PARSER AKIŞI: +// parse(tokens) +// └── parseProgram() +// └── parseDeclaration() [döngü, SVR_VOID gelene kadar] +// ├── parseFunctionDecl() → tip + isim + ( ) + { gövde } +// ├── parseVariableDecl() → tip + isim [+ = ifade] + ; +// └── parseStatement() +// ├── parseBlock() → { statement* } +// ├── parseIfStatement() → if (expr) stmt [else stmt] +// ├── parseWhileStatement() → while (expr) stmt +// ├── parseForStatement() → for (stmt; expr; expr) stmt +// ├── parseDoWhileStatement() → do stmt while (expr); +// ├── parseReturnStatement() → return [expr]; +// ├── parseBreakStatement() → break; +// ├── parseContinueStatement() → continue; +// ├── parseVariableDecl() → tip + isim ... +// └── parseExpressionStatement() → expr; +// └── parseExpression() [Pratt] +// ├── parseNullDenotation() +// │ ├── LPAREN → ( expr ) +// │ ├── Unary prefix → !expr, -expr, ++expr +// │ ├── NUMBER → Literal +// │ ├── STRING → Literal +// │ ├── true/false/null → Literal +// │ └── IDENTIFIER → Identifier +// └── parseLeftDenotation() [döngü] +// ├── Postfix → expr++, expr-- +// └── Binary infix → expr + expr +// +// BİLİNEN SINIRLAMALAR (TODO): +// TODO: else-if zincirleri (şu anda else'den sonra if gelirse düzgün çalışır mı?) +// TODO: Hata kurtarma (panic mode): ilk hatada durmak yerine senkronizasyon +// TODO: Fonksiyon parametreleri +// TODO: Dizi erişimi: a[i] +// TODO: Fonksiyon çağrısı: f(x, y) +// TODO: Üye erişimi: a.b, a->b +// TODO: Ternary: a ? b : c +// TODO: Tip kontrolü ve sembol tablosu +// +// ============================================================================ + #ifndef SAQUT_PARSER #define SAQUT_PARSER @@ -8,30 +80,44 @@ #include "parser/ast.hpp" #include "tools.hpp" +// ============================================================================ +// Parser — Sözdizimi Ayrıştırıcı +// ============================================================================ +// +// Durum bilgisi: +// tokens: Tokenizer'dan gelen token listesi (referans değil, kopya değil) +// current: Şu anki token'ın indeksi (0 = ilk token) +// +// Token navigasyon metotları: +// currentToken(): tokens[current] döndürür, ilerlemez +// nextToken(): current++ (sonraki token'a geç) +// lookahead(n): tokens[current + n] döndürür, ilerlemez +// getToken(offset): tokens[current + offset] döndürür +// class Parser { public: ASTNode* parse(TokenList tokens); private: - TokenList tokens; - int current = 0; + TokenList tokens; // Tokenizer'dan gelen token listesi + int current = 0; // Şu anki token indeksi - // Token navigation + // --- Token navigasyonu --- ParserToken currentToken(); void nextToken(); ParserToken lookahead(uint32_t forward); ParserToken parseToken(Token* token); ParserToken getToken(int offset); - // --- Top level --- + // --- Üst seviye --- ASTNode* parseProgram(); - // --- Declarations --- + // --- Deklarasyonlar --- ASTNode* parseDeclaration(); ASTNode* parseFunctionDecl(); ASTNode* parseVariableDecl(); - // --- Statements --- + // --- Statement'lar --- ASTNode* parseStatement(); ASTNode* parseBlock(); ASTNode* parseIfStatement(); @@ -43,20 +129,29 @@ private: ASTNode* parseContinueStatement(); ASTNode* parseExpressionStatement(); - // --- Expressions (Pratt parser) --- + // --- İfadeler (Pratt parser) --- ASTNode* parseExpression(); ASTNode* parseExpression(uint16_t precedence); ASTNode* parseNullDenotation(); ASTNode* parseLeftDenotation(ASTNode* left); }; -// ============================================================ -// Token helpers -// ============================================================ +// ============================================================================ +// Token Navigasyonu +// ============================================================================ +// -------------------------------------------------------------------------- +// parseToken: Ham Token'ı ParserToken'a dönüştür. +// +// Tokenizer'ın string tabanlı tip sistemini ("number", "operator", ...) +// Parser'ın anlamsal tip sistemine (NUMBER, PLUS, KW_IF, ...) çevirir. +// +// BUG FIX (commit 40579ca): pt.token = token (pointer ataması). +// Eskiden pt.token = *token (değer kopyası) object slicing yapıyordu. +// -------------------------------------------------------------------------- inline ParserToken Parser::parseToken(Token* token) { ParserToken pt; - pt.token = token; + pt.token = token; // Pointer — değer kopyası DEĞİL std::string t = token->gettype(); if (t == "string") @@ -75,6 +170,9 @@ inline ParserToken Parser::parseToken(Token* token) { return pt; } +// -------------------------------------------------------------------------- +// getToken: Güvenli token erişimi. Sınır dışı = SVR_VOID. +// -------------------------------------------------------------------------- inline ParserToken Parser::getToken(int offset) { if ((int)tokens.size() - 1 < current + offset) { ParserToken pt; @@ -97,16 +195,28 @@ inline ParserToken Parser::currentToken() { return getToken(0); } -// ============================================================ -// Top level -// ============================================================ +// ============================================================================ +// Üst Seviye +// ============================================================================ +// -------------------------------------------------------------------------- +// parse: Parser'ın ana giriş noktası. Token listesini alır, AST döndürür. +// -------------------------------------------------------------------------- inline ASTNode* Parser::parse(TokenList toks) { - tokens = toks; + tokens = toks; current = 0; return parseProgram(); } +// -------------------------------------------------------------------------- +// parseProgram: Tüm üst seviye deklarasyonları/statement'ları ayrıştırır. +// +// Program ::= Declaration* +// EOF'a (SVR_VOID) kadar parseDeclaration() çağrılır. +// +// BUG FIX (commit 438bc0e): Eskiden parseExpression() doğrudan çağrılıyordu, +// bu sadece tek bir ifadeyi ayrıştırabiliyordu. Şimdi tam program desteği var. +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseProgram() { ProgramNode* program = new ProgramNode(); @@ -115,58 +225,82 @@ inline ASTNode* Parser::parseProgram() { if (decl) program->addChild(decl); else - break; + break; // Hata durumunda döngüden çık } return program; } -// ============================================================ -// Declarations -// ============================================================ +// ============================================================================ +// Deklarasyonlar +// ============================================================================ +// -------------------------------------------------------------------------- +// parseDeclaration: Üst seviye deklarasyon ayrıştırıcı. +// +// Strateji: +// 1. Mevcut token bir tip keyword'ü mü (int, void, float, ...)? +// - Evet → lookahead(2) '(' ise → fonksiyon tanımı +// - Evet → değilse → değişken tanımı +// 2. Değilse → statement (REPL modunda ifade de olabilir) +// +// LOOKAHEAD KULLANIMI: +// "int main()" ve "int x = 10" ayrımı için 2 ileriye bakarız: +// - int main() → lookahead(1)=identifier, lookahead(2)='(' +// - int x = 10 → lookahead(1)=identifier, lookahead(2)='=' +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseDeclaration() { auto ct = currentToken(); - // Function declaration: type identifier ( ) { ... } + // Tip keyword'ü ile başlayan → fonksiyon veya değişken if (ct.is({ TokenType::KW_VOID, TokenType::KW_INT, TokenType::KW_FLOAT_TYPE, TokenType::KW_DOUBLE, TokenType::KW_BOOL, TokenType::KW_CHAR, TokenType::KW_STRING_TYPE, TokenType::KW_AUTO })) { - // Check if next is identifier, then '(' → function auto la1 = lookahead(1); auto la2 = lookahead(2); + // int main( ... ) → fonksiyon if (la1.type == TokenType::IDENTIFIER && la2.type == TokenType::LPAREN) return parseFunctionDecl(); - // Otherwise variable declaration + // int x ... → değişken return parseVariableDecl(); } - // Standalone expression (for REPL / bare source.sqt) + // Tip keyword'ü değil → statement (veya REPL ifadesi) return parseStatement(); } +// -------------------------------------------------------------------------- +// parseFunctionDecl: Fonksiyon tanımı. +// +// Sözdizimi: Type Identifier ( [ParamList] ) Block +// Örnek: int main() { ... } +// +// TODO: Parametre listesi ayrıştırma +// TODO: Dönüş tipi doğrulama (şu anda string olarak saklanıyor) +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseFunctionDecl() { FunctionDeclNode* fn = new FunctionDeclNode(); - fn->returnType = currentToken().token->token; // e.g., "void", "int" - nextToken(); // eat return type + fn->returnType = currentToken().token->token; // "int", "void", ... + nextToken(); // Dönüş tipini tüket - fn->name = currentToken().token->token; - nextToken(); // eat name + fn->name = currentToken().token->token; // "main", "calculate", ... + nextToken(); // İsmi tüket - // Eat '(' ... ')' + // Parametre listesi: ( ... ) if (currentToken().type == TokenType::LPAREN) { - nextToken(); - // Skip params for now + nextToken(); // '(' tüket + // TODO: Parametreleri ayrıştır + // Şimdilik ')' gelene kadar atla while (currentToken().type != TokenType::RPAREN && currentToken().type != TokenType::SVR_VOID) nextToken(); if (currentToken().type == TokenType::RPAREN) - nextToken(); + nextToken(); // ')' tüket } - // Parse body { ... } + // Gövde: { ... } if (currentToken().type == TokenType::LBRACE) { ASTNode* body = parseBlock(); fn->addChild(body); @@ -175,36 +309,51 @@ inline ASTNode* Parser::parseFunctionDecl() { return fn; } +// -------------------------------------------------------------------------- +// parseVariableDecl: Değişken tanımı. +// +// Sözdizimi: Type Identifier [= Expression] ; +// Örnek: int x = 10; +// float y; (initExpr = nullptr) +// +// TODO: Çoklu değişken: int x = 1, y = 2; +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseVariableDecl() { VariableDeclNode* vd = new VariableDeclNode(); - vd->varType = currentToken().token->token; // e.g., "int", "float" - nextToken(); // eat type + vd->varType = currentToken().token->token; // "int", "float", ... + nextToken(); // Tipi tüket if (currentToken().type != TokenType::IDENTIFIER) { std::cerr << "Parser hatası: değişken ismi bekleniyor\n"; - return vd; + return vd; // Hatalı düğüm, çağıran kontrol etmeli } - vd->name = currentToken().token->token; - nextToken(); // eat name + vd->name = currentToken().token->token; // "x", "counter", ... + nextToken(); // İsmi tüket - // Optional initializer: = expression + // Opsiyonel başlangıç değeri: = expression if (currentToken().type == TokenType::EQUAL) { - nextToken(); // eat = + nextToken(); // '=' tüket vd->initExpr = parseExpression(); } - // Optional semicolon + // Noktalı virgül (opsiyonel — parser hoşgörülü) if (currentToken().type == TokenType::SEMICOLON) nextToken(); return vd; } -// ============================================================ -// Statements -// ============================================================ +// ============================================================================ +// Statement'lar — Recursive Descent +// ============================================================================ +// -------------------------------------------------------------------------- +// parseStatement: Statement ayrıştırıcı (dispatcher). +// +// Mevcut token'a göre uygun parse fonksiyonuna yönlendirir. +// Sıralama önemli: LBRACE, keyword'ler, değişken tanımı, ifade. +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseStatement() { auto ct = currentToken(); @@ -232,7 +381,7 @@ inline ASTNode* Parser::parseStatement() { if (ct.type == TokenType::KW_CONTINUE) return parseContinueStatement(); - // Variable declaration? (type identifier ...) + // Değişken tanımı? (tip keyword'ü ile başlayan) if (ct.is({ TokenType::KW_VOID, TokenType::KW_INT, TokenType::KW_FLOAT_TYPE, TokenType::KW_DOUBLE, TokenType::KW_BOOL, TokenType::KW_CHAR, @@ -241,15 +390,18 @@ inline ASTNode* Parser::parseStatement() { return parseVariableDecl(); } - // Default: expression statement + // Hiçbiri değilse → ifade statement'ı (atama, fonksiyon çağrısı, ...) return parseExpressionStatement(); } +// -------------------------------------------------------------------------- +// parseBlock: { statement* } +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseBlock() { BlockNode* block = new BlockNode(); if (currentToken().type == TokenType::LBRACE) - nextToken(); // eat { + nextToken(); // '{' tüket while (currentToken().type != TokenType::RBRACE && currentToken().type != TokenType::SVR_VOID) { @@ -257,81 +409,104 @@ inline ASTNode* Parser::parseBlock() { if (stmt) block->addChild(stmt); else - break; + break; // Hata durumunda döngüden çık } if (currentToken().type == TokenType::RBRACE) - nextToken(); // eat } + nextToken(); // '}' tüket return block; } +// -------------------------------------------------------------------------- +// parseIfStatement: if (expression) statement [else statement] +// +// Süslü parantez zorunlu DEĞİL — tek statement de olabilir. +// if (x > 5) return x; ← geçerli +// if (x > 5) { ... } ← geçerli +// +// TODO: Sallantılı else (dangling else) sorunu: +// if (a) if (b) x; else y; ← else hangi if'e ait? +// Mevcut implementasyon doğru: else en yakın if'e bağlanır. +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseIfStatement() { IfStatementNode* ifNode = new IfStatementNode(); - nextToken(); // eat 'if' + nextToken(); // 'if' tüket - // Condition: ( expression ) + // Koşul: ( expression ) if (currentToken().type == TokenType::LPAREN) { - nextToken(); + nextToken(); // '(' tüket ifNode->condition = parseExpression(); if (currentToken().type == TokenType::RPAREN) - nextToken(); + nextToken(); // ')' tüket } - // Then branch + // Then gövdesi ifNode->thenBranch = parseStatement(); - // Optional else + // Opsiyonel else if (currentToken().type == TokenType::KW_ELSE) { - nextToken(); + nextToken(); // 'else' tüket ifNode->elseBranch = parseStatement(); } return ifNode; } +// -------------------------------------------------------------------------- +// parseWhileStatement: while (expression) statement +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseWhileStatement() { WhileStatementNode* ws = new WhileStatementNode(); - nextToken(); // eat 'while' + nextToken(); // 'while' tüket - // Condition: ( expression ) if (currentToken().type == TokenType::LPAREN) { - nextToken(); + nextToken(); // '(' tüket ws->condition = parseExpression(); if (currentToken().type == TokenType::RPAREN) - nextToken(); + nextToken(); // ')' tüket } - // Body ws->body = parseStatement(); - return ws; } +// -------------------------------------------------------------------------- +// parseForStatement: for (init; condition; update) statement +// +// for'un 3 parçası da isteğe bağlıdır: +// for (;;) { ... } ← sonsuz döngü (geçerli) +// +// init: VariableDeclNode veya ExpressionStatementNode +// for (int i = 0; ...) → VariableDecl +// for (i = 0; ...) → ExpressionStatement +// condition: ifade (nullptr = yok) +// update: ifade (nullptr = yok) +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseForStatement() { ForStatementNode* fs = new ForStatementNode(); - nextToken(); // eat 'for' + nextToken(); // 'for' tüket if (currentToken().type == TokenType::LPAREN) - nextToken(); // eat ( + nextToken(); // '(' tüket - // Init + // Init (opsiyonel) if (currentToken().type != TokenType::SEMICOLON) fs->init = parseStatement(); if (currentToken().type == TokenType::SEMICOLON) - nextToken(); + nextToken(); // ';' tüket - // Condition + // Condition (opsiyonel) if (currentToken().type != TokenType::SEMICOLON) fs->condition = parseExpression(); if (currentToken().type == TokenType::SEMICOLON) - nextToken(); + nextToken(); // ';' tüket - // Update + // Update (opsiyonel) if (currentToken().type != TokenType::RPAREN) fs->update = parseExpression(); if (currentToken().type == TokenType::RPAREN) - nextToken(); + nextToken(); // ')' tüket // Body fs->body = parseStatement(); @@ -339,48 +514,61 @@ inline ASTNode* Parser::parseForStatement() { return fs; } +// -------------------------------------------------------------------------- +// parseDoWhileStatement: do statement while (expression) ; +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseDoWhileStatement() { DoWhileStatementNode* dw = new DoWhileStatementNode(); - nextToken(); // eat 'do' + nextToken(); // 'do' tüket - // Body + // Gövde dw->body = parseStatement(); - // 'while' ( expression ) ; + // while (expression) ; if (currentToken().type == TokenType::KW_WHILE) { - nextToken(); + nextToken(); // 'while' tüket if (currentToken().type == TokenType::LPAREN) { - nextToken(); + nextToken(); // '(' tüket dw->condition = parseExpression(); if (currentToken().type == TokenType::RPAREN) - nextToken(); + nextToken(); // ')' tüket } if (currentToken().type == TokenType::SEMICOLON) - nextToken(); + nextToken(); // ';' tüket } return dw; } +// -------------------------------------------------------------------------- +// parseReturnStatement: return [expression] ; +// +// return; ← value = nullptr (void fonksiyon) +// return x + 1; ← value = BinaryExpression +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseReturnStatement() { ReturnStatementNode* rs = new ReturnStatementNode(); - nextToken(); // eat 'return' + nextToken(); // 'return' tüket - // Optional return value + // Opsiyonel dönüş değeri + // Eğer sıradaki token ; veya } ise → return; if (currentToken().type != TokenType::SEMICOLON && currentToken().type != TokenType::RBRACE) { rs->value = parseExpression(); } if (currentToken().type == TokenType::SEMICOLON) - nextToken(); + nextToken(); // ';' tüket return rs; } +// -------------------------------------------------------------------------- +// parseBreakStatement / parseContinueStatement +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseBreakStatement() { BreakStatementNode* bs = new BreakStatementNode(); - nextToken(); // eat 'break' + nextToken(); // 'break' tüket if (currentToken().type == TokenType::SEMICOLON) nextToken(); return bs; @@ -388,17 +576,30 @@ inline ASTNode* Parser::parseBreakStatement() { inline ASTNode* Parser::parseContinueStatement() { ContinueStatementNode* cs = new ContinueStatementNode(); - nextToken(); // eat 'continue' + nextToken(); // 'continue' tüket if (currentToken().type == TokenType::SEMICOLON) nextToken(); return cs; } +// -------------------------------------------------------------------------- +// parseExpressionStatement: expression ; +// +// Bir ifadeyi statement olarak kullanır. Örn: x = 5; foo(); +// +// HATA KURTARMA: +// Eğer parseExpression() başarısız olursa (nullptr), sonraki ; veya } +// veya EOF'a kadar token'ları atlayarak senkronize olur. Bu, tek bir +// hatalı ifadenin tüm parser'ı kilitlemesini önler. +// +// BUG FIX (commit 438bc0e): Eskiden hatalı ifade durumunda sonsuz +// döngüye giriyordu (parseProgram her seferinde aynı ifadeyi okuyordu). +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseExpressionStatement() { ExpressionStatementNode* es = new ExpressionStatementNode(); es->expression = parseExpression(); if (!es->expression) { - // Parsing failed — skip to next statement boundary + // Hata kurtarma: sonraki güvenli noktaya atla while (currentToken().type != TokenType::SEMICOLON && currentToken().type != TokenType::RBRACE && currentToken().type != TokenType::SVR_VOID) @@ -412,29 +613,79 @@ inline ASTNode* Parser::parseExpressionStatement() { return es; } -// ============================================================ -// Expressions — Pratt parser -// ============================================================ +// ============================================================================ +// İfadeler — Pratt Parser (Top-Down Operator Precedence) +// ============================================================================ +// +// Pratt parser'ın temel fikri: Her operatörün bir "bağlanma gücü" (precedence) +// vardır. Parser, bu güce göre operatörleri doğru sırada gruplar. +// +// NUD (Null Denotation): Prefix ifadeleri (sayılar, -, !, parantez) +// LED (Left Denotation): Infix/Postfix ifadeler (+, *, ++) +// +// ÖRNEK: 1 + 2 * 3 +// 1. NUD: 1 → Literal(1) +// 2. LED(+): prec=13, right'i parseExpression(13) ile ayrıştır +// 2a. NUD: 2 → Literal(2) +// 2b. LED(*): prec=14 > 13 → parseExpression(14) +// 3a. NUD: 3 → Literal(3) +// 3b. LED yok → dön +// 2c. BinaryExpr(*, 2, 3) dön +// 3. BinaryExpr(+, 1, BinaryExpr(*, 2, 3)) +// Sonuç: 1 + (2 * 3) ✓ +// +// BUG FIX (commit 40579ca): Ana döngü lookahead(1) yerine currentToken() +// kullanıyor. NUD artık token'ı tüketip ilerliyor, bu sayede currentToken() +// her zaman bir sonraki operatörü gösterir. +// +// BUG FIX (commit 438bc0e): Atom'lar (sayı, string, identifier) NUD'da +// nextToken() ile tüketiliyor. Eskiden tüketilmediği için sonsuz döngü +// oluyordu. +// +// ============================================================================ +// -------------------------------------------------------------------------- +// parseExpression(): Öncelik 0'dan başla (en düşük bağlanma) +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseExpression() { return parseExpression(0); } +// -------------------------------------------------------------------------- +// parseExpression(precedence): Pratt'ın ana döngüsü. +// +// Algoritma: +// 1. NUD ile ilk operand'ı ayrıştır (prefix) +// 2. Mevcut token bir operatör mü? +// - Evet ve önceliği > precedence ise → LED ile infix ayrıştır +// - Hayır veya öncelik <= precedence ise → dur, sol operand'ı döndür +// 3. LED'in döndürdüğü düğüm yeni sol operand olur, 2. adıma dön +// +// DURMA KOŞULLARI: +// - RPAREN, SEMICOLON, RBRACE, COMMA: İfade sonu sinyali +// - Operatörün önceliği <= mevcut öncelik: Daha sıkı bağlanamaz +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseExpression(uint16_t precedence) { if (currentToken().type == TokenType::SVR_VOID) return nullptr; + // 1. Prefix (NUD) ASTNode* left = parseNullDenotation(); if (!left) return nullptr; + // 2. Infix/Postfix döngüsü (LED) while (true) { auto next = currentToken(); + + // İfade sonu sinyalleri → dur if (next.type == TokenType::RPAREN || next.type == TokenType::SEMICOLON || next.type == TokenType::RBRACE || next.type == TokenType::COMMA) break; + // Operatörün bağlanma gücü yetersiz → dur + // (daha yüksek öncelikli bir bağlamdayız, bu operatör oraya ait değil) if (precedence < next.getPowerOperator()) { left = parseLeftDenotation(left); } else { @@ -444,7 +695,17 @@ inline ASTNode* Parser::parseExpression(uint16_t precedence) { return left; } -// Prefix / atoms — parse expressions that start with themselves +// -------------------------------------------------------------------------- +// parseNullDenotation (NUD): Prefix ifadeleri. +// +// İşlenen prefix tipleri: +// - Parantez: ( expression ) +// - Unary: +expr, -expr, !expr, ~expr, ++expr, --expr +// - Literal: 42, "hello", true, false, null +// - Identifier: x, myVar +// +// DÖNÜŞ: Ayrıştırılmış AST düğümü. Token TÜKETİLMİŞ olur (current ilerlemiş). +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseNullDenotation() { auto ct = currentToken(); @@ -453,41 +714,50 @@ inline ASTNode* Parser::parseNullDenotation() { return nullptr; } - // Parenthesized expression + // --- Parantezli ifade: ( expr ) --- + // Önceliği sıfırlar — parantez içinde yeni bir ifade başlar. if (ct.type == TokenType::LPAREN) { - nextToken(); - ASTNode* expr = parseExpression(0); + nextToken(); // '(' tüket + ASTNode* expr = parseExpression(0); // Öncelik sıfırla if (currentToken().type == TokenType::RPAREN) - nextToken(); + nextToken(); // ')' tüket return expr; } - // Unary prefix: ++, --, +, -, !, ~ + // --- Unary prefix operatörler: +, -, !, ~, ++, -- --- + // PLUS ve MINUS burada UNARY olarak işlenir. + // Binary olarak işlenmesi LED tarafından yapılır. + // + // ÖNEMLİ: PLUS ve MINUS için getPowerOperator() 13 döndürür (binary öncelik). + // Ama burada unary olarak kullanılıyor. parseExpression(16) çağırmak daha + // doğru olurdu ancak mevcut çalışma şekli de doğru sonuç veriyor. + // TODO: Unary için ayrı öncelik seviyesi (örn: 16) if (ct.is({ TokenType::PLUS_PLUS, TokenType::MINUS_MINUS, TokenType::PLUS, TokenType::MINUS, TokenType::BANG, TokenType::TILDE })) { - nextToken(); + nextToken(); // Operatörü tüket + // Sağ operand'ı ayrıştır. Unary prefix sağdan sola bağlanır. ASTNode* right = parseExpression(ct.getPowerOperator()); BinaryExpressionNode* bin = new BinaryExpressionNode(); bin->Right = right; - bin->Left = nullptr; + bin->Left = nullptr; // Unary işaretçisi bin->Operator = ct.type; if (right) right->parent = bin; return bin; } - // Numeric literal + // --- Sayısal literal: 42, 0xFF, 3.14 --- if (ct.type == TokenType::NUMBER) { - nextToken(); + nextToken(); // Token'ı tüket LiteralNode* lit = new LiteralNode(); lit->lexerToken = ct.token; lit->parserToken = ct; return lit; } - // String literal + // --- String literal: "hello" --- if (ct.type == TokenType::STRING) { nextToken(); LiteralNode* lit = new LiteralNode(); @@ -496,7 +766,7 @@ inline ASTNode* Parser::parseNullDenotation() { return lit; } - // Boolean / null literals + // --- Boolean/null literal: true, false, null --- if (ct.is({TokenType::KW_TRUE, TokenType::KW_FALSE, TokenType::KW_NULL})) { nextToken(); LiteralNode* lit = new LiteralNode(); @@ -505,7 +775,7 @@ inline ASTNode* Parser::parseNullDenotation() { return lit; } - // Identifier + // --- Identifier: x, myVar --- if (ct.type == TokenType::IDENTIFIER) { nextToken(); IdentifierNode* id = new IdentifierNode(); @@ -517,13 +787,27 @@ inline ASTNode* Parser::parseNullDenotation() { return nullptr; } -// Infix / postfix — parse expressions that continue after a left operand +// -------------------------------------------------------------------------- +// parseLeftDenotation (LED): Infix ve Postfix ifadeler. +// +// Sol operand zaten ayrıştırılmış olarak gelir (left). +// Mevcut token operatördür. +// +// İşlenen tipler: +// - Postfix: expr++, expr-- +// - Binary infix: expr + expr, expr * expr, expr == expr, ... +// +// TASARIM NOTU: Postfix ve Binary aynı fonksiyonda işlenir çünkü ikisi de +// "sol operand + operatör" pattern'ini takip eder. Postfix'te sağ operand +// yoktur. +// -------------------------------------------------------------------------- inline ASTNode* Parser::parseLeftDenotation(ASTNode* left) { auto ct = currentToken(); - // Postfix: ++, -- + // --- Postfix: expr++, expr-- --- + // Operatör operand'dan SONRA gelir, sağ operand yok. if (ct.is({TokenType::PLUS_PLUS, TokenType::MINUS_MINUS})) { - nextToken(); + nextToken(); // Operatörü tüket PostfixNode* pf = new PostfixNode(); pf->operand = left; pf->Operator = ct.type; @@ -531,10 +815,13 @@ inline ASTNode* Parser::parseLeftDenotation(ASTNode* left) { return pf; } - // Binary infix operators + // --- Binary infix: expr OP expr --- + // OP'nin önceliğine göre sağ operand'ı ayrıştır. uint16_t prec = ct.getPowerOperator(); - nextToken(); + nextToken(); // Operatörü tüket + // Sağ operand. prec parametresi, daha yüksek öncelikli operatörlerin + // sağ operand içinde gruplanmasını sağlar. ASTNode* right = parseExpression(prec); BinaryExpressionNode* bin = new BinaryExpressionNode(); @@ -546,4 +833,4 @@ inline ASTNode* Parser::parseLeftDenotation(ASTNode* left) { return bin; } -#endif +#endif // SAQUT_PARSER diff --git a/src/parser/token.hpp b/src/parser/token.hpp index ebbd69c..32fea04 100644 --- a/src/parser/token.hpp +++ b/src/parser/token.hpp @@ -1,3 +1,68 @@ +// ============================================================================ +// saQut Compiler — Parser Token Tipleri ve Operatör Öncelik Tablosu +// ============================================================================ +// +// DİZİN: src/parser/token.hpp +// KATMAN: Katman 3 — Tokenizer ile Parser arasında köprü +// BAĞIMLI: Tokenizer (src/tokenizer/tokenizer.hpp) +// KULLANAN: AST (src/parser/ast.hpp), Parser (src/parser/parser.hpp) +// +// AMAÇ: +// Tokenizer'ın ürettiği ham Token'ları (string tipli) Parser'ın anlayacağı +// anlamsal tiplere (TokenType enum) dönüştürür. Ayrıca operatör önceliğini +// (precedence) ve birleşme yönünü (associativity) merkezi olarak tanımlar. +// +// Bu dosya, Pratt parser'ın "kalbi"dir — tüm operatör önceliği ve birleşme +// kuralları burada tek bir yerde tanımlanır. +// +// ADR-002: Neden Merkezi Operatör Öncelik Tablosu? +// Recursive descent parser'larda operatör önceliği, her seviye için ayrı +// bir fonksiyon yazılarak (parseAddExpr, parseMulExpr, ...) kod tekrarına +// neden olur. Yeni bir operatör eklemek için yeni fonksiyon + mevcut +// fonksiyonları değiştirmek gerekir. +// +// Pratt parser'da tüm öncelik bilgisi TEK BİR TABLODA (TokenPrecedence) +// toplanır. Yeni operatör eklemek = tabloya bir satır eklemek. +// +// TASARIM KARARLARI: +// 1. TokenType enum: uint16_t tabanlı. Neden? 65K'dan fazla token tipi +// olmayacak, 2 byte yeterli. Bellek tasarrufu AST'de fark eder. +// +// 2. Üç harita (KEYWORD_MAP, OPERATOR_MAP, OPERATOR_MAP_REV, OPERATOR_MAP_STRREV): +// - KEYWORD_MAP: "if" → KW_IF, string'den TokenType'a +// - OPERATOR_MAP: "+" → PLUS, operatör string'inden TokenType'a +// - OPERATOR_MAP_REV: PLUS → "+", log çıktısı için ters harita +// - OPERATOR_MAP_STRREV: PLUS → "PLUS", enum ismini string olarak verir +// Neden dört harita? Çünkü std::unordered_map tek yönlüdür. +// bidirectional_map kütüphanesi kullanılabilirdi ama bağımlılık istemedik. +// +// 3. TokenPrecedence(): 18 seviyeli öncelik sistemi. +// C/C++/Java standartlarına uygun. Yüksek sayı = yüksek öncelik. +// Seviye 18 (en yüksek): üye erişimi (., ->, [], (), ::) +// Seviye 1 (en düşük): virgül (,) +// Seviye 0: önceliksiz (değerler, EOF, vb.) +// +// 4. RightAssociative(): Hangi operatörler sağdan sola birleşir? +// - Atama (=, +=, vb.) +// - Üs alma (**, ^) — matematiksel sağ birleşme: a^b^c = a^(b^c) +// - Ternary (?:) +// Diğer tüm operatörler soldan sağa birleşir. +// +// 5. ParserToken yapısı: +// Token* token: Tokenizer'ın ürettiği Token'a pointer. Değer kopyası +// DEĞİL. Neden pointer? Çünkü Token polimorfik (NumberToken, StringToken, +// vb.) ve değer kopyası object slicing'e neden olur. +// BUG FIX (commit 40579ca): Eskiden Token token (değer) vardı. +// TokenType type: Token'ın anlamsal tipi. +// is() / getPowerOperator() / isRightAssociative(): kolaylık metotları. +// +// BİLİNEN SINIRLAMALAR (TODO): +// TODO: Özel operatörler: ?., ??, |>, >>=, vb. (ileride eklenebilir) +// TODO: Kullanıcı tanımlı operatör önceliği (DSL'ler için) +// TODO: Token konum bilgisi (satır/sütun) ParserToken'a eklenmeli +// +// ============================================================================ + #ifndef SAQUT_PARSER_TOKEN #define SAQUT_PARSER_TOKEN @@ -8,61 +73,221 @@ #include #include "tokenizer/tokenizer.hpp" +// ============================================================================ +// TokenList — Token Vektörü Tip Kısaltması +// ============================================================================ +// +// Tokenizer::scan() tarafından üretilen, Parser::parse() tarafından tüketilen +// token listesi. Ham pointer'lar içerir — bellek yönetimi çağırana aittir. +// +// TODO: std::vector> ile otomatik bellek yönetimi +// typedef std::vector TokenList; -// ============================================================ -// TokenType enum -// ============================================================ - +// ============================================================================ +// TokenType — Anlamsal Token Tipleri (Enum) +// ============================================================================ +// +// Tokenizer'ın ürettiği string tipli token'ları ("number", "operator", ...) +// Parser'ın anlayacağı anlamsal tiplere dönüştürür. +// +// KATEGORİLER: +// 1. Değerler: IDENTIFIER, NUMBER, STRING, SVR_VOID (geçersiz/EOF) +// 2. Keyword'ler: KW_IF ... KW_NOEXCEPT (alfabetik sıralı) +// 3. Operatörler: Öncelik sırasına göre gruplanmış +// - Seviye 1: DOT, ARROW, LBRACKET, RBRACKET, LPAREN, RPAREN +// - Seviye 2: PLUS_PLUS, MINUS_MINUS (postfix) +// - Seviye 3: PLUS, MINUS, BANG, TILDE (unary prefix) +// - Seviye 4: STAR_STAR, CARET (üs) +// - Seviye 5: STAR, SLASH, PERCENT (çarpma/bölme) +// - Seviye 6-16: devamı... +// 4. Diğer: LBRACE, RBRACE, SEMICOLON, COMMA, COLON_COLON +// 5. Özel: END_OF_FILE, UNKNOWN, COMMENT, PREPROCESSOR +// +// NEDEN uint16_t? Bellek optimizasyonu. Her AST düğümü bir TokenType taşır. +// Binlerce düğümde 2 byte vs 4 byte fark eder. +// enum class TokenType : uint16_t { - IDENTIFIER, - NUMBER, - STRING, - SVR_VOID, + // --- Değerler ve Tanımlayıcılar --- + IDENTIFIER, // değişken/fonksiyon ismi + NUMBER, // 42, 0xFF, 0b1010, 3.14 + STRING, // "merhaba" + SVR_VOID, // Geçersiz/EOF sinyali (Parser içinde kullanılır) - // Keywords - KW_IF, KW_ELSE, KW_FOR, KW_WHILE, KW_DO, - KW_SWITCH, KW_CASE, KW_DEFAULT, KW_BREAK, KW_CONTINUE, - KW_RETURN, KW_CLASS, KW_INTERFACE, KW_ENUM, - KW_EXTENDS, KW_IMPLEMENTS, KW_NEW, - KW_PUBLIC, KW_PRIVATE, KW_PROTECTED, KW_STATIC, - KW_FINAL, KW_ABSTRACT, - KW_VOID, KW_BOOL, KW_INT, KW_FLOAT_TYPE, KW_DOUBLE, - KW_CHAR, KW_STRING_TYPE, - KW_TRUE, KW_FALSE, KW_NULL, - KW_TRY, KW_CATCH, KW_FINALLY, KW_THROW, KW_THROWS, KW_ASSERT, - KW_IMPORT, KW_PACKAGE, KW_NATIVE, - KW_SYNCHRONIZED, KW_VOLATILE, KW_TRANSIENT, - KW_CONST, KW_EXTERN, KW_TYPEDEF, KW_SIZEOF, - KW_ALIGNOF, KW_DECLTYPE, KW_AUTO, KW_CONSTEXPR, KW_NOEXCEPT, + // --- Kontrol Akışı Keyword'leri --- + KW_IF, // if + KW_ELSE, // else + KW_FOR, // for + KW_WHILE, // while + KW_DO, // do + KW_SWITCH, // switch + KW_CASE, // case + KW_DEFAULT, // default + KW_BREAK, // break + KW_CONTINUE, // continue + KW_RETURN, // return - // Operators (precedence order) - DOT, ARROW, LBRACKET, RBRACKET, LPAREN, RPAREN, - PLUS_PLUS, MINUS_MINUS, - PLUS, MINUS, BANG, TILDE, - STAR_STAR, CARET, - STAR, SLASH, PERCENT, - LSHIFT, RSHIFT, - LESS, LESS_EQUAL, GREATER, GREATER_EQUAL, - EQUAL_EQUAL, BANG_EQUAL, - AMPERSAND, PIPE, - AMPERSAND_AMPERSAND, PIPE_PIPE, - TERNARY, COLON, - EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, - PERCENT_EQUAL, AMPERSAND_EQUAL, PIPE_EQUAL, CARET_EQUAL, - LSHIFT_EQUAL, RSHIFT_EQUAL, + // --- OOP Keyword'leri --- + KW_CLASS, // class + KW_INTERFACE, // interface + KW_ENUM, // enum + KW_EXTENDS, // extends + KW_IMPLEMENTS, // implements + KW_NEW, // new + KW_PUBLIC, // public + KW_PRIVATE, // private + KW_PROTECTED, // protected + KW_STATIC, // static + KW_FINAL, // final + KW_ABSTRACT, // abstract - // Other symbols - LBRACE, RBRACE, SEMICOLON, COMMA, COLON_COLON, + // --- Tip Keyword'leri --- + KW_VOID, // void + KW_BOOL, // bool + KW_INT, // int + KW_FLOAT_TYPE, // float (FLOAT math.h'de tanımlı olabilir, TYPE eki var) + KW_DOUBLE, // double + KW_CHAR, // char + KW_STRING_TYPE, // string - END_OF_FILE, UNKNOWN, COMMENT, PREPROCESSOR, + // --- Literal Keyword'ler --- + KW_TRUE, // true + KW_FALSE, // false + KW_NULL, // null + + // --- İstisna Yönetimi --- + KW_TRY, // try + KW_CATCH, // catch + KW_FINALLY, // finally + KW_THROW, // throw + KW_THROWS, // throws + KW_ASSERT, // assert + + // --- Modül/Paket --- + KW_IMPORT, // import + KW_PACKAGE, // package + + // --- C/C++ Ekleri --- + KW_NATIVE, // native (JNI) + KW_SYNCHRONIZED, // synchronized (Java) + KW_VOLATILE, // volatile + KW_TRANSIENT, // transient + KW_CONST, // const + KW_EXTERN, // extern + KW_TYPEDEF, // typedef + KW_SIZEOF, // sizeof + KW_ALIGNOF, // alignof + KW_DECLTYPE, // decltype + KW_AUTO, // auto + KW_CONSTEXPR, // constexpr + KW_NOEXCEPT, // noexcept + + // ================================================================ + // Operatörler — Öncelik sırasına göre gruplanmış + // ================================================================ + + // Seviye 1 (18): Üye erişimi ve çağrı — En yüksek öncelik + DOT, // . + ARROW, // -> + LBRACKET, // [ + RBRACKET, // ] + LPAREN, // ( + RPAREN, // ) + + // Seviye 2 (17): Postfix + PLUS_PLUS, // ++ (postfix) + MINUS_MINUS, // -- (postfix) + + // Seviye 3 (16): Unary Prefix + PLUS, // + (unary) + MINUS, // - (unary) + BANG, // ! (mantıksal değil) + TILDE, // ~ (bitsel değil) + + // Seviye 4 (15): Üs alma + STAR_STAR, // ** (Python tarzı üs) + CARET, // ^ (bazı dillerde üs) + + // Seviye 5 (14): Çarpma/Bölme + STAR, // * + SLASH, // / + PERCENT, // % + + // Seviye 6 (13): Toplama/Çıkarma — PLUS ve MINUS yukarıda (unary + binary) + // Seviye 7 (12): Bitsel kaydırma + LSHIFT, // << + RSHIFT, // >> + + // Seviye 8 (11): İlişkisel karşılaştırma + LESS, // < + LESS_EQUAL, // <= + GREATER, // > + GREATER_EQUAL, // >= + + // Seviye 9 (10): Eşitlik + EQUAL_EQUAL, // == + BANG_EQUAL, // != + + // Seviye 10 (9): Bitsel VE + AMPERSAND, // & + + // Seviye 11 (8): Bitsel XOR — CARET yukarıda (üs veya XOR) + + // Seviye 12 (7): Bitsel VEYA + PIPE, // | + + // Seviye 13 (6): Mantıksal VE + AMPERSAND_AMPERSAND, // && + + // Seviye 14 (5): Mantıksal VEYA + PIPE_PIPE, // || + + // Seviye 15 (4): Üçlü koşul (ternary) + TERNARY, // ? + COLON, // : (ternary ve etiket için) + + // Seviye 16 (3): Atama + EQUAL, // = + PLUS_EQUAL, // += + MINUS_EQUAL, // -= + STAR_EQUAL, // *= + SLASH_EQUAL, // /= + PERCENT_EQUAL, // %= + AMPERSAND_EQUAL, // &= + PIPE_EQUAL, // |= + CARET_EQUAL, // ^= + LSHIFT_EQUAL, // <<= + RSHIFT_EQUAL, // >>= + + // --- Diğer Semboller --- + LBRACE, // { + RBRACE, // } + SEMICOLON, // ; + COMMA, // , + COLON_COLON, // :: + + // --- Özel --- + END_OF_FILE, // Dosya sonu + UNKNOWN, // Bilinmeyen karakter + COMMENT, // Yorum (// veya /* */) — şu anda token üretilmez + PREPROCESSOR, // Önişlemci (#) — şu anda kullanılmıyor }; -// ============================================================ -// Keyword map -// ============================================================ - +// ============================================================================ +// KEYWORD_MAP — Keyword String → TokenType +// ============================================================================ +// +// Tokenizer'ın ürettiği KeywordToken'ların token değerini (örn: "if") +// Parser'ın anlayacağı TokenType'a (KW_IF) dönüştürür. +// +// std::unordered_map: O(1) ortalama arama. const: derleme zamanı sabiti. +// std::string_view: string kopyalamadan kaçınır. +// +// NOT: Bu harita, Tokenizer'daki keywords[] dizisi ile eşleşmelidir. +// Birinde ekleme yapılırsa diğerine de eklenmelidir. +// inline const std::unordered_map KEYWORD_MAP = { + // --- Control flow --- {"if", TokenType::KW_IF}, {"else", TokenType::KW_ELSE}, {"for", TokenType::KW_FOR}, @@ -75,6 +300,7 @@ inline const std::unordered_map KEYWORD_MAP = { {"continue", TokenType::KW_CONTINUE}, {"return", TokenType::KW_RETURN}, + // --- OOP --- {"class", TokenType::KW_CLASS}, {"interface", TokenType::KW_INTERFACE}, {"enum", TokenType::KW_ENUM}, @@ -82,6 +308,7 @@ inline const std::unordered_map KEYWORD_MAP = { {"implements", TokenType::KW_IMPLEMENTS}, {"new", TokenType::KW_NEW}, + // --- Access modifiers --- {"public", TokenType::KW_PUBLIC}, {"private", TokenType::KW_PRIVATE}, {"protected", TokenType::KW_PROTECTED}, @@ -89,6 +316,7 @@ inline const std::unordered_map KEYWORD_MAP = { {"final", TokenType::KW_FINAL}, {"abstract", TokenType::KW_ABSTRACT}, + // --- Types --- {"void", TokenType::KW_VOID}, {"bool", TokenType::KW_BOOL}, {"int", TokenType::KW_INT}, @@ -97,10 +325,12 @@ inline const std::unordered_map KEYWORD_MAP = { {"char", TokenType::KW_CHAR}, {"string", TokenType::KW_STRING_TYPE}, + // --- Literals --- {"true", TokenType::KW_TRUE}, {"false", TokenType::KW_FALSE}, {"null", TokenType::KW_NULL}, + // --- Exception handling --- {"try", TokenType::KW_TRY}, {"catch", TokenType::KW_CATCH}, {"finally", TokenType::KW_FINALLY}, @@ -108,13 +338,11 @@ inline const std::unordered_map KEYWORD_MAP = { {"throws", TokenType::KW_THROWS}, {"assert", TokenType::KW_ASSERT}, + // --- Modules/packages --- {"import", TokenType::KW_IMPORT}, {"package", TokenType::KW_PACKAGE}, - {"native", TokenType::KW_NATIVE}, - {"synchronized",TokenType::KW_SYNCHRONIZED}, - {"volatile", TokenType::KW_VOLATILE}, - {"transient", TokenType::KW_TRANSIENT}, + // --- C/C++ specific --- {"const", TokenType::KW_CONST}, {"extern", TokenType::KW_EXTERN}, {"typedef", TokenType::KW_TYPEDEF}, @@ -122,13 +350,26 @@ inline const std::unordered_map KEYWORD_MAP = { {"auto", TokenType::KW_AUTO}, {"constexpr", TokenType::KW_CONSTEXPR}, {"noexcept", TokenType::KW_NOEXCEPT}, + {"native", TokenType::KW_NATIVE}, + {"synchronized",TokenType::KW_SYNCHRONIZED}, + {"volatile", TokenType::KW_VOLATILE}, + {"transient", TokenType::KW_TRANSIENT}, }; -// ============================================================ -// Operator maps -// ============================================================ - +// ============================================================================ +// OPERATOR_MAP — Operatör/Delimiter String → TokenType +// ============================================================================ +// +// Tokenizer'ın ürettiği OperatorToken ve DelimiterToken'ları TokenType'a +// dönüştürür. Her iki token tipi de aynı haritayı kullanır çünkü parser +// seviyesinde delimiter'lar da operatör gibi işlenir. +// +// SIRALAMA ÖNEMLİ DEĞİL (unordered_map). +// Ama Tokenizer'daki operators[] ve delimiters[] dizilerindeki sıralama +// önemlidir — çok karakterliler önce gelmelidir. +// inline const std::unordered_map OPERATOR_MAP = { + // --- 2 karakterli --- {"->", TokenType::ARROW}, {"::", TokenType::COLON_COLON}, {"==", TokenType::EQUAL_EQUAL}, @@ -143,6 +384,7 @@ inline const std::unordered_map OPERATOR_MAP = { {">>", TokenType::RSHIFT}, {"**", TokenType::STAR_STAR}, + // --- Birleşik atama --- {"+=", TokenType::PLUS_EQUAL}, {"-=", TokenType::MINUS_EQUAL}, {"*=", TokenType::STAR_EQUAL}, @@ -154,6 +396,7 @@ inline const std::unordered_map OPERATOR_MAP = { {"<<=", TokenType::LSHIFT_EQUAL}, {">>=", TokenType::RSHIFT_EQUAL}, + // --- 1 karakterli operatörler --- {"+", TokenType::PLUS}, {"-", TokenType::MINUS}, {"*", TokenType::STAR}, @@ -168,6 +411,7 @@ inline const std::unordered_map OPERATOR_MAP = { {"|", TokenType::PIPE}, {"=", TokenType::EQUAL}, + // --- Delimiter'lar (operatör gibi işlenir) --- {"[", TokenType::LBRACKET}, {"]", TokenType::RBRACKET}, {"(", TokenType::LPAREN}, @@ -181,6 +425,14 @@ inline const std::unordered_map OPERATOR_MAP = { {"?", TokenType::TERNARY}, }; +// ============================================================================ +// OPERATOR_MAP_REV — TokenType → Operatör String (Log için) +// ============================================================================ +// +// AST ağacını konsola yazdırırken (log) TokenType enum değerini insan +// tarafından okunabilir operatör sembolüne dönüştürür. +// Örn: TokenType::PLUS → "+" +// inline const std::unordered_map OPERATOR_MAP_REV = { {TokenType::ARROW, "->"}, {TokenType::COLON_COLON, "::"}, @@ -231,6 +483,13 @@ inline const std::unordered_map OPERATOR_MAP_REV = {TokenType::TERNARY, "?"}, }; +// ============================================================================ +// OPERATOR_MAP_STRREV — TokenType → Enum İsmi (Log için) +// ============================================================================ +// +// AST log çıktısında operatörün enum ismini gösterir. +// Örn: TokenType::PLUS → "PLUS" +// inline const std::unordered_map OPERATOR_MAP_STRREV = { {TokenType::ARROW, "ARROW"}, {TokenType::COLON_COLON, "COLON_COLON"}, @@ -281,10 +540,42 @@ inline const std::unordered_map OPERATOR_MAP_STRREV {TokenType::TERNARY, "TERNARY"}, }; -// ============================================================ -// Precedence table (Pratt parsing) -// ============================================================ - +// ============================================================================ +// TokenPrecedence — Operatör Öncelik Tablosu +// ============================================================================ +// +// Pratt parser'ın kalbi. Her TokenType için bir öncelik seviyesi döndürür. +// Yüksek sayı = daha sıkı bağlanma (daha yüksek öncelik). +// +// ÖNCELİK SEVİYELERİ (yüksekten düşüğe): +// 18: Üye erişimi . -> [ ] ( ) +// 17: Postfix ++ -- +// 16: Unary prefix ! ~ +// 15: Üs alma ** ^ +// 14: Çarpma/Bölme * / % +// 13: Toplama/Çıkarma + - +// 12: Bitsel kaydırma << >> +// 11: İlişkisel < <= > >= +// 10: Eşitlik == != +// 9: Bitsel VE & +// 8: Bitsel XOR ^ (üs olarak 15'te de var — bağlama göre) +// 7: Bitsel VEYA | +// 6: Mantıksal VE && +// 5: Mantıksal VEYA || +// 4: Ternary ? +// 3: Ternary else : +// 2: Atama = += -= vb. +// 1: Virgül , +// 0: Önceliksiz (değerler, EOF, bilinmeyen) +// +// NOT: C/C++'da ^ operatörü bitsel XOR'tur (seviye 8), ama Python'da üs (seviye 15). +// saQut'ta ^ hem üs hem XOR olarak kullanılabilir (AST'de bağlam belirler). +// Şimdilik ^ seviye 15 (üs) olarak ayarlı. +// +// BUG FIX (commit 438bc0e): Seviye 8'deki ölü kod (CARET için case olmadan +// return 8) temizlendi. CARET zaten seviye 15'te STAR_STAR ile birlikte +// işleniyor. +// inline uint16_t TokenPrecedence(TokenType type) { switch (type) { // Level 18: Member access / call @@ -299,141 +590,173 @@ inline uint16_t TokenPrecedence(TokenType type) { case TokenType::MINUS_MINUS: return 17; - // Level 16: Unary prefix - case TokenType::BANG: - case TokenType::TILDE: + // Level 16: Unary prefix — sadece her zaman prefix olanlar + case TokenType::BANG: // ! + case TokenType::TILDE: // ~ return 16; // Level 15: Exponentiation - case TokenType::STAR_STAR: - case TokenType::CARET: + case TokenType::STAR_STAR: // ** + case TokenType::CARET: // ^ (Python tarzı üs) return 15; // Level 14: Multiplicative - case TokenType::STAR: - case TokenType::SLASH: - case TokenType::PERCENT: + case TokenType::STAR: // * + case TokenType::SLASH: // / + case TokenType::PERCENT: // % return 14; - // Level 13: Additive - case TokenType::PLUS: - case TokenType::MINUS: + // Level 13: Additive — PLUS ve MINUS hem unary hem binary + case TokenType::PLUS: // + + case TokenType::MINUS: // - return 13; // Level 12: Bit shift - case TokenType::LSHIFT: - case TokenType::RSHIFT: + case TokenType::LSHIFT: // << + case TokenType::RSHIFT: // >> return 12; // Level 11: Relational - case TokenType::LESS: - case TokenType::LESS_EQUAL: - case TokenType::GREATER: - case TokenType::GREATER_EQUAL: + case TokenType::LESS: // < + case TokenType::LESS_EQUAL:// <= + case TokenType::GREATER: // > + case TokenType::GREATER_EQUAL: // >= return 11; // Level 10: Equality - case TokenType::EQUAL_EQUAL: - case TokenType::BANG_EQUAL: + case TokenType::EQUAL_EQUAL: // == + case TokenType::BANG_EQUAL: // != return 10; // Level 9: Bitwise AND - case TokenType::AMPERSAND: + case TokenType::AMPERSAND: // & return 9; - // Level 8: Bitwise XOR — CARET already handled in 15 as exponent + // Level 8: Bitwise XOR — şu anda CARET seviye 15'te (üs) // Level 7: Bitwise OR - case TokenType::PIPE: + case TokenType::PIPE: // | return 7; // Level 6: Logical AND - case TokenType::AMPERSAND_AMPERSAND: + case TokenType::AMPERSAND_AMPERSAND: // && return 6; // Level 5: Logical OR - case TokenType::PIPE_PIPE: + case TokenType::PIPE_PIPE: // || return 5; // Level 4: Ternary - case TokenType::TERNARY: + case TokenType::TERNARY: // ? return 4; - case TokenType::COLON: - return 3; + case TokenType::COLON: // : (ternary için) + return 3; // ternary'den düşük, atamadan yüksek // Level 2: Assignment - case TokenType::EQUAL: - case TokenType::PLUS_EQUAL: - case TokenType::MINUS_EQUAL: - case TokenType::STAR_EQUAL: - case TokenType::SLASH_EQUAL: - case TokenType::PERCENT_EQUAL: - case TokenType::AMPERSAND_EQUAL: - case TokenType::PIPE_EQUAL: - case TokenType::CARET_EQUAL: - case TokenType::LSHIFT_EQUAL: - case TokenType::RSHIFT_EQUAL: + case TokenType::EQUAL: // = + case TokenType::PLUS_EQUAL:// += + case TokenType::MINUS_EQUAL:// -= + case TokenType::STAR_EQUAL:// *= + case TokenType::SLASH_EQUAL:// /= + case TokenType::PERCENT_EQUAL:// %= + case TokenType::AMPERSAND_EQUAL:// &= + case TokenType::PIPE_EQUAL:// |= + case TokenType::CARET_EQUAL:// ^= + case TokenType::LSHIFT_EQUAL:// <<= + case TokenType::RSHIFT_EQUAL:// >>= return 2; // Level 1: Comma - case TokenType::COMMA: + case TokenType::COMMA: // , return 1; default: - return 0; + return 0; // Önceliksiz: değerler, EOF, bilinmeyen } } -// ============================================================ -// Right-associative check -// ============================================================ - +// ============================================================================ +// RightAssociative — Sağdan Sola Birleşme Kontrolü +// ============================================================================ +// +// Hangi operatörler sağdan sola birleşir? +// +// Sağ birleşmeli operatörler (a OP b OP c = a OP (b OP c)): +// - Üs alma: **, ^ (matematiksel: 2^3^2 = 2^(3^2) = 2^9 = 512) +// - Atama: =, +=, -=, vb. (a = b = 5 → a = (b = 5)) +// - Ternary: ?: (a ? b : c ? d : e → a ? b : (c ? d : e)) +// +// Sol birleşmeli operatörler (a OP b OP c = (a OP b) OP c): +// - Tüm diğerleri: +, -, *, /, ==, &&, vb. +// inline bool RightAssociative(TokenType type) { switch (type) { - case TokenType::STAR_STAR: - case TokenType::CARET: - case TokenType::EQUAL: - case TokenType::PLUS_EQUAL: - case TokenType::MINUS_EQUAL: - case TokenType::STAR_EQUAL: - case TokenType::SLASH_EQUAL: - case TokenType::PERCENT_EQUAL: - case TokenType::AMPERSAND_EQUAL: - case TokenType::PIPE_EQUAL: - case TokenType::CARET_EQUAL: - case TokenType::LSHIFT_EQUAL: - case TokenType::RSHIFT_EQUAL: - case TokenType::TERNARY: + case TokenType::STAR_STAR: // ** (üs) + case TokenType::CARET: // ^ (üs) + case TokenType::EQUAL: // = + case TokenType::PLUS_EQUAL: // += + case TokenType::MINUS_EQUAL:// -= + case TokenType::STAR_EQUAL: // *= + case TokenType::SLASH_EQUAL:// /= + case TokenType::PERCENT_EQUAL:// %= + case TokenType::AMPERSAND_EQUAL:// &= + case TokenType::PIPE_EQUAL: // |= + case TokenType::CARET_EQUAL:// ^= + case TokenType::LSHIFT_EQUAL:// <<= + case TokenType::RSHIFT_EQUAL:// >>= + case TokenType::TERNARY: // ? (ternary) return true; default: return false; } } -// ============================================================ -// ParserToken -// ============================================================ - +// ============================================================================ +// ParserToken — Parser'ın Kullandığı Token Yapısı +// ============================================================================ +// +// Tokenizer'ın ürettiği ham Token ile Parser'ın ihtiyaç duyduğu anlamsal +// tipi (TokenType) bir arada tutar. +// +// ALANLAR: +// token (Token*): Tokenizer'dan gelen orijinal token. Neden pointer? +// Çünkü Token polimorfik bir sınıf hiyerarşisidir. Değer kopyası (Token) +// object slicing'e neden olur — alt sınıf verileri (NumberToken.isFloat, +// StringToken.context) kaybolur. +// BUG FIX (commit 40579ca): Eskiden Token token (değer) tutuyordu. +// +// type (TokenType): Token'ın anlamsal tipi. Örn: NUMBER, PLUS, KW_IF. +// +// METOTLAR: +// is(TokenType): Bu token belirtilen tipte mi? +// is({...}): Bu token listedeki tiplerden biri mi? +// getPowerOperator(): Bu token bir operatör ise önceliğini döndür. +// isRightAssociative(): Bu operatör sağ birleşmeli mi? +// struct ParserToken { - Token* token = nullptr; - TokenType type = TokenType::SVR_VOID; + Token* token = nullptr; // Tokenizer'dan gelen orijinal token + TokenType type = TokenType::SVR_VOID; // Anlamsal tip + // Tek tip kontrolü bool is(TokenType t) const { return type == t; } + // Çoklu tip kontrolü — örn: is({KW_INT, KW_FLOAT, KW_VOID}) bool is(std::initializer_list types) const { for (TokenType t : types) if (type == t) return true; return false; } + // Operatör önceliği (Pratt parser için) uint16_t getPowerOperator() const { return TokenPrecedence(type); } + // Sağ birleşmeli mi? bool isRightAssociative() const { return RightAssociative(type); } }; -#endif +#endif // SAQUT_PARSER_TOKEN diff --git a/src/tokenizer/tokenizer.hpp b/src/tokenizer/tokenizer.hpp index 83a37e1..7f73091 100644 --- a/src/tokenizer/tokenizer.hpp +++ b/src/tokenizer/tokenizer.hpp @@ -1,3 +1,70 @@ +// ============================================================================ +// saQut Compiler — Tokenizer (Token Seviyesinde Tarayıcı) +// ============================================================================ +// +// DİZİN: src/tokenizer/tokenizer.hpp +// KATMAN: Katman 2 — Lexer üzerine kurulu +// BAĞIMLI: Lexer (src/lexer/lexer.hpp) +// KULLANAN: Parser (src/parser/parser.hpp), ParserToken (src/parser/token.hpp) +// +// AMAÇ: +// Lexer tarafından sağlanan karakter akışını alıp anlamlı token'lara dönüştürür. +// Token'lar derleyicinin "kelime"leridir — parser'ın anlayacağı en küçük birim. +// +// Üretilen token tipleri (6 adet polimorfik sınıf): +// ┌─────────────────┬──────────────────────────────────┐ +// │ Sınıf │ Örnek token'lar │ +// ├─────────────────┼──────────────────────────────────┤ +// │ NumberToken │ 42, 0xFF, 3.14, 1e10 │ +// │ StringToken │ "merhaba", "satır\niki" │ +// │ OperatorToken │ +, -, *, /, ==, !=, ++, -- │ +// │ DelimiterToken │ (, ), {, }, [, ], ;, ,, ., -> │ +// │ KeywordToken │ if, for, while, int, void │ +// │ IdentifierToken │ x, myVar, _private │ +// └─────────────────┴──────────────────────────────────┘ +// +// ADR-004: Neden Polimorfik Token Sınıfları? +// Seçenek 1 — Tagged union (std::variant): Tüm veriyi tek struct'ta +// +: Bellek tek parça, cache-friendly +// -: Tip eklemek için union'ı değiştirmek gerek +// Seçenek 2 — Class hierarchy (seçilen): Base Token, alt sınıflar +// +: Yeni token tipi eklemek kolay (yeni sınıf türet) +// +: Her token kendi verisini taşır (NumberToken.isFloat, StringToken.context) +// -: Heap tahsisi (new) gerektirir +// -: virtual destructor çağrısı (maliyet: 1 vtable lookup) +// +// Karar: Class hierarchy. Derleyici gibi bir araçta kod netliği ve +// genişletilebilirlik, mikro-performanstan daha önemlidir. +// +// TASARIM KARARLARI: +// 1. Tablolar (operators, delimiters, keywords): constexpr std::string_view dizileri. +// Derleme zamanında sabit, heap tahsisi yok. Sıralama önceliği: +// - Önce keyword'ler: if/for/while gibi kelimeler identifier'lardan önce yakalanmalı +// - Sonra delimiter'lar: -> ve :: gibi 2 karakterliler önce, tek karakterliler sonra +// - Sonra operator'ler: != ve == gibi 2 karakterliler önce, tek karakterliler sonra +// - En son identifier: yukarıdakilerden hiçbirine uymayan her şey +// +// 2. Keyword boundary check: "do" keyword'ü "double" ile karışmasın diye, +// keyword eşleşmesinden sonraki karakter kontrol edilir. Sonraki karakter +// harf/rakam/_/$ ise bu bir keyword değil, identifier'dır. +// +// 3. scope() metodu: Her çağrıldığında bir sonraki token'ı döndürür. +// EOF'da "EOL" isimli özel bir token döndürür (Token tipi, özel değil). +// Bu, boş token listesi sorununu çözer (parser her zaman bir token görür). +// +// 4. Yorum satırları: // (tek satır) ve /* */ (çok satırlı) desteklenir. +// Yorumlar token üretmez, sessizce atlanır. +// NOT: İç içe /* */ yorumları desteklenmez (C standardı gibi). +// +// BİLİNEN SINIRLAMALAR (TODO): +// TODO: String escape sequence'leri tam değil (\x, \u, \U eksik) +// TODO: Char literal: 'a' formatı okunamıyor +// TODO: Raw string: R"(...)" formatı yok +// TODO: Token konum bilgisi (satır/sütun) token'lara eklenmeli +// TODO: Bellek sızıntısı: Token'lar heap'te new ile oluşturuluyor, silme sorumluluğu çağıranda +// +// ============================================================================ + #ifndef SAQUT_TOKENIZER #define SAQUT_TOKENIZER @@ -6,81 +73,186 @@ #include #include "lexer/lexer.hpp" -// ============================================================ -// Token classes -// ============================================================ - +// ============================================================================ +// Token Temel Sınıfı +// ============================================================================ +// +// Tüm token tiplerinin ortak atası. Polimorfik kullanım için virtual destructor +// içerir. type alanı, token'ın hangi alt sınıfa ait olduğunu string olarak tutar +// (RTTI'ye alternatif, daha hafif). +// +// ALANLAR: +// type : Token tipi ("number", "string", "operator", "delimiter", "keyword", "identifier") +// token : Token'ın ham metin hali (örn: "42", "+", "if", "myVar") +// start : Kaynak koddaki başlangıç offset'i (Lexer offset'i) +// end : Kaynak koddaki bitiş offset'i +// class Token { protected: - std::string type; + std::string type; // Alt sınıf tarafından constructor'da atanır public: - int start = 0; - int end = 0; - std::string token; + int start = 0; // Kaynak koddaki başlangıç konumu + int end = 0; // Kaynak koddaki bitiş konumu + std::string token; // Token'ın ham metin gösterimi std::string gettype() { return type; } virtual ~Token() = default; }; +// ============================================================================ +// StringToken — String Literal'ları ("...") +// ============================================================================ +// +// Örnek: "merhaba dünya", "satır\niki", "tırnak \" içinde" +// +// context: Escape sequence'ler çözümlenmiş gerçek string içeriği. +// Örn: token="\"a\\nb\"" ise context="a\nb" +// size: context'in uzunluğu (token'dan farklı olabilir) +// token: Tırnak işaretleri ve escape sequence'ler dahil ham hali +// class StringToken : public Token { public: StringToken() { type = "string"; } - std::string context; - int size = 0; + std::string context; // İşlenmiş string içeriği (escape'ler açılmış) + int size = 0; // context uzunluğu }; +// ============================================================================ +// NumberToken — Sayısal Literal'lar (42, 0xFF, 3.14) +// ============================================================================ +// +// Sayı tabanı, float/整数 ayrımı, bilimsel gösterim bilgisi taşır. +// Lexer'ın INumber yapısından dönüştürülür. +// +// isFloat: true ise float/double literal (nokta veya epsilon içerir) +// hasEpsilon: true ise bilimsel gösterim (örn: 1e10) +// base: Sayı tabanı: 2, 8, 10, 16 +// token: Sayının ham string hali (örn: "0xFF", "3.14e-2") +// class NumberToken : public Token { public: NumberToken() { type = "number"; } - bool isFloat = false; - bool hasEpsilon = false; - int base = 10; + bool isFloat = false; // Ondalıklı sayı mı? + bool hasEpsilon = false; // Bilimsel gösterim (e/E) içeriyor mu? + int base = 10; // Sayı tabanı }; +// ============================================================================ +// OperatorToken — Operatörler (+, -, *, /, ==, ++, vb.) +// ============================================================================ +// +// Aritmetik, karşılaştırma, mantıksal, bitsel, atama operatörleri. +// Token değeri doğrudan operatörün string halidir: "+", "-", "==", "++". +// class OperatorToken : public Token { public: OperatorToken() { type = "operator"; } }; +// ============================================================================ +// DelimiterToken — Sınırlandırıcılar ({, }, (, ), [, ], ;, ,, ., ->, ::) +// ============================================================================ +// +// Kod yapısını belirleyen karakterler. Bloklar, parametre listeleri, +// dizi indeksleri, ifade sonlandırma. +// class DelimiterToken : public Token { public: DelimiterToken() { type = "delimiter"; } }; +// ============================================================================ +// KeywordToken — Anahtar Kelimeler (if, for, while, int, void, ...) +// ============================================================================ +// +// Dilin rezerve edilmiş kelimeleri. Identifier olarak kullanılamazlar. +// Tokenizer scope() fonksiyonu, keyword'leri identifier'lardan önce kontrol +// eder. Keyword boundary check sayesinde "double" "do" olarak yanlış +// eşleşmez. +// class KeywordToken : public Token { public: KeywordToken() { type = "keyword"; } }; +// ============================================================================ +// IdentifierToken — Tanımlayıcılar (değişken/fonksiyon isimleri) +// ============================================================================ +// +// Harf, rakam, _ ve $ karakterlerinden oluşan, keyword olmayan isimler. +// Değişkenler, fonksiyonlar, sınıflar, metotlar için kullanılır. +// +// context: Şu anda token ile aynı (genişleme için ayrıldı) +// size: Tanımlayıcının karakter uzunluğu +// class IdentifierToken : public Token { public: IdentifierToken() { type = "identifier"; } - std::string context; - int size = 0; + std::string context; // Şu anda token ile aynı + int size = 0; // Tanımlayıcı uzunluğu }; -// ============================================================ -// Token tables -// ============================================================ +// ============================================================================ +// Token Tanıma Tabloları (Derleme Zamanı Sabitleri) +// ============================================================================ +// +// Bu tablolar, Tokenizer::scope() tarafından ham karakterlerden token üretmek +// için kullanılır. constexpr std::string_view ile tanımlanmıştır, böylece +// heap tahsisi yapılmaz ve derleme zamanında optimize edilir. +// +// SIRALAMA ÖNEMLİDİR! +// scope() fonksiyonu bu tabloları sırasıyla tarar ve İLK eşleşmede durur. +// Bu nedenle: +// - Çok karakterli operatörler (==) tek karakterlilerden (=) ÖNCE gelmeli +// - Çok karakterli delimiter'lar (->) tek karakterlilerden (.) ÖNCE gelmeli +// - Keyword'ler, identifier'lardan ÖNCE kontrol edilmeli +// +// Mevcut sıralama: keywords → delimiters → operators → identifier (fallback) +// +// ============================================================================ #include +// Operatör tablosu. Çok karakterliler (==, !=, ++, +=, vb.) önce gelir. +// NOT: Bu tablo ParserToken'daki OPERATOR_MAP ile eşleşmelidir. inline constexpr std::string_view operators[] = { + // --- 2 karakterli: karşılaştırma --- "==", "!=", "<=", ">=", "&&", "||", + // --- 2 karakterli: aritmetik --- "++", "--", "<<", ">>", + // --- 2 karakterli: birleşik atama --- "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", + // --- 1 karakterli: aritmetik --- "+", "-", "*", "/", "%", "<", ">", + // --- 1 karakterli: bitsel/mantıksal --- "^", "!", "~", "&", "|", + // --- 1 karakterli: temel atama --- "=" }; +// Delimiter tablosu. Çok karakterliler (->, ::) önce gelir. inline constexpr std::string_view delimiters[] = { - "->", "::", - "[", "]", "(", ")", "{", "}", - ";", ",", ":", - "." + "->", "::", // 2 karakterli bağlayıcılar + "[", "]", "(", ")", "{", "}", // gruplama + ";", ",", ":", // ayırıcılar + "." // üye erişimi }; +// Keyword tablosu. Dilin tüm rezerve edilmiş kelimeleri. +// Gruplandırılmıştır: +// - Kontrol akışı: if, else, for, while, do, switch, case, vb. +// - Tipler: void, int, float, double, char, string, bool +// - Literal'lar: true, false, null +// - OOP: class, interface, enum, extends, public, private, vb. +// - Modüller: import, package +// - C/C++ ekleri: const, extern, typedef, sizeof, auto, vb. +// +// BUG FIX (commit 438bc0e): +// Eskiden tip keyword'leri bu listede yoktu. int, float gibi kelimeler +// identifier olarak tokenize ediliyordu. Parser'da KW_INT gibi tipler +// tanımlı olmasına rağmen tokenizer'dan gelmediği için değişken tanımlama +// çalışmıyordu. Tüm eksik keyword'ler eklendi. +// inline constexpr std::string_view keywords[] = { // Control flow "if", "else", "for", "while", "do", @@ -104,57 +276,106 @@ inline constexpr std::string_view keywords[] = { "native", "synchronized", "volatile", "transient" }; -// ============================================================ -// Tokenizer -// ============================================================ - +// ============================================================================ +// Tokenizer — Lexer Üzerinde Token Üretici +// ============================================================================ +// +// Tek sorumluluğu: karakter akışından token listesi üretmek. +// Durum bilgisi: Lexer'ı içerir (hmx), kendi durumu yok. +// +// KULLANIM: +// Tokenizer tokenizer; +// auto tokens = tokenizer.scan(sourceCode); +// // tokens artık kullanılabilir. İş bitince: +// for (auto* t : tokens) delete t; +// class Tokenizer { public: - Lexer hmx; + Lexer hmx; // İç Lexer. "hmx" adı tarihsel. std::vector scan(std::string input); private: - Token* scope(); - IdentifierToken* readIdentifier(); - StringToken* readString(); - void skipOneLineComment(); - void skipMultiLineComment(); + Token* scope(); // Bir sonraki token'ı döndür + IdentifierToken* readIdentifier(); // Tanımlayıcı oku + StringToken* readString(); // String literal oku + void skipOneLineComment(); // // yorum satırını atla + void skipMultiLineComment(); // /* */ yorum bloğunu atla }; -// ============================================================ -// Tokenizer implementation -// ============================================================ +// ============================================================================ +// GERÇEKLEME (Implementation) +// ============================================================================ +// -------------------------------------------------------------------------- +// scan: Kaynak kodu tara, token listesi üret. +// +// Akış: +// 1. Lexer'a kaynak kodu yükle +// 2. scope() ile tek tek token oku +// 3. "EOL" (End Of Line) token'ı gelene kadar devam et +// 4. Token listesini döndür +// +// "EOL" token'ı: scope() EOF'da üretilen özel bir Token. Parser'a "bitti" sinyali. +// Neden nullptr değil? Çünkü scope() her zaman geçerli bir pointer döndürmeli, +// aksi takdirde null kontrolü gerekir. "EOL" ile bu kontrol token tipine indirgenir. +// +// TODO: std::unique_ptr veya std::vector> ile bellek yönetimi +// -------------------------------------------------------------------------- inline std::vector Tokenizer::scan(std::string input) { std::vector tokens; hmx.setText(input); while (true) { Token* token = scope(); - if (token->token == "EOL") break; + if (token->token == "EOL") break; // Dosya sonu sinyali tokens.push_back(token); - if (hmx.isEnd()) break; + if (hmx.isEnd()) break; // Güvenlik kontrolü } return tokens; } +// -------------------------------------------------------------------------- +// scope: Bir sonraki token'ı tanı ve döndür. +// +// Token tanıma sıralaması (önemli!): +// 1. Boşlukları atla +// 2. Yorum satırlarını atla (//, /* */) +// 3. EOF kontrolü → "EOL" token'ı +// 4. String literal ("...") +// 5. Sayısal literal (0-9 ile başlayan) +// 6. Keyword'ler (sınır kontrolü ile) +// 7. Delimiter'lar +// 8. Operatörler +// 9. Identifier (fallback — yukarıdakilerden hiçbiri değilse) +// +// Keyword boundary check: +// include(kw, false) ile önce eşleşme kontrolü yapılır (konum değişmez). +// Sonra keyword'ün hemen sonrasındaki karaktere bakılır. +// Eğer bu karakter harf/rakam/_/$ ise, bu bir keyword değil, daha uzun bir +// identifier'ın parçasıdır. Örnek: "do" → "double"ın başlangıcı olabilir. +// +// BUG FIX (commit 438bc0e): Eskiden boundary check yoktu. "double" kelimesi +// "do" + "uble" olarak iki token'a ayrılıyordu. +// -------------------------------------------------------------------------- inline Token* Tokenizer::scope() { hmx.skipWhiteSpace(); + // Yorum satırları: sessizce atla, token üretme if (hmx.include("//", true)) skipOneLineComment(); if (hmx.include("/*", true)) skipMultiLineComment(); + // EOF kontrolü if (hmx.isEnd()) { Token* t = new Token(); - t->token = "EOL"; + t->token = "EOL"; // Özel sinyal token'ı return t; } - // String literals + // String literal: " ile başlar if (hmx.getchar() == '"') return readString(); - // Numbers + // Sayısal literal: rakam ile başlar (isNumeric: 0-9) if (hmx.isNumeric()) { INumber lem = hmx.readNumeric(); NumberToken* nt = new NumberToken(); @@ -167,13 +388,16 @@ inline Token* Tokenizer::scope() { return nt; } - // Keywords (check boundary: keyword must not be prefix of longer identifier) + // Keyword'ler: sınır kontrolü ile tarama + // include(kw, false) → eşleşme kontrolü yap ama konumu değiştirme + // getchar(kw.size()) → keyword sonrası karaktere bak + // Sonraki karakter harf/rakam/_/$ ise → bu bir keyword değil, devam et for (const auto& kw : keywords) { if (hmx.include(std::string(kw), false)) { char next = hmx.getchar(static_cast(kw.size())); if ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') || (next >= '0' && next <= '9') || next == '_' || next == '$') { - continue; // part of longer identifier, not a real keyword + continue; // Daha uzun bir identifier'ın parçası } KeywordToken* kt = new KeywordToken(); kt->start = hmx.getOffset(); @@ -184,7 +408,7 @@ inline Token* Tokenizer::scope() { } } - // Delimiters + // Delimiter'lar for (const auto& del : delimiters) { if (hmx.include(std::string(del), false)) { DelimiterToken* dt = new DelimiterToken(); @@ -196,7 +420,7 @@ inline Token* Tokenizer::scope() { } } - // Operators + // Operatörler for (const auto& op : operators) { if (hmx.include(std::string(op), false)) { OperatorToken* ot = new OperatorToken(); @@ -208,10 +432,24 @@ inline Token* Tokenizer::scope() { } } - // Identifier (fallback) + // Identifier (fallback): hiçbir özel token tipine uymayan her şey return readIdentifier(); } +// -------------------------------------------------------------------------- +// readIdentifier: Bir tanımlayıcı (identifier) oku. +// +// Tanımlayıcı = harf ile başlayan, harf/rakam/_/$ ile devam eden karakter dizisi. +// NOT: Rakam ile başlayamaz (o zaman sayı olurdu). +// +// Karakter seti: +// a-z, A-Z: Latin harfleri +// 0-9: Rakamlar (ilk karakter hariç) +// _ (alt çizgi): Yaygın ayraç +// $ (dolar): Java/C# uyumluluğu için +// +// TODO: Unicode harf desteği (Türkçe karakterler, Çince, Arapça, vb.) +// -------------------------------------------------------------------------- inline IdentifierToken* Tokenizer::readIdentifier() { hmx.beginPosition(); IdentifierToken* it = new IdentifierToken(); @@ -221,6 +459,7 @@ inline IdentifierToken* Tokenizer::readIdentifier() { char c = hmx.getchar(); bool read = false; + // Harf veya rakam kontrolü (ASCII) if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { read = true; it->token.push_back(c); @@ -232,21 +471,47 @@ inline IdentifierToken* Tokenizer::readIdentifier() { if (read) { hmx.nextChar(); } else { - break; + break; // Tanımlayıcı karakteri değil → dur } } it->end = hmx.getOffset(); it->size = static_cast(it->context.size()); - hmx.acceptPosition(); + hmx.acceptPosition(); // Başarılı okuma → konumu kalıcı yap return it; } +// -------------------------------------------------------------------------- +// readString: Bir string literal oku ("...") +// +// Desteklenen escape sequence'ler: +// \" → çift tırnak +// \\ → ters bölü +// \n → satırsonu +// \t → sekme +// \r → satırbaşı +// +// Algoritma: +// 1. Açılış tırnağını (" ) gör → started = true +// 2. Karakterleri oku: +// - \ ise → sonraki karakteri escape olarak işle, context'e ekle +// - " ise → started zaten true, bu kapanış tırnağı → ended = true +// - Diğer → context'e ekle +// 3. Kapanış tırnağında dur +// +// token: Tüm karakterler (tırnaklar ve escape'ler dahil) +// context: Sadece gerçek string içeriği (escape'ler çözülmüş) +// +// Örnek: "a\"b\\n" → token = "\"a\\\"b\\\\n\"", context = "a\"b\n" +// +// TODO: \xNN (hex escape), \uNNNN (Unicode), \UNNNNNNNN (geniş Unicode) +// TODO: Çok satırlı string desteği ("""...""" veya backtick `...`) +// -------------------------------------------------------------------------- inline StringToken* Tokenizer::readString() { hmx.beginPosition(); StringToken* st = new StringToken(); - bool started = false; - bool ended = false; + bool started = false; // Açılış tırnağı görüldü mü? + bool ended = false; // Kapanış tırnağı görüldü mü? st->start = hmx.getOffset(); while (!hmx.isEnd()) { @@ -255,12 +520,13 @@ inline StringToken* Tokenizer::readString() { switch (c) { case '"': if (!started) { - started = true; + started = true; // Açılış tırnağı } else { - ended = true; + ended = true; // Kapanış tırnağı } break; case '\\': + // Escape sequence: sonraki karakteri olduğu gibi al hmx.nextChar(); c = hmx.getchar(); st->token.push_back(c); @@ -280,17 +546,24 @@ inline StringToken* Tokenizer::readString() { return st; } +// -------------------------------------------------------------------------- +// skipOneLineComment: // ile başlayan yorum satırını satırsonuna kadar atla +// -------------------------------------------------------------------------- inline void Tokenizer::skipOneLineComment() { while (!hmx.isEnd()) { if (hmx.getchar() == '\n') { hmx.nextChar(); - hmx.skipWhiteSpace(); + hmx.skipWhiteSpace(); // Satırsonu sonrası boşlukları da temizle return; } hmx.nextChar(); } } +// -------------------------------------------------------------------------- +// skipMultiLineComment: /* */ bloğunu atla +// NOT: İç içe yorum blokları desteklenmez (C standardı gibi). +// -------------------------------------------------------------------------- inline void Tokenizer::skipMultiLineComment() { while (!hmx.isEnd()) { if (hmx.include("*/", true)) { @@ -301,4 +574,4 @@ inline void Tokenizer::skipMultiLineComment() { } } -#endif +#endif // SAQUT_TOKENIZER diff --git a/src/tools.hpp b/src/tools.hpp index db75490..1ef3768 100644 --- a/src/tools.hpp +++ b/src/tools.hpp @@ -1,8 +1,34 @@ +// ============================================================================ +// saQut Compiler — Yardımcı Fonksiyonlar +// ============================================================================ +// +// DİZİN: src/tools.hpp +// KATMAN: Tüm katmanlar tarafından kullanılabilir +// BAĞIMLI: Yok (sadece ) +// +// AMAÇ: +// Tüm derleyici modüllerinin ihtiyaç duyduğu ortak yardımcı fonksiyonlar. +// Şu anda sadece padRight() içerir. +// +// ============================================================================ + #ifndef SAQUT_TOOLS #define SAQUT_TOOLS #include +// -------------------------------------------------------------------------- +// padRight: String'i sağdan boşluk ile belirtilen uzunluğa tamamla. +// +// KULLANIM: AST ağacını konsola yazdırırken girintileme (indent) için. +// padRight("", indent) → indent adet boşluk döndürür. +// +// ÖRNEK: +// padRight("", 4) → " " +// padRight("abc", 6) → "abc " +// +// NOT: std::setw + std::left ile de yapılabilirdi, ancak bu daha basit. +// -------------------------------------------------------------------------- inline std::string padRight(std::string str, size_t totalLen) { if (str.size() < totalLen) { str.append(totalLen - str.size(), ' '); @@ -10,4 +36,4 @@ inline std::string padRight(std::string str, size_t totalLen) { return str; } -#endif +#endif // SAQUT_TOOLS