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