docs: kapsamli ADR dokumantasyonu, tum kaynak dosyalara detayli kommentler

This commit is contained in:
abdussamedulutas 2026-05-26 00:24:27 +03:00
parent 438bc0e200
commit 3e685ea960
12 changed files with 2415 additions and 467 deletions

View File

@ -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ı
# -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 ==="

385
fikirler.md Normal file
View File

@ -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<int,float>}
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<IROpData>`
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.

BIN
saqut

Binary file not shown.

View File

@ -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;
}

View File

@ -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ıı 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 <variant>
#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<int,float> 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<int, float> value;
bool isRegister; // true: register referansı, false: sabit değer
std::variant<int, float> 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<int,float> 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<IROpData> IROpDatas;
Identifier identifier; // Sanal register yöneticisi
std::vector<IROpData> 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

View File

@ -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<int>): İç 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 <string>
#include <vector>
// ============================================================================
// 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<int> 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<int> 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<int,int> 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

View File

@ -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 <iostream>
#include <fstream>
#include <sstream>
@ -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ıı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;

View File

@ -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 << "<Unknown>\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<ASTNode*> children;
std::vector<ASTNode*> 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

View File

@ -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;
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

View File

@ -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 <vector>
#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<std::unique_ptr<Token>> ile otomatik bellek yönetimi
//
typedef std::vector<Token*> 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<std::string_view, TokenType> 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<std::string_view, TokenType> 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<std::string_view, TokenType> 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<std::string_view, TokenType> 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<std::string_view, TokenType> 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<std::string_view, TokenType> 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<std::string_view, TokenType> 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<std::string_view, TokenType> OPERATOR_MAP = {
// --- 2 karakterli ---
{"->", TokenType::ARROW},
{"::", TokenType::COLON_COLON},
{"==", TokenType::EQUAL_EQUAL},
@ -143,6 +384,7 @@ inline const std::unordered_map<std::string_view, TokenType> 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<std::string_view, TokenType> 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<std::string_view, TokenType> 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<std::string_view, TokenType> 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<TokenType, std::string_view> OPERATOR_MAP_REV = {
{TokenType::ARROW, "->"},
{TokenType::COLON_COLON, "::"},
@ -231,6 +483,13 @@ inline const std::unordered_map<TokenType, std::string_view> 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<TokenType, std::string_view> OPERATOR_MAP_STRREV = {
{TokenType::ARROW, "ARROW"},
{TokenType::COLON_COLON, "COLON_COLON"},
@ -281,10 +540,42 @@ inline const std::unordered_map<TokenType, std::string_view> 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<TokenType> 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

View File

@ -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 <vector>
#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 <string_view>
// 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<Token*> 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<std::unique_ptr<Token>> ile bellek yönetimi
// --------------------------------------------------------------------------
inline std::vector<Token*> Tokenizer::scan(std::string input) {
std::vector<Token*> 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<int>(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<int>(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

View File

@ -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 <string>)
//
// 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 <string>
// --------------------------------------------------------------------------
// 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