docs: kapsamli ADR dokumantasyonu, tum kaynak dosyalara detayli kommentler
This commit is contained in:
parent
438bc0e200
commit
3e685ea960
31
compile.sh
31
compile.sh
|
|
@ -1,8 +1,33 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# saQut Compiler — build script
|
# ============================================================================
|
||||||
# Derleme: g++ src/main.cpp -Isrc -o saqut
|
# saQut Compiler — Derleme Betiği
|
||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
# AMAÇ: Projeyi tek komutla derlemek.
|
||||||
|
#
|
||||||
|
# KULLANIM:
|
||||||
|
# ./compile.sh → saqut binary'sini üret
|
||||||
|
# ./saqut → derleyiciyi çalıştır
|
||||||
|
#
|
||||||
|
# DERLEME SÜRECİ:
|
||||||
|
# Tek bir .cpp dosyası (src/main.cpp) tüm header-only kütüphaneleri
|
||||||
|
# include eder. Harici bağımlılık yoktur.
|
||||||
|
#
|
||||||
|
# g++ parametreleri:
|
||||||
|
# -Isrc : include path (header'lar src/ altında)
|
||||||
|
# -std=c++17 : C++17 standardı (std::variant, constexpr, vb.)
|
||||||
|
# -Wall -Wextra : Tüm uyarıları aç
|
||||||
|
# -O0 -g : Optimizasyon kapalı, debug sembolleri açık
|
||||||
|
# -o saqut : Çıktı binary adı
|
||||||
|
#
|
||||||
|
# GELECEK:
|
||||||
|
# - Makefile veya CMakeLists.txt ile daha esnek build
|
||||||
|
# - Release modu: -O2 -DNDEBUG
|
||||||
|
# - Test modu: ayrı bir test binary'si
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
set -e
|
set -e # Hata durumunda dur
|
||||||
|
|
||||||
echo "=== saQut Compiler Build ==="
|
echo "=== saQut Compiler Build ==="
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
11
source.sqt
11
source.sqt
|
|
@ -1 +1,10 @@
|
||||||
1 / (74 - 63 + !1) - 74 * 2 / -0.7e+10
|
int main() {
|
||||||
|
int x = 0;
|
||||||
|
while (x < 5) {
|
||||||
|
x = x + 1;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
x = x - 1;
|
||||||
|
} while (x > 0);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
|
||||||
219
src/ir/ir.hpp
219
src/ir/ir.hpp
|
|
@ -1,3 +1,54 @@
|
||||||
|
// ============================================================================
|
||||||
|
// saQut Compiler — Intermediate Representation (Ara Gösterim)
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// DİZİN: src/ir/ir.hpp
|
||||||
|
// KATMAN: Katman 4 — AST'yi alır, IR üretir
|
||||||
|
// BAĞIMLI: AST (src/parser/ast.hpp), dolaylı olarak Tokenizer ve Parser
|
||||||
|
// KULLANAN: main.cpp (debug çıktısı), gelecekte Code Generator
|
||||||
|
//
|
||||||
|
// AMAÇ:
|
||||||
|
// Zengin AST'yi, çalıştırılabilir düşük seviyeli komutlara (IR) dönüştürür.
|
||||||
|
// IR, bir "virtual register machine" (sanal kayıtçı makinesi) modelidir.
|
||||||
|
//
|
||||||
|
// IR MODELİ:
|
||||||
|
// Her işlem (instruction) şu bileşenlerden oluşur:
|
||||||
|
// - opcode: İşlem kodu (mathadd, mathsub, mathmul, mathdiv, declare)
|
||||||
|
// - targetReg: Sonucun yazılacağı sanal register numarası
|
||||||
|
// - arg1-arg3: İşlem parametreleri (register veya sabit değer)
|
||||||
|
//
|
||||||
|
// Sanal register'lar sınırsızdır (gerçek register tahsisi sonraki aşamada).
|
||||||
|
// Bu yaklaşım, register allocation'ı ayrı bir probleme dönüştürür.
|
||||||
|
//
|
||||||
|
// ADR-005: IR Tasarımı
|
||||||
|
// İki katmanlı IR planlanıyor:
|
||||||
|
// - LightIR: Sadece çalıştırma için minimum bilgi (JIT/compiler)
|
||||||
|
// - HeavyIR: Debug bilgisi, tip bilgisi, değişken isimleri (interpreter/debug)
|
||||||
|
//
|
||||||
|
// Mevcut IR, LightIR'in embriyonik halidir.
|
||||||
|
//
|
||||||
|
// MEVCUT DURUM:
|
||||||
|
// Desteklenen AST düğümleri:
|
||||||
|
// ✅ BinaryExpression (sadece +, -, *, /)
|
||||||
|
// ✅ Literal (NumberToken)
|
||||||
|
// ✅ Program, FunctionDecl, Block (çocukları dolaşır)
|
||||||
|
// ✅ ExpressionStatement, VariableDecl, ReturnStatement
|
||||||
|
// ✅ IfStatement, WhileStatement, ForStatement, DoWhileStatement
|
||||||
|
// ❌ Kontrol akışı (branch/jump/compare) — TODO
|
||||||
|
// ❌ Fonksiyon çağrısı (call/ret) — TODO
|
||||||
|
// ❌ Mantıksal/kıyaslama operatörleri — TODO
|
||||||
|
//
|
||||||
|
// BİLİNEN SINIRLAMALAR (TODO):
|
||||||
|
// TODO: Kontrol akışı opcode'ları: br, jmp, cmp, br_eq, br_lt, vb.
|
||||||
|
// TODO: Fonksiyon çağrısı: call, ret, param
|
||||||
|
// TODO: Bellek: load, store, alloca
|
||||||
|
// TODO: Tip bilgisi: IR opcode'ları tipleri taşımıyor
|
||||||
|
// TODO: Float/int ayrımı düzgün değil (processNumber void* döndürüyor)
|
||||||
|
// TODO: String, bool, null literal'ları işlenmiyor
|
||||||
|
// TODO: Identifier (değişken okuma) işlenmiyor
|
||||||
|
//
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
#ifndef SAQUT_IR
|
#ifndef SAQUT_IR
|
||||||
#define SAQUT_IR
|
#define SAQUT_IR
|
||||||
|
|
||||||
|
|
@ -5,41 +56,95 @@
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include "parser/ast.hpp"
|
#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 {
|
enum class OPCode {
|
||||||
mathadd,
|
mathadd, // targetReg = arg1 + arg2
|
||||||
mathsub,
|
mathsub, // targetReg = arg1 - arg2
|
||||||
mathdiv,
|
mathdiv, // targetReg = arg1 / arg2
|
||||||
mathmul,
|
mathmul, // targetReg = arg1 * arg2
|
||||||
declare
|
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 {
|
struct Param {
|
||||||
bool isRegister;
|
bool isRegister; // true: register referansı, false: sabit değer
|
||||||
std::variant<int, float> value;
|
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 {
|
struct IROpData {
|
||||||
OPCode op;
|
OPCode op; // İşlem kodu
|
||||||
int targetReg;
|
int targetReg; // Sonuç register'ı (sanal, sınırsız)
|
||||||
Param arg1;
|
Param arg1; // Birinci parametre
|
||||||
Param arg2;
|
Param arg2; // İkinci parametre
|
||||||
Param arg3;
|
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 {
|
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 {
|
class CodeGenerator {
|
||||||
private:
|
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) {
|
void* processNumber(NumberToken* num, const std::string& rawStr) {
|
||||||
if (num->isFloat || num->hasEpsilon)
|
if (num->isFloat || num->hasEpsilon)
|
||||||
return new float(std::strtof(rawStr.c_str(), nullptr));
|
return new float(std::strtof(rawStr.c_str(), nullptr));
|
||||||
|
|
@ -47,9 +152,16 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Identifier identifier;
|
Identifier identifier; // Sanal register yöneticisi
|
||||||
std::vector<IROpData> IROpDatas;
|
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) {
|
int parse(ASTNode* ast) {
|
||||||
if (!ast) return 0;
|
if (!ast) return 0;
|
||||||
switch (ast->kind) {
|
switch (ast->kind) {
|
||||||
|
|
@ -73,46 +185,46 @@ public:
|
||||||
return parseIf((IfStatementNode*)ast);
|
return parseIf((IfStatementNode*)ast);
|
||||||
case ASTKind::WhileStatement:
|
case ASTKind::WhileStatement:
|
||||||
return parseWhile((WhileStatementNode*)ast);
|
return parseWhile((WhileStatementNode*)ast);
|
||||||
|
case ASTKind::ForStatement:
|
||||||
|
return parseFor((ForStatementNode*)ast);
|
||||||
|
case ASTKind::DoWhileStatement:
|
||||||
|
return parseDoWhile((DoWhileStatementNode*)ast);
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Yapısal düğümler: çocukları dolaş ---
|
||||||
|
|
||||||
int parseProgram(ProgramNode* prog) {
|
int parseProgram(ProgramNode* prog) {
|
||||||
for (auto* child : prog->getChildren())
|
for (auto* child : prog->getChildren()) parse(child);
|
||||||
parse(child);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int parseFunctionDecl(FunctionDeclNode* fn) {
|
int parseFunctionDecl(FunctionDeclNode* fn) {
|
||||||
for (auto* child : fn->getChildren())
|
for (auto* child : fn->getChildren()) parse(child);
|
||||||
parse(child);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int parseBlock(BlockNode* block) {
|
int parseBlock(BlockNode* block) {
|
||||||
for (auto* child : block->getChildren())
|
for (auto* child : block->getChildren()) parse(child);
|
||||||
parse(child);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int parseReturn(ReturnStatementNode* ret) {
|
int parseReturn(ReturnStatementNode* ret) {
|
||||||
if (ret->value)
|
if (ret->value) return parse(ret->value);
|
||||||
return parse(ret->value);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int parseVariableDecl(VariableDeclNode* vd) {
|
int parseVariableDecl(VariableDeclNode* vd) {
|
||||||
if (vd->initExpr)
|
if (vd->initExpr) return parse(vd->initExpr);
|
||||||
return parse(vd->initExpr);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int parseIf(IfStatementNode* ifn) {
|
int parseIf(IfStatementNode* ifn) {
|
||||||
parse(ifn->condition);
|
parse(ifn->condition);
|
||||||
parse(ifn->thenBranch);
|
parse(ifn->thenBranch);
|
||||||
if (ifn->elseBranch)
|
if (ifn->elseBranch) parse(ifn->elseBranch);
|
||||||
parse(ifn->elseBranch);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,6 +234,29 @@ public:
|
||||||
return 0;
|
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) {
|
int parseBinaryExpr(BinaryExpressionNode* bin) {
|
||||||
OPCode op;
|
OPCode op;
|
||||||
switch (bin->Operator) {
|
switch (bin->Operator) {
|
||||||
|
|
@ -129,7 +264,7 @@ public:
|
||||||
case TokenType::PLUS: op = OPCode::mathadd; break;
|
case TokenType::PLUS: op = OPCode::mathadd; break;
|
||||||
case TokenType::MINUS: op = OPCode::mathsub; break;
|
case TokenType::MINUS: op = OPCode::mathsub; break;
|
||||||
case TokenType::SLASH: op = OPCode::mathdiv; 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;
|
int left = bin->Left ? parse(bin->Left) : 0;
|
||||||
|
|
@ -145,6 +280,16 @@ public:
|
||||||
return identifier.last;
|
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) {
|
int parseLiteral(LiteralNode* lit) {
|
||||||
NumberToken* num = (NumberToken*)lit->parserToken.token;
|
NumberToken* num = (NumberToken*)lit->parserToken.token;
|
||||||
|
|
||||||
|
|
@ -171,4 +316,4 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif // SAQUT_IR
|
||||||
|
|
|
||||||
|
|
@ -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
|
#ifndef SAQUT_LEXER
|
||||||
#define SAQUT_LEXER
|
#define SAQUT_LEXER
|
||||||
|
|
||||||
|
|
@ -5,83 +64,172 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#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 {
|
struct INumber {
|
||||||
int start = 0;
|
int start = 0; // Kaynak koddaki başlangıç offset'i
|
||||||
int end = 0;
|
int end = 0; // Kaynak koddaki bitiş offset'i
|
||||||
std::string token;
|
std::string token; // Sayının ham metni (örn: "42", "0xFF", "3.14e-2")
|
||||||
bool isFloat = false;
|
bool isFloat = false; // true ise float/double literal
|
||||||
bool hasEpsilon = false;
|
bool hasEpsilon = false; // true ise bilimsel gösterim (örn: 1e10)
|
||||||
int base = 10;
|
int base = 10; // Sayı tabanı: 2, 8, 10, 16
|
||||||
bool positive = true;
|
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 {
|
class Lexer {
|
||||||
public:
|
public:
|
||||||
std::string input;
|
// --- Ham Veri ---
|
||||||
int size = 0;
|
std::string input; // Kaynak kodun tamamı
|
||||||
int offset = 0;
|
int size = 0; // input.length() önbelleği
|
||||||
std::vector<int> offsetMap;
|
int offset = 0; // Mevcut okuma konumu (0 = başlangıç)
|
||||||
|
std::vector<int> offsetMap; // Backtracking yığını
|
||||||
|
|
||||||
// --- Position tracking ---
|
// --- Pozisyon Yönetimi (Backtracking API) ---
|
||||||
void beginPosition();
|
//
|
||||||
int getLastPosition();
|
// Kullanım örneği:
|
||||||
void acceptPosition();
|
// lexer.beginPosition(); // konumu kaydet
|
||||||
void setLastPosition(int n);
|
// if (lexer.include("for", false)) // dene (false = eşleşse de geri al)
|
||||||
void rejectPosition();
|
// lexer.acceptPosition(); // başarılı → kalıcı yap
|
||||||
|
// else
|
||||||
|
// lexer.rejectPosition(); // başarısız → geri al
|
||||||
|
|
||||||
// --- Reading ---
|
void beginPosition(); // Şu anki konumu yığına kaydet
|
||||||
bool isEnd();
|
int getLastPosition(); // Yığındaki son konumu döndür
|
||||||
int* positionRange();
|
void acceptPosition(); // Yığındaki son konumu kalıcı yap (apply)
|
||||||
std::string getPositionRange();
|
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);
|
bool include(std::string word, bool accept = true);
|
||||||
|
|
||||||
int getOffset();
|
// --- Konum Okuma/Yazma ---
|
||||||
int setOffset(int n);
|
int getOffset(); // Mevcut offset'i döndür
|
||||||
char getchar(int additionalOffset);
|
int setOffset(int n); // Offset'i n olarak ayarla, yeni değeri döndür
|
||||||
char getchar();
|
|
||||||
void nextChar();
|
|
||||||
void toChar(int n);
|
|
||||||
|
|
||||||
void setText(std::string input);
|
// --- Karakter Okuma ---
|
||||||
void skipWhiteSpace();
|
char getchar(int additionalOffset); // offset + ek'teki karakteri oku
|
||||||
bool isNumeric();
|
char getchar(); // Mevcut offset'teki karakteri oku
|
||||||
INumber readNumeric();
|
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());
|
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;
|
if (offsetMap.empty()) return offset;
|
||||||
return offsetMap.back();
|
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();
|
int t = offsetMap.back();
|
||||||
setLastPosition(t);
|
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())
|
if (offsetMap.empty())
|
||||||
offset = n;
|
offset = n;
|
||||||
else
|
else
|
||||||
offsetMap.back() = n;
|
offsetMap.back() = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Lexer::isEnd() {
|
// --------------------------------------------------------------------------
|
||||||
|
// isEnd: Dosya sonuna gelindi mi? offset >= size.
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
inline bool Lexer::isEnd() {
|
||||||
return size <= getOffset();
|
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();
|
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();
|
int len = offsetMap.size();
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
return new int[2]{0, offset};
|
return new int[2]{0, offset};
|
||||||
|
|
@ -90,7 +238,10 @@ int* Lexer::positionRange() {
|
||||||
return new int[2]{offsetMap[len - 2], offsetMap[len - 1]};
|
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();
|
int* a = positionRange();
|
||||||
std::string mem;
|
std::string mem;
|
||||||
for (int i = a[0]; i < a[1]; i++)
|
for (int i = a[0]; i < a[1]; i++)
|
||||||
|
|
@ -98,7 +249,25 @@ std::string Lexer::getPositionRange() {
|
||||||
return mem;
|
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();
|
beginPosition();
|
||||||
for (size_t i = 0; i < word.size(); i++) {
|
for (size_t i = 0; i < word.size(); i++) {
|
||||||
if (isEnd()) {
|
if (isEnd()) {
|
||||||
|
|
@ -118,16 +287,25 @@ bool Lexer::include(std::string word, bool accept) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Lexer::getOffset() {
|
// --------------------------------------------------------------------------
|
||||||
|
// getOffset / setOffset: Konum erişimcileri.
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
inline int Lexer::getOffset() {
|
||||||
return getLastPosition();
|
return getLastPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Lexer::setOffset(int n) {
|
inline int Lexer::setOffset(int n) {
|
||||||
setLastPosition(n);
|
setLastPosition(n);
|
||||||
return getLastPosition();
|
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;
|
int target = getOffset() + additionalOffset;
|
||||||
if (target >= size) {
|
if (target >= size) {
|
||||||
std::cerr << "Lexer hatası: sınır aşımı\n";
|
std::cerr << "Lexer hatası: sınır aşımı\n";
|
||||||
|
|
@ -136,7 +314,7 @@ char Lexer::getchar(int additionalOffset) {
|
||||||
return input.at(target);
|
return input.at(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
char Lexer::getchar() {
|
inline char Lexer::getchar() {
|
||||||
int target = getOffset();
|
int target = getOffset();
|
||||||
if (target >= size) {
|
if (target >= size) {
|
||||||
std::cerr << "Lexer hatası: sınır aşımı\n";
|
std::cerr << "Lexer hatası: sınır aşımı\n";
|
||||||
|
|
@ -145,29 +323,40 @@ char Lexer::getchar() {
|
||||||
return input.at(target);
|
return input.at(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lexer::nextChar() {
|
// --------------------------------------------------------------------------
|
||||||
|
// nextChar / toChar: Konum ilerletme.
|
||||||
|
// EOF kontrolü yapar — dosya sonundaysa ilerlemez.
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
inline void Lexer::nextChar() {
|
||||||
if (!isEnd())
|
if (!isEnd())
|
||||||
setOffset(getOffset() + 1);
|
setOffset(getOffset() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lexer::toChar(int n) {
|
inline void Lexer::toChar(int n) {
|
||||||
if (!isEnd())
|
if (!isEnd())
|
||||||
setOffset(getOffset() + n);
|
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;
|
input = text;
|
||||||
size = text.length();
|
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()) {
|
while (!isEnd()) {
|
||||||
switch (getchar()) {
|
switch (getchar()) {
|
||||||
case '\r':
|
case '\r': // carriage return (Windows satırsonu \r\n)
|
||||||
case '\n':
|
case '\n': // line feed (Unix satırsonu)
|
||||||
case '\b':
|
case '\b': // backspace
|
||||||
case '\t':
|
case '\t': // tab
|
||||||
case ' ':
|
case ' ': // boşluk
|
||||||
nextChar();
|
nextChar();
|
||||||
break;
|
break;
|
||||||
default:
|
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();
|
char c = getchar();
|
||||||
return (c >= '0' && c <= '9');
|
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;
|
INumber num;
|
||||||
num.start = getLastPosition();
|
num.start = getLastPosition();
|
||||||
|
|
||||||
|
// --- Adım 1: İsteğe bağlı işaret ---
|
||||||
if (getchar() == '-') {
|
if (getchar() == '-') {
|
||||||
nextChar();
|
nextChar();
|
||||||
num.positive = false;
|
num.positive = false;
|
||||||
|
|
@ -195,23 +414,24 @@ INumber Lexer::readNumeric() {
|
||||||
num.positive = true;
|
num.positive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Adım 2: İlk karakter '0' ise özel format kontrolü ---
|
||||||
bool nextDot = false;
|
bool nextDot = false;
|
||||||
if (getchar() == '0') {
|
if (getchar() == '0') {
|
||||||
num.token.push_back('0');
|
num.token.push_back('0');
|
||||||
nextChar();
|
nextChar();
|
||||||
char c = getchar();
|
char c = getchar();
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'x': case 'X':
|
case 'x': case 'X': // Hex: 0xFF, 0X1A
|
||||||
num.token.push_back(c);
|
num.token.push_back(c);
|
||||||
num.base = 16;
|
num.base = 16;
|
||||||
nextChar();
|
nextChar();
|
||||||
break;
|
break;
|
||||||
case 'b': case 'B':
|
case 'b': case 'B': // Binary: 0b1010
|
||||||
num.token.push_back(c);
|
num.token.push_back(c);
|
||||||
num.base = 2;
|
num.base = 2;
|
||||||
nextChar();
|
nextChar();
|
||||||
break;
|
break;
|
||||||
case '.':
|
case '.': // Float: 0.5, 0.0
|
||||||
num.token.push_back(c);
|
num.token.push_back(c);
|
||||||
num.base = 10;
|
num.base = 10;
|
||||||
nextDot = true;
|
nextDot = true;
|
||||||
|
|
@ -220,11 +440,15 @@ INumber Lexer::readNumeric() {
|
||||||
break;
|
break;
|
||||||
case '0': case '1': case '2': case '3': case '4':
|
case '0': case '1': case '2': case '3': case '4':
|
||||||
case '5': case '6': case '7':
|
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;
|
num.base = 8;
|
||||||
break;
|
break;
|
||||||
default:
|
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();
|
num.end = getLastPosition();
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
@ -232,6 +456,15 @@ INumber Lexer::readNumeric() {
|
||||||
num.base = 10;
|
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()) {
|
while (!isEnd()) {
|
||||||
char c = getchar();
|
char c = getchar();
|
||||||
switch (c) {
|
switch (c) {
|
||||||
|
|
@ -267,6 +500,8 @@ INumber Lexer::readNumeric() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '.':
|
case '.':
|
||||||
|
// Nokta: Sadece bir kere izin verilir.
|
||||||
|
// .5 gibi başıboş noktalı sayılar için "0." öneki eklenir.
|
||||||
if (!nextDot) {
|
if (!nextDot) {
|
||||||
if (num.token.empty())
|
if (num.token.empty())
|
||||||
num.token += "0.";
|
num.token += "0.";
|
||||||
|
|
@ -275,11 +510,15 @@ INumber Lexer::readNumeric() {
|
||||||
nextDot = true;
|
nextDot = true;
|
||||||
num.isFloat = true;
|
num.isFloat = true;
|
||||||
} else {
|
} else {
|
||||||
|
// İkinci nokta → sayı bitti
|
||||||
num.end = getLastPosition();
|
num.end = getLastPosition();
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'e': case 'E':
|
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) {
|
if (num.base == 16) {
|
||||||
num.token.push_back(c);
|
num.token.push_back(c);
|
||||||
break;
|
break;
|
||||||
|
|
@ -289,10 +528,12 @@ INumber Lexer::readNumeric() {
|
||||||
num.token.push_back(c);
|
num.token.push_back(c);
|
||||||
nextChar();
|
nextChar();
|
||||||
c = getchar();
|
c = getchar();
|
||||||
|
// İsteğe bağlı işaret: e+10, E-3
|
||||||
if (c == '+' || c == '-') {
|
if (c == '+' || c == '-') {
|
||||||
num.token.push_back(c);
|
num.token.push_back(c);
|
||||||
nextChar();
|
nextChar();
|
||||||
}
|
}
|
||||||
|
// Epsilon sonrası rakamları oku
|
||||||
while (!isEnd()) {
|
while (!isEnd()) {
|
||||||
c = getchar();
|
c = getchar();
|
||||||
if (c >= '0' && c <= '9') {
|
if (c >= '0' && c <= '9') {
|
||||||
|
|
@ -308,6 +549,7 @@ INumber Lexer::readNumeric() {
|
||||||
num.end = getLastPosition();
|
num.end = getLastPosition();
|
||||||
return num;
|
return num;
|
||||||
default:
|
default:
|
||||||
|
// Tanınmayan karakter → sayı bitti
|
||||||
num.end = getLastPosition();
|
num.end = getLastPosition();
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
@ -317,4 +559,4 @@ INumber Lexer::readNumeric() {
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif // SAQUT_LEXER
|
||||||
|
|
|
||||||
66
src/main.cpp
66
src/main.cpp
|
|
@ -1,3 +1,30 @@
|
||||||
|
// ============================================================================
|
||||||
|
// saQut Compiler — Giriş Noktası (main)
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// DİZİN: src/main.cpp
|
||||||
|
// KATMAN: En üst — tüm alt katmanları birleştirir
|
||||||
|
// BAĞIMLI: Tokenizer, Parser, IR (ve dolaylı olarak Lexer, AST, Token)
|
||||||
|
//
|
||||||
|
// AMAÇ:
|
||||||
|
// Derleyici pipeline'ını başlatır:
|
||||||
|
// 1. source.sqt dosyasını oku
|
||||||
|
// 2. Lexing + Tokenizing
|
||||||
|
// 3. Parsing (AST üretimi)
|
||||||
|
// 4. IR üretimi
|
||||||
|
// 5. Sonuçları konsola yazdır (debug modu)
|
||||||
|
//
|
||||||
|
// KULLANIM:
|
||||||
|
// ./saqut → source.sqt dosyasını derler
|
||||||
|
// echo "1+2" > source.sqt && ./saqut → hızlı test
|
||||||
|
//
|
||||||
|
// GELECEK:
|
||||||
|
// - Komut satırı argümanları: ./saqut file.sqt -o output
|
||||||
|
// - Mod seçimi: ./saqut --mode=parse|ir|compile|run
|
||||||
|
// - Birden fazla dosya: ./saqut file1.sqt file2.sqt
|
||||||
|
//
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
@ -7,7 +34,12 @@
|
||||||
#include "ir/ir.hpp"
|
#include "ir/ir.hpp"
|
||||||
|
|
||||||
int main() {
|
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);
|
std::ifstream file("source.sqt", std::ios::in | std::ios::binary);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
std::cerr << "Hata: source.sqt dosyası açılamadı\n";
|
std::cerr << "Hata: source.sqt dosyası açılamadı\n";
|
||||||
|
|
@ -22,7 +54,12 @@ int main() {
|
||||||
std::cout << "=== saQut Compiler ===\n";
|
std::cout << "=== saQut Compiler ===\n";
|
||||||
std::cout << "Kaynak kod:\n" << source << "\n\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;
|
Tokenizer tokenizer;
|
||||||
auto tokens = tokenizer.scan(source);
|
auto tokens = tokenizer.scan(source);
|
||||||
|
|
||||||
|
|
@ -32,16 +69,27 @@ int main() {
|
||||||
}
|
}
|
||||||
std::cout << "\n";
|
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;
|
Parser parser;
|
||||||
ASTNode* ast = parser.parse(tokens);
|
ASTNode* ast = parser.parse(tokens);
|
||||||
|
|
||||||
if (ast) {
|
if (ast) {
|
||||||
std::cout << "AST:\n";
|
std::cout << "AST:\n";
|
||||||
ast->log(0);
|
ast->log(0); // Ağacı girintili olarak yazdır
|
||||||
std::cout << "\n";
|
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;
|
CodeGenerator cg;
|
||||||
cg.parse(ast);
|
cg.parse(ast);
|
||||||
std::cout << "IR (" << cg.IROpDatas.size() << " komut):\n";
|
std::cout << "IR (" << cg.IROpDatas.size() << " komut):\n";
|
||||||
|
|
@ -55,11 +103,17 @@ int main() {
|
||||||
case OPCode::mathdiv: std::cout << "div"; break;
|
case OPCode::mathdiv: std::cout << "div"; break;
|
||||||
case OPCode::declare: std::cout << "literal"; break;
|
case OPCode::declare: std::cout << "literal"; break;
|
||||||
}
|
}
|
||||||
|
// arg1.value.index(): 0=int, 1=float
|
||||||
std::cout << " (" << op.arg1.value.index() << ")\n";
|
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;
|
for (auto* t : tokens) delete t;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -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
|
#ifndef SAQUT_AST
|
||||||
#define SAQUT_AST
|
#define SAQUT_AST
|
||||||
|
|
||||||
|
|
@ -6,43 +62,58 @@
|
||||||
#include "parser/token.hpp"
|
#include "parser/token.hpp"
|
||||||
#include "tools.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 {
|
enum class ASTKind {
|
||||||
Program,
|
Program, // Kök düğüm
|
||||||
FunctionDecl,
|
FunctionDecl, // Fonksiyon tanımı
|
||||||
Block,
|
Block, // { } bloğu
|
||||||
VariableDecl,
|
VariableDecl, // Değişken tanımı
|
||||||
BinaryExpression,
|
BinaryExpression, // İkili işlem (a + b)
|
||||||
UnaryExpression,
|
UnaryExpression, // Tekli işlem (-a, !a) — ileride kullanılacak
|
||||||
Literal,
|
Literal, // Sabit değer
|
||||||
Identifier,
|
Identifier, // İsim referansı
|
||||||
Postfix,
|
Postfix, // Son ek (a++)
|
||||||
IfStatement,
|
IfStatement, // if/else
|
||||||
ForStatement,
|
ForStatement, // for
|
||||||
WhileStatement,
|
WhileStatement, // while
|
||||||
DoWhileStatement,
|
DoWhileStatement, // do-while
|
||||||
ReturnStatement,
|
ReturnStatement, // return
|
||||||
BreakStatement,
|
BreakStatement, // break
|
||||||
ContinueStatement,
|
ContinueStatement, // continue
|
||||||
ExpressionStatement,
|
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 {
|
class ASTNode {
|
||||||
public:
|
public:
|
||||||
ASTKind kind;
|
ASTKind kind; // Düğüm tipi (alt sınıf constructor'ında atanır)
|
||||||
ASTNode* parent = nullptr;
|
ASTNode* parent = nullptr; // Ebeveyn düğüm (kök = nullptr)
|
||||||
|
|
||||||
virtual void log(int indent = 0) {
|
virtual void log(int indent = 0) {
|
||||||
|
(void)indent; // Kullanılmayan parametre uyarısını sustur
|
||||||
std::cout << "<Unknown>\n";
|
std::cout << "<Unknown>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Çocuk ekleme. Otomatik olarak parent pointer'ı ayarlar.
|
||||||
void addChild(ASTNode* child) {
|
void addChild(ASTNode* child) {
|
||||||
children.push_back(child);
|
children.push_back(child);
|
||||||
child->parent = this;
|
child->parent = this;
|
||||||
|
|
@ -53,13 +124,16 @@ public:
|
||||||
virtual ~ASTNode() = default;
|
virtual ~ASTNode() = default;
|
||||||
|
|
||||||
protected:
|
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 {
|
class ProgramNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
ProgramNode() { kind = ASTKind::Program; }
|
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 {
|
class FunctionDeclNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
std::string name;
|
std::string name; // Fonksiyon adı
|
||||||
std::string returnType;
|
std::string returnType; // Dönüş tipi (string olarak, ileride tip sistemi)
|
||||||
|
|
||||||
FunctionDeclNode() { kind = ASTKind::FunctionDecl; }
|
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 {
|
class BlockNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
BlockNode() { kind = ASTKind::Block; }
|
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 {
|
class VariableDeclNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
std::string varType;
|
std::string varType; // Değişken tipi
|
||||||
std::string name;
|
std::string name; // Değişken adı
|
||||||
ASTNode* initExpr = nullptr;
|
ASTNode* initExpr = nullptr; // Başlangıç ifadesi (opsiyonel)
|
||||||
|
|
||||||
VariableDeclNode() { kind = ASTKind::VariableDecl; }
|
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 {
|
class BinaryExpressionNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
TokenType Operator;
|
TokenType Operator; // İşlem tipi
|
||||||
ASTNode* Left = nullptr;
|
ASTNode* Left = nullptr; // Sol operand
|
||||||
ASTNode* Right = nullptr;
|
ASTNode* Right = nullptr; // Sağ operand
|
||||||
|
|
||||||
BinaryExpressionNode() { kind = ASTKind::BinaryExpression; }
|
BinaryExpressionNode() { kind = ASTKind::BinaryExpression; }
|
||||||
|
|
||||||
void log(int indent = 0) override {
|
void log(int indent = 0) override {
|
||||||
|
// Operatörün enum ismini ve sembolünü göster
|
||||||
auto it = OPERATOR_MAP_STRREV.find(Operator);
|
auto it = OPERATOR_MAP_STRREV.find(Operator);
|
||||||
std::string sym = (it != OPERATOR_MAP_STRREV.end()) ? std::string(it->second) : "?";
|
std::string sym = (it != OPERATOR_MAP_STRREV.end()) ? std::string(it->second) : "?";
|
||||||
std::string val;
|
std::string val;
|
||||||
|
|
@ -150,15 +252,24 @@ public:
|
||||||
|
|
||||||
std::cout << padRight("", indent) << "BinaryExpr " << sym
|
std::cout << padRight("", indent) << "BinaryExpr " << sym
|
||||||
<< " (" << val << ")\n";
|
<< " (" << val << ")\n";
|
||||||
|
// Önce sağ, sonra sol yazdır — ağaç görselleştirmesi için
|
||||||
if (Right) Right->log(indent + 2);
|
if (Right) Right->log(indent + 2);
|
||||||
if (Left) Left->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 {
|
class LiteralNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
Token* lexerToken = nullptr;
|
Token* lexerToken = nullptr; // Tokenizer'dan gelen orijinal token
|
||||||
ParserToken parserToken;
|
ParserToken parserToken; // Parser tarafından zenginleştirilmiş token
|
||||||
|
|
||||||
LiteralNode() { kind = ASTKind::Literal; }
|
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 {
|
class IdentifierNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
Token* lexerToken = nullptr;
|
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 {
|
class PostfixNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
ASTNode* operand = nullptr;
|
ASTNode* operand = nullptr; // İşlem yapılan ifade
|
||||||
TokenType Operator;
|
TokenType Operator; // PLUS_PLUS veya MINUS_MINUS
|
||||||
|
|
||||||
PostfixNode() { kind = ASTKind::Postfix; }
|
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 {
|
class IfStatementNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
ASTNode* condition = nullptr;
|
ASTNode* condition = nullptr; // Koşul
|
||||||
ASTNode* thenBranch = nullptr; // BlockNode or single statement
|
ASTNode* thenBranch = nullptr; // if gövdesi
|
||||||
ASTNode* elseBranch = nullptr; // optional
|
ASTNode* elseBranch = nullptr; // else gövdesi (opsiyonel)
|
||||||
|
|
||||||
IfStatementNode() { kind = ASTKind::IfStatement; }
|
IfStatementNode() { kind = ASTKind::IfStatement; }
|
||||||
|
|
||||||
|
|
@ -226,10 +356,16 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// WhileStatementNode — while Döngüsü
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// while (condition) body
|
||||||
|
//
|
||||||
class WhileStatementNode : public ASTNode {
|
class WhileStatementNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
ASTNode* condition = nullptr;
|
ASTNode* condition = nullptr; // Döngü koşulu
|
||||||
ASTNode* body = nullptr;
|
ASTNode* body = nullptr; // Döngü gövdesi
|
||||||
|
|
||||||
WhileStatementNode() { kind = ASTKind::WhileStatement; }
|
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 {
|
class ForStatementNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
ASTNode* init = nullptr; // VariableDecl or ExpressionStatement
|
ASTNode* init = nullptr; // Başlangıç
|
||||||
ASTNode* condition = nullptr; // expression
|
ASTNode* condition = nullptr; // Koşul
|
||||||
ASTNode* update = nullptr; // expression
|
ASTNode* update = nullptr; // Güncelleme
|
||||||
ASTNode* body = nullptr;
|
ASTNode* body = nullptr; // Gövde
|
||||||
|
|
||||||
ForStatementNode() { kind = ASTKind::ForStatement; }
|
ForStatementNode() { kind = ASTKind::ForStatement; }
|
||||||
|
|
||||||
|
|
@ -270,6 +417,12 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DoWhileStatementNode — do-while Döngüsü
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// do body while (condition);
|
||||||
|
//
|
||||||
class DoWhileStatementNode : public ASTNode {
|
class DoWhileStatementNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
ASTNode* condition = nullptr;
|
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 {
|
class ReturnStatementNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
ASTNode* value = nullptr; // optional
|
ASTNode* value = nullptr; // Dönüş değeri (opsiyonel)
|
||||||
|
|
||||||
ReturnStatementNode() { kind = ASTKind::ReturnStatement; }
|
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 {
|
class BreakStatementNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
BreakStatementNode() { kind = ASTKind::BreakStatement; }
|
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 {
|
class ContinueStatementNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
ContinueStatementNode() { kind = ASTKind::ContinueStatement; }
|
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 {
|
class ExpressionStatementNode : public ASTNode {
|
||||||
public:
|
public:
|
||||||
ASTNode* expression = nullptr;
|
ASTNode* expression = nullptr; // İç ifade
|
||||||
|
|
||||||
ExpressionStatementNode() { kind = ASTKind::ExpressionStatement; }
|
ExpressionStatementNode() { kind = ASTKind::ExpressionStatement; }
|
||||||
|
|
||||||
|
|
@ -331,4 +510,4 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif // SAQUT_AST
|
||||||
|
|
|
||||||
|
|
@ -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
|
#ifndef SAQUT_PARSER
|
||||||
#define SAQUT_PARSER
|
#define SAQUT_PARSER
|
||||||
|
|
||||||
|
|
@ -8,30 +80,44 @@
|
||||||
#include "parser/ast.hpp"
|
#include "parser/ast.hpp"
|
||||||
#include "tools.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 {
|
class Parser {
|
||||||
public:
|
public:
|
||||||
ASTNode* parse(TokenList tokens);
|
ASTNode* parse(TokenList tokens);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TokenList tokens;
|
TokenList tokens; // Tokenizer'dan gelen token listesi
|
||||||
int current = 0;
|
int current = 0; // Şu anki token indeksi
|
||||||
|
|
||||||
// Token navigation
|
// --- Token navigasyonu ---
|
||||||
ParserToken currentToken();
|
ParserToken currentToken();
|
||||||
void nextToken();
|
void nextToken();
|
||||||
ParserToken lookahead(uint32_t forward);
|
ParserToken lookahead(uint32_t forward);
|
||||||
ParserToken parseToken(Token* token);
|
ParserToken parseToken(Token* token);
|
||||||
ParserToken getToken(int offset);
|
ParserToken getToken(int offset);
|
||||||
|
|
||||||
// --- Top level ---
|
// --- Üst seviye ---
|
||||||
ASTNode* parseProgram();
|
ASTNode* parseProgram();
|
||||||
|
|
||||||
// --- Declarations ---
|
// --- Deklarasyonlar ---
|
||||||
ASTNode* parseDeclaration();
|
ASTNode* parseDeclaration();
|
||||||
ASTNode* parseFunctionDecl();
|
ASTNode* parseFunctionDecl();
|
||||||
ASTNode* parseVariableDecl();
|
ASTNode* parseVariableDecl();
|
||||||
|
|
||||||
// --- Statements ---
|
// --- Statement'lar ---
|
||||||
ASTNode* parseStatement();
|
ASTNode* parseStatement();
|
||||||
ASTNode* parseBlock();
|
ASTNode* parseBlock();
|
||||||
ASTNode* parseIfStatement();
|
ASTNode* parseIfStatement();
|
||||||
|
|
@ -43,20 +129,29 @@ private:
|
||||||
ASTNode* parseContinueStatement();
|
ASTNode* parseContinueStatement();
|
||||||
ASTNode* parseExpressionStatement();
|
ASTNode* parseExpressionStatement();
|
||||||
|
|
||||||
// --- Expressions (Pratt parser) ---
|
// --- İfadeler (Pratt parser) ---
|
||||||
ASTNode* parseExpression();
|
ASTNode* parseExpression();
|
||||||
ASTNode* parseExpression(uint16_t precedence);
|
ASTNode* parseExpression(uint16_t precedence);
|
||||||
ASTNode* parseNullDenotation();
|
ASTNode* parseNullDenotation();
|
||||||
ASTNode* parseLeftDenotation(ASTNode* left);
|
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) {
|
inline ParserToken Parser::parseToken(Token* token) {
|
||||||
ParserToken pt;
|
ParserToken pt;
|
||||||
pt.token = token;
|
pt.token = token; // Pointer — değer kopyası DEĞİL
|
||||||
|
|
||||||
std::string t = token->gettype();
|
std::string t = token->gettype();
|
||||||
if (t == "string")
|
if (t == "string")
|
||||||
|
|
@ -75,6 +170,9 @@ inline ParserToken Parser::parseToken(Token* token) {
|
||||||
return pt;
|
return pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// getToken: Güvenli token erişimi. Sınır dışı = SVR_VOID.
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
inline ParserToken Parser::getToken(int offset) {
|
inline ParserToken Parser::getToken(int offset) {
|
||||||
if ((int)tokens.size() - 1 < current + offset) {
|
if ((int)tokens.size() - 1 < current + offset) {
|
||||||
ParserToken pt;
|
ParserToken pt;
|
||||||
|
|
@ -97,16 +195,28 @@ inline ParserToken Parser::currentToken() {
|
||||||
return getToken(0);
|
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) {
|
inline ASTNode* Parser::parse(TokenList toks) {
|
||||||
tokens = toks;
|
tokens = toks;
|
||||||
current = 0;
|
current = 0;
|
||||||
return parseProgram();
|
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() {
|
inline ASTNode* Parser::parseProgram() {
|
||||||
ProgramNode* program = new ProgramNode();
|
ProgramNode* program = new ProgramNode();
|
||||||
|
|
||||||
|
|
@ -115,58 +225,82 @@ inline ASTNode* Parser::parseProgram() {
|
||||||
if (decl)
|
if (decl)
|
||||||
program->addChild(decl);
|
program->addChild(decl);
|
||||||
else
|
else
|
||||||
break;
|
break; // Hata durumunda döngüden çık
|
||||||
}
|
}
|
||||||
|
|
||||||
return program;
|
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() {
|
inline ASTNode* Parser::parseDeclaration() {
|
||||||
auto ct = currentToken();
|
auto ct = currentToken();
|
||||||
|
|
||||||
// Function declaration: type identifier ( ) { ... }
|
// Tip keyword'ü ile başlayan → fonksiyon veya değişken
|
||||||
if (ct.is({
|
if (ct.is({
|
||||||
TokenType::KW_VOID, TokenType::KW_INT, TokenType::KW_FLOAT_TYPE,
|
TokenType::KW_VOID, TokenType::KW_INT, TokenType::KW_FLOAT_TYPE,
|
||||||
TokenType::KW_DOUBLE, TokenType::KW_BOOL, TokenType::KW_CHAR,
|
TokenType::KW_DOUBLE, TokenType::KW_BOOL, TokenType::KW_CHAR,
|
||||||
TokenType::KW_STRING_TYPE, TokenType::KW_AUTO
|
TokenType::KW_STRING_TYPE, TokenType::KW_AUTO
|
||||||
})) {
|
})) {
|
||||||
// Check if next is identifier, then '(' → function
|
|
||||||
auto la1 = lookahead(1);
|
auto la1 = lookahead(1);
|
||||||
auto la2 = lookahead(2);
|
auto la2 = lookahead(2);
|
||||||
|
// int main( ... ) → fonksiyon
|
||||||
if (la1.type == TokenType::IDENTIFIER && la2.type == TokenType::LPAREN)
|
if (la1.type == TokenType::IDENTIFIER && la2.type == TokenType::LPAREN)
|
||||||
return parseFunctionDecl();
|
return parseFunctionDecl();
|
||||||
// Otherwise variable declaration
|
// int x ... → değişken
|
||||||
return parseVariableDecl();
|
return parseVariableDecl();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standalone expression (for REPL / bare source.sqt)
|
// Tip keyword'ü değil → statement (veya REPL ifadesi)
|
||||||
return parseStatement();
|
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() {
|
inline ASTNode* Parser::parseFunctionDecl() {
|
||||||
FunctionDeclNode* fn = new FunctionDeclNode();
|
FunctionDeclNode* fn = new FunctionDeclNode();
|
||||||
fn->returnType = currentToken().token->token; // e.g., "void", "int"
|
fn->returnType = currentToken().token->token; // "int", "void", ...
|
||||||
nextToken(); // eat return type
|
nextToken(); // Dönüş tipini tüket
|
||||||
|
|
||||||
fn->name = currentToken().token->token;
|
fn->name = currentToken().token->token; // "main", "calculate", ...
|
||||||
nextToken(); // eat name
|
nextToken(); // İsmi tüket
|
||||||
|
|
||||||
// Eat '(' ... ')'
|
// Parametre listesi: ( ... )
|
||||||
if (currentToken().type == TokenType::LPAREN) {
|
if (currentToken().type == TokenType::LPAREN) {
|
||||||
nextToken();
|
nextToken(); // '(' tüket
|
||||||
// Skip params for now
|
// TODO: Parametreleri ayrıştır
|
||||||
|
// Şimdilik ')' gelene kadar atla
|
||||||
while (currentToken().type != TokenType::RPAREN &&
|
while (currentToken().type != TokenType::RPAREN &&
|
||||||
currentToken().type != TokenType::SVR_VOID)
|
currentToken().type != TokenType::SVR_VOID)
|
||||||
nextToken();
|
nextToken();
|
||||||
if (currentToken().type == TokenType::RPAREN)
|
if (currentToken().type == TokenType::RPAREN)
|
||||||
nextToken();
|
nextToken(); // ')' tüket
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse body { ... }
|
// Gövde: { ... }
|
||||||
if (currentToken().type == TokenType::LBRACE) {
|
if (currentToken().type == TokenType::LBRACE) {
|
||||||
ASTNode* body = parseBlock();
|
ASTNode* body = parseBlock();
|
||||||
fn->addChild(body);
|
fn->addChild(body);
|
||||||
|
|
@ -175,36 +309,51 @@ inline ASTNode* Parser::parseFunctionDecl() {
|
||||||
return fn;
|
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() {
|
inline ASTNode* Parser::parseVariableDecl() {
|
||||||
VariableDeclNode* vd = new VariableDeclNode();
|
VariableDeclNode* vd = new VariableDeclNode();
|
||||||
vd->varType = currentToken().token->token; // e.g., "int", "float"
|
vd->varType = currentToken().token->token; // "int", "float", ...
|
||||||
nextToken(); // eat type
|
nextToken(); // Tipi tüket
|
||||||
|
|
||||||
if (currentToken().type != TokenType::IDENTIFIER) {
|
if (currentToken().type != TokenType::IDENTIFIER) {
|
||||||
std::cerr << "Parser hatası: değişken ismi bekleniyor\n";
|
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;
|
vd->name = currentToken().token->token; // "x", "counter", ...
|
||||||
nextToken(); // eat name
|
nextToken(); // İsmi tüket
|
||||||
|
|
||||||
// Optional initializer: = expression
|
// Opsiyonel başlangıç değeri: = expression
|
||||||
if (currentToken().type == TokenType::EQUAL) {
|
if (currentToken().type == TokenType::EQUAL) {
|
||||||
nextToken(); // eat =
|
nextToken(); // '=' tüket
|
||||||
vd->initExpr = parseExpression();
|
vd->initExpr = parseExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional semicolon
|
// Noktalı virgül (opsiyonel — parser hoşgörülü)
|
||||||
if (currentToken().type == TokenType::SEMICOLON)
|
if (currentToken().type == TokenType::SEMICOLON)
|
||||||
nextToken();
|
nextToken();
|
||||||
|
|
||||||
return vd;
|
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() {
|
inline ASTNode* Parser::parseStatement() {
|
||||||
auto ct = currentToken();
|
auto ct = currentToken();
|
||||||
|
|
||||||
|
|
@ -232,7 +381,7 @@ inline ASTNode* Parser::parseStatement() {
|
||||||
if (ct.type == TokenType::KW_CONTINUE)
|
if (ct.type == TokenType::KW_CONTINUE)
|
||||||
return parseContinueStatement();
|
return parseContinueStatement();
|
||||||
|
|
||||||
// Variable declaration? (type identifier ...)
|
// Değişken tanımı? (tip keyword'ü ile başlayan)
|
||||||
if (ct.is({
|
if (ct.is({
|
||||||
TokenType::KW_VOID, TokenType::KW_INT, TokenType::KW_FLOAT_TYPE,
|
TokenType::KW_VOID, TokenType::KW_INT, TokenType::KW_FLOAT_TYPE,
|
||||||
TokenType::KW_DOUBLE, TokenType::KW_BOOL, TokenType::KW_CHAR,
|
TokenType::KW_DOUBLE, TokenType::KW_BOOL, TokenType::KW_CHAR,
|
||||||
|
|
@ -241,15 +390,18 @@ inline ASTNode* Parser::parseStatement() {
|
||||||
return parseVariableDecl();
|
return parseVariableDecl();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default: expression statement
|
// Hiçbiri değilse → ifade statement'ı (atama, fonksiyon çağrısı, ...)
|
||||||
return parseExpressionStatement();
|
return parseExpressionStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// parseBlock: { statement* }
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
inline ASTNode* Parser::parseBlock() {
|
inline ASTNode* Parser::parseBlock() {
|
||||||
BlockNode* block = new BlockNode();
|
BlockNode* block = new BlockNode();
|
||||||
|
|
||||||
if (currentToken().type == TokenType::LBRACE)
|
if (currentToken().type == TokenType::LBRACE)
|
||||||
nextToken(); // eat {
|
nextToken(); // '{' tüket
|
||||||
|
|
||||||
while (currentToken().type != TokenType::RBRACE &&
|
while (currentToken().type != TokenType::RBRACE &&
|
||||||
currentToken().type != TokenType::SVR_VOID) {
|
currentToken().type != TokenType::SVR_VOID) {
|
||||||
|
|
@ -257,81 +409,104 @@ inline ASTNode* Parser::parseBlock() {
|
||||||
if (stmt)
|
if (stmt)
|
||||||
block->addChild(stmt);
|
block->addChild(stmt);
|
||||||
else
|
else
|
||||||
break;
|
break; // Hata durumunda döngüden çık
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentToken().type == TokenType::RBRACE)
|
if (currentToken().type == TokenType::RBRACE)
|
||||||
nextToken(); // eat }
|
nextToken(); // '}' tüket
|
||||||
|
|
||||||
return block;
|
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() {
|
inline ASTNode* Parser::parseIfStatement() {
|
||||||
IfStatementNode* ifNode = new IfStatementNode();
|
IfStatementNode* ifNode = new IfStatementNode();
|
||||||
nextToken(); // eat 'if'
|
nextToken(); // 'if' tüket
|
||||||
|
|
||||||
// Condition: ( expression )
|
// Koşul: ( expression )
|
||||||
if (currentToken().type == TokenType::LPAREN) {
|
if (currentToken().type == TokenType::LPAREN) {
|
||||||
nextToken();
|
nextToken(); // '(' tüket
|
||||||
ifNode->condition = parseExpression();
|
ifNode->condition = parseExpression();
|
||||||
if (currentToken().type == TokenType::RPAREN)
|
if (currentToken().type == TokenType::RPAREN)
|
||||||
nextToken();
|
nextToken(); // ')' tüket
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then branch
|
// Then gövdesi
|
||||||
ifNode->thenBranch = parseStatement();
|
ifNode->thenBranch = parseStatement();
|
||||||
|
|
||||||
// Optional else
|
// Opsiyonel else
|
||||||
if (currentToken().type == TokenType::KW_ELSE) {
|
if (currentToken().type == TokenType::KW_ELSE) {
|
||||||
nextToken();
|
nextToken(); // 'else' tüket
|
||||||
ifNode->elseBranch = parseStatement();
|
ifNode->elseBranch = parseStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ifNode;
|
return ifNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// parseWhileStatement: while (expression) statement
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
inline ASTNode* Parser::parseWhileStatement() {
|
inline ASTNode* Parser::parseWhileStatement() {
|
||||||
WhileStatementNode* ws = new WhileStatementNode();
|
WhileStatementNode* ws = new WhileStatementNode();
|
||||||
nextToken(); // eat 'while'
|
nextToken(); // 'while' tüket
|
||||||
|
|
||||||
// Condition: ( expression )
|
|
||||||
if (currentToken().type == TokenType::LPAREN) {
|
if (currentToken().type == TokenType::LPAREN) {
|
||||||
nextToken();
|
nextToken(); // '(' tüket
|
||||||
ws->condition = parseExpression();
|
ws->condition = parseExpression();
|
||||||
if (currentToken().type == TokenType::RPAREN)
|
if (currentToken().type == TokenType::RPAREN)
|
||||||
nextToken();
|
nextToken(); // ')' tüket
|
||||||
}
|
}
|
||||||
|
|
||||||
// Body
|
|
||||||
ws->body = parseStatement();
|
ws->body = parseStatement();
|
||||||
|
|
||||||
return ws;
|
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() {
|
inline ASTNode* Parser::parseForStatement() {
|
||||||
ForStatementNode* fs = new ForStatementNode();
|
ForStatementNode* fs = new ForStatementNode();
|
||||||
nextToken(); // eat 'for'
|
nextToken(); // 'for' tüket
|
||||||
|
|
||||||
if (currentToken().type == TokenType::LPAREN)
|
if (currentToken().type == TokenType::LPAREN)
|
||||||
nextToken(); // eat (
|
nextToken(); // '(' tüket
|
||||||
|
|
||||||
// Init
|
// Init (opsiyonel)
|
||||||
if (currentToken().type != TokenType::SEMICOLON)
|
if (currentToken().type != TokenType::SEMICOLON)
|
||||||
fs->init = parseStatement();
|
fs->init = parseStatement();
|
||||||
if (currentToken().type == TokenType::SEMICOLON)
|
if (currentToken().type == TokenType::SEMICOLON)
|
||||||
nextToken();
|
nextToken(); // ';' tüket
|
||||||
|
|
||||||
// Condition
|
// Condition (opsiyonel)
|
||||||
if (currentToken().type != TokenType::SEMICOLON)
|
if (currentToken().type != TokenType::SEMICOLON)
|
||||||
fs->condition = parseExpression();
|
fs->condition = parseExpression();
|
||||||
if (currentToken().type == TokenType::SEMICOLON)
|
if (currentToken().type == TokenType::SEMICOLON)
|
||||||
nextToken();
|
nextToken(); // ';' tüket
|
||||||
|
|
||||||
// Update
|
// Update (opsiyonel)
|
||||||
if (currentToken().type != TokenType::RPAREN)
|
if (currentToken().type != TokenType::RPAREN)
|
||||||
fs->update = parseExpression();
|
fs->update = parseExpression();
|
||||||
if (currentToken().type == TokenType::RPAREN)
|
if (currentToken().type == TokenType::RPAREN)
|
||||||
nextToken();
|
nextToken(); // ')' tüket
|
||||||
|
|
||||||
// Body
|
// Body
|
||||||
fs->body = parseStatement();
|
fs->body = parseStatement();
|
||||||
|
|
@ -339,48 +514,61 @@ inline ASTNode* Parser::parseForStatement() {
|
||||||
return fs;
|
return fs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// parseDoWhileStatement: do statement while (expression) ;
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
inline ASTNode* Parser::parseDoWhileStatement() {
|
inline ASTNode* Parser::parseDoWhileStatement() {
|
||||||
DoWhileStatementNode* dw = new DoWhileStatementNode();
|
DoWhileStatementNode* dw = new DoWhileStatementNode();
|
||||||
nextToken(); // eat 'do'
|
nextToken(); // 'do' tüket
|
||||||
|
|
||||||
// Body
|
// Gövde
|
||||||
dw->body = parseStatement();
|
dw->body = parseStatement();
|
||||||
|
|
||||||
// 'while' ( expression ) ;
|
// while (expression) ;
|
||||||
if (currentToken().type == TokenType::KW_WHILE) {
|
if (currentToken().type == TokenType::KW_WHILE) {
|
||||||
nextToken();
|
nextToken(); // 'while' tüket
|
||||||
if (currentToken().type == TokenType::LPAREN) {
|
if (currentToken().type == TokenType::LPAREN) {
|
||||||
nextToken();
|
nextToken(); // '(' tüket
|
||||||
dw->condition = parseExpression();
|
dw->condition = parseExpression();
|
||||||
if (currentToken().type == TokenType::RPAREN)
|
if (currentToken().type == TokenType::RPAREN)
|
||||||
nextToken();
|
nextToken(); // ')' tüket
|
||||||
}
|
}
|
||||||
if (currentToken().type == TokenType::SEMICOLON)
|
if (currentToken().type == TokenType::SEMICOLON)
|
||||||
nextToken();
|
nextToken(); // ';' tüket
|
||||||
}
|
}
|
||||||
|
|
||||||
return dw;
|
return dw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// parseReturnStatement: return [expression] ;
|
||||||
|
//
|
||||||
|
// return; ← value = nullptr (void fonksiyon)
|
||||||
|
// return x + 1; ← value = BinaryExpression
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
inline ASTNode* Parser::parseReturnStatement() {
|
inline ASTNode* Parser::parseReturnStatement() {
|
||||||
ReturnStatementNode* rs = new ReturnStatementNode();
|
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 &&
|
if (currentToken().type != TokenType::SEMICOLON &&
|
||||||
currentToken().type != TokenType::RBRACE) {
|
currentToken().type != TokenType::RBRACE) {
|
||||||
rs->value = parseExpression();
|
rs->value = parseExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentToken().type == TokenType::SEMICOLON)
|
if (currentToken().type == TokenType::SEMICOLON)
|
||||||
nextToken();
|
nextToken(); // ';' tüket
|
||||||
|
|
||||||
return rs;
|
return rs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// parseBreakStatement / parseContinueStatement
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
inline ASTNode* Parser::parseBreakStatement() {
|
inline ASTNode* Parser::parseBreakStatement() {
|
||||||
BreakStatementNode* bs = new BreakStatementNode();
|
BreakStatementNode* bs = new BreakStatementNode();
|
||||||
nextToken(); // eat 'break'
|
nextToken(); // 'break' tüket
|
||||||
if (currentToken().type == TokenType::SEMICOLON)
|
if (currentToken().type == TokenType::SEMICOLON)
|
||||||
nextToken();
|
nextToken();
|
||||||
return bs;
|
return bs;
|
||||||
|
|
@ -388,17 +576,30 @@ inline ASTNode* Parser::parseBreakStatement() {
|
||||||
|
|
||||||
inline ASTNode* Parser::parseContinueStatement() {
|
inline ASTNode* Parser::parseContinueStatement() {
|
||||||
ContinueStatementNode* cs = new ContinueStatementNode();
|
ContinueStatementNode* cs = new ContinueStatementNode();
|
||||||
nextToken(); // eat 'continue'
|
nextToken(); // 'continue' tüket
|
||||||
if (currentToken().type == TokenType::SEMICOLON)
|
if (currentToken().type == TokenType::SEMICOLON)
|
||||||
nextToken();
|
nextToken();
|
||||||
return cs;
|
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() {
|
inline ASTNode* Parser::parseExpressionStatement() {
|
||||||
ExpressionStatementNode* es = new ExpressionStatementNode();
|
ExpressionStatementNode* es = new ExpressionStatementNode();
|
||||||
es->expression = parseExpression();
|
es->expression = parseExpression();
|
||||||
if (!es->expression) {
|
if (!es->expression) {
|
||||||
// Parsing failed — skip to next statement boundary
|
// Hata kurtarma: sonraki güvenli noktaya atla
|
||||||
while (currentToken().type != TokenType::SEMICOLON &&
|
while (currentToken().type != TokenType::SEMICOLON &&
|
||||||
currentToken().type != TokenType::RBRACE &&
|
currentToken().type != TokenType::RBRACE &&
|
||||||
currentToken().type != TokenType::SVR_VOID)
|
currentToken().type != TokenType::SVR_VOID)
|
||||||
|
|
@ -412,29 +613,79 @@ inline ASTNode* Parser::parseExpressionStatement() {
|
||||||
return es;
|
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() {
|
inline ASTNode* Parser::parseExpression() {
|
||||||
return parseExpression(0);
|
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) {
|
inline ASTNode* Parser::parseExpression(uint16_t precedence) {
|
||||||
if (currentToken().type == TokenType::SVR_VOID)
|
if (currentToken().type == TokenType::SVR_VOID)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
|
// 1. Prefix (NUD)
|
||||||
ASTNode* left = parseNullDenotation();
|
ASTNode* left = parseNullDenotation();
|
||||||
if (!left) return nullptr;
|
if (!left) return nullptr;
|
||||||
|
|
||||||
|
// 2. Infix/Postfix döngüsü (LED)
|
||||||
while (true) {
|
while (true) {
|
||||||
auto next = currentToken();
|
auto next = currentToken();
|
||||||
|
|
||||||
|
// İfade sonu sinyalleri → dur
|
||||||
if (next.type == TokenType::RPAREN ||
|
if (next.type == TokenType::RPAREN ||
|
||||||
next.type == TokenType::SEMICOLON ||
|
next.type == TokenType::SEMICOLON ||
|
||||||
next.type == TokenType::RBRACE ||
|
next.type == TokenType::RBRACE ||
|
||||||
next.type == TokenType::COMMA)
|
next.type == TokenType::COMMA)
|
||||||
break;
|
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()) {
|
if (precedence < next.getPowerOperator()) {
|
||||||
left = parseLeftDenotation(left);
|
left = parseLeftDenotation(left);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -444,7 +695,17 @@ inline ASTNode* Parser::parseExpression(uint16_t precedence) {
|
||||||
return left;
|
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() {
|
inline ASTNode* Parser::parseNullDenotation() {
|
||||||
auto ct = currentToken();
|
auto ct = currentToken();
|
||||||
|
|
||||||
|
|
@ -453,41 +714,50 @@ inline ASTNode* Parser::parseNullDenotation() {
|
||||||
return nullptr;
|
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) {
|
if (ct.type == TokenType::LPAREN) {
|
||||||
nextToken();
|
nextToken(); // '(' tüket
|
||||||
ASTNode* expr = parseExpression(0);
|
ASTNode* expr = parseExpression(0); // Öncelik sıfırla
|
||||||
if (currentToken().type == TokenType::RPAREN)
|
if (currentToken().type == TokenType::RPAREN)
|
||||||
nextToken();
|
nextToken(); // ')' tüket
|
||||||
return expr;
|
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({
|
if (ct.is({
|
||||||
TokenType::PLUS_PLUS, TokenType::MINUS_MINUS,
|
TokenType::PLUS_PLUS, TokenType::MINUS_MINUS,
|
||||||
TokenType::PLUS, TokenType::MINUS,
|
TokenType::PLUS, TokenType::MINUS,
|
||||||
TokenType::BANG, TokenType::TILDE
|
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());
|
ASTNode* right = parseExpression(ct.getPowerOperator());
|
||||||
BinaryExpressionNode* bin = new BinaryExpressionNode();
|
BinaryExpressionNode* bin = new BinaryExpressionNode();
|
||||||
bin->Right = right;
|
bin->Right = right;
|
||||||
bin->Left = nullptr;
|
bin->Left = nullptr; // Unary işaretçisi
|
||||||
bin->Operator = ct.type;
|
bin->Operator = ct.type;
|
||||||
if (right) right->parent = bin;
|
if (right) right->parent = bin;
|
||||||
return bin;
|
return bin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Numeric literal
|
// --- Sayısal literal: 42, 0xFF, 3.14 ---
|
||||||
if (ct.type == TokenType::NUMBER) {
|
if (ct.type == TokenType::NUMBER) {
|
||||||
nextToken();
|
nextToken(); // Token'ı tüket
|
||||||
LiteralNode* lit = new LiteralNode();
|
LiteralNode* lit = new LiteralNode();
|
||||||
lit->lexerToken = ct.token;
|
lit->lexerToken = ct.token;
|
||||||
lit->parserToken = ct;
|
lit->parserToken = ct;
|
||||||
return lit;
|
return lit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// String literal
|
// --- String literal: "hello" ---
|
||||||
if (ct.type == TokenType::STRING) {
|
if (ct.type == TokenType::STRING) {
|
||||||
nextToken();
|
nextToken();
|
||||||
LiteralNode* lit = new LiteralNode();
|
LiteralNode* lit = new LiteralNode();
|
||||||
|
|
@ -496,7 +766,7 @@ inline ASTNode* Parser::parseNullDenotation() {
|
||||||
return lit;
|
return lit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boolean / null literals
|
// --- Boolean/null literal: true, false, null ---
|
||||||
if (ct.is({TokenType::KW_TRUE, TokenType::KW_FALSE, TokenType::KW_NULL})) {
|
if (ct.is({TokenType::KW_TRUE, TokenType::KW_FALSE, TokenType::KW_NULL})) {
|
||||||
nextToken();
|
nextToken();
|
||||||
LiteralNode* lit = new LiteralNode();
|
LiteralNode* lit = new LiteralNode();
|
||||||
|
|
@ -505,7 +775,7 @@ inline ASTNode* Parser::parseNullDenotation() {
|
||||||
return lit;
|
return lit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identifier
|
// --- Identifier: x, myVar ---
|
||||||
if (ct.type == TokenType::IDENTIFIER) {
|
if (ct.type == TokenType::IDENTIFIER) {
|
||||||
nextToken();
|
nextToken();
|
||||||
IdentifierNode* id = new IdentifierNode();
|
IdentifierNode* id = new IdentifierNode();
|
||||||
|
|
@ -517,13 +787,27 @@ inline ASTNode* Parser::parseNullDenotation() {
|
||||||
return nullptr;
|
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) {
|
inline ASTNode* Parser::parseLeftDenotation(ASTNode* left) {
|
||||||
auto ct = currentToken();
|
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})) {
|
if (ct.is({TokenType::PLUS_PLUS, TokenType::MINUS_MINUS})) {
|
||||||
nextToken();
|
nextToken(); // Operatörü tüket
|
||||||
PostfixNode* pf = new PostfixNode();
|
PostfixNode* pf = new PostfixNode();
|
||||||
pf->operand = left;
|
pf->operand = left;
|
||||||
pf->Operator = ct.type;
|
pf->Operator = ct.type;
|
||||||
|
|
@ -531,10 +815,13 @@ inline ASTNode* Parser::parseLeftDenotation(ASTNode* left) {
|
||||||
return pf;
|
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();
|
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);
|
ASTNode* right = parseExpression(prec);
|
||||||
|
|
||||||
BinaryExpressionNode* bin = new BinaryExpressionNode();
|
BinaryExpressionNode* bin = new BinaryExpressionNode();
|
||||||
|
|
@ -546,4 +833,4 @@ inline ASTNode* Parser::parseLeftDenotation(ASTNode* left) {
|
||||||
return bin;
|
return bin;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif // SAQUT_PARSER
|
||||||
|
|
|
||||||
|
|
@ -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
|
#ifndef SAQUT_PARSER_TOKEN
|
||||||
#define SAQUT_PARSER_TOKEN
|
#define SAQUT_PARSER_TOKEN
|
||||||
|
|
||||||
|
|
@ -8,61 +73,221 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "tokenizer/tokenizer.hpp"
|
#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;
|
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 {
|
enum class TokenType : uint16_t {
|
||||||
IDENTIFIER,
|
// --- Değerler ve Tanımlayıcılar ---
|
||||||
NUMBER,
|
IDENTIFIER, // değişken/fonksiyon ismi
|
||||||
STRING,
|
NUMBER, // 42, 0xFF, 0b1010, 3.14
|
||||||
SVR_VOID,
|
STRING, // "merhaba"
|
||||||
|
SVR_VOID, // Geçersiz/EOF sinyali (Parser içinde kullanılır)
|
||||||
|
|
||||||
// Keywords
|
// --- Kontrol Akışı Keyword'leri ---
|
||||||
KW_IF, KW_ELSE, KW_FOR, KW_WHILE, KW_DO,
|
KW_IF, // if
|
||||||
KW_SWITCH, KW_CASE, KW_DEFAULT, KW_BREAK, KW_CONTINUE,
|
KW_ELSE, // else
|
||||||
KW_RETURN, KW_CLASS, KW_INTERFACE, KW_ENUM,
|
KW_FOR, // for
|
||||||
KW_EXTENDS, KW_IMPLEMENTS, KW_NEW,
|
KW_WHILE, // while
|
||||||
KW_PUBLIC, KW_PRIVATE, KW_PROTECTED, KW_STATIC,
|
KW_DO, // do
|
||||||
KW_FINAL, KW_ABSTRACT,
|
KW_SWITCH, // switch
|
||||||
KW_VOID, KW_BOOL, KW_INT, KW_FLOAT_TYPE, KW_DOUBLE,
|
KW_CASE, // case
|
||||||
KW_CHAR, KW_STRING_TYPE,
|
KW_DEFAULT, // default
|
||||||
KW_TRUE, KW_FALSE, KW_NULL,
|
KW_BREAK, // break
|
||||||
KW_TRY, KW_CATCH, KW_FINALLY, KW_THROW, KW_THROWS, KW_ASSERT,
|
KW_CONTINUE, // continue
|
||||||
KW_IMPORT, KW_PACKAGE, KW_NATIVE,
|
KW_RETURN, // return
|
||||||
KW_SYNCHRONIZED, KW_VOLATILE, KW_TRANSIENT,
|
|
||||||
KW_CONST, KW_EXTERN, KW_TYPEDEF, KW_SIZEOF,
|
|
||||||
KW_ALIGNOF, KW_DECLTYPE, KW_AUTO, KW_CONSTEXPR, KW_NOEXCEPT,
|
|
||||||
|
|
||||||
// Operators (precedence order)
|
// --- OOP Keyword'leri ---
|
||||||
DOT, ARROW, LBRACKET, RBRACKET, LPAREN, RPAREN,
|
KW_CLASS, // class
|
||||||
PLUS_PLUS, MINUS_MINUS,
|
KW_INTERFACE, // interface
|
||||||
PLUS, MINUS, BANG, TILDE,
|
KW_ENUM, // enum
|
||||||
STAR_STAR, CARET,
|
KW_EXTENDS, // extends
|
||||||
STAR, SLASH, PERCENT,
|
KW_IMPLEMENTS, // implements
|
||||||
LSHIFT, RSHIFT,
|
KW_NEW, // new
|
||||||
LESS, LESS_EQUAL, GREATER, GREATER_EQUAL,
|
KW_PUBLIC, // public
|
||||||
EQUAL_EQUAL, BANG_EQUAL,
|
KW_PRIVATE, // private
|
||||||
AMPERSAND, PIPE,
|
KW_PROTECTED, // protected
|
||||||
AMPERSAND_AMPERSAND, PIPE_PIPE,
|
KW_STATIC, // static
|
||||||
TERNARY, COLON,
|
KW_FINAL, // final
|
||||||
EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL,
|
KW_ABSTRACT, // abstract
|
||||||
PERCENT_EQUAL, AMPERSAND_EQUAL, PIPE_EQUAL, CARET_EQUAL,
|
|
||||||
LSHIFT_EQUAL, RSHIFT_EQUAL,
|
|
||||||
|
|
||||||
// Other symbols
|
// --- Tip Keyword'leri ---
|
||||||
LBRACE, RBRACE, SEMICOLON, COMMA, COLON_COLON,
|
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 = {
|
inline const std::unordered_map<std::string_view, TokenType> KEYWORD_MAP = {
|
||||||
|
// --- Control flow ---
|
||||||
{"if", TokenType::KW_IF},
|
{"if", TokenType::KW_IF},
|
||||||
{"else", TokenType::KW_ELSE},
|
{"else", TokenType::KW_ELSE},
|
||||||
{"for", TokenType::KW_FOR},
|
{"for", TokenType::KW_FOR},
|
||||||
|
|
@ -75,6 +300,7 @@ inline const std::unordered_map<std::string_view, TokenType> KEYWORD_MAP = {
|
||||||
{"continue", TokenType::KW_CONTINUE},
|
{"continue", TokenType::KW_CONTINUE},
|
||||||
{"return", TokenType::KW_RETURN},
|
{"return", TokenType::KW_RETURN},
|
||||||
|
|
||||||
|
// --- OOP ---
|
||||||
{"class", TokenType::KW_CLASS},
|
{"class", TokenType::KW_CLASS},
|
||||||
{"interface", TokenType::KW_INTERFACE},
|
{"interface", TokenType::KW_INTERFACE},
|
||||||
{"enum", TokenType::KW_ENUM},
|
{"enum", TokenType::KW_ENUM},
|
||||||
|
|
@ -82,6 +308,7 @@ inline const std::unordered_map<std::string_view, TokenType> KEYWORD_MAP = {
|
||||||
{"implements", TokenType::KW_IMPLEMENTS},
|
{"implements", TokenType::KW_IMPLEMENTS},
|
||||||
{"new", TokenType::KW_NEW},
|
{"new", TokenType::KW_NEW},
|
||||||
|
|
||||||
|
// --- Access modifiers ---
|
||||||
{"public", TokenType::KW_PUBLIC},
|
{"public", TokenType::KW_PUBLIC},
|
||||||
{"private", TokenType::KW_PRIVATE},
|
{"private", TokenType::KW_PRIVATE},
|
||||||
{"protected", TokenType::KW_PROTECTED},
|
{"protected", TokenType::KW_PROTECTED},
|
||||||
|
|
@ -89,6 +316,7 @@ inline const std::unordered_map<std::string_view, TokenType> KEYWORD_MAP = {
|
||||||
{"final", TokenType::KW_FINAL},
|
{"final", TokenType::KW_FINAL},
|
||||||
{"abstract", TokenType::KW_ABSTRACT},
|
{"abstract", TokenType::KW_ABSTRACT},
|
||||||
|
|
||||||
|
// --- Types ---
|
||||||
{"void", TokenType::KW_VOID},
|
{"void", TokenType::KW_VOID},
|
||||||
{"bool", TokenType::KW_BOOL},
|
{"bool", TokenType::KW_BOOL},
|
||||||
{"int", TokenType::KW_INT},
|
{"int", TokenType::KW_INT},
|
||||||
|
|
@ -97,10 +325,12 @@ inline const std::unordered_map<std::string_view, TokenType> KEYWORD_MAP = {
|
||||||
{"char", TokenType::KW_CHAR},
|
{"char", TokenType::KW_CHAR},
|
||||||
{"string", TokenType::KW_STRING_TYPE},
|
{"string", TokenType::KW_STRING_TYPE},
|
||||||
|
|
||||||
|
// --- Literals ---
|
||||||
{"true", TokenType::KW_TRUE},
|
{"true", TokenType::KW_TRUE},
|
||||||
{"false", TokenType::KW_FALSE},
|
{"false", TokenType::KW_FALSE},
|
||||||
{"null", TokenType::KW_NULL},
|
{"null", TokenType::KW_NULL},
|
||||||
|
|
||||||
|
// --- Exception handling ---
|
||||||
{"try", TokenType::KW_TRY},
|
{"try", TokenType::KW_TRY},
|
||||||
{"catch", TokenType::KW_CATCH},
|
{"catch", TokenType::KW_CATCH},
|
||||||
{"finally", TokenType::KW_FINALLY},
|
{"finally", TokenType::KW_FINALLY},
|
||||||
|
|
@ -108,13 +338,11 @@ inline const std::unordered_map<std::string_view, TokenType> KEYWORD_MAP = {
|
||||||
{"throws", TokenType::KW_THROWS},
|
{"throws", TokenType::KW_THROWS},
|
||||||
{"assert", TokenType::KW_ASSERT},
|
{"assert", TokenType::KW_ASSERT},
|
||||||
|
|
||||||
|
// --- Modules/packages ---
|
||||||
{"import", TokenType::KW_IMPORT},
|
{"import", TokenType::KW_IMPORT},
|
||||||
{"package", TokenType::KW_PACKAGE},
|
{"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},
|
{"const", TokenType::KW_CONST},
|
||||||
{"extern", TokenType::KW_EXTERN},
|
{"extern", TokenType::KW_EXTERN},
|
||||||
{"typedef", TokenType::KW_TYPEDEF},
|
{"typedef", TokenType::KW_TYPEDEF},
|
||||||
|
|
@ -122,13 +350,26 @@ inline const std::unordered_map<std::string_view, TokenType> KEYWORD_MAP = {
|
||||||
{"auto", TokenType::KW_AUTO},
|
{"auto", TokenType::KW_AUTO},
|
||||||
{"constexpr", TokenType::KW_CONSTEXPR},
|
{"constexpr", TokenType::KW_CONSTEXPR},
|
||||||
{"noexcept", TokenType::KW_NOEXCEPT},
|
{"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 = {
|
inline const std::unordered_map<std::string_view, TokenType> OPERATOR_MAP = {
|
||||||
|
// --- 2 karakterli ---
|
||||||
{"->", TokenType::ARROW},
|
{"->", TokenType::ARROW},
|
||||||
{"::", TokenType::COLON_COLON},
|
{"::", TokenType::COLON_COLON},
|
||||||
{"==", TokenType::EQUAL_EQUAL},
|
{"==", TokenType::EQUAL_EQUAL},
|
||||||
|
|
@ -143,6 +384,7 @@ inline const std::unordered_map<std::string_view, TokenType> OPERATOR_MAP = {
|
||||||
{">>", TokenType::RSHIFT},
|
{">>", TokenType::RSHIFT},
|
||||||
{"**", TokenType::STAR_STAR},
|
{"**", TokenType::STAR_STAR},
|
||||||
|
|
||||||
|
// --- Birleşik atama ---
|
||||||
{"+=", TokenType::PLUS_EQUAL},
|
{"+=", TokenType::PLUS_EQUAL},
|
||||||
{"-=", TokenType::MINUS_EQUAL},
|
{"-=", TokenType::MINUS_EQUAL},
|
||||||
{"*=", TokenType::STAR_EQUAL},
|
{"*=", TokenType::STAR_EQUAL},
|
||||||
|
|
@ -154,6 +396,7 @@ inline const std::unordered_map<std::string_view, TokenType> OPERATOR_MAP = {
|
||||||
{"<<=", TokenType::LSHIFT_EQUAL},
|
{"<<=", TokenType::LSHIFT_EQUAL},
|
||||||
{">>=", TokenType::RSHIFT_EQUAL},
|
{">>=", TokenType::RSHIFT_EQUAL},
|
||||||
|
|
||||||
|
// --- 1 karakterli operatörler ---
|
||||||
{"+", TokenType::PLUS},
|
{"+", TokenType::PLUS},
|
||||||
{"-", TokenType::MINUS},
|
{"-", TokenType::MINUS},
|
||||||
{"*", TokenType::STAR},
|
{"*", TokenType::STAR},
|
||||||
|
|
@ -168,6 +411,7 @@ inline const std::unordered_map<std::string_view, TokenType> OPERATOR_MAP = {
|
||||||
{"|", TokenType::PIPE},
|
{"|", TokenType::PIPE},
|
||||||
{"=", TokenType::EQUAL},
|
{"=", TokenType::EQUAL},
|
||||||
|
|
||||||
|
// --- Delimiter'lar (operatör gibi işlenir) ---
|
||||||
{"[", TokenType::LBRACKET},
|
{"[", TokenType::LBRACKET},
|
||||||
{"]", TokenType::RBRACKET},
|
{"]", TokenType::RBRACKET},
|
||||||
{"(", TokenType::LPAREN},
|
{"(", TokenType::LPAREN},
|
||||||
|
|
@ -181,6 +425,14 @@ inline const std::unordered_map<std::string_view, TokenType> OPERATOR_MAP = {
|
||||||
{"?", TokenType::TERNARY},
|
{"?", 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 = {
|
inline const std::unordered_map<TokenType, std::string_view> OPERATOR_MAP_REV = {
|
||||||
{TokenType::ARROW, "->"},
|
{TokenType::ARROW, "->"},
|
||||||
{TokenType::COLON_COLON, "::"},
|
{TokenType::COLON_COLON, "::"},
|
||||||
|
|
@ -231,6 +483,13 @@ inline const std::unordered_map<TokenType, std::string_view> OPERATOR_MAP_REV =
|
||||||
{TokenType::TERNARY, "?"},
|
{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 = {
|
inline const std::unordered_map<TokenType, std::string_view> OPERATOR_MAP_STRREV = {
|
||||||
{TokenType::ARROW, "ARROW"},
|
{TokenType::ARROW, "ARROW"},
|
||||||
{TokenType::COLON_COLON, "COLON_COLON"},
|
{TokenType::COLON_COLON, "COLON_COLON"},
|
||||||
|
|
@ -281,10 +540,42 @@ inline const std::unordered_map<TokenType, std::string_view> OPERATOR_MAP_STRREV
|
||||||
{TokenType::TERNARY, "TERNARY"},
|
{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) {
|
inline uint16_t TokenPrecedence(TokenType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
// Level 18: Member access / call
|
// Level 18: Member access / call
|
||||||
|
|
@ -299,141 +590,173 @@ inline uint16_t TokenPrecedence(TokenType type) {
|
||||||
case TokenType::MINUS_MINUS:
|
case TokenType::MINUS_MINUS:
|
||||||
return 17;
|
return 17;
|
||||||
|
|
||||||
// Level 16: Unary prefix
|
// Level 16: Unary prefix — sadece her zaman prefix olanlar
|
||||||
case TokenType::BANG:
|
case TokenType::BANG: // !
|
||||||
case TokenType::TILDE:
|
case TokenType::TILDE: // ~
|
||||||
return 16;
|
return 16;
|
||||||
|
|
||||||
// Level 15: Exponentiation
|
// Level 15: Exponentiation
|
||||||
case TokenType::STAR_STAR:
|
case TokenType::STAR_STAR: // **
|
||||||
case TokenType::CARET:
|
case TokenType::CARET: // ^ (Python tarzı üs)
|
||||||
return 15;
|
return 15;
|
||||||
|
|
||||||
// Level 14: Multiplicative
|
// Level 14: Multiplicative
|
||||||
case TokenType::STAR:
|
case TokenType::STAR: // *
|
||||||
case TokenType::SLASH:
|
case TokenType::SLASH: // /
|
||||||
case TokenType::PERCENT:
|
case TokenType::PERCENT: // %
|
||||||
return 14;
|
return 14;
|
||||||
|
|
||||||
// Level 13: Additive
|
// Level 13: Additive — PLUS ve MINUS hem unary hem binary
|
||||||
case TokenType::PLUS:
|
case TokenType::PLUS: // +
|
||||||
case TokenType::MINUS:
|
case TokenType::MINUS: // -
|
||||||
return 13;
|
return 13;
|
||||||
|
|
||||||
// Level 12: Bit shift
|
// Level 12: Bit shift
|
||||||
case TokenType::LSHIFT:
|
case TokenType::LSHIFT: // <<
|
||||||
case TokenType::RSHIFT:
|
case TokenType::RSHIFT: // >>
|
||||||
return 12;
|
return 12;
|
||||||
|
|
||||||
// Level 11: Relational
|
// Level 11: Relational
|
||||||
case TokenType::LESS:
|
case TokenType::LESS: // <
|
||||||
case TokenType::LESS_EQUAL:
|
case TokenType::LESS_EQUAL:// <=
|
||||||
case TokenType::GREATER:
|
case TokenType::GREATER: // >
|
||||||
case TokenType::GREATER_EQUAL:
|
case TokenType::GREATER_EQUAL: // >=
|
||||||
return 11;
|
return 11;
|
||||||
|
|
||||||
// Level 10: Equality
|
// Level 10: Equality
|
||||||
case TokenType::EQUAL_EQUAL:
|
case TokenType::EQUAL_EQUAL: // ==
|
||||||
case TokenType::BANG_EQUAL:
|
case TokenType::BANG_EQUAL: // !=
|
||||||
return 10;
|
return 10;
|
||||||
|
|
||||||
// Level 9: Bitwise AND
|
// Level 9: Bitwise AND
|
||||||
case TokenType::AMPERSAND:
|
case TokenType::AMPERSAND: // &
|
||||||
return 9;
|
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
|
// Level 7: Bitwise OR
|
||||||
case TokenType::PIPE:
|
case TokenType::PIPE: // |
|
||||||
return 7;
|
return 7;
|
||||||
|
|
||||||
// Level 6: Logical AND
|
// Level 6: Logical AND
|
||||||
case TokenType::AMPERSAND_AMPERSAND:
|
case TokenType::AMPERSAND_AMPERSAND: // &&
|
||||||
return 6;
|
return 6;
|
||||||
|
|
||||||
// Level 5: Logical OR
|
// Level 5: Logical OR
|
||||||
case TokenType::PIPE_PIPE:
|
case TokenType::PIPE_PIPE: // ||
|
||||||
return 5;
|
return 5;
|
||||||
|
|
||||||
// Level 4: Ternary
|
// Level 4: Ternary
|
||||||
case TokenType::TERNARY:
|
case TokenType::TERNARY: // ?
|
||||||
return 4;
|
return 4;
|
||||||
case TokenType::COLON:
|
case TokenType::COLON: // : (ternary için)
|
||||||
return 3;
|
return 3; // ternary'den düşük, atamadan yüksek
|
||||||
|
|
||||||
// Level 2: Assignment
|
// Level 2: Assignment
|
||||||
case TokenType::EQUAL:
|
case TokenType::EQUAL: // =
|
||||||
case TokenType::PLUS_EQUAL:
|
case TokenType::PLUS_EQUAL:// +=
|
||||||
case TokenType::MINUS_EQUAL:
|
case TokenType::MINUS_EQUAL:// -=
|
||||||
case TokenType::STAR_EQUAL:
|
case TokenType::STAR_EQUAL:// *=
|
||||||
case TokenType::SLASH_EQUAL:
|
case TokenType::SLASH_EQUAL:// /=
|
||||||
case TokenType::PERCENT_EQUAL:
|
case TokenType::PERCENT_EQUAL:// %=
|
||||||
case TokenType::AMPERSAND_EQUAL:
|
case TokenType::AMPERSAND_EQUAL:// &=
|
||||||
case TokenType::PIPE_EQUAL:
|
case TokenType::PIPE_EQUAL:// |=
|
||||||
case TokenType::CARET_EQUAL:
|
case TokenType::CARET_EQUAL:// ^=
|
||||||
case TokenType::LSHIFT_EQUAL:
|
case TokenType::LSHIFT_EQUAL:// <<=
|
||||||
case TokenType::RSHIFT_EQUAL:
|
case TokenType::RSHIFT_EQUAL:// >>=
|
||||||
return 2;
|
return 2;
|
||||||
|
|
||||||
// Level 1: Comma
|
// Level 1: Comma
|
||||||
case TokenType::COMMA:
|
case TokenType::COMMA: // ,
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
default:
|
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) {
|
inline bool RightAssociative(TokenType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TokenType::STAR_STAR:
|
case TokenType::STAR_STAR: // ** (üs)
|
||||||
case TokenType::CARET:
|
case TokenType::CARET: // ^ (üs)
|
||||||
case TokenType::EQUAL:
|
case TokenType::EQUAL: // =
|
||||||
case TokenType::PLUS_EQUAL:
|
case TokenType::PLUS_EQUAL: // +=
|
||||||
case TokenType::MINUS_EQUAL:
|
case TokenType::MINUS_EQUAL:// -=
|
||||||
case TokenType::STAR_EQUAL:
|
case TokenType::STAR_EQUAL: // *=
|
||||||
case TokenType::SLASH_EQUAL:
|
case TokenType::SLASH_EQUAL:// /=
|
||||||
case TokenType::PERCENT_EQUAL:
|
case TokenType::PERCENT_EQUAL:// %=
|
||||||
case TokenType::AMPERSAND_EQUAL:
|
case TokenType::AMPERSAND_EQUAL:// &=
|
||||||
case TokenType::PIPE_EQUAL:
|
case TokenType::PIPE_EQUAL: // |=
|
||||||
case TokenType::CARET_EQUAL:
|
case TokenType::CARET_EQUAL:// ^=
|
||||||
case TokenType::LSHIFT_EQUAL:
|
case TokenType::LSHIFT_EQUAL:// <<=
|
||||||
case TokenType::RSHIFT_EQUAL:
|
case TokenType::RSHIFT_EQUAL:// >>=
|
||||||
case TokenType::TERNARY:
|
case TokenType::TERNARY: // ? (ternary)
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
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 {
|
struct ParserToken {
|
||||||
Token* token = nullptr;
|
Token* token = nullptr; // Tokenizer'dan gelen orijinal token
|
||||||
TokenType type = TokenType::SVR_VOID;
|
TokenType type = TokenType::SVR_VOID; // Anlamsal tip
|
||||||
|
|
||||||
|
// Tek tip kontrolü
|
||||||
bool is(TokenType t) const {
|
bool is(TokenType t) const {
|
||||||
return type == t;
|
return type == t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Çoklu tip kontrolü — örn: is({KW_INT, KW_FLOAT, KW_VOID})
|
||||||
bool is(std::initializer_list<TokenType> types) const {
|
bool is(std::initializer_list<TokenType> types) const {
|
||||||
for (TokenType t : types)
|
for (TokenType t : types)
|
||||||
if (type == t) return true;
|
if (type == t) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Operatör önceliği (Pratt parser için)
|
||||||
uint16_t getPowerOperator() const {
|
uint16_t getPowerOperator() const {
|
||||||
return TokenPrecedence(type);
|
return TokenPrecedence(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sağ birleşmeli mi?
|
||||||
bool isRightAssociative() const {
|
bool isRightAssociative() const {
|
||||||
return RightAssociative(type);
|
return RightAssociative(type);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif // SAQUT_PARSER_TOKEN
|
||||||
|
|
|
||||||
|
|
@ -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
|
#ifndef SAQUT_TOKENIZER
|
||||||
#define SAQUT_TOKENIZER
|
#define SAQUT_TOKENIZER
|
||||||
|
|
||||||
|
|
@ -6,81 +73,186 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "lexer/lexer.hpp"
|
#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 {
|
class Token {
|
||||||
protected:
|
protected:
|
||||||
std::string type;
|
std::string type; // Alt sınıf tarafından constructor'da atanır
|
||||||
public:
|
public:
|
||||||
int start = 0;
|
int start = 0; // Kaynak koddaki başlangıç konumu
|
||||||
int end = 0;
|
int end = 0; // Kaynak koddaki bitiş konumu
|
||||||
std::string token;
|
std::string token; // Token'ın ham metin gösterimi
|
||||||
|
|
||||||
std::string gettype() { return type; }
|
std::string gettype() { return type; }
|
||||||
virtual ~Token() = default;
|
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 {
|
class StringToken : public Token {
|
||||||
public:
|
public:
|
||||||
StringToken() { type = "string"; }
|
StringToken() { type = "string"; }
|
||||||
std::string context;
|
std::string context; // İşlenmiş string içeriği (escape'ler açılmış)
|
||||||
int size = 0;
|
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 {
|
class NumberToken : public Token {
|
||||||
public:
|
public:
|
||||||
NumberToken() { type = "number"; }
|
NumberToken() { type = "number"; }
|
||||||
bool isFloat = false;
|
bool isFloat = false; // Ondalıklı sayı mı?
|
||||||
bool hasEpsilon = false;
|
bool hasEpsilon = false; // Bilimsel gösterim (e/E) içeriyor mu?
|
||||||
int base = 10;
|
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 {
|
class OperatorToken : public Token {
|
||||||
public:
|
public:
|
||||||
OperatorToken() { type = "operator"; }
|
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 {
|
class DelimiterToken : public Token {
|
||||||
public:
|
public:
|
||||||
DelimiterToken() { type = "delimiter"; }
|
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 {
|
class KeywordToken : public Token {
|
||||||
public:
|
public:
|
||||||
KeywordToken() { type = "keyword"; }
|
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 {
|
class IdentifierToken : public Token {
|
||||||
public:
|
public:
|
||||||
IdentifierToken() { type = "identifier"; }
|
IdentifierToken() { type = "identifier"; }
|
||||||
std::string context;
|
std::string context; // Şu anda token ile aynı
|
||||||
int size = 0;
|
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>
|
#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[] = {
|
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[] = {
|
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[] = {
|
inline constexpr std::string_view keywords[] = {
|
||||||
// Control flow
|
// Control flow
|
||||||
"if", "else", "for", "while", "do",
|
"if", "else", "for", "while", "do",
|
||||||
|
|
@ -104,57 +276,106 @@ inline constexpr std::string_view keywords[] = {
|
||||||
"native", "synchronized", "volatile", "transient"
|
"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 {
|
class Tokenizer {
|
||||||
public:
|
public:
|
||||||
Lexer hmx;
|
Lexer hmx; // İç Lexer. "hmx" adı tarihsel.
|
||||||
|
|
||||||
std::vector<Token*> scan(std::string input);
|
std::vector<Token*> scan(std::string input);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Token* scope();
|
Token* scope(); // Bir sonraki token'ı döndür
|
||||||
IdentifierToken* readIdentifier();
|
IdentifierToken* readIdentifier(); // Tanımlayıcı oku
|
||||||
StringToken* readString();
|
StringToken* readString(); // String literal oku
|
||||||
void skipOneLineComment();
|
void skipOneLineComment(); // // yorum satırını atla
|
||||||
void skipMultiLineComment();
|
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) {
|
inline std::vector<Token*> Tokenizer::scan(std::string input) {
|
||||||
std::vector<Token*> tokens;
|
std::vector<Token*> tokens;
|
||||||
hmx.setText(input);
|
hmx.setText(input);
|
||||||
while (true) {
|
while (true) {
|
||||||
Token* token = scope();
|
Token* token = scope();
|
||||||
if (token->token == "EOL") break;
|
if (token->token == "EOL") break; // Dosya sonu sinyali
|
||||||
tokens.push_back(token);
|
tokens.push_back(token);
|
||||||
if (hmx.isEnd()) break;
|
if (hmx.isEnd()) break; // Güvenlik kontrolü
|
||||||
}
|
}
|
||||||
return tokens;
|
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() {
|
inline Token* Tokenizer::scope() {
|
||||||
hmx.skipWhiteSpace();
|
hmx.skipWhiteSpace();
|
||||||
|
|
||||||
|
// Yorum satırları: sessizce atla, token üretme
|
||||||
if (hmx.include("//", true)) skipOneLineComment();
|
if (hmx.include("//", true)) skipOneLineComment();
|
||||||
if (hmx.include("/*", true)) skipMultiLineComment();
|
if (hmx.include("/*", true)) skipMultiLineComment();
|
||||||
|
|
||||||
|
// EOF kontrolü
|
||||||
if (hmx.isEnd()) {
|
if (hmx.isEnd()) {
|
||||||
Token* t = new Token();
|
Token* t = new Token();
|
||||||
t->token = "EOL";
|
t->token = "EOL"; // Özel sinyal token'ı
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
// String literals
|
// String literal: " ile başlar
|
||||||
if (hmx.getchar() == '"')
|
if (hmx.getchar() == '"')
|
||||||
return readString();
|
return readString();
|
||||||
|
|
||||||
// Numbers
|
// Sayısal literal: rakam ile başlar (isNumeric: 0-9)
|
||||||
if (hmx.isNumeric()) {
|
if (hmx.isNumeric()) {
|
||||||
INumber lem = hmx.readNumeric();
|
INumber lem = hmx.readNumeric();
|
||||||
NumberToken* nt = new NumberToken();
|
NumberToken* nt = new NumberToken();
|
||||||
|
|
@ -167,13 +388,16 @@ inline Token* Tokenizer::scope() {
|
||||||
return nt;
|
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) {
|
for (const auto& kw : keywords) {
|
||||||
if (hmx.include(std::string(kw), false)) {
|
if (hmx.include(std::string(kw), false)) {
|
||||||
char next = hmx.getchar(static_cast<int>(kw.size()));
|
char next = hmx.getchar(static_cast<int>(kw.size()));
|
||||||
if ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') ||
|
if ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') ||
|
||||||
(next >= '0' && next <= '9') || next == '_' || next == '$') {
|
(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();
|
KeywordToken* kt = new KeywordToken();
|
||||||
kt->start = hmx.getOffset();
|
kt->start = hmx.getOffset();
|
||||||
|
|
@ -184,7 +408,7 @@ inline Token* Tokenizer::scope() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delimiters
|
// Delimiter'lar
|
||||||
for (const auto& del : delimiters) {
|
for (const auto& del : delimiters) {
|
||||||
if (hmx.include(std::string(del), false)) {
|
if (hmx.include(std::string(del), false)) {
|
||||||
DelimiterToken* dt = new DelimiterToken();
|
DelimiterToken* dt = new DelimiterToken();
|
||||||
|
|
@ -196,7 +420,7 @@ inline Token* Tokenizer::scope() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operators
|
// Operatörler
|
||||||
for (const auto& op : operators) {
|
for (const auto& op : operators) {
|
||||||
if (hmx.include(std::string(op), false)) {
|
if (hmx.include(std::string(op), false)) {
|
||||||
OperatorToken* ot = new OperatorToken();
|
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();
|
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() {
|
inline IdentifierToken* Tokenizer::readIdentifier() {
|
||||||
hmx.beginPosition();
|
hmx.beginPosition();
|
||||||
IdentifierToken* it = new IdentifierToken();
|
IdentifierToken* it = new IdentifierToken();
|
||||||
|
|
@ -221,6 +459,7 @@ inline IdentifierToken* Tokenizer::readIdentifier() {
|
||||||
char c = hmx.getchar();
|
char c = hmx.getchar();
|
||||||
bool read = false;
|
bool read = false;
|
||||||
|
|
||||||
|
// Harf veya rakam kontrolü (ASCII)
|
||||||
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
|
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
|
||||||
read = true;
|
read = true;
|
||||||
it->token.push_back(c);
|
it->token.push_back(c);
|
||||||
|
|
@ -232,21 +471,47 @@ inline IdentifierToken* Tokenizer::readIdentifier() {
|
||||||
if (read) {
|
if (read) {
|
||||||
hmx.nextChar();
|
hmx.nextChar();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break; // Tanımlayıcı karakteri değil → dur
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it->end = hmx.getOffset();
|
it->end = hmx.getOffset();
|
||||||
it->size = static_cast<int>(it->context.size());
|
it->size = static_cast<int>(it->context.size());
|
||||||
hmx.acceptPosition();
|
hmx.acceptPosition(); // Başarılı okuma → konumu kalıcı yap
|
||||||
return it;
|
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() {
|
inline StringToken* Tokenizer::readString() {
|
||||||
hmx.beginPosition();
|
hmx.beginPosition();
|
||||||
StringToken* st = new StringToken();
|
StringToken* st = new StringToken();
|
||||||
bool started = false;
|
bool started = false; // Açılış tırnağı görüldü mü?
|
||||||
bool ended = false;
|
bool ended = false; // Kapanış tırnağı görüldü mü?
|
||||||
st->start = hmx.getOffset();
|
st->start = hmx.getOffset();
|
||||||
|
|
||||||
while (!hmx.isEnd()) {
|
while (!hmx.isEnd()) {
|
||||||
|
|
@ -255,12 +520,13 @@ inline StringToken* Tokenizer::readString() {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '"':
|
case '"':
|
||||||
if (!started) {
|
if (!started) {
|
||||||
started = true;
|
started = true; // Açılış tırnağı
|
||||||
} else {
|
} else {
|
||||||
ended = true;
|
ended = true; // Kapanış tırnağı
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '\\':
|
case '\\':
|
||||||
|
// Escape sequence: sonraki karakteri olduğu gibi al
|
||||||
hmx.nextChar();
|
hmx.nextChar();
|
||||||
c = hmx.getchar();
|
c = hmx.getchar();
|
||||||
st->token.push_back(c);
|
st->token.push_back(c);
|
||||||
|
|
@ -280,17 +546,24 @@ inline StringToken* Tokenizer::readString() {
|
||||||
return st;
|
return st;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// skipOneLineComment: // ile başlayan yorum satırını satırsonuna kadar atla
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
inline void Tokenizer::skipOneLineComment() {
|
inline void Tokenizer::skipOneLineComment() {
|
||||||
while (!hmx.isEnd()) {
|
while (!hmx.isEnd()) {
|
||||||
if (hmx.getchar() == '\n') {
|
if (hmx.getchar() == '\n') {
|
||||||
hmx.nextChar();
|
hmx.nextChar();
|
||||||
hmx.skipWhiteSpace();
|
hmx.skipWhiteSpace(); // Satırsonu sonrası boşlukları da temizle
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hmx.nextChar();
|
hmx.nextChar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// skipMultiLineComment: /* */ bloğunu atla
|
||||||
|
// NOT: İç içe yorum blokları desteklenmez (C standardı gibi).
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
inline void Tokenizer::skipMultiLineComment() {
|
inline void Tokenizer::skipMultiLineComment() {
|
||||||
while (!hmx.isEnd()) {
|
while (!hmx.isEnd()) {
|
||||||
if (hmx.include("*/", true)) {
|
if (hmx.include("*/", true)) {
|
||||||
|
|
@ -301,4 +574,4 @@ inline void Tokenizer::skipMultiLineComment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif // SAQUT_TOKENIZER
|
||||||
|
|
|
||||||
|
|
@ -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
|
#ifndef SAQUT_TOOLS
|
||||||
#define SAQUT_TOOLS
|
#define SAQUT_TOOLS
|
||||||
|
|
||||||
#include <string>
|
#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) {
|
inline std::string padRight(std::string str, size_t totalLen) {
|
||||||
if (str.size() < totalLen) {
|
if (str.size() < totalLen) {
|
||||||
str.append(totalLen - str.size(), ' ');
|
str.append(totalLen - str.size(), ' ');
|
||||||
|
|
@ -10,4 +36,4 @@ inline std::string padRight(std::string str, size_t totalLen) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif // SAQUT_TOOLS
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue