feat(string+error+null): ADR-024/025/021 uygulama — string concat, try/catch/throw, nullable akış-analizi
## String cilası (ADR-024) - STRING_CONCAT opcode: s1 + s2 ve s1 += s2 → yeni string üretir - Tip denetleyicide string + string → string; içerik == zaten vardı - Golden: tests/golden/string/concat.sqt ## Hata yönetimi (ADR-025, #57) - AST: TryStatementNode, ThrowStatementNode - Parser: try { } catch (Error e) { }, throw <ifade>; - IR: ENTER_TRY / LEAVE_TRY / THROW opcode'ları + backpatch - VM: TryFrame yığını + pendingThrow_ unwind; runtime hatalar (DIV/0, OOB) artık yakalanabilir Error nesnesi olarak yükseltiliyor - Builtin: Error{line,col,message,trace,code} sembol tablosuna kayıtlı - Golden: tests/golden/error/basic_catch.sqt, throw_and_nested.sqt ## Null akış-analizi (ADR-021 — REVİZE, a!/??/?. yasak) - Type: nullable bool alanı; asNullable/asNonNull/equalsBase/isNullLiteral - fromName("int?") → nullable int; toString() → "int?" - Parser: T? değişken/parametre/dönüş tipi; int? f() dispatch bug'ı düzeltildi - IR: LOAD_NULL opcode; null literal → Value::null() - Tip denetleyici: - checkAssign: T? ← null OK; T ← T? E003; T? ← T OK (widening) - Nullable operand aritmetikte/karşılaştırmada E003 - MemberAccess: nullable nesnede doğrudan erişim E003 - Nested: if (a != null) → then'de non-null - Guard: if (a == null) return; → sonrasında non-null - &&: sol null-check → sağ tarafta non-null - Golden: tests/golden/null/{narrowing,and_narrowing,nullable_assign_error,nullable_operand_error} Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YAu3MGu3sWAUvTu7N9agPk
This commit is contained in:
parent
996868efeb
commit
786812c717
|
|
@ -17,6 +17,4 @@ scripts/gitea.py
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
||||||
# CMake versiyona özgü dosyalar (otomatik üretilir, her güncellemede değişir)
|
# CMake versiyona özgü dosyalar (otomatik üretilir, her güncellemede değişir)
|
||||||
build/CMakeFiles/[0-9]*/
|
build/*
|
||||||
build/.cmake/
|
|
||||||
build/compile_commands.json
|
|
||||||
|
|
|
||||||
32
CLAUDE.md
32
CLAUDE.md
|
|
@ -30,12 +30,14 @@ git'te izlenir.
|
||||||
**rezerve** (semantik ileride). `interface` **ertelendi** (ADR-018).
|
**rezerve** (semantik ileride). `interface` **ertelendi** (ADR-018).
|
||||||
⚠️ Referans semantiği döngüsel-referans **sızıntısını** açtı → GC/döngü
|
⚠️ Referans semantiği döngüsel-referans **sızıntısını** açtı → GC/döngü
|
||||||
toplayıcı borcu (**#56**, `karar-gerekli`).
|
toplayıcı borcu (**#56**, `karar-gerekli`).
|
||||||
- **Null güvenliği (ADR-021):** varsayılan non-null; nullable açıkça `Type?`
|
- **Null güvenliği (ADR-021, REVİZE):** varsayılan non-null; nullable açıkça `Type?`
|
||||||
(Kotlin/Swift modeli). `null` yalnızca `T?`'ye atanır; `T?` üstünde doğrudan
|
(Kotlin/Swift). `null` yalnızca `T?`'ye atanır; `T?` üstünde doğrudan erişim derleme
|
||||||
erişim derleme hatası. **Akış-duyarlı null analizi** (`if (a != null)` daraltır;
|
hatası. **Atama kuralı `T <: T?`** (notnull→nullable serbest, nullable→notnull yasak).
|
||||||
guard/early-return; `&&` sağ tarafı). Runtime maliyeti **sıfır** (compile-time).
|
**Katı operand kuralı:** non-null bağlamda her operand statik non-null olmalı
|
||||||
`a!` = runtime-kontrollü non-null iddiası. İlk gerçek akış-duyarlı analiz →
|
(`int a=b+c+d`, biri nullable → hata). Aklama **yalnızca görünür `if` narrowing**
|
||||||
yapısal akış yeter, SSA gerekmez (#2 için veri).
|
(nested + sıralı guard + `&&`; alias takibi yok). ⚠️ **`a!`/`??`/`?.` YASAK** (gizli
|
||||||
|
runtime null-aklama yok). Runtime maliyeti **sıfır**; frontend kesin çözer (backend
|
||||||
|
yeniden analiz etmez) → runtime null-deref esasen FFI backstop'u. SSA gerekmez (#2).
|
||||||
- **Bellek/GC (ADR-022):** **basit, taşımasız, stop-the-world, deterministik
|
- **Bellek/GC (ADR-022):** **basit, taşımasız, stop-the-world, deterministik
|
||||||
mark-sweep** (döngüleri toplar, "cage" korunur). **`shared_ptr`'ı kalıcı model
|
mark-sweep** (döngüleri toplar, "cage" korunur). **`shared_ptr`'ı kalıcı model
|
||||||
YAPMA** (refcount döngüde sızar = topuğa-sıkma). Kural: nesne modelini **baştan
|
YAPMA** (refcount döngüde sızar = topuğa-sıkma). Kural: nesne modelini **baştan
|
||||||
|
|
@ -52,6 +54,20 @@ git'te izlenir.
|
||||||
ayrı (sahte O(1) karakter indeksi YOK). Verimli birleştirme için ileride builder.
|
ayrı (sahte O(1) karakter indeksi YOK). Verimli birleştirme için ileride builder.
|
||||||
Çözdüğü: #40 (yüzey), #9 (iç temsil). Mevcut `Value` string'i inline tutuyor —
|
Çözdüğü: #40 (yüzey), #9 (iç temsil). Mevcut `Value` string'i inline tutuyor —
|
||||||
immutable olduğu için bu yeterli; heap/object-model'e taşımak zorunlu değil.
|
immutable olduğu için bu yeterli; heap/object-model'e taşımak zorunlu değil.
|
||||||
|
- **Hata yönetimi (ADR-025, #57):** **Swift-tarzı** yakalanabilir, **struct-tabanlı**
|
||||||
|
hata — OOP/extend YOK. Standart `Error { line; char; message; trace; code }`
|
||||||
|
(message=W/E metni, code=W/E kodu). **Klasik `try{}catch{}` bloğu, UNCHECKED**
|
||||||
|
(Java/C#/JS usulü): fonksiyon işaretlenmez (`noexcept`/`constexpr` tarzı YOK),
|
||||||
|
çağrıda `try f()` yok — developer'a güven, alışkanlık bozulmaz. Runtime null-deref
|
||||||
|
(NPE analoğu), array OOB, /0, `a!` patlaması → yakalanabilir hata (ADR-021'in runtime
|
||||||
|
backstop'u). `throw` ile kullanıcı da kaldırır. Deterministik stacktrace (IR satır
|
||||||
|
tablosu önkoşul). **Tuple → ertelendi** (ADR-014'teki "yok" gevşedi). `finally` yerine
|
||||||
|
ileride `defer`.
|
||||||
|
- **Tip dönüşümü (ADR-026, #42):** açık **`as`** (infix, sola-bağlı): `x as int`.
|
||||||
|
Yalnızca **skaler + string** arası; **struct/array cast YOK** (elle yapıcı fonksiyon —
|
||||||
|
derleyiciyi sade tutar, sessiz alan kaybı önlenir). Başarısızlık **hedef tipin
|
||||||
|
nullable'lığıyla:** `as int` → `Error` fırlatır; `as int?` → `null`. Ayrı `as?` YOK.
|
||||||
|
`float→int` sıfıra kırpar (NaN/Inf/taşma fallible). `int(x)` fonksiyon-stili reddedildi.
|
||||||
- **Analiz vs Optimizasyon:** Analiz orijinal AST üstünde annotation; optimizasyon
|
- **Analiz vs Optimizasyon:** Analiz orijinal AST üstünde annotation; optimizasyon
|
||||||
**klon** üstünde dönüşüm. `ASTNode::clone()` yük taşıyan merkezi bileşen
|
**klon** üstünde dönüşüm. `ASTNode::clone()` yük taşıyan merkezi bileşen
|
||||||
(parent pointer'lar + sembol tablosu remap edilir, ADR-007). Fixpoint döngüsü +
|
(parent pointer'lar + sembol tablosu remap edilir, ADR-007). Fixpoint döngüsü +
|
||||||
|
|
@ -88,9 +104,9 @@ git'te izlenir.
|
||||||
## Belge haritası
|
## Belge haritası
|
||||||
- `readme.md` — toolbox çerçevesi, built-vs-planned, dil kimliği, çalıştırma modeli.
|
- `readme.md` — toolbox çerçevesi, built-vs-planned, dil kimliği, çalıştırma modeli.
|
||||||
- `docs/fikirler.md` — ADR-001…005 (backend stratejisi, parser, header-only, token, IR).
|
- `docs/fikirler.md` — ADR-001…005 (backend stratejisi, parser, header-only, token, IR).
|
||||||
- `docs/adr-frontend-analiz.md` — ADR-006…024 (frontend, analiz/optimizasyon,
|
- `docs/adr-frontend-analiz.md` — ADR-006…026 (frontend, analiz/optimizasyon,
|
||||||
çalıştırma modeli, FFI, interface, bellek, **değer/referans semantiği, null
|
çalıştırma modeli, FFI, interface, bellek, **değer/referans semantiği, null
|
||||||
güvenliği, mark-sweep GC, eşitlik, string**).
|
güvenliği, mark-sweep GC, eşitlik, string, hata yönetimi, tip dönüşümü**).
|
||||||
- `docs/sonnet-handoff.md` — **Sonnet için uygulama promptu** (ADR-020…024'ü koda
|
- `docs/sonnet-handoff.md` — **Sonnet için uygulama promptu** (ADR-020…024'ü koda
|
||||||
döken sıralı görev planı; ilk görev: GC-hazır nesne modeli + array runtime).
|
döken sıralı görev planı; ilk görev: GC-hazır nesne modeli + array runtime).
|
||||||
- `docs/roadmap-frontend.md` — faz-faz uygulama planı (Faz 0–4 → fibonacci).
|
- `docs/roadmap-frontend.md` — faz-faz uygulama planı (Faz 0–4 → fibonacci).
|
||||||
|
|
|
||||||
80
TODO.md
80
TODO.md
|
|
@ -10,21 +10,85 @@ tasarım noktalarını tutar. Her giriş hangi issue'ya bağlı olduğunu belirt
|
||||||
ADR-020…024 (değer/referans semantiği, null güvenliği, mark-sweep GC, eşitlik)
|
ADR-020…024 (değer/referans semantiği, null güvenliği, mark-sweep GC, eşitlik)
|
||||||
doğrultusunda uçtan uca array çalışıyor:
|
doğrultusunda uçtan uca array çalışıyor:
|
||||||
- `src/vm/object.hpp` — Object, ArrayObject, Heap (v1: toplama yok, GC-hazır header)
|
- `src/vm/object.hpp` — Object, ArrayObject, Heap (v1: toplama yok, GC-hazır header)
|
||||||
- `src/vm/value.hpp` — ValueKind::Ref + Nil eklendi
|
- `src/vm/value.hpp` — ValueKind::Ref + Null eklendi (dil anahtar sözcüğü `null`, `nil` DEĞİL)
|
||||||
- `src/ir/instruction.hpp` — ARRAY_NEW/GET/SET/LEN eklendi
|
- `src/ir/instruction.hpp` — ARRAY_NEW/GET/SET/LEN eklendi
|
||||||
- Array literal parser (`[1,2,3]`), `int[]` tip sözdizimi (Java/C# stili)
|
- Array literal parser (`[1,2,3]`), `int[]` tip sözdizimi (Java/C# stili)
|
||||||
- Referans semantiği, kimlik `==` (ADR-023), sınır kontrolü
|
- Referans semantiği, kimlik `==` (ADR-023), sınır kontrolü
|
||||||
- Golden test: `tests/golden/array/ref_semantics.sqt` ✓
|
- Golden test: `tests/golden/array/ref_semantics.sqt` ✓
|
||||||
|
|
||||||
## 🚀 SIRADAKİ İŞ (docs/sonnet-handoff.md Bölüm 2)
|
## ✅ TAMAMLANDI — Struct runtime + E010 revizyonu (2026-06-20)
|
||||||
|
|
||||||
1. **Struct runtime** — StructObject : Object, alan erişimi, E010 revizyonu
|
ADR-020 doğrultusunda struct referans semantiğiyle uçtan uca çalışıyor:
|
||||||
2. **String cilası (ADR-024)** — içerik `==`, UTF-8 concat+print
|
- `src/vm/object.hpp` — StructObject : Object, alan erişimi (GC-hazır header)
|
||||||
3. **Null akış-analizi (ADR-021)** — `Type?`, flow-narrowing, `a!`
|
- `src/vm/value.hpp` — ValueKind::Null eklendi (dil anahtar sözcüğü `null`)
|
||||||
4. **float/double (#44)** — Value::Float + FADD/FSUB/… opcodes
|
- E010 revizyonu: referansla tutulan `struct Node { Node next; }` artık meşru
|
||||||
5. **mark-sweep GC v2 (#56)** — header+kök kancası üstünde aç
|
(döngü kurulabilir, GC v2 ile toplanacak)
|
||||||
|
|
||||||
Açık mimari borç: **#56** (döngüsel referans sızıntısı → mark-sweep GC v2).
|
## ✅ TAMAMLANDI — float/double aritmetik runtime (#44) (2026-06-20)
|
||||||
|
|
||||||
|
- `src/vm/value.hpp` — ValueKind::Float + floatValue alanı
|
||||||
|
- IR opcode'ları: FADD/FSUB/FMUL/FDIV + float karşılaştırma
|
||||||
|
- Literal bağlama-göre tipleme korundu (sabit folding dahil)
|
||||||
|
|
||||||
|
## ✅ TAMAMLANDI — String cilası (ADR-024) (2026-06-20)
|
||||||
|
|
||||||
|
- `src/core/type.hpp` — `isString()` yüklem yardımcısı eklendi
|
||||||
|
- `src/ir/instruction.hpp` — `STRING_CONCAT` opcode eklendi
|
||||||
|
- `src/semantic/type_checker.cpp` — `+` string için izin (her iki taraf `string` → sonuç `string`)
|
||||||
|
- `src/ir/ir_generator.cpp` — `generateBinaryArithmetic` string tespiti: `ADD` → `STRING_CONCAT`; `+=` için de `STRING_CONCAT`
|
||||||
|
- `src/vm/interpreter.cpp` — `STRING_CONCAT` case: `stringValue + stringValue`
|
||||||
|
- `tests/golden/string/concat.sqt` — concat, `+=`, içerik `==` golden testi
|
||||||
|
- İçerik `==` / `!=` VM'de zaten vardı (ADR-023/024); `print(string)` zaten çalışıyordu
|
||||||
|
|
||||||
|
## ✅ TAMAMLANDI — Hata yönetimi (ADR-025, #57) (2026-06-20)
|
||||||
|
|
||||||
|
- `src/parser/ast_node.hpp` — `TryStatement`, `ThrowStatement` ASTKind eklendi
|
||||||
|
- `src/parser/nodes/statements.hpp/.cpp` — `TryStatementNode`, `ThrowStatementNode`
|
||||||
|
- `src/parser/parser_base.hpp` + `parser.cpp` — `parseTryStatement()`, `parseThrowStatement()`
|
||||||
|
- `src/ir/instruction.hpp` — `ENTER_TRY`, `LEAVE_TRY`, `THROW` opcode'ları
|
||||||
|
- `src/ir/ir_generator.cpp` — try/catch/throw IR üretimi
|
||||||
|
- `src/vm/interpreter.hpp` — `TryFrame` struct, `tryStack_`, `pendingThrow_`, `makeErrorValue()`
|
||||||
|
- `src/vm/interpreter.cpp` — ENTER_TRY/LEAVE_TRY/THROW VM uygulaması; runtime hataları
|
||||||
|
(DIV/0, array OOB) `pendingThrow_` üzerinden Error nesnesi olarak yakalanabilir hale getirildi
|
||||||
|
- `src/symbol/symbol_collector.cpp` — `Error` builtin struct kaydı (alan sırası: line,col,message,trace,code)
|
||||||
|
- `src/semantic/type_checker.cpp` — TryStatement/ThrowStatement tip kontrolü (unchecked)
|
||||||
|
- `tests/golden/error/basic_catch.sqt` — sıfıra bölme yakalama
|
||||||
|
- `tests/golden/error/throw_and_nested.sqt` — iç içe fonksiyon unwind + array OOB + throw
|
||||||
|
- **Not:** IR satır tablosu (stacktrace doldurma) ertelendi — trace alanı boş kalıyor
|
||||||
|
|
||||||
|
## ✅ TAMAMLANDI — Null akış-analizi (ADR-021) (2026-06-20)
|
||||||
|
|
||||||
|
- `src/core/type.hpp` — `bool nullable` alanı; `asNullable()`, `asNonNull()`, `equalsBase()`, `isNullLiteral()` yardımcıları; `fromName("int?")` desteği; `toString()` → `int?`
|
||||||
|
- `src/parser/parser.cpp` — `?` suffix ayrıştırma (değişken, parametre, dönüş tipi); dispatch'te `int? f()` → `parseFunctionDecl()` yönlendirmesi düzeltildi
|
||||||
|
- `src/ir/instruction.hpp` — `LOAD_NULL` opcode eklendi
|
||||||
|
- `src/ir/ir_generator.cpp` — `LiteralType::BOŞ` → `LOAD_NULL`
|
||||||
|
- `src/vm/interpreter.cpp` — `LOAD_NULL` case → `Value::null()`
|
||||||
|
- `src/semantic/type_checker.hpp` — `narrowedNonNull_` set; `extractNullCheck()`, `alwaysExits()` yardımcıları
|
||||||
|
- `src/semantic/type_checker.cpp`:
|
||||||
|
- `checkAssign`: `T? ← null` OK; `T ← T?` hata (E003); `T? ← T` OK (widening)
|
||||||
|
- `checkExpr Literal BOŞ`: null sentinel tipi (`Void+nullable`)
|
||||||
|
- `checkExpr Identifier`: `narrowedNonNull_`'dan non-null olduğu bilinenlerde nullable flag'i kaldırılıyor
|
||||||
|
- `checkExpr BinaryExpression`: `&&` sağ taraf narrowing; nullable operand hatası (E003)
|
||||||
|
- `checkExpr MemberAccess`: nullable nesne üstünde doğrudan erişim hatası
|
||||||
|
- `checkStmt Block`: guard pattern (`if (a == null) return;` → sonrasında `a` non-null)
|
||||||
|
- `checkStmt IfStatement`: nested narrowing (`if (a != null)` → then'de non-null, `if (a == null)` → else'de non-null)
|
||||||
|
- `tests/golden/null/narrowing.sqt` — nested + guard narrowing, `int?` dönüş tipi ✓
|
||||||
|
- `tests/golden/null/nullable_assign_error.sqt` — `T? → T` atama E003 ✓
|
||||||
|
- `tests/golden/null/nullable_operand_error.sqt` — nullable aritmetik operand E003 ✓
|
||||||
|
- `tests/golden/null/and_narrowing.sqt` — `&&` sağ taraf narrowing ✓
|
||||||
|
|
||||||
|
## 🚀 SIRADAKİ İŞ
|
||||||
|
|
||||||
|
1. **mark-sweep GC v2 (#56)** — en son; özellik bloklamaz, en karmaşık. Trigger basit
|
||||||
|
"her N tahsiste" yeter; nesne modeli zaten GC-hazır.
|
||||||
|
|
||||||
|
Açık mimari borçlar: **#56** (döngüsel referans → mark-sweep GC v2), **#57** (hata
|
||||||
|
modeli görünürlük alt-ekseni).
|
||||||
|
|
||||||
|
> ⚠️ **Terminoloji kilidi (Sonnet):** anlaşılan isimleri değiştirme/icat etme.
|
||||||
|
> Dil anahtar sözcüğü **`null`** (`nil` değil), array literal **`[...]`** (`{...}` değil),
|
||||||
|
> hata tipi **`Error`**. İsmi belirsizse **icat etme, Opus'a sor.** Ayrıntı:
|
||||||
|
> `docs/sonnet-handoff.md` Bölüm 6 (terminoloji kilidi).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -511,6 +511,11 @@ bir yükümlülüktür ama **frontend'i bloklamaz** ve kolay yolu vardır:
|
||||||
|
|
||||||
### Güncelleme — Scope-tabanlı bellek artık GEREKÇELİ (bağımlılığı belgele)
|
### Güncelleme — Scope-tabanlı bellek artık GEREKÇELİ (bağımlılığı belgele)
|
||||||
|
|
||||||
|
> ⚠️ **İPTAL — bu güncelleme ADR-020 ile geçersiz kılındı.** Bileşik tipler artık
|
||||||
|
> runtime'da referans (JS/Java/C# modeli); bileşikler scope'tan kaçar, bellek
|
||||||
|
> erişilebilirliğe bağlı, geri-kazanım stratejisi (#56) gerekecek. Aşağıdaki
|
||||||
|
> "GC gerekmez" sonucu **artık geçerli değildir** — tarihsel bağlam için bırakıldı.
|
||||||
|
|
||||||
Önceki kaygı ("scope çıkışında free, aliasing/escape altında bozulur") kilitli
|
Önceki kaygı ("scope çıkışında free, aliasing/escape altında bozulur") kilitli
|
||||||
dil kimliğiyle **lehte çözüldü:**
|
dil kimliğiyle **lehte çözüldü:**
|
||||||
|
|
||||||
|
|
@ -660,6 +665,451 @@ modelini birlikte zorlar — ikisi de bu yüzden ertelendi.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## ADR-020: Değer vs Referans Semantiği — Bileşik Tipler Runtime'da Referanstır
|
||||||
|
|
||||||
|
### Bağlam
|
||||||
|
|
||||||
|
ADR-014/018/019 boyunca bellek modeli tek bir **taşıyıcı varsayıma** dayanıyordu:
|
||||||
|
|
||||||
|
> "kullanıcı pointer'ı yok + kaçan referans yok → array/struct scope'tan kaçamaz
|
||||||
|
> → scope-tabanlı bellek çalışır, **GC gerekmez**."
|
||||||
|
|
||||||
|
Bu varsayım, `interface`'in (ADR-018) ve closure'ın ertelenmesinin de ikinci
|
||||||
|
gerekçesiydi. Tasarım oturumunda **bilinçli olarak değiştirildi.** "Pointer yok"
|
||||||
|
ilkesinin gerçekte ne demek olduğu netleşti:
|
||||||
|
|
||||||
|
> **"Pointer/referans yok" = kullanıcıya `&`/`*` *sözdizimi* verilmez.**
|
||||||
|
> Bu bir *value-semantics* iddiası değildi; amacı sözdizimsel pointer kontrolünü
|
||||||
|
> kullanıcıdan almaktı. Derleyici ve runtime, bileşik değerleri her aşamada
|
||||||
|
> **referansla** taşır — aksi halde her atama/çağrı/dönüşte derin kopya yaşanır
|
||||||
|
> ve bağlı yapılar (node) imkânsızlaşırdı.
|
||||||
|
|
||||||
|
### Karar
|
||||||
|
|
||||||
|
✅ **İki katmanlı semantik (JavaScript / Java / C# nesne modeli):**
|
||||||
|
|
||||||
|
| Kategori | Tipler | Atama / parametre semantiği |
|
||||||
|
|---|---|---|
|
||||||
|
| **Primitive** | `int`, `float`, `bool`, (`char` vb.) | **Saf değer** — kopyalanır |
|
||||||
|
| **Bileşik (referans)** | `struct`, `array`, `string`\*, (ileride `class`, `function`) | **Referans** — paylaşılır |
|
||||||
|
|
||||||
|
- `a=0; b=a; b=5` → `a` hâlâ `0` (primitive kopya). Fonksiyon parametresinde de aynı.
|
||||||
|
- `func(arr)` → array'in **kendisi** geçer; `func` içinde değişen çağıranı **etkiler**.
|
||||||
|
- `func(arr[0])` → eleman primitive → **kopya**; çağıranı **etkilemez**.
|
||||||
|
- \* `string`'in primitive-gibi mi (immutable değer) yoksa referans mı sayılacağı
|
||||||
|
ayrı bir alt-karar; #40 ile beraber netleşecek.
|
||||||
|
|
||||||
|
✅ **`class` ve `function` tipleri sözdizimsel olarak rezerve** — şu an semantik
|
||||||
|
yok, backend'i ilgilendirmez; ileride referans tip olarak gelecekler. Lexer/parser
|
||||||
|
keyword'leri tanıyıp "henüz desteklenmiyor" diyebilir. (ADR-014'teki "class yok
|
||||||
|
sayılır" maddesi bu yönde yumuşatıldı: yok sayılmaz, rezerve edilir.)
|
||||||
|
|
||||||
|
### Bilinçli geri açtığımız problem: kaçma / yaşam-süresi
|
||||||
|
|
||||||
|
Bu karar, ADR-014'ün "scope-tabanlı bellek GEREKÇELİ / GC gerekmez" sonucunu
|
||||||
|
**iptal eder.** Referansla:
|
||||||
|
|
||||||
|
1. **Aliasing gerçek.** `b = a; b.x = 5` → `a.x` de değişir. "Takma ad yok, akıl
|
||||||
|
yürütmesi kolay" sadeliği takas edildi. **Determinizm korunur** — tek
|
||||||
|
iş-parçacığı, deterministik kayıt-tekrar / time-travel debug hâlâ doğal; takas
|
||||||
|
edilen yalnızca aliasing-özgürlüğüdür.
|
||||||
|
2. **Bileşikler scope'tan kaçar.** Bir node `return` edilebilir veya başka bir
|
||||||
|
struct'ın alanında saklanabilir → "scope çıkışında free" **artık yanlış.**
|
||||||
|
Sahiplik scope'a değil **erişilebilirliğe** bağlı.
|
||||||
|
3. **Döngüsel yapılar artık meşru ve istenen.** `struct Node { Node next; }`
|
||||||
|
ADR-011/014'te `E010` ile yasaktı (by-value → sonsuz boyut). Referansla alan
|
||||||
|
pointer-boyutlu → **sonlu** → bağlı liste / ağaç / graf **yazılabilir.** Bunlar
|
||||||
|
dilin hedef kullanımının kalbi: XML node'ları, JSON kalıpları, class'sız ORM.
|
||||||
|
**Sonuç:** `E010` revize edilmeli — referansla tutulan struct alanı için döngü
|
||||||
|
artık hata değildir.
|
||||||
|
|
||||||
|
### Bunun açtığı zorunlu problem (ayrı issue)
|
||||||
|
|
||||||
|
Döngüsel referans → naif **referans sayımı (`shared_ptr`) sızdırır.** Bu artık
|
||||||
|
"olabilir" değil, dilin **hedeflediği** yapıların (graf/döngü) doğrudan sonucu.
|
||||||
|
Bir geri-kazanım stratejisi (izleyici GC / döngü toplayıcı) **kesinlikle**
|
||||||
|
gerekecek. Bu güçlü mimari borç **#56**'da izlenir ve `karar-gerekli`. v1 motoru
|
||||||
|
`shared_ptr` ile başlayıp döngüyü **bilinçli ve belgeleyerek** sızdırabilir, ama
|
||||||
|
ürünleşmeden önce çözülmek zorundadır.
|
||||||
|
|
||||||
|
### İptal/revize edilen önceki kararlar
|
||||||
|
|
||||||
|
- **ADR-014** — "scope-tabanlı bellek GEREKÇELİ / GC gerekmez" sonucu **iptal.**
|
||||||
|
Bellek artık scope'a değil erişilebilirliğe bağlı; geri-kazanım stratejisi #56.
|
||||||
|
- **ADR-018 / ADR-019** — `interface` / closure'ı ertelemenin "kaçma problemini
|
||||||
|
yeniden açar" gerekçesi **artık geçersiz** (problem zaten açık). Bu ikisini
|
||||||
|
*daha kolay* alınabilir kılar — ama hâlâ kapsam dışı, sadece engeli değişti.
|
||||||
|
- **ADR-011** — `E010` döngüsel struct kuralı revize edilecek (yukarı bkz.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-021: Null Güvenliği — `Type?` Nullable + Akış-Duyarlı Null Analizi
|
||||||
|
|
||||||
|
### Bağlam
|
||||||
|
|
||||||
|
ADR-020 ile bileşik tipler referans oldu. Referans, "gösterecek bir şey yok"
|
||||||
|
durumunu (bağlı listenin sonu, başlatılmamış alan) **zorunlu** kılar. "null her
|
||||||
|
yerde" (Java/C#/JS) milyar dolarlık hatadır: null-deref çalışma zamanında patlar.
|
||||||
|
saQut'un kimliği "kafes — derleyici/VM seni korur" → bunu derleme zamanında
|
||||||
|
yakalamak istiyoruz.
|
||||||
|
|
||||||
|
### Karar
|
||||||
|
|
||||||
|
✅ **Kotlin/Swift modeli: varsayılan null-OLAMAZ, nullable açıkça `?` ile.**
|
||||||
|
|
||||||
|
- `Node a` → asla null olamaz; başlatılması zorunlu.
|
||||||
|
- `Node? a` → null olabilir; başlatılmazsa değeri **`null`**.
|
||||||
|
- `null` literali yalnızca `T?` tipine atanabilir; `Node a = null` → **derleme hatası**.
|
||||||
|
- `T?` üstünde doğrudan alan/eleman erişimi (`a.next`) → **derleme hatası**
|
||||||
|
(önce null-kontrolü şart).
|
||||||
|
|
||||||
|
### Atama/operand kuralı — `T <: T?` (tek yönlü), katı
|
||||||
|
|
||||||
|
✅ **Alt-tip:** `T <: T?`. Yani:
|
||||||
|
- `int? a = 5;` → ✓ (int → int?, **genişletme serbest**).
|
||||||
|
- `int a = bir_int?;` → ✗ (int? → int, **daraltma yasak**).
|
||||||
|
|
||||||
|
✅ **Katı operand kuralı:** non-null bir bağlamda — atamanın sol tarafı, **her
|
||||||
|
operatör operandı**, non-null bekleyen bir argüman — değer **statik olarak non-null**
|
||||||
|
olmalı. `int a = b + c + d`'de `b/c/d`'den **biri bile** nullable ise → **derleme
|
||||||
|
hatası.** Sembol tablosu/akış görünümü seviyesinde: `notnull = notnull + notnull + …`.
|
||||||
|
Sezgi yok, deterministik. (`notnull + notnull` → `notnull`.)
|
||||||
|
|
||||||
|
### Akış-duyarlı null analizi (flow-sensitive narrowing)
|
||||||
|
|
||||||
|
`T?` bir değişken **program noktasına göre** "kesin non-null" kanıtlandıysa
|
||||||
|
daraltılır:
|
||||||
|
|
||||||
|
```c
|
||||||
|
Node? a = ...;
|
||||||
|
// a.next; // E0xx: a null olabilir
|
||||||
|
if (a != null) {
|
||||||
|
a.next; // OK — bu dalda a, Node'a daraltıldı
|
||||||
|
}
|
||||||
|
// a.next; // yine hata (daldan çıkıldı)
|
||||||
|
|
||||||
|
if (a == null) return; // guard / erken çıkış
|
||||||
|
a.next; // OK — buradan sonrası kesin non-null
|
||||||
|
```
|
||||||
|
|
||||||
|
`if` bu sistemin **bel kemiğidir** — sadece büyük/küçük/eşitlik değil, nullable
|
||||||
|
aklamanın da aracı. **İki form da desteklenir:**
|
||||||
|
|
||||||
|
1. **Nested (blok-kapsamlı):** `if (a != null) { /* a: T burada */ }`. Ayrıca
|
||||||
|
`if/else`'in zıt dalı, `while (a != null) { … }`.
|
||||||
|
2. **Sıralı (guard / erken-çıkış):** `if (a == null) return; /* a: T bundan sonra */`.
|
||||||
|
Dal kesin çıkıyorsa (`return`/`throw`/`break`/`continue`) negasyonu **ardışık**
|
||||||
|
koda taşınır.
|
||||||
|
|
||||||
|
**Mekanik:** CFG üzerinde ileri-yönlü dataflow; her nullable değişken için kafes
|
||||||
|
`{MaybeNull, NonNull}`. Koşullarda daraltma (`!= null`, `== null` guard, `&&`
|
||||||
|
kısa-devre sağ tarafı); kesin-çıkış dalları negasyonu ardına taşır; birleşme (join)
|
||||||
|
muhafazakâr (bir daldan MaybeNull gelirse MaybeNull); atama RHS'e göre sıfırlar.
|
||||||
|
|
||||||
|
> **Karmaşıklaştırma sınırı:** narrowing yalnızca **doğrudan test edilen değişken**
|
||||||
|
> için tanınır (`x == null`/`x != null`). **Alias takibi YOK** (`y = x; if (y != null)`
|
||||||
|
> → x daralmaz) ve keyfi teorem-ispatı yok. Bu, derleyiciyi basit tutarken yaygın
|
||||||
|
> durumların hepsini kapsar → developer **uzun/karmaşık kod yazmak zorunda kalmaz.**
|
||||||
|
|
||||||
|
> **Runtime maliyeti SIFIR** — tamamen derleme-zamanı analizi; üretilen kodda
|
||||||
|
> fazladan kontrol yok.
|
||||||
|
|
||||||
|
### Kaçış kapısı YOK — `!` ve `??` YASAK
|
||||||
|
|
||||||
|
Null **yalnızca görünür kontrol akışıyla** (yukarıdaki `if` narrowing) aklanır.
|
||||||
|
Gizli runtime null-aklama operatörleri **yasaktır:**
|
||||||
|
|
||||||
|
- ❌ **`x!`** (non-null iddiası) — "compiler'a güvenme, runtime'da kontrol et"
|
||||||
|
= statik garantiyi delen gizli backdoor. *(ADR-021'in ilk taslağındaki `a!`
|
||||||
|
KALDIRILDI.)*
|
||||||
|
- ❌ **`x ?? default`** (elvis), **`x?.field`** (güvenli çağrı) — null durumunu
|
||||||
|
sessizce gizleyen şeker.
|
||||||
|
|
||||||
|
> Ayrım: `as int`'in başarısızlıkta fırlatması yasak **değil** — o bir null-backdoor
|
||||||
|
> değil, kendiliğinden başarısız olabilen bir *dönüşüm* (ADR-026).
|
||||||
|
|
||||||
|
### Frontend her şeyi kesin çözer (backend-bağımsızlık)
|
||||||
|
|
||||||
|
Nullability **tamamen frontend'de** çözülür; tüm null-güvenlik hataları IR'den
|
||||||
|
**önce** verilir. Backend'ler (IR+VM, ileride C-transpile) null-güvenliği **yeniden
|
||||||
|
analiz etmez** — garantiyi hazır devralır (ADR-006/019). Bu sayede: well-typed saf
|
||||||
|
saQut kodu **statik null-güvenlidir** → non-null referans deref'i runtime null-kontrolü
|
||||||
|
**gerektirmez** (perf + sadelik). Runtime null-deref hatası (ADR-025) bu yüzden
|
||||||
|
geriye esas olarak **FFI sınırı** (host non-null sözünü çiğnerse) ve savunma amaçlı
|
||||||
|
backstop olarak kalır — saf saQut kodu bunu üretmez.
|
||||||
|
|
||||||
|
### Mimari yeri
|
||||||
|
|
||||||
|
Bu, saQut'un ilk gerçek **akış-duyarlı** analizidir. **Yapısal kontrol akışı**
|
||||||
|
üstünde (AST + structured CFG) yapılabilir; tam SSA gerektirmez → **#2 (CFG/SSA
|
||||||
|
gerekli mi?)** için somut veri: şimdilik yapısal akış analizi yeter. **#20**
|
||||||
|
(akıllı diagnostic) bu analizden beslenir ("burada null olabilir, çünkü …").
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-022: Bellek Geri-Kazanımı — Basit Deterministik Mark-Sweep + GC-Hazır Nesne Modeli
|
||||||
|
|
||||||
|
### Bağlam
|
||||||
|
|
||||||
|
ADR-020 referans semantiği → döngüsel yapılar (#56). Kısıtlar: GC **basit ve
|
||||||
|
deterministik** olmalı, "karmaşık ve rastgele" istenmiyor. Ayrıca bu, **geç
|
||||||
|
değiştirilmesi en pahalı** karardır (nesne modeline işler) → topuğa sıkmamak
|
||||||
|
kritik.
|
||||||
|
|
||||||
|
### Önce yanlış-eşleştirmeyi temizle
|
||||||
|
|
||||||
|
**`null`/`?` GC'yi zorlaştırmaz.** Nullable tamamen derleme-zamanı/tip meselesidir;
|
||||||
|
runtime'da null referans sadece "boş işaretçi" → GC için *daha kolay* (izlenecek
|
||||||
|
nesne yok). null ile GC **dik (orthogonal)**; aralarında gerilim yoktur.
|
||||||
|
|
||||||
|
### Seçenekler ve neden mark-sweep
|
||||||
|
|
||||||
|
| Strateji | Döngü | Basitlik | Topuğa-sıkma riski |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Refcount (`shared_ptr` her yerde) | ❌ sızdırır | başta basit | **Yüksek** — node dilinde döngü kaçınılmaz; üstüne döngü toplayıcı = CPython karmaşıklığı (tam "karmaşık/rastgele") |
|
||||||
|
| **Mark-sweep, taşımasız, stop-the-world** | ✅ | **en basit *doğru* GC** | **Düşük** — gelişmiş GC'lerin tabanı; üstüne eklenir, yeniden yazılmaz |
|
||||||
|
| Generational / incremental / compacting | ✅ | karmaşık (write barrier, remembered set) | pause'lar belirsizleşir = istenmeyen "rastgele" |
|
||||||
|
|
||||||
|
✅ **Karar: taşımasız (non-moving), stop-the-world, basit mark-sweep.**
|
||||||
|
- Döngüleri **bedavaya** toplar (izleme döngü umursamaz) → #56'yı gerçekten çözer.
|
||||||
|
- **Deterministik:** GC belirli safepoint'lerde çalışır (ör. her N tahsiste) →
|
||||||
|
kayıt-tekrar / time-travel bit-aynı kalır ("cage" korunur). "Rastgele" değil.
|
||||||
|
- Taşımasız → işaretçi düzeltme / barrier yok → VM'in geri kalanı GC'ye katılmak
|
||||||
|
zorunda değil. *Crafting Interpreters*'ın `clox`'u tam bunu yapar (~birkaç yüz satır).
|
||||||
|
|
||||||
|
### Topuğa-sıkmama kuralı — nesne modelini ŞİMDİ GC-hazır kur
|
||||||
|
|
||||||
|
Asıl risk GC'yi *yazmak* değil, nesne modelini sonradan ona uyduramamaktır. O
|
||||||
|
yüzden **bugünden** (toplama yokken bile):
|
||||||
|
|
||||||
|
1. Her heap nesnesine küçük **header**: tip tag + mark biti + tüm-nesneler listesi için `next`.
|
||||||
|
2. VM **kök (root) sayımı** yapabilsin: operand stack, frame local'leri, global'ler.
|
||||||
|
3. Bir nesne **içerdiği referansları** sayabilsin: referans-tipli struct alanları,
|
||||||
|
referans-tipli array elemanları.
|
||||||
|
|
||||||
|
Bu üçü hazırsa "mark-sweep'i aç" **lokal bir ekleme** olur, nesne-modeli yeniden
|
||||||
|
yazımı değil.
|
||||||
|
|
||||||
|
### Aşamalandırma (#56'nın yönü)
|
||||||
|
|
||||||
|
- **v1 (şimdi):** GC-header'lı tahsis + intrusive tüm-nesneler listesi + kök sayımı.
|
||||||
|
**Toplama yok** (program sonunda hepsini bırak / arena). Fibonacci/test ölçeğinde
|
||||||
|
sorunsuz; kısa programlar sızıntıdan etkilenmez.
|
||||||
|
- **v2 (#56 ciddileşince):** aynı header+kök+çocuk-sayımı üstünde mark-sweep'i aç.
|
||||||
|
Model yeniden yazılmaz.
|
||||||
|
- **`shared_ptr`'dan kaçın:** v1'de bile her referansa refcount gömmek, sonra
|
||||||
|
mark-sweep için **sökmek** ayrı bir topuğa-sıkmadır. Baştan GC-header modeli kur,
|
||||||
|
sadece henüz toplama.
|
||||||
|
|
||||||
|
### Performans notu — asıl "katil" nerede?
|
||||||
|
|
||||||
|
- **Nullability / null:** runtime maliyeti **sıfır** — katil değil.
|
||||||
|
- **Referans modeli:** her bileşik heap'te + işaretçi dolaylılığı → düzenli ama
|
||||||
|
yönetilebilir maliyet; ileride **escape analizi** ile kaçmayan nesneleri stack'e
|
||||||
|
alıp *semantiği bozmadan* hızlandırılır (opt-in, sonra).
|
||||||
|
- **Tek yüksek-değişim-maliyetli karar = GC.** Onu da (a) basit mark-sweep seçip
|
||||||
|
(b) modeli baştan GC-hazır kurarak de-risk ettik. **Kaçınılacak gerçek katil:
|
||||||
|
refcount'u kalıcı model yapmak.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-023: Eşitlik Semantiği — Referanslarda Kimlik Eşitliği (`==`)
|
||||||
|
|
||||||
|
### Bağlam
|
||||||
|
|
||||||
|
ADR-020 ile bileşik tipler referans. `==` / `!=` referans tipler için ne yapsın?
|
||||||
|
Yapısal (derin) eşitlik sezgisel ama üç sorunu var: (1) büyük yapıda **derin
|
||||||
|
gezinme maliyeti**, (2) yeni açtığımız **döngüsel grafta sonsuz döngü** riski
|
||||||
|
(ziyaret-takibi şart), (3) seçtiğimiz referans modeliyle **tutarsız**.
|
||||||
|
|
||||||
|
### Karar
|
||||||
|
|
||||||
|
✅ **Kimlik eşitliği (A):**
|
||||||
|
|
||||||
|
| Kategori | `==` davranışı |
|
||||||
|
|---|---|
|
||||||
|
| Primitive (`int`/`float`/`bool`) | **değer** karşılaştırması (`3 == 3`) |
|
||||||
|
| Referans (`struct`, `array`) | **kimlik** — aynı nesne mi? (işaretçi aynılığı) |
|
||||||
|
| `string` | ⏸️ **#40'a bağlı** — aşağıdaki nota bak |
|
||||||
|
| `null` | `null == null` → true; `null == nesne` → false; `a == null` null-daraltma deyimi (ADR-021) |
|
||||||
|
|
||||||
|
İçerik karşılaştırması istenirse **ayrı, niyeti görünür** bir mekanizmayla gelir
|
||||||
|
(ileride builtin `deepEquals()` / PHP'nin `==` vs `===` vs `clone` ailesi gibi) —
|
||||||
|
asla sessizce `==`'e bağlanmaz. Gerekçe: deepEqual'ı `==`'e bağlamak büyük/döngüsel
|
||||||
|
yapılarda performans ve sonsuz-döngü tuzağıdır; "cam kutu, sürpriz yok" kimliğiyle
|
||||||
|
de çelişir.
|
||||||
|
|
||||||
|
### ⚠️ String istisnası (Java gotcha'sı)
|
||||||
|
|
||||||
|
Saf kimlik eşitliğini string'e de uygularsak `"abc" == "abc"` → **false** olur —
|
||||||
|
Java'nın en çok sövülen hatası. Çoğu dil string'i istisna yapar (JS'te string
|
||||||
|
primitive → içerik; C# overload; Python intern). Bu yüzden **string'in `==`'i
|
||||||
|
içerik eşitliği olmalı**, ki bu string'i **immutable değer-tipi** olarak modellemeyi
|
||||||
|
güçlü biçimde öneriyor (bkz. #40). ADR-023 struct/array'i kilitler; string'in `==`'i
|
||||||
|
#40'ta netleşir ama **varsayılan yön: içerik eşitliği.**
|
||||||
|
|
||||||
|
### Açık (ileride, çok uzak — şimdi karar değil)
|
||||||
|
|
||||||
|
- **`obj == obj`'i hata/uyarı yapmak:** kullanıcıyı niyetini açık yazmaya zorlamak
|
||||||
|
(kimlik mi içerik mi). Daha katı bir duruş; v0'da `==` = kimlik serbest.
|
||||||
|
- **Kullanıcı-tanımlı eşitlik (OOP'siz):** ileride bir tip için `equals(T,T)->bool`
|
||||||
|
konvansiyonu veya benzeri ile `==`'i kullanıcının tanımlamasına izin vermek —
|
||||||
|
operator-overload'un OOP'siz karşılığı. Çok uzak.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-024: String — Immutable Değer-Tipi, İç Temsil UTF-8
|
||||||
|
|
||||||
|
### Bağlam
|
||||||
|
|
||||||
|
ADR-020 string'i "bileşik (referans)" listesine `?` ile koymuştu; ADR-023 string
|
||||||
|
`==`'inin **içerik** olmasını istedi (Java gotcha'sından kaçınmak için). İkisi de
|
||||||
|
string'i değişmez-değer modeline itti.
|
||||||
|
|
||||||
|
### Karar
|
||||||
|
|
||||||
|
✅ **String = immutable (değişmez) değer-tipi; iç temsil UTF-8 bayt.**
|
||||||
|
|
||||||
|
- **Immutable:** oluşturulduktan sonra içeriği değişmez; `s = s + "x"` **yeni**
|
||||||
|
string üretir, eskisini değiştirmez.
|
||||||
|
- **`==` içerik eşitliği** (ADR-023 istisnası). Paylaşılınca değişmediği için
|
||||||
|
içerik-eşitliği güvenlidir; aliasing sürprizi yok (JS'in string'i primitive gibi
|
||||||
|
davranmasının sebebi budur).
|
||||||
|
- **GC dostu:** serbestçe paylaşılır / intern edilebilir.
|
||||||
|
- **İç temsil UTF-8** (Rust/Go/Swift hattı): kompakt, web-doğal, ASCII'de ucuz.
|
||||||
|
`s[i]` **karakter** indeksi O(1) **değildir** → bayt / scalar / grapheme erişimi
|
||||||
|
**açıkça** ayrılır; sahte O(1) vaat edilmez (Java/JS'in "uzunluk emoji'de yalan
|
||||||
|
söylüyor" sürprizinden kaçın). Host tarafında `std::string` ham bayt olarak oturur.
|
||||||
|
- **Verimli birleştirme** için ileride ayrı **builder** tipi (StringBuilder / `join`)
|
||||||
|
— çekirdeği kirletmeden, döngüde O(n²)'den kaçınmak için.
|
||||||
|
|
||||||
|
### Etkilenen
|
||||||
|
|
||||||
|
- **#40** (string işlem yüzeyi) bu kararla netleşti; **#9** (iç temsil) = UTF-8.
|
||||||
|
- ADR-020'deki string `?` işareti → "değer-tipi" olarak çözüldü.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-025: Hata Yönetim Modeli — Struct-Tabanlı Yakalanabilir Hatalar (Swift-tarzı)
|
||||||
|
|
||||||
|
### Bağlam
|
||||||
|
|
||||||
|
ADR-020 (struct = referans) → null bir struct alanına erişim/yazma ihtimali doğdu:
|
||||||
|
klasik NullPointerException. ADR-021 statik analizi *kanıtlayabildiğini* derleme
|
||||||
|
zamanında yakalar, ama `!` iddiası ve kanıtlanamayan durumlar (struct alanı,
|
||||||
|
cross-fonksiyon) için bir **runtime backstop** gerekir. Ayrıca array OOB, /0 gibi
|
||||||
|
faults. Java/C#/JS bunları **yakalanabilir** hata yapar — ama OOP exception
|
||||||
|
hiyerarşisi (`extends Exception`) bizde yok.
|
||||||
|
|
||||||
|
### Karar
|
||||||
|
|
||||||
|
✅ **Yakalanabilir, struct-tabanlı hata modeli — OOP'siz.**
|
||||||
|
Hata *değeri* Swift gibi (düz struct, hiyerarşi/extend yok); *görünürlük* Java/C#/JS
|
||||||
|
gibi (**unchecked** — fonksiyon işaretlenmez, klasik `try{}catch{}`). "Exception'ın
|
||||||
|
tanıdık catch-and-jump ergonomisi + OOP'suz değer."
|
||||||
|
|
||||||
|
1. **Hata değeri = standart built-in struct** — extend yok, OOP yok, deterministik:
|
||||||
|
```
|
||||||
|
struct Error {
|
||||||
|
int line; // hata satırı
|
||||||
|
int col; // sütun ("char" tip adıyla çakışmaması için col)
|
||||||
|
string message; // insan-okunur (derleyicinin W/E kataloğundan)
|
||||||
|
string trace; // stacktrace, en içten dışa
|
||||||
|
string code; // makine-okunur W/E kodu (E010 vb.) — JSON/toolbox filtresi
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2. **try/catch (unwind + jump):** hata oluşunca en yakın çevreleyen `catch`'e
|
||||||
|
zıplanır; `catch (e)` → `e : Error`.
|
||||||
|
3. **Runtime null-deref = yakalanabilir hata** (NPE analoğu). ADR-021 statik
|
||||||
|
analizinin **backstop'u**: `a!` patlayınca + analizin kanıtlayamadığı durumlar.
|
||||||
|
Array OOB ve /0 da aynı kapıdan.
|
||||||
|
4. **`throw`** ile kullanıcı da hata kaldırabilir (`Error` doldurup).
|
||||||
|
5. **Determinizm:** unwind deterministik; stacktrace frame'lerden üretilir;
|
||||||
|
time-travel/replay handle eklenebilir.
|
||||||
|
|
||||||
|
### Görünürlük — KARAR: (ii) görünmez / unchecked (Java/C#/JS usulü)
|
||||||
|
|
||||||
|
✅ **Fonksiyonlar işaretlenmez.** "Bu hata yapabilir / yapamaz" anotasyonu **YOK**
|
||||||
|
(C++'ın `noexcept`/`constexpr` benzeri kirlilik istenmiyor). Çağrıda `try f()`
|
||||||
|
işareti de yok. **Klasik `try { ... } catch (e) { ... }` bloğu** — "anam babam usulü".
|
||||||
|
|
||||||
|
**Gerekçe:** developer'a **güven** + insanların derin try-catch alışkanlığını bozmamak
|
||||||
|
(sözdizimini tanıdık tut, içgüdüye dokunma). Hatalar zaten çoğunlukla FFI, bellek
|
||||||
|
dolması ve derleyici-içi durumlardan doğar; her çağrıyı işaretlemenin bedeli faydadan
|
||||||
|
büyük.
|
||||||
|
|
||||||
|
> Not: Bu, `Type?` (explicit nullable) ile **bilinçli** felsefi ayrışmadır — null
|
||||||
|
> *tipte* görünür, ama hata akışı *blok* düzeyinde tanıdık tutulur. Reddedilen (i):
|
||||||
|
> Swift/Zig'in imza-işaretli + çağrıda `try f()` modeli.
|
||||||
|
|
||||||
|
### Stacktrace mekaniği (modelden bağımsız önkoşul)
|
||||||
|
|
||||||
|
- Her `CallFrame` → `IRFunction` + komut işaretçisi; **IR'a satır tablosu**
|
||||||
|
(komut index → kaynak konum) eklenir (önce taşıyıp taşımadığı doğrulanmalı).
|
||||||
|
- panic/throw'da frame stack gezilir → `fonksiyon + konum`, en içten dışa → `trace`.
|
||||||
|
- Sunum: derleme-zamanı diagnostic ile **aynı kabuk** (kod + mesaj + konum +
|
||||||
|
"nasıl düzelt" #20), hem insan hem **JSON** (toolbox: her hata yapılandırılmış nesne).
|
||||||
|
- Farklılaştırıcı: deterministik → trace'e adım indeksi → hataya **geri sar**.
|
||||||
|
|
||||||
|
### İlişkili güncellemeler
|
||||||
|
|
||||||
|
- **ADR-014 "tuple yok" → "tuple ERTELENDİ"** (reddedilmedi; çoklu-dönüş kodu
|
||||||
|
spagettileştirir, şimdilik uzak ama masada — `interface` gibi).
|
||||||
|
- **`finally` yerine ileride `defer`** (GC'li, RAII'siz dilde daha temiz). Ayrı küçük karar.
|
||||||
|
- ADR-021 ile uyum: statik analiz provable null'ı yakalar; bu hata onun backstop'u + `!`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-026: Tip Dönüşümü — `as` (Skaler/String), Başarısızlık Hedef Tipinin Nullable'lığıyla
|
||||||
|
|
||||||
|
### Bağlam
|
||||||
|
|
||||||
|
ADR-010 "gizli int↔float yok" → değişken-değişken dönüşüm **açık** olmalı. float
|
||||||
|
runtime'ı var ama cast sözdizimi yoktu → int↔float dönüşümü imkânsızdı. Ayrıca
|
||||||
|
elimizde null (ADR-021) + hata (ADR-025) modelleri var; cast bunlarla örtüşmeli.
|
||||||
|
|
||||||
|
### Karar
|
||||||
|
|
||||||
|
✅ **Sözdizimi: `as` (infix, sola-bağlı).** `deger as int`.
|
||||||
|
- Sola-bağlı olduğu için zincir **lineer** okunur: `a as int as string` =
|
||||||
|
`((a as int) as string)`, parantez gerekmez (fonksiyon-stili `int(float(a))`'nın
|
||||||
|
iç içe çirkinliği yok).
|
||||||
|
- `int(x)` fonksiyon-stili **reddedildi** ("int adlı fonksiyon mu, cast mı"
|
||||||
|
belirsizliği); C-tarzı `(int)x` ve `static_cast<>` reddedildi.
|
||||||
|
|
||||||
|
✅ **Kapsam: yalnızca skaler + string** (`int`/`float`/`bool`/`string` arası).
|
||||||
|
- **Struct/array cast'e GİRMEZ.** Farklı struct'lar ayrı tiplerdir; "dönüşümleri"
|
||||||
|
geliştiricinin yazdığı **açık yapıcı fonksiyonlarla** olur (`Employee yap(Person p)`).
|
||||||
|
Gerekçe: yapısal/duck eşleme veya reinterpret = derleyiciyi karmaşıklaştırır +
|
||||||
|
sessiz alan kaybı = hataya açık. OOP'siz "cage" kimliğiyle uyumsuz.
|
||||||
|
|
||||||
|
✅ **Başarısızlık davranışı = HEDEF TİPİN nullable'lığı** (ayrı `as?` operatörü YOK):
|
||||||
|
- `x as int` → hedef non-null → başarısızsa **`Error` fırlatır** (ADR-025), sonuç `int`.
|
||||||
|
- `x as int?` → hedef nullable → başarısızsa **`null` döner**, sonuç `int?`.
|
||||||
|
|
||||||
|
Nullable her zaman **tipte** (`?`) yaşar, ayrı operatör icat edilmez. Sonra `int?`'i
|
||||||
|
`int`'e çevirmek için **narrowing** (`if`) gerekir — `!`/`??` yasak (ADR-021).
|
||||||
|
|
||||||
|
### Dönüşüm matrisi
|
||||||
|
|
||||||
|
| Dönüşüm | Hatasız mı? | Not |
|
||||||
|
|---|---|---|
|
||||||
|
| `int → float` | ✅ hatasız | büyük int'te kesinlik kaybı olabilir, patlamaz |
|
||||||
|
| `int → string`, `float → string` | ✅ hatasız | biçimlendirme |
|
||||||
|
| `string → int`/`float` | ⚠️ fallible | parse; `"abc"` → `as int` fırlatır / `as int?` null |
|
||||||
|
| `float → int` | ⚠️ fallible | sonlu & aralık-içi: **sıfıra doğru kırpılır** (`1.71→1`, `-1.71→-1`); NaN/Inf/taşma → fırlatır / null |
|
||||||
|
| `bool ↔ int` | (karar) | başta yasak tutmak en güvenlisi; gerekirse açılır |
|
||||||
|
|
||||||
|
### Örnek (ADR-021 ile birlikte)
|
||||||
|
|
||||||
|
```
|
||||||
|
int a = 1.71 as int?; // ✗ DERLEME HATASI: int? → int (daraltma); cast başarılı olsa bile statik tip int?
|
||||||
|
int a = 1.71 as int; // ✓ a = 1 (kırpma); başarısızsa Error
|
||||||
|
int? a = 1.71 as int?; // ✓ tipler eşit
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Kararların Özet Tablosu
|
## Kararların Özet Tablosu
|
||||||
|
|
||||||
| ADR | Konu | Karar |
|
| ADR | Konu | Karar |
|
||||||
|
|
@ -678,3 +1128,10 @@ modelini birlikte zorlar — ikisi de bu yüzden ertelendi.
|
||||||
| 017 | Batteries/stdlib | Sınır problemi; küçük builtin + FFI/kütüphane; ertelendi |
|
| 017 | Batteries/stdlib | Sınır problemi; küçük builtin + FFI/kütüphane; ertelendi |
|
||||||
| 018 | `interface` | Ertelendi (reddedilmedi); struct+fonksiyon yeter |
|
| 018 | `interface` | Ertelendi (reddedilmedi); struct+fonksiyon yeter |
|
||||||
| 019 | Frontend↔runtime | Frontend yapı+anlam; çekirdek/cihaz/çıktı runtime'a ait |
|
| 019 | Frontend↔runtime | Frontend yapı+anlam; çekirdek/cihaz/çıktı runtime'a ait |
|
||||||
|
| 020 | Değer/referans semantiği | Primitive=değer, bileşik (struct/array/string)=referans; "pointer yok"=`&`/`*` sözdizimi yok; kaçma/lifetime problemi bilinçli açıldı → GC borcu (#56); ADR-014'ün "GC gerekmez" sonucu iptal |
|
||||||
|
| 021 | Null güvenliği | `Type?` nullable, varsayılan non-null; akış-duyarlı null analizi (compile-time, runtime maliyeti sıfır); `!` runtime-kontrollü non-null iddiası |
|
||||||
|
| 022 | Bellek geri-kazanımı | Basit taşımasız stop-the-world mark-sweep (deterministik); nesne modeli baştan GC-hazır (header+root+child); v1 toplamasız, v2 mark-sweep; refcount kalıcı model DEĞİL; #56'nın yönü |
|
||||||
|
| 023 | Eşitlik semantiği | `==` = primitive değer / referans (struct,array) **kimlik**; deepEqual asla `==`'e bağlanmaz (ayrı `deepEquals()`); string `==` içerik (→ #40, Java gotcha'sından kaçın); `obj==obj` hata + kullanıcı-tanımlı eşitlik = uzak gelecek |
|
||||||
|
| 024 | String | Immutable değer-tipi, iç temsil **UTF-8**; `==` içerik; mutasyon yeni string üretir; bayt/scalar/grapheme açıkça ayrı; verimli birleştirme için ileride builder; #40/#9'u çözer |
|
||||||
|
| 025 | Hata yönetimi | Struct-tabanlı yakalanabilir hata (değer Swift gibi, OOP yok); standart `Error{line,col,message,trace,code}`; klasik `try{}catch{}` **unchecked** (fonksiyon işaretsiz, Java usulü); runtime null-deref/OOB yakalanabilir (esasen FFI backstop); deterministik stacktrace (IR satır tablosu); tuple→ertelendi; finally→`defer`; #57 |
|
||||||
|
| 026 | Tip dönüşümü | `as` (infix, sola-bağlı), yalnızca skaler+string; struct/array cast YOK (elle yapıcı fonksiyon); başarısızlık hedef nullable'lığıyla (`as int` fırlatır / `as int?` null); float→int kırpma; #42 |
|
||||||
|
|
|
||||||
|
|
@ -121,12 +121,20 @@ func main() {
|
||||||
(`"şğü"` concat+print bozulmasın); bayt-uzunluğu vs karakter ayrımını açık API
|
(`"şğü"` concat+print bozulmasın); bayt-uzunluğu vs karakter ayrımını açık API
|
||||||
olarak işaretle. (İstege bağlı: string'i de Object modeline taşıyıp intern et —
|
olarak işaretle. (İstege bağlı: string'i de Object modeline taşıyıp intern et —
|
||||||
zorunlu değil.)
|
zorunlu değil.)
|
||||||
3. **Null akış-analizi (ADR-021).** Ayrı **frontend** görevi: `Type?` tip sistemi +
|
3. **Null akış-analizi (ADR-021 — REVİZE).** Ayrı **frontend** görevi: `Type?` tip
|
||||||
akış-duyarlı narrowing (`if (a != null)`, guard, `&&`), `T?` üstünde doğrudan
|
sistemi + `T <: T?` atama kuralı + katı operand kuralı + akış-duyarlı narrowing
|
||||||
erişim → derleme hatası, `a!` runtime-kontrollü iddia. CFG/yapısal akış üstünde;
|
(nested `if` + sıralı guard + `&&`). ⚠️ **`a!`/`??`/`?.` YASAK** — null yalnızca
|
||||||
SSA gerekmez. #20 (akıllı diagnostic) buradan beslenir.
|
görünür `if` ile aklanır. `T?` üstünde doğrudan erişim → derleme hatası. Frontend
|
||||||
4. **float/double (#44).** Bağımsız; `Value::Float` + FADD… opcode + tip denetleyici.
|
kesin çözer (backend yeniden analiz etmez). Detay: TODO Bölüm "SIRADAKİ İŞ" + ADR-021.
|
||||||
5. **mark-sweep GC v2 (#56).** Adım 1.1'de bırakılan header+kök kancası üstünde aç.
|
4. **Hata yönetimi (ADR-025, #57).** Struct-tabanlı yakalanabilir hata (Swift-tarzı):
|
||||||
|
- **Önkoşul:** IR'a **satır tablosu** (komut index → kaynak konum) — stacktrace için. Şu an taşıyor mu doğrula.
|
||||||
|
- Standart built-in `struct Error { int line; int char; string message; string trace; string code; }`.
|
||||||
|
- Klasik **`try { ... } catch (e) { ... }` bloğu** (unwind + en yakın handler'a zıpla); `catch (e)` → `e : Error`. `throw` ile kullanıcı da kaldırır.
|
||||||
|
- Runtime null-deref (NPE analoğu), array OOB, /0, `a!` patlaması → **yakalanabilir hata**; `message`/`code` = derleyicinin W/E kataloğu.
|
||||||
|
- **UNCHECKED (Java/C#/JS usulü):** fonksiyon **işaretlenmez** (`noexcept`/`constexpr` tarzı YOK), çağrıda **`try f()` YOK**. İmza-işaretli Swift/Zig modeli KULLANMA.
|
||||||
|
- Stacktrace = frame stack'ten en içten dışa; insan + JSON; deterministik (adım indeksi/replay handle).
|
||||||
|
5. **float/double (#44).** Bağımsız; `Value::Float` + FADD… opcode + tip denetleyici.
|
||||||
|
6. **mark-sweep GC v2 (#56).** Adım 1.1'de bırakılan header+kök kancası üstünde aç.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -150,5 +158,29 @@ func main() {
|
||||||
|
|
||||||
## 5. Özet — tek cümle
|
## 5. Özet — tek cümle
|
||||||
GC-hazır basit nesne modelini kur (header + all-objects listesi, toplama yok), `Value`'ya
|
GC-hazır basit nesne modelini kur (header + all-objects listesi, toplama yok), `Value`'ya
|
||||||
referans (`Ref`) + `Nil` ekle, **array**'i referans semantiği + kimlik `==` + sınır
|
referans (`Ref`) + `Null` ekle, **array**'i referans semantiği + kimlik `==` + sınır
|
||||||
kontrolüyle uçtan uca çalıştır; struct ve null-analizi sonraki görevler.
|
kontrolüyle uçtan uca çalıştır; struct ve null-analizi sonraki görevler.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. ⚠️ TERMİNOLOJİ KİLİDİ — isimleri değiştirme/icat etme
|
||||||
|
|
||||||
|
Bu oturumda `null` yerine `nil` yazıldı; bu tür sapmalar **olmamalı.** Anlaşılan
|
||||||
|
isimler **aynen** kullanılır. İsmi belirsiz bir şeyle karşılaşırsan **icat etme —
|
||||||
|
Opus'a sor.**
|
||||||
|
|
||||||
|
| Kavram | DOĞRU | YANLIŞ (kullanma) |
|
||||||
|
|---|---|---|
|
||||||
|
| Null anahtar sözcüğü / literal | **`null`** | ~~`nil`~~, ~~`none`~~, ~~`void`~~ |
|
||||||
|
| Nullable tip işareti | **`Type?`** (ör. `int?`, `Node?`) | ~~`Optional<T>`~~, ~~`Type \| null`~~ |
|
||||||
|
| Non-null iddiası / elvis / safe-call | **YASAK** — `if` narrowing kullan | ~~`a!`~~, ~~`a ?? x`~~, ~~`a?.f`~~ (ADR-021 revize) |
|
||||||
|
| Array literal | **`[1, 2, 3]`** | ~~`{1,2,3}`~~ |
|
||||||
|
| Array tip | **`int[]`** | ~~`array<int>`~~, ~~`[]int`~~ |
|
||||||
|
| Hata tipi | **`Error`** (`{line,char,message,trace,code}`) | ~~`Exception`~~, ~~`Err`~~ |
|
||||||
|
| Hata kaldırma / yakalama | **`throw` / `try` / `catch`** | ~~`raise`~~, ~~`rescue`~~ |
|
||||||
|
| Fonksiyon | **`func`** | ~~`fn`~~, ~~`function`~~, ~~`def`~~ |
|
||||||
|
| C++ ValueKind null'u | **`ValueKind::Null`** | ~~`Nil`~~ |
|
||||||
|
|
||||||
|
**Genel kural:** ADR'lerde/handoff'ta yazan tam ismi kullan. Yeni bir isim gerekiyorsa
|
||||||
|
ve ADR'de yoksa, **kendin karar verme** — TODO'ya not düş veya Opus'a sor. Kod
|
||||||
|
identifier'ları İngilizce (dil sözdizimi), yorum/commit Türkçe.
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ struct Type {
|
||||||
std::shared_ptr<Type> returnType; // kind == Function
|
std::shared_ptr<Type> returnType; // kind == Function
|
||||||
std::vector<Type> paramTypes; // kind == Function
|
std::vector<Type> paramTypes; // kind == Function
|
||||||
std::string structName; // kind == Struct
|
std::string structName; // kind == Struct
|
||||||
|
bool nullable = false; // ADR-021: Type? sözdizimi
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
// Factory'ler
|
// Factory'ler
|
||||||
|
|
@ -132,11 +133,26 @@ struct Type {
|
||||||
prim == PrimitiveKind::Double);
|
prim == PrimitiveKind::Double);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isString() const {
|
||||||
|
return kind == TypeKind::Primitive && prim == PrimitiveKind::String;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADR-021: "null" literal tipi — yalnızca nullable değişkene atanabilir
|
||||||
|
bool isNullLiteral() const {
|
||||||
|
return kind == TypeKind::Primitive && prim == PrimitiveKind::Void && nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nullable kopyası döndür
|
||||||
|
Type asNullable() const { Type t = *this; t.nullable = true; return t; }
|
||||||
|
Type asNonNull() const { Type t = *this; t.nullable = false; return t; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
// equals — Yapısal eşitlik (katı; gizli dönüşüm yok, ADR-010)
|
// equals — Yapısal eşitlik (katı; gizli dönüşüm yok, ADR-010)
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
|
// Yapısal eşitlik — nullable dahil (ADR-021: int ≠ int?)
|
||||||
bool equals(const Type& o) const {
|
bool equals(const Type& o) const {
|
||||||
if (kind != o.kind) return false;
|
if (kind != o.kind) return false;
|
||||||
|
if (nullable != o.nullable) return false;
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case TypeKind::Primitive:
|
case TypeKind::Primitive:
|
||||||
return prim == o.prim;
|
return prim == o.prim;
|
||||||
|
|
@ -154,13 +170,14 @@ struct Type {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case TypeKind::Error:
|
case TypeKind::Error:
|
||||||
// Error == Error: ardışık sahte hataların bastırılması tip
|
|
||||||
// denetleyicinin sorumluluğundadır (operandı Error ise hata üretme).
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false; // erişilemez (tüm enum değerleri kapsandı)
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Temel yapısal eşitlik — nullable farkını yok say (T == T? üstün çakışma için)
|
||||||
|
bool equalsBase(const Type& o) const { return asNonNull().equals(o.asNonNull()); }
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
// İsim yardımcıları
|
// İsim yardımcıları
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
|
|
@ -177,10 +194,15 @@ struct Type {
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bir tip adından (parser tipleri string olarak tutar) primitif Type üretir.
|
// Bir tip adından (parser tipleri string olarak tutar) Type üretir.
|
||||||
// Bilinen primitif değilse Error döner — bilinmeyen tip adının teşhisi
|
// "int?" → nullable int; "int[]" → int array; bilinen değilse Error.
|
||||||
// (E007) çağıranın (Faz 2/3) işidir; bu fonksiyon sessizce Error verir.
|
|
||||||
static Type fromName(const std::string& n) {
|
static Type fromName(const std::string& n) {
|
||||||
|
// Nullable soneki: "int?", "string?" vb. (ADR-021)
|
||||||
|
if (!n.empty() && n.back() == '?') {
|
||||||
|
Type base = fromName(n.substr(0, n.size() - 1));
|
||||||
|
if (!base.isError()) return base.asNullable();
|
||||||
|
return error();
|
||||||
|
}
|
||||||
if (n == "int") return Int();
|
if (n == "int") return Int();
|
||||||
if (n == "float") return Float();
|
if (n == "float") return Float();
|
||||||
if (n == "double") return Double();
|
if (n == "double") return Double();
|
||||||
|
|
@ -200,27 +222,28 @@ struct Type {
|
||||||
// toString — İnsan-okur ("int", "int[]", "fn(int,int)->int")
|
// toString — İnsan-okur ("int", "int[]", "fn(int,int)->int")
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
std::string toString() const {
|
std::string toString() const {
|
||||||
|
std::string base;
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case TypeKind::Primitive:
|
case TypeKind::Primitive:
|
||||||
return primName(prim);
|
base = primName(prim); break;
|
||||||
case TypeKind::Array:
|
case TypeKind::Array:
|
||||||
return (elementType ? elementType->toString() : "<?>") + "[]";
|
base = (elementType ? elementType->toString() : "<?>") + "[]"; break;
|
||||||
case TypeKind::Struct:
|
case TypeKind::Struct:
|
||||||
return "struct " + structName;
|
base = "struct " + structName; break;
|
||||||
case TypeKind::Function: {
|
case TypeKind::Function: {
|
||||||
std::string s = "fn(";
|
base = "fn(";
|
||||||
for (size_t i = 0; i < paramTypes.size(); ++i) {
|
for (size_t i = 0; i < paramTypes.size(); ++i) {
|
||||||
if (i) s += ",";
|
if (i) base += ",";
|
||||||
s += paramTypes[i].toString();
|
base += paramTypes[i].toString();
|
||||||
}
|
}
|
||||||
s += ")->";
|
base += ")->";
|
||||||
s += returnType ? returnType->toString() : "<?>";
|
base += returnType ? returnType->toString() : "<?>";
|
||||||
return s;
|
break;
|
||||||
}
|
}
|
||||||
case TypeKind::Error:
|
case TypeKind::Error:
|
||||||
return "<error>";
|
return "<error>";
|
||||||
}
|
}
|
||||||
return "<?>";
|
return nullable ? base + "?" : base;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ enum class Opcode {
|
||||||
// Örnek: LOAD_CONST dest=3 val=10 → slot[3] = 10
|
// Örnek: LOAD_CONST dest=3 val=10 → slot[3] = 10
|
||||||
|
|
||||||
LOAD_STRING, // slots[dest] = stringValue (metin sabitini slota yükle)
|
LOAD_STRING, // slots[dest] = stringValue (metin sabitini slota yükle)
|
||||||
|
LOAD_NULL, // slots[dest] = null (ADR-021: ValueKind::Null)
|
||||||
// Örnek: LOAD_STRING dest=2 val="Merhaba" → slot[2] = "Merhaba"
|
// Örnek: LOAD_STRING dest=2 val="Merhaba" → slot[2] = "Merhaba"
|
||||||
|
|
||||||
LOAD_SLOT, // slots[dest] = slots[src]
|
LOAD_SLOT, // slots[dest] = slots[src]
|
||||||
|
|
@ -111,6 +112,18 @@ enum class Opcode {
|
||||||
LOAD_GLOBAL, // slots[dest] = moduleSlots[intValue] (bu modülün modül-düzeyi değişkeni)
|
LOAD_GLOBAL, // slots[dest] = moduleSlots[intValue] (bu modülün modül-düzeyi değişkeni)
|
||||||
STORE_GLOBAL, // moduleSlots[intValue] = slots[src]
|
STORE_GLOBAL, // moduleSlots[intValue] = slots[src]
|
||||||
|
|
||||||
|
// --- String işlemleri (ADR-024: immutable değer-tipi, içerik ==) ---
|
||||||
|
STRING_CONCAT, // slots[dest] = slots[left] + slots[right] (yeni string üretir)
|
||||||
|
|
||||||
|
// --- Hata yönetimi (ADR-025: UNCHECKED try/catch/throw) ---
|
||||||
|
ENTER_TRY, // try bloğuna giriş: TryFrame'i yığına it
|
||||||
|
// dest = catch bloğundaki Error değerinin yazılacağı slot
|
||||||
|
// jumpTarget = catch bloğunun IR konumu (-1 → backpatch)
|
||||||
|
// callDepth = VM, callStack.size()'ı kayıt altına alır (unwind için)
|
||||||
|
LEAVE_TRY, // try bloğundan normal çıkış: TryFrame'i çıkar (istisna olmadı)
|
||||||
|
THROW, // slots[src] değerini fırlat → en yakın ENTER_TRY'a unwind
|
||||||
|
// Yakalanmamışsa C++ exception olarak yükseltilir
|
||||||
|
|
||||||
// --- Dış dünya (FFI — Foreign Function Interface) ---
|
// --- Dış dünya (FFI — Foreign Function Interface) ---
|
||||||
CALLHOST, // Host (C++) fonksiyonunu çağır. Şu an sadece "print" destekli.
|
CALLHOST, // Host (C++) fonksiyonunu çağır. Şu an sadece "print" destekli.
|
||||||
// Dönüş değeri yok; sadece yan etki (stdout'a yazmak gibi).
|
// Dönüş değeri yok; sadece yan etki (stdout'a yazmak gibi).
|
||||||
|
|
@ -121,6 +134,7 @@ inline const char* opcodeName(Opcode op) {
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case Opcode::LOAD_CONST: return "LOAD_CONST";
|
case Opcode::LOAD_CONST: return "LOAD_CONST";
|
||||||
case Opcode::LOAD_STRING: return "LOAD_STRING";
|
case Opcode::LOAD_STRING: return "LOAD_STRING";
|
||||||
|
case Opcode::LOAD_NULL: return "LOAD_NULL";
|
||||||
case Opcode::LOAD_SLOT: return "LOAD_SLOT";
|
case Opcode::LOAD_SLOT: return "LOAD_SLOT";
|
||||||
case Opcode::ADD: return "ADD";
|
case Opcode::ADD: return "ADD";
|
||||||
case Opcode::SUB: return "SUB";
|
case Opcode::SUB: return "SUB";
|
||||||
|
|
@ -160,6 +174,10 @@ inline const char* opcodeName(Opcode op) {
|
||||||
case Opcode::JIF_TRUE: return "JIF_TRUE";
|
case Opcode::JIF_TRUE: return "JIF_TRUE";
|
||||||
case Opcode::CALL: return "CALL";
|
case Opcode::CALL: return "CALL";
|
||||||
case Opcode::RETURN: return "RETURN";
|
case Opcode::RETURN: return "RETURN";
|
||||||
|
case Opcode::STRING_CONCAT: return "STRING_CONCAT";
|
||||||
|
case Opcode::ENTER_TRY: return "ENTER_TRY";
|
||||||
|
case Opcode::LEAVE_TRY: return "LEAVE_TRY";
|
||||||
|
case Opcode::THROW: return "THROW";
|
||||||
case Opcode::CALLHOST: return "CALLHOST";
|
case Opcode::CALLHOST: return "CALLHOST";
|
||||||
}
|
}
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
// Error struct alan sırası (ADR-025): makeError için IR tarafından bilinir
|
||||||
|
// 0=line, 1=col, 2=message, 3=trace, 4=code
|
||||||
|
static constexpr int ERROR_FIELD_COUNT = 5;
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// generate — Ana giriş noktası
|
// generate — Ana giriş noktası
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -301,6 +305,54 @@ void IRGenerator::generateStatement(ASTNode* node) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── try { body } catch (Error e) { handler } (ADR-025) ────────────
|
||||||
|
case ASTKind::TryStatement: {
|
||||||
|
auto* ts = (TryStatementNode*)node;
|
||||||
|
|
||||||
|
// Catch değişkeni için slot; VM bu slota Error nesnesini yazar
|
||||||
|
int errorSlot = freshSlot();
|
||||||
|
if (!ts->catchVar.empty())
|
||||||
|
registerVariable(ts->catchVar, errorSlot);
|
||||||
|
|
||||||
|
// ENTER_TRY: catch hedefi henüz bilinmiyor (-1), sonradan patchlanır
|
||||||
|
Instruction enterTry(Opcode::ENTER_TRY);
|
||||||
|
enterTry.dest = errorSlot;
|
||||||
|
enterTry.jumpTarget = -1;
|
||||||
|
currentFunction_->instructions.push_back(std::move(enterTry));
|
||||||
|
int enterTryIdx = (int)currentFunction_->instructions.size() - 1;
|
||||||
|
|
||||||
|
// Try gövdesi
|
||||||
|
if (ts->body) generateStatement(ts->body);
|
||||||
|
|
||||||
|
// Normal çıkış: try frame'ini çıkar
|
||||||
|
Instruction leaveTry(Opcode::LEAVE_TRY);
|
||||||
|
currentFunction_->instructions.push_back(std::move(leaveTry));
|
||||||
|
|
||||||
|
// Catch bloğunu atla (normal akışta)
|
||||||
|
int jumpOverCatch = emitJumpUnconditional(-1);
|
||||||
|
|
||||||
|
// Catch etiketi: ENTER_TRY buraya atlayacak
|
||||||
|
int catchLabel = currentInstrIndex();
|
||||||
|
currentFunction_->instructions[enterTryIdx].jumpTarget = catchLabel;
|
||||||
|
|
||||||
|
// Catch gövdesi
|
||||||
|
if (ts->handler) generateStatement(ts->handler);
|
||||||
|
|
||||||
|
// Catch bitti
|
||||||
|
patchJump(jumpOverCatch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── throw <ifade>; (ADR-025) ────────────────────────────────────────
|
||||||
|
case ASTKind::ThrowStatement: {
|
||||||
|
auto* th = (ThrowStatementNode*)node;
|
||||||
|
int valSlot = th->value ? generateExpression(th->value) : freshSlot();
|
||||||
|
Instruction ins(Opcode::THROW);
|
||||||
|
ins.src = valSlot;
|
||||||
|
currentFunction_->instructions.push_back(std::move(ins));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -376,9 +428,9 @@ int IRGenerator::generateExpression(ASTNode* node) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LiteralType::BOŞ:
|
case LiteralType::BOŞ:
|
||||||
// null literal → Null kind Value (ADR-021)
|
// null literal → ValueKind::Null (ADR-021)
|
||||||
{ Instruction ins(Opcode::LOAD_CONST); ins.dest = slot; ins.intValue = 0;
|
{ Instruction ins(Opcode::LOAD_NULL); ins.dest = slot;
|
||||||
currentFunction_->instructions.push_back(std::move(ins)); } // placeholder; VM'de Null üretmeli
|
currentFunction_->instructions.push_back(std::move(ins)); }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return slot;
|
return slot;
|
||||||
|
|
@ -465,6 +517,12 @@ int IRGenerator::generateExpression(ASTNode* node) {
|
||||||
else if (bin->Operator == TokenType::LSHIFT_EQUAL) arithOp = Opcode::SHL;
|
else if (bin->Operator == TokenType::LSHIFT_EQUAL) arithOp = Opcode::SHL;
|
||||||
else if (bin->Operator == TokenType::RSHIFT_EQUAL) arithOp = Opcode::SHR;
|
else if (bin->Operator == TokenType::RSHIFT_EQUAL) arithOp = Opcode::SHR;
|
||||||
|
|
||||||
|
// string += string → STRING_CONCAT (ADR-024)
|
||||||
|
if (bin->Operator == TokenType::PLUS_EQUAL) {
|
||||||
|
if (auto* e = dynamic_cast<ExpressionNode*>(bin->Right))
|
||||||
|
if (e->resolvedType.isString()) arithOp = Opcode::STRING_CONCAT;
|
||||||
|
}
|
||||||
|
|
||||||
int resultSlot = freshSlot();
|
int resultSlot = freshSlot();
|
||||||
|
|
||||||
if (isGlobal(varName)) {
|
if (isGlobal(varName)) {
|
||||||
|
|
@ -679,14 +737,25 @@ int IRGenerator::generateBinaryArithmetic(Opcode opcode, ASTNode* leftNode, ASTN
|
||||||
|
|
||||||
// Float tip kontrolü — resolvedType üstünden (tip denetleyici tarafından yazıldı)
|
// Float tip kontrolü — resolvedType üstünden (tip denetleyici tarafından yazıldı)
|
||||||
bool leftIsFloat = false, rightIsFloat = false;
|
bool leftIsFloat = false, rightIsFloat = false;
|
||||||
if (auto* e = dynamic_cast<ExpressionNode*>(leftNode))
|
bool leftIsString = false, rightIsString = false;
|
||||||
|
if (auto* e = dynamic_cast<ExpressionNode*>(leftNode)) {
|
||||||
leftIsFloat = e->resolvedType.isPrimitive() &&
|
leftIsFloat = e->resolvedType.isPrimitive() &&
|
||||||
(e->resolvedType.prim == PrimitiveKind::Float ||
|
(e->resolvedType.prim == PrimitiveKind::Float ||
|
||||||
e->resolvedType.prim == PrimitiveKind::Double);
|
e->resolvedType.prim == PrimitiveKind::Double);
|
||||||
if (auto* e = dynamic_cast<ExpressionNode*>(rightNode))
|
leftIsString = e->resolvedType.isString();
|
||||||
|
}
|
||||||
|
if (auto* e = dynamic_cast<ExpressionNode*>(rightNode)) {
|
||||||
rightIsFloat = e->resolvedType.isPrimitive() &&
|
rightIsFloat = e->resolvedType.isPrimitive() &&
|
||||||
(e->resolvedType.prim == PrimitiveKind::Float ||
|
(e->resolvedType.prim == PrimitiveKind::Float ||
|
||||||
e->resolvedType.prim == PrimitiveKind::Double);
|
e->resolvedType.prim == PrimitiveKind::Double);
|
||||||
|
rightIsString = e->resolvedType.isString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// String birleştirme (ADR-024): + → STRING_CONCAT
|
||||||
|
if ((leftIsString || rightIsString) && opcode == Opcode::ADD) {
|
||||||
|
emitBinaryOp(Opcode::STRING_CONCAT, destSlot, leftSlot, rightSlot);
|
||||||
|
return destSlot;
|
||||||
|
}
|
||||||
|
|
||||||
if (leftIsFloat || rightIsFloat) {
|
if (leftIsFloat || rightIsFloat) {
|
||||||
// Int operandı float'a çevir
|
// Int operandı float'a çevir
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,8 @@ enum class ASTKind {
|
||||||
ExpressionStatement, // ifade + noktalı virgül (;)
|
ExpressionStatement, // ifade + noktalı virgül (;)
|
||||||
// children: [expression]
|
// children: [expression]
|
||||||
// Örn: x = 5; veya foo();
|
// Örn: x = 5; veya foo();
|
||||||
|
TryStatement, // try { body } catch (Error e) { handler } (ADR-025)
|
||||||
|
ThrowStatement, // throw <ifade>; (ADR-025)
|
||||||
|
|
||||||
/* ====== İfadeler (Expressions) ====== */
|
/* ====== İfadeler (Expressions) ====== */
|
||||||
BinaryExpression, // İkili işlem: sol OP sağ.
|
BinaryExpression, // İkili işlem: sol OP sağ.
|
||||||
|
|
|
||||||
|
|
@ -147,3 +147,36 @@ std::string ExpressionStatementNode::toJson(int depth) {
|
||||||
obj.addRaw("location", loc.toJson());
|
obj.addRaw("location", loc.toJson());
|
||||||
return obj.str();
|
return obj.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TryStatementNode (ADR-025)
|
||||||
|
TryStatementNode::TryStatementNode() { kind = ASTKind::TryStatement; }
|
||||||
|
void TryStatementNode::log(int indent) {
|
||||||
|
std::cout << jsonIndent(indent) << "TryStatement (catch " << catchVar << ")\n";
|
||||||
|
if (body) body->log(indent + 1);
|
||||||
|
if (handler) handler->log(indent + 1);
|
||||||
|
}
|
||||||
|
std::string TryStatementNode::toJson(int depth) {
|
||||||
|
JsonObject obj(depth);
|
||||||
|
obj.add("kind", "TryStatement");
|
||||||
|
obj.add("catchVar", catchVar);
|
||||||
|
if (body) obj.addRaw("body", body->toJson(depth + 1));
|
||||||
|
if (handler) obj.addRaw("handler", handler->toJson(depth + 1));
|
||||||
|
obj.add("isReachable", isReachable);
|
||||||
|
obj.addRaw("location", loc.toJson());
|
||||||
|
return obj.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThrowStatementNode (ADR-025)
|
||||||
|
ThrowStatementNode::ThrowStatementNode() { kind = ASTKind::ThrowStatement; }
|
||||||
|
void ThrowStatementNode::log(int indent) {
|
||||||
|
std::cout << jsonIndent(indent) << "ThrowStatement\n";
|
||||||
|
if (value) value->log(indent + 1);
|
||||||
|
}
|
||||||
|
std::string ThrowStatementNode::toJson(int depth) {
|
||||||
|
JsonObject obj(depth);
|
||||||
|
obj.add("kind", "ThrowStatement");
|
||||||
|
if (value) obj.addRaw("value", value->toJson(depth + 1));
|
||||||
|
obj.add("isReachable", isReachable);
|
||||||
|
obj.addRaw("location", loc.toJson());
|
||||||
|
return obj.str();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,4 +85,26 @@ public:
|
||||||
std::string toJson(int depth = 0) override;
|
std::string toJson(int depth = 0) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ADR-025: try { body } catch (Error catchVar) { handler }
|
||||||
|
class TryStatementNode : public StatementNode {
|
||||||
|
public:
|
||||||
|
ASTNode* body = nullptr;
|
||||||
|
std::string catchVar; // catch değişken adı (ör. "e")
|
||||||
|
ASTNode* handler = nullptr;
|
||||||
|
TryStatementNode();
|
||||||
|
~TryStatementNode() override { delete body; delete handler; }
|
||||||
|
void log(int indent = 0) override;
|
||||||
|
std::string toJson(int depth = 0) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ADR-025: throw <ifade>;
|
||||||
|
class ThrowStatementNode : public StatementNode {
|
||||||
|
public:
|
||||||
|
ASTNode* value = nullptr;
|
||||||
|
ThrowStatementNode();
|
||||||
|
~ThrowStatementNode() override { delete value; }
|
||||||
|
void log(int indent = 0) override;
|
||||||
|
std::string toJson(int depth = 0) override;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,15 @@ ASTNode* Parser::parseDeclaration() {
|
||||||
})) {
|
})) {
|
||||||
auto la1 = lookahead(1);
|
auto la1 = lookahead(1);
|
||||||
auto la2 = lookahead(2);
|
auto la2 = lookahead(2);
|
||||||
|
// int name( → fonksiyon
|
||||||
if (la1.type == TokenType::IDENTIFIER && la2.type == TokenType::LPAREN)
|
if (la1.type == TokenType::IDENTIFIER && la2.type == TokenType::LPAREN)
|
||||||
return parseFunctionDecl();
|
return parseFunctionDecl();
|
||||||
|
// int? name( → nullable dönüş tipli fonksiyon (ADR-021)
|
||||||
|
if (la1.type == TokenType::TERNARY) {
|
||||||
|
auto la3 = lookahead(3);
|
||||||
|
if (la2.type == TokenType::IDENTIFIER && la3.type == TokenType::LPAREN)
|
||||||
|
return parseFunctionDecl();
|
||||||
|
}
|
||||||
return parseVariableDecl();
|
return parseVariableDecl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,6 +106,11 @@ ASTNode* Parser::parseDeclaration() {
|
||||||
auto la2 = lookahead(2);
|
auto la2 = lookahead(2);
|
||||||
if (la1.type == TokenType::IDENTIFIER && la2.type == TokenType::LPAREN)
|
if (la1.type == TokenType::IDENTIFIER && la2.type == TokenType::LPAREN)
|
||||||
return parseFunctionDecl();
|
return parseFunctionDecl();
|
||||||
|
if (la1.type == TokenType::TERNARY) {
|
||||||
|
auto la3 = lookahead(3);
|
||||||
|
if (la2.type == TokenType::IDENTIFIER && la3.type == TokenType::LPAREN)
|
||||||
|
return parseFunctionDecl();
|
||||||
|
}
|
||||||
if (la1.type == TokenType::IDENTIFIER)
|
if (la1.type == TokenType::IDENTIFIER)
|
||||||
return parseVariableDecl();
|
return parseVariableDecl();
|
||||||
}
|
}
|
||||||
|
|
@ -316,6 +328,10 @@ ASTNode* Parser::parseFunctionDecl() {
|
||||||
fn->returnType = currentToken().token->token;
|
fn->returnType = currentToken().token->token;
|
||||||
nextToken();
|
nextToken();
|
||||||
|
|
||||||
|
// ADR-021: nullable dönüş tipi — int? f()
|
||||||
|
if (currentToken().type == TokenType::TERNARY)
|
||||||
|
{ nextToken(); fn->returnType += "?"; }
|
||||||
|
|
||||||
fn->name = currentToken().token->token;
|
fn->name = currentToken().token->token;
|
||||||
nextToken();
|
nextToken();
|
||||||
|
|
||||||
|
|
@ -339,6 +355,9 @@ ASTNode* Parser::parseFunctionDecl() {
|
||||||
nextToken();
|
nextToken();
|
||||||
paramType += "[]";
|
paramType += "[]";
|
||||||
}
|
}
|
||||||
|
// ADR-021: nullable parametre — int? a
|
||||||
|
if (currentToken().type == TokenType::TERNARY)
|
||||||
|
{ nextToken(); paramType += "?"; }
|
||||||
if (currentToken().type != TokenType::IDENTIFIER || !currentToken().token) break;
|
if (currentToken().type != TokenType::IDENTIFIER || !currentToken().token) break;
|
||||||
VariableDeclNode* param = new VariableDeclNode();
|
VariableDeclNode* param = new VariableDeclNode();
|
||||||
param->loc = currentToken().token->loc;
|
param->loc = currentToken().token->loc;
|
||||||
|
|
@ -396,6 +415,10 @@ ASTNode* Parser::parseVariableDecl() {
|
||||||
vd->varType += "[]";
|
vd->varType += "[]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ADR-021: nullable soneki — int? x
|
||||||
|
if (currentToken().type == TokenType::TERNARY)
|
||||||
|
{ nextToken(); vd->varType += "?"; }
|
||||||
|
|
||||||
if (currentToken().type != TokenType::IDENTIFIER) {
|
if (currentToken().type != TokenType::IDENTIFIER) {
|
||||||
std::cerr << "Parser hatası: değişken ismi bekleniyor\n";
|
std::cerr << "Parser hatası: değişken ismi bekleniyor\n";
|
||||||
return vd;
|
return vd;
|
||||||
|
|
@ -486,6 +509,12 @@ ASTNode* Parser::parseStatement() {
|
||||||
if (ct.type == TokenType::KW_CONTINUE)
|
if (ct.type == TokenType::KW_CONTINUE)
|
||||||
return parseContinueStatement();
|
return parseContinueStatement();
|
||||||
|
|
||||||
|
if (ct.type == TokenType::KW_TRY)
|
||||||
|
return parseTryStatement();
|
||||||
|
|
||||||
|
if (ct.type == TokenType::KW_THROW)
|
||||||
|
return parseThrowStatement();
|
||||||
|
|
||||||
if (ct.is({
|
if (ct.is({
|
||||||
TokenType::KW_VOID, TokenType::KW_INT, TokenType::KW_FLOAT_TYPE,
|
TokenType::KW_VOID, TokenType::KW_INT, TokenType::KW_FLOAT_TYPE,
|
||||||
TokenType::KW_DOUBLE, TokenType::KW_BOOL, TokenType::KW_CHAR,
|
TokenType::KW_DOUBLE, TokenType::KW_BOOL, TokenType::KW_CHAR,
|
||||||
|
|
@ -670,3 +699,43 @@ ASTNode* Parser::parseExpressionStatement() {
|
||||||
|
|
||||||
return es;
|
return es;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ADR-025: try { body } catch (Error catchVar) { handler }
|
||||||
|
ASTNode* Parser::parseTryStatement() {
|
||||||
|
TryStatementNode* ts = new TryStatementNode();
|
||||||
|
ts->loc = currentToken().token->loc;
|
||||||
|
nextToken(); // tüket: try
|
||||||
|
|
||||||
|
ts->body = parseBlock();
|
||||||
|
|
||||||
|
// catch (Error e)
|
||||||
|
if (currentToken().type == TokenType::KW_CATCH) {
|
||||||
|
nextToken(); // tüket: catch
|
||||||
|
if (currentToken().type == TokenType::LPAREN)
|
||||||
|
nextToken(); // tüket: (
|
||||||
|
// "Error" tip adını atla
|
||||||
|
if (currentToken().type == TokenType::IDENTIFIER ||
|
||||||
|
currentToken().type == TokenType::KW_STRING_TYPE)
|
||||||
|
nextToken(); // tüket: Error (ya da herhangi bir tip adı)
|
||||||
|
// catch değişken adını al
|
||||||
|
if (currentToken().type == TokenType::IDENTIFIER && currentToken().token)
|
||||||
|
ts->catchVar = currentToken().token->token;
|
||||||
|
nextToken(); // tüket: değişken adı
|
||||||
|
if (currentToken().type == TokenType::RPAREN)
|
||||||
|
nextToken(); // tüket: )
|
||||||
|
ts->handler = parseBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADR-025: throw <ifade>;
|
||||||
|
ASTNode* Parser::parseThrowStatement() {
|
||||||
|
ThrowStatementNode* th = new ThrowStatementNode();
|
||||||
|
th->loc = currentToken().token->loc;
|
||||||
|
nextToken(); // tüket: throw
|
||||||
|
th->value = parseExpression();
|
||||||
|
if (currentToken().type == TokenType::SEMICOLON)
|
||||||
|
nextToken();
|
||||||
|
return th;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ private:
|
||||||
ASTNode* parseBreakStatement();
|
ASTNode* parseBreakStatement();
|
||||||
ASTNode* parseContinueStatement();
|
ASTNode* parseContinueStatement();
|
||||||
ASTNode* parseExpressionStatement();
|
ASTNode* parseExpressionStatement();
|
||||||
|
ASTNode* parseTryStatement();
|
||||||
|
ASTNode* parseThrowStatement();
|
||||||
|
|
||||||
// --- İfadeler (Pratt parser) ---
|
// --- İfadeler (Pratt parser) ---
|
||||||
ASTNode* parseExpression();
|
ASTNode* parseExpression();
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,50 @@ int TypeChecker::numericRank(const Type& t) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ADR-021: "a != null" / "a == null" kalıbını ayrıştır
|
||||||
|
// Dönüş: {varName, isNotNull} — varName boşsa kalıp tanınmadı.
|
||||||
|
std::pair<std::string, bool> TypeChecker::extractNullCheck(ASTNode* cond) {
|
||||||
|
if (!cond || cond->kind != ASTKind::BinaryExpression) return {"", false};
|
||||||
|
auto* bin = (BinaryExpressionNode*)cond;
|
||||||
|
bool isNE = (bin->Operator == TokenType::BANG_EQUAL);
|
||||||
|
bool isEE = (bin->Operator == TokenType::EQUAL_EQUAL);
|
||||||
|
if (!isNE && !isEE) return {"", false};
|
||||||
|
|
||||||
|
// Hangi taraf null literal?
|
||||||
|
auto isNullLit = [](ASTNode* n) -> bool {
|
||||||
|
if (!n || n->kind != ASTKind::Literal) return false;
|
||||||
|
return ((LiteralNode*)n)->literalType == LiteralType::BOŞ;
|
||||||
|
};
|
||||||
|
auto identName = [](ASTNode* n) -> std::string {
|
||||||
|
if (!n || n->kind != ASTKind::Identifier) return "";
|
||||||
|
auto* id = (IdentifierNode*)n;
|
||||||
|
return id->parserToken.token ? id->parserToken.token->token : "";
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string var;
|
||||||
|
if (isNullLit(bin->Right)) var = identName(bin->Left);
|
||||||
|
else if (isNullLit(bin->Left)) var = identName(bin->Right);
|
||||||
|
if (var.empty()) return {"", false};
|
||||||
|
return {var, isNE}; // isNE=true → "a != null"; false → "a == null"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADR-021: guard pattern — bu statement her zaman çıkış yapıyor mu?
|
||||||
|
bool TypeChecker::alwaysExits(ASTNode* stmt) {
|
||||||
|
if (!stmt) return false;
|
||||||
|
switch (stmt->kind) {
|
||||||
|
case ASTKind::ReturnStatement:
|
||||||
|
case ASTKind::ThrowStatement:
|
||||||
|
case ASTKind::BreakStatement:
|
||||||
|
case ASTKind::ContinueStatement:
|
||||||
|
return true;
|
||||||
|
case ASTKind::Block: {
|
||||||
|
auto& ch = stmt->getChildren();
|
||||||
|
return !ch.empty() && alwaysExits(ch.back());
|
||||||
|
}
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// check — giriş noktası
|
// check — giriş noktası
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -62,21 +106,41 @@ bool TypeChecker::checkAssign(const Type& target, const Type& src,
|
||||||
const SourceLocation& loc,
|
const SourceLocation& loc,
|
||||||
const std::string& ctx) {
|
const std::string& ctx) {
|
||||||
if (target.isError() || src.isError()) return true; // önceki hata, sessiz geç
|
if (target.isError() || src.isError()) return true; // önceki hata, sessiz geç
|
||||||
if (target.equals(src)) return true;
|
|
||||||
|
// ADR-021: null literal ataması
|
||||||
|
if (src.isNullLiteral()) {
|
||||||
|
if (target.nullable) return true; // T? ← null → OK
|
||||||
|
diag_.report("E003", loc,
|
||||||
|
"'" + ctx + "': null non-null tipine (" + target.toString() + ") atanamaz");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADR-021: nullable uyumu
|
||||||
|
// T? ← T → OK (widening: non-null, nullable'a gider)
|
||||||
|
// T ← T? → E (narrowing: nullable, non-null'a gidemez; narrowing gerekli)
|
||||||
|
if (src.nullable && !target.nullable && src.equalsBase(target)) {
|
||||||
|
diag_.report("E003", loc,
|
||||||
|
"'" + ctx + "': " + src.toString() +
|
||||||
|
" nullable tipi non-null " + target.toString() + " tipine atanamaz"
|
||||||
|
" (if ile null kontrolü yapın)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// T? ← T → OK (equalsBase eşleşiyorsa, nullable farkı widening)
|
||||||
|
if (!src.nullable && target.nullable && src.equalsBase(target)) return true;
|
||||||
|
|
||||||
|
if (target.equals(src)) return true;
|
||||||
|
|
||||||
int tRank = numericRank(target);
|
int tRank = numericRank(target);
|
||||||
int sRank = numericRank(src);
|
int sRank = numericRank(src);
|
||||||
|
|
||||||
if (tRank >= 0 && sRank >= 0) {
|
if (tRank >= 0 && sRank >= 0) {
|
||||||
if (tRank > sRank) {
|
if (tRank > sRank) {
|
||||||
// Genişletme (widening): int→float, int→double, float→double
|
if (srcIsLiteral) return true;
|
||||||
if (srcIsLiteral) return true; // literal bağlama-göre tiplenir, uyarısız
|
|
||||||
diag_.report("W004", loc,
|
diag_.report("W004", loc,
|
||||||
"'" + ctx + "': " + src.toString() +
|
"'" + ctx + "': " + src.toString() +
|
||||||
" → " + target.toString() + " örtük genişletme");
|
" → " + target.toString() + " örtük genişletme");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// Daraltma (narrowing): float→int, double→float, vb.
|
|
||||||
diag_.report("E003", loc,
|
diag_.report("E003", loc,
|
||||||
"'" + ctx + "': " + src.toString() +
|
"'" + ctx + "': " + src.toString() +
|
||||||
" → " + target.toString() + " daraltma (veri kaybı)");
|
" → " + target.toString() + " daraltma (veri kaybı)");
|
||||||
|
|
@ -84,7 +148,6 @@ bool TypeChecker::checkAssign(const Type& target, const Type& src,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tamamen farklı tipler
|
|
||||||
diag_.report("E003", loc,
|
diag_.report("E003", loc,
|
||||||
"'" + ctx + "': " + src.toString() +
|
"'" + ctx + "': " + src.toString() +
|
||||||
" tipi " + target.toString() + " tipine atanamaz");
|
" tipi " + target.toString() + " tipine atanamaz");
|
||||||
|
|
@ -100,9 +163,26 @@ void TypeChecker::checkStmt(ASTNode* node) {
|
||||||
|
|
||||||
switch (node->kind) {
|
switch (node->kind) {
|
||||||
|
|
||||||
case ASTKind::Block:
|
case ASTKind::Block: {
|
||||||
for (ASTNode* child : node->getChildren()) checkStmt(child);
|
// ADR-021: guard/sıralı narrowing — if (a == null) return; → sonrasında a non-null
|
||||||
|
std::vector<std::string> guardNarrowed; // bu blokta guard'la daraltılanlar
|
||||||
|
for (ASTNode* child : node->getChildren()) {
|
||||||
|
checkStmt(child);
|
||||||
|
// guard kontrolü: if (a == null) { return/throw/break/continue; }
|
||||||
|
if (child->kind == ASTKind::IfStatement) {
|
||||||
|
auto* ifn = (IfStatementNode*)child;
|
||||||
|
if (!ifn->elseBranch && ifn->thenBranch && alwaysExits(ifn->thenBranch)) {
|
||||||
|
auto [var, isNotNull] = extractNullCheck(ifn->condition);
|
||||||
|
if (!var.empty() && !isNotNull) { // "a == null" → guard
|
||||||
|
narrowedNonNull_.insert(var);
|
||||||
|
guardNarrowed.push_back(var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto& v : guardNarrowed) narrowedNonNull_.erase(v);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case ASTKind::VariableDecl: {
|
case ASTKind::VariableDecl: {
|
||||||
auto* vd = (VariableDeclNode*)node;
|
auto* vd = (VariableDeclNode*)node;
|
||||||
|
|
@ -144,9 +224,23 @@ void TypeChecker::checkStmt(ASTNode* node) {
|
||||||
|
|
||||||
case ASTKind::IfStatement: {
|
case ASTKind::IfStatement: {
|
||||||
auto* ifn = (IfStatementNode*)node;
|
auto* ifn = (IfStatementNode*)node;
|
||||||
if (ifn->condition) checkExpr(ifn->condition);
|
if (ifn->condition) checkExpr(ifn->condition);
|
||||||
|
|
||||||
|
// ADR-021: nested narrowing — if (a != null) { a non-null } else { a null }
|
||||||
|
auto [narrowVar, isNotNull] = extractNullCheck(ifn->condition);
|
||||||
|
|
||||||
|
if (!narrowVar.empty() && isNotNull) // "a != null" → then'de non-null
|
||||||
|
narrowedNonNull_.insert(narrowVar);
|
||||||
if (ifn->thenBranch) checkStmt(ifn->thenBranch);
|
if (ifn->thenBranch) checkStmt(ifn->thenBranch);
|
||||||
|
if (!narrowVar.empty() && isNotNull)
|
||||||
|
narrowedNonNull_.erase(narrowVar);
|
||||||
|
|
||||||
|
if (!narrowVar.empty() && !isNotNull) // "a == null" → else'de non-null
|
||||||
|
narrowedNonNull_.insert(narrowVar);
|
||||||
if (ifn->elseBranch) checkStmt(ifn->elseBranch);
|
if (ifn->elseBranch) checkStmt(ifn->elseBranch);
|
||||||
|
if (!narrowVar.empty() && !isNotNull)
|
||||||
|
narrowedNonNull_.erase(narrowVar);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,6 +274,21 @@ void TypeChecker::checkStmt(ASTNode* node) {
|
||||||
case ASTKind::ContinueStatement:
|
case ASTKind::ContinueStatement:
|
||||||
break; // yapısal doğrulama StructuralValidator'ın işi
|
break; // yapısal doğrulama StructuralValidator'ın işi
|
||||||
|
|
||||||
|
// ADR-025: try { body } catch (Error e) { handler }
|
||||||
|
case ASTKind::TryStatement: {
|
||||||
|
auto* ts = (TryStatementNode*)node;
|
||||||
|
if (ts->body) checkStmt(ts->body);
|
||||||
|
if (ts->handler) checkStmt(ts->handler);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADR-025: throw <ifade>; — unchecked, herhangi bir değer atılabilir
|
||||||
|
case ASTKind::ThrowStatement: {
|
||||||
|
auto* th = (ThrowStatementNode*)node;
|
||||||
|
if (th->value) checkExpr(th->value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -222,7 +331,14 @@ Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) {
|
||||||
break;
|
break;
|
||||||
case LiteralType::BOOLEAN: result = Type::Bool(); break;
|
case LiteralType::BOOLEAN: result = Type::Bool(); break;
|
||||||
case LiteralType::STRING: result = Type::String(); break;
|
case LiteralType::STRING: result = Type::String(); break;
|
||||||
default: result = Type::error(); break;
|
case LiteralType::BOŞ:
|
||||||
|
// null literal: bağlam nullable ise o tip, değilse Void+nullable (null sentinel)
|
||||||
|
if (!expected.isError() && expected.nullable)
|
||||||
|
result = expected;
|
||||||
|
else
|
||||||
|
result = Type::Void().asNullable(); // null sentinel
|
||||||
|
break;
|
||||||
|
default: result = Type::error(); break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +347,12 @@ Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) {
|
||||||
case ASTKind::Identifier: {
|
case ASTKind::Identifier: {
|
||||||
auto* id = (IdentifierNode*)node;
|
auto* id = (IdentifierNode*)node;
|
||||||
result = id->resolvedSymbol ? id->resolvedSymbol->type : Type::error();
|
result = id->resolvedSymbol ? id->resolvedSymbol->type : Type::error();
|
||||||
|
// ADR-021: narrowing — bu değişken null kontrolünden geçtiyse non-null say
|
||||||
|
if (result.nullable && id->parserToken.token) {
|
||||||
|
std::string name = id->parserToken.token->token;
|
||||||
|
if (narrowedNonNull_.count(name))
|
||||||
|
result = result.asNonNull();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -267,11 +389,23 @@ Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Type leftType = checkExpr(bin->Left);
|
Type leftType = checkExpr(bin->Left);
|
||||||
|
|
||||||
|
// ADR-021: && kısa-devre sağ taraf narrowing — "a != null && a.field"
|
||||||
|
if (bin->Operator == TokenType::AMPERSAND_AMPERSAND) {
|
||||||
|
auto [narrowVar, isNotNull] = extractNullCheck(bin->Left);
|
||||||
|
if (!narrowVar.empty() && isNotNull)
|
||||||
|
narrowedNonNull_.insert(narrowVar);
|
||||||
|
checkExpr(bin->Right);
|
||||||
|
if (!narrowVar.empty() && isNotNull)
|
||||||
|
narrowedNonNull_.erase(narrowVar);
|
||||||
|
result = Type::Bool();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
Type rightType = checkExpr(bin->Right);
|
Type rightType = checkExpr(bin->Right);
|
||||||
|
|
||||||
// Mantıksal
|
// Mantıksal (||)
|
||||||
if (bin->Operator == TokenType::AMPERSAND_AMPERSAND ||
|
if (bin->Operator == TokenType::PIPE_PIPE) {
|
||||||
bin->Operator == TokenType::PIPE_PIPE) {
|
|
||||||
result = Type::Bool();
|
result = Type::Bool();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -302,6 +436,24 @@ Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ADR-021: katı operand kuralı — non-null bağlamda nullable operand yasak
|
||||||
|
// (eşitlik / null karşılaştırmaları için geçerli değil)
|
||||||
|
if (!leftType.isError() && !rightType.isError() &&
|
||||||
|
(leftType.nullable || rightType.nullable)) {
|
||||||
|
diag_.report("E003", bin->loc,
|
||||||
|
"Nullable operand: '" + leftType.toString() + "' ve '" +
|
||||||
|
rightType.toString() + "' — null kontrolü yapın veya daraltın");
|
||||||
|
result = Type::error();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// String birleştirme: yalnızca + operatörü (ADR-024)
|
||||||
|
if (bin->Operator == TokenType::PLUS &&
|
||||||
|
leftType.isString() && rightType.isString()) {
|
||||||
|
result = Type::String();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Aritmetik: +, -, *, /, %
|
// Aritmetik: +, -, *, /, %
|
||||||
int lRank = numericRank(leftType);
|
int lRank = numericRank(leftType);
|
||||||
int rRank = numericRank(rightType);
|
int rRank = numericRank(rightType);
|
||||||
|
|
@ -377,6 +529,14 @@ Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) {
|
||||||
case ASTKind::MemberAccess: {
|
case ASTKind::MemberAccess: {
|
||||||
auto* ma = (MemberAccessNode*)node;
|
auto* ma = (MemberAccessNode*)node;
|
||||||
Type objType = checkExpr(ma->object);
|
Type objType = checkExpr(ma->object);
|
||||||
|
// ADR-021: nullable nesne üstünde doğrudan alan erişimi yasak
|
||||||
|
if (objType.nullable) {
|
||||||
|
diag_.report("E003", node->loc,
|
||||||
|
"Nullable tip '" + objType.toString() + "' üstünde doğrudan erişim"
|
||||||
|
" — if ile null kontrolü yapın");
|
||||||
|
result = Type::error();
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (objType.isStruct()) {
|
if (objType.isStruct()) {
|
||||||
result = table_.getFieldType(objType.structName, ma->member);
|
result = table_.getFieldType(objType.structName, ma->member);
|
||||||
if (result.isError())
|
if (result.isError())
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef SAQUT_SEMANTIC_TYPE_CHECKER
|
#ifndef SAQUT_SEMANTIC_TYPE_CHECKER
|
||||||
#define SAQUT_SEMANTIC_TYPE_CHECKER
|
#define SAQUT_SEMANTIC_TYPE_CHECKER
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <string>
|
||||||
#include "symbol/symbol_table.hpp"
|
#include "symbol/symbol_table.hpp"
|
||||||
#include "diagnostic/diagnostic_engine.hpp"
|
#include "diagnostic/diagnostic_engine.hpp"
|
||||||
#include "parser/ast_node.hpp"
|
#include "parser/ast_node.hpp"
|
||||||
|
|
@ -31,11 +33,20 @@ private:
|
||||||
// İki sayısal tipin genişlik sırası: int=0, float=1, double=2; -1 = sayısal değil.
|
// İki sayısal tipin genişlik sırası: int=0, float=1, double=2; -1 = sayısal değil.
|
||||||
static int numericRank(const Type& t);
|
static int numericRank(const Type& t);
|
||||||
|
|
||||||
|
// ADR-021: if-narrowing — null kontrolü kalıbını ayrıştır
|
||||||
|
// Dönüş: {varName, isNotNull} — "a != null" → {a, true}; "a == null" → {a, false}; {"", _} = kalıp yok
|
||||||
|
static std::pair<std::string, bool> extractNullCheck(ASTNode* cond);
|
||||||
|
// Bir statement her zaman çıkış yapıyor mu? (return/throw/break/continue)
|
||||||
|
static bool alwaysExits(ASTNode* stmt);
|
||||||
|
|
||||||
SymbolTable& table_;
|
SymbolTable& table_;
|
||||||
DiagnosticEngine& diag_;
|
DiagnosticEngine& diag_;
|
||||||
|
|
||||||
Type currentReturnType_; // aktif fonksiyonun beklenen dönüş tipi
|
Type currentReturnType_; // aktif fonksiyonun beklenen dönüş tipi
|
||||||
bool inFunction_ = false;
|
bool inFunction_ = false;
|
||||||
|
|
||||||
|
// ADR-021: akış-duyarlı null daraltma — bu kapsamda non-null olduğu bilinen değişkenler
|
||||||
|
std::unordered_set<std::string> narrowedNonNull_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SAQUT_SEMANTIC_TYPE_CHECKER
|
#endif // SAQUT_SEMANTIC_TYPE_CHECKER
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,18 @@ void SymbolCollector::seedBuiltins() {
|
||||||
Type::function(Type::Void(), {}),
|
Type::function(Type::Void(), {}),
|
||||||
SourceLocation{});
|
SourceLocation{});
|
||||||
if (s) s->isBuiltin = true;
|
if (s) s->isBuiltin = true;
|
||||||
|
|
||||||
|
// ADR-025: Error builtin struct — try/catch için
|
||||||
|
// Alan sırası VM makeErrorValue() ile eşleşmeli: [line, col, message, trace, code]
|
||||||
|
table_.structLayouts["Error"] = {
|
||||||
|
{"line", Type::Int()},
|
||||||
|
{"col", Type::Int()},
|
||||||
|
{"message", Type::String()},
|
||||||
|
{"trace", Type::String()},
|
||||||
|
{"code", Type::String()}
|
||||||
|
};
|
||||||
|
table_.define("Error", SymbolKind::Struct, Type::structType("Error"), {});
|
||||||
|
structFields_["Error"]; // cycle checker'a tanıt
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -273,6 +285,29 @@ void SymbolCollector::walkStmt(ASTNode* node) {
|
||||||
case ASTKind::ContinueStatement:
|
case ASTKind::ContinueStatement:
|
||||||
break; // yaprak
|
break; // yaprak
|
||||||
|
|
||||||
|
// ADR-025: try { body } catch (Error e) { handler }
|
||||||
|
case ASTKind::TryStatement: {
|
||||||
|
auto* ts = (TryStatementNode*)node;
|
||||||
|
if (ts->body) walkStmt(ts->body);
|
||||||
|
// catch değişkeni catch bloğu kapsamında görünür
|
||||||
|
if (ts->handler) {
|
||||||
|
table_.enterScope();
|
||||||
|
if (!ts->catchVar.empty())
|
||||||
|
table_.define(ts->catchVar, SymbolKind::Variable,
|
||||||
|
Type::structType("Error"), {});
|
||||||
|
walkStmt(ts->handler);
|
||||||
|
table_.exitScope();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADR-025: throw <ifade>;
|
||||||
|
case ASTKind::ThrowStatement: {
|
||||||
|
auto* th = (ThrowStatementNode*)node;
|
||||||
|
if (th->value) walkExpr(th->value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,20 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
// ── makeErrorValue ─────────────────────────────────────────────────────────────
|
||||||
|
// ADR-025: Error struct oluşturur — alan sırası: [line, col, message, trace, code]
|
||||||
|
Value Interpreter::makeErrorValue(const std::string& message,
|
||||||
|
const std::string& code,
|
||||||
|
int line, int col) {
|
||||||
|
StructObject* obj = heap_.allocStruct(5);
|
||||||
|
obj->fields[0] = Value::fromInt(line);
|
||||||
|
obj->fields[1] = Value::fromInt(col);
|
||||||
|
obj->fields[2] = Value::fromString(message);
|
||||||
|
obj->fields[3] = Value::fromString(""); // trace — ileride IR satır tablosuyla doldurulacak
|
||||||
|
obj->fields[4] = Value::fromString(code);
|
||||||
|
return Value::fromRef(obj);
|
||||||
|
}
|
||||||
|
|
||||||
int Interpreter::run() {
|
int Interpreter::run() {
|
||||||
// Global slot'ları sıfırla
|
// Global slot'ları sıfırla
|
||||||
globalSlots_.assign(program_.globalCount, Value::fromInt(0));
|
globalSlots_.assign(program_.globalCount, Value::fromInt(0));
|
||||||
|
|
@ -42,6 +56,10 @@ int Interpreter::run() {
|
||||||
frame.slots[instr.dest] = Value::fromString(instr.stringValue);
|
frame.slots[instr.dest] = Value::fromString(instr.stringValue);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Opcode::LOAD_NULL:
|
||||||
|
frame.slots[instr.dest] = Value::null();
|
||||||
|
break;
|
||||||
|
|
||||||
case Opcode::LOAD_SLOT:
|
case Opcode::LOAD_SLOT:
|
||||||
frame.slots[instr.dest] = frame.slots[instr.src];
|
frame.slots[instr.dest] = frame.slots[instr.src];
|
||||||
break;
|
break;
|
||||||
|
|
@ -63,13 +81,13 @@ int Interpreter::run() {
|
||||||
break;
|
break;
|
||||||
case Opcode::DIV: {
|
case Opcode::DIV: {
|
||||||
int d = frame.slots[instr.right].intValue;
|
int d = frame.slots[instr.right].intValue;
|
||||||
if (d == 0) throw std::runtime_error("Çalışma hatası: sıfıra bölme");
|
if (d == 0) { pendingThrow_ = makeErrorValue("Sıfıra bölme", "E_DIVZERO"); break; }
|
||||||
frame.slots[instr.dest] = Value::fromInt(frame.slots[instr.left].intValue / d);
|
frame.slots[instr.dest] = Value::fromInt(frame.slots[instr.left].intValue / d);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Opcode::MOD: {
|
case Opcode::MOD: {
|
||||||
int d = frame.slots[instr.right].intValue;
|
int d = frame.slots[instr.right].intValue;
|
||||||
if (d == 0) throw std::runtime_error("Çalışma hatası: sıfıra bölme (mod)");
|
if (d == 0) { pendingThrow_ = makeErrorValue("Sıfıra bölme (mod)", "E_DIVZERO"); break; }
|
||||||
frame.slots[instr.dest] = Value::fromInt(frame.slots[instr.left].intValue % d);
|
frame.slots[instr.dest] = Value::fromInt(frame.slots[instr.left].intValue % d);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -211,7 +229,7 @@ int Interpreter::run() {
|
||||||
break;
|
break;
|
||||||
case Opcode::FDIV: {
|
case Opcode::FDIV: {
|
||||||
double r = frame.slots[instr.right].floatValue;
|
double r = frame.slots[instr.right].floatValue;
|
||||||
if (r == 0.0) throw std::runtime_error("Çalışma hatası: float sıfıra bölme");
|
if (r == 0.0) { pendingThrow_ = makeErrorValue("Float sıfıra bölme", "E_DIVZERO"); break; }
|
||||||
frame.slots[instr.dest] = Value::fromFloat(frame.slots[instr.left].floatValue / r);
|
frame.slots[instr.dest] = Value::fromFloat(frame.slots[instr.left].floatValue / r);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -263,27 +281,33 @@ int Interpreter::run() {
|
||||||
}
|
}
|
||||||
case Opcode::ARRAY_GET: {
|
case Opcode::ARRAY_GET: {
|
||||||
Value& arrVal = frame.slots[instr.left];
|
Value& arrVal = frame.slots[instr.left];
|
||||||
if (arrVal.kind != ValueKind::Ref || !arrVal.ref)
|
if (arrVal.kind != ValueKind::Ref || !arrVal.ref) {
|
||||||
throw std::runtime_error("Çalışma hatası: dizi değil");
|
pendingThrow_ = makeErrorValue("Dizi beklendi, farklı tip alındı", "E_TYPE"); break;
|
||||||
|
}
|
||||||
auto* arr = (ArrayObject*)arrVal.ref;
|
auto* arr = (ArrayObject*)arrVal.ref;
|
||||||
int idx = frame.slots[instr.right].intValue;
|
int idx = frame.slots[instr.right].intValue;
|
||||||
if (idx < 0 || idx >= (int)arr->elements.size())
|
if (idx < 0 || idx >= (int)arr->elements.size()) {
|
||||||
throw std::runtime_error(
|
pendingThrow_ = makeErrorValue(
|
||||||
"Çalışma hatası: dizi sınır dışı (indeks=" + std::to_string(idx) +
|
"Dizi sınır dışı (indeks=" + std::to_string(idx) +
|
||||||
", uzunluk=" + std::to_string(arr->elements.size()) + ")");
|
", uzunluk=" + std::to_string(arr->elements.size()) + ")", "E_OOB");
|
||||||
|
break;
|
||||||
|
}
|
||||||
frame.slots[instr.dest] = arr->elements[idx];
|
frame.slots[instr.dest] = arr->elements[idx];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Opcode::ARRAY_SET: {
|
case Opcode::ARRAY_SET: {
|
||||||
Value& arrVal = frame.slots[instr.dest];
|
Value& arrVal = frame.slots[instr.dest];
|
||||||
if (arrVal.kind != ValueKind::Ref || !arrVal.ref)
|
if (arrVal.kind != ValueKind::Ref || !arrVal.ref) {
|
||||||
throw std::runtime_error("Çalışma hatası: dizi değil");
|
pendingThrow_ = makeErrorValue("Dizi beklendi, farklı tip alındı", "E_TYPE"); break;
|
||||||
|
}
|
||||||
auto* arr = (ArrayObject*)arrVal.ref;
|
auto* arr = (ArrayObject*)arrVal.ref;
|
||||||
int idx = frame.slots[instr.left].intValue;
|
int idx = frame.slots[instr.left].intValue;
|
||||||
if (idx < 0 || idx >= (int)arr->elements.size())
|
if (idx < 0 || idx >= (int)arr->elements.size()) {
|
||||||
throw std::runtime_error(
|
pendingThrow_ = makeErrorValue(
|
||||||
"Çalışma hatası: dizi sınır dışı (indeks=" + std::to_string(idx) +
|
"Dizi sınır dışı (indeks=" + std::to_string(idx) +
|
||||||
", uzunluk=" + std::to_string(arr->elements.size()) + ")");
|
", uzunluk=" + std::to_string(arr->elements.size()) + ")", "E_OOB");
|
||||||
|
break;
|
||||||
|
}
|
||||||
arr->elements[idx] = frame.slots[instr.right];
|
arr->elements[idx] = frame.slots[instr.right];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -296,11 +320,61 @@ int Interpreter::run() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── String (ADR-024: immutable değer-tipi, içerik ==) ────────────
|
||||||
|
case Opcode::STRING_CONCAT:
|
||||||
|
frame.slots[instr.dest] = Value::fromString(
|
||||||
|
frame.slots[instr.left].stringValue +
|
||||||
|
frame.slots[instr.right].stringValue);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ── Hata yönetimi (ADR-025) ──────────────────────────────────────
|
||||||
|
case Opcode::ENTER_TRY:
|
||||||
|
tryStack_.push_back({callStack_.size(), instr.jumpTarget, instr.dest});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Opcode::LEAVE_TRY:
|
||||||
|
if (!tryStack_.empty()) tryStack_.pop_back();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Opcode::THROW:
|
||||||
|
pendingThrow_ = frame.slots[instr.src];
|
||||||
|
break;
|
||||||
|
|
||||||
// ── FFI ───────────────────────────────────────────────────────────
|
// ── FFI ───────────────────────────────────────────────────────────
|
||||||
case Opcode::CALLHOST:
|
case Opcode::CALLHOST:
|
||||||
executeHostFunction(instr.functionName, frame.slots, instr.argSlots);
|
executeHostFunction(instr.functionName, frame.slots, instr.argSlots);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── pendingThrow_ işle: try varsa catch'e unwind, yoksa fırlat ───
|
||||||
|
if (pendingThrow_.has_value()) {
|
||||||
|
Value errVal = std::move(*pendingThrow_);
|
||||||
|
pendingThrow_.reset();
|
||||||
|
|
||||||
|
if (!tryStack_.empty()) {
|
||||||
|
TryFrame tf = tryStack_.back();
|
||||||
|
tryStack_.pop_back();
|
||||||
|
// catch bloğunun bulunduğu frame'e unwind
|
||||||
|
while (callStack_.size() > tf.callStackDepth)
|
||||||
|
callStack_.pop_back();
|
||||||
|
// Error'ı catch değişkenine bağla ve catch etiketine atla
|
||||||
|
callStack_.back().slots[tf.errorSlot] = errVal;
|
||||||
|
callStack_.back().instructionPointer = tf.catchTarget;
|
||||||
|
} else {
|
||||||
|
// Yakalanmamış hata — mesajı çıkar ve C++ exception olarak yükselt
|
||||||
|
std::string msg = "Yakalanmamış hata";
|
||||||
|
if (errVal.kind == ValueKind::Ref && errVal.ref) {
|
||||||
|
auto* s = static_cast<StructObject*>(errVal.ref);
|
||||||
|
if ((int)s->fields.size() > 2 &&
|
||||||
|
s->fields[2].kind == ValueKind::String)
|
||||||
|
msg = s->fields[2].stringValue;
|
||||||
|
} else if (errVal.kind == ValueKind::String) {
|
||||||
|
msg = errVal.stringValue;
|
||||||
|
}
|
||||||
|
throw std::runtime_error(msg);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,18 @@
|
||||||
#define SAQUT_VM_INTERPRETER
|
#define SAQUT_VM_INTERPRETER
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
#include "ir/ir_program.hpp"
|
#include "ir/ir_program.hpp"
|
||||||
#include "vm/call_frame.hpp"
|
#include "vm/call_frame.hpp"
|
||||||
#include "vm/object.hpp"
|
#include "vm/object.hpp"
|
||||||
|
|
||||||
|
// ADR-025: try bloğu girişinde yığına eklenen kayıt
|
||||||
|
struct TryFrame {
|
||||||
|
size_t callStackDepth; // ENTER_TRY anındaki callStack_.size() — unwind için
|
||||||
|
int catchTarget; // catch bloğunun IR instruction indeksi
|
||||||
|
int errorSlot; // catch değişkeninin slot numarası (catch frame'inde)
|
||||||
|
};
|
||||||
|
|
||||||
class Interpreter {
|
class Interpreter {
|
||||||
public:
|
public:
|
||||||
explicit Interpreter(IRProgram& program) : program_(program) {}
|
explicit Interpreter(IRProgram& program) : program_(program) {}
|
||||||
|
|
@ -27,10 +35,17 @@ public:
|
||||||
int run();
|
int run();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IRProgram& program_;
|
IRProgram& program_;
|
||||||
std::vector<CallFrame> callStack_;
|
std::vector<CallFrame> callStack_;
|
||||||
std::vector<Value> globalSlots_;
|
std::vector<Value> globalSlots_;
|
||||||
Heap heap_;
|
Heap heap_;
|
||||||
|
std::vector<TryFrame> tryStack_; // ADR-025: aktif try çerçeveleri
|
||||||
|
std::optional<Value> pendingThrow_; // bekleyen istisna değeri
|
||||||
|
|
||||||
|
// Error StructObject oluştur (ADR-025): [line, col, message, trace, code]
|
||||||
|
Value makeErrorValue(const std::string& message,
|
||||||
|
const std::string& code = "",
|
||||||
|
int line = 0, int col = 0);
|
||||||
|
|
||||||
// Host (C++) fonksiyon çağrısı — şu an sadece "print" destekli
|
// Host (C++) fonksiyon çağrısı — şu an sadece "print" destekli
|
||||||
void executeHostFunction(const std::string& name,
|
void executeHostFunction(const std::string& name,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
yakalandi
|
||||||
|
Sıfıra bölme
|
||||||
|
E_DIVZERO
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// ADR-025: temel try/catch — runtime hatasını yakala
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
try {
|
||||||
|
int x = 10 / 0;
|
||||||
|
print("erişilmez");
|
||||||
|
} catch (Error e) {
|
||||||
|
print("yakalandi");
|
||||||
|
print(e.message);
|
||||||
|
print(e.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
OOB yakalandi
|
||||||
|
E_OOB
|
||||||
|
throw yakalandi
|
||||||
|
devam
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// ADR-025: throw + iç içe fonksiyon çağrısında unwind + array OOB
|
||||||
|
|
||||||
|
int riskli() {
|
||||||
|
int arr[3] = [1, 2, 3];
|
||||||
|
return arr[10];
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Dış fonksiyonda throw
|
||||||
|
try {
|
||||||
|
riskli();
|
||||||
|
print("erişilmez");
|
||||||
|
} catch (Error e) {
|
||||||
|
print("OOB yakalandi");
|
||||||
|
print(e.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Açık throw — string değer atıp catch et
|
||||||
|
try {
|
||||||
|
throw "kullanici hatasi";
|
||||||
|
} catch (Error e) {
|
||||||
|
print("throw yakalandi");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch sonrası akış devam etmeli
|
||||||
|
print("devam");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
1
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// ADR-021: && sağ taraf narrowing
|
||||||
|
int? getValue() {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool check(int? v) {
|
||||||
|
if (v != null && v > 5) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
bool r = check(getValue());
|
||||||
|
if (r) {
|
||||||
|
print(1);
|
||||||
|
} else {
|
||||||
|
print(0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
42
|
||||||
|
0
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// ADR-021: akış-duyarlı null analizi
|
||||||
|
// nested + guard narrowing
|
||||||
|
|
||||||
|
int? maybeGet(bool flag) {
|
||||||
|
if (flag) {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int? a = maybeGet(true);
|
||||||
|
|
||||||
|
// nested: if (a != null) { a non-null }
|
||||||
|
if (a != null) {
|
||||||
|
int x = a; // OK: narrowed
|
||||||
|
print(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// guard: if (a == null) return; sonrasında a non-null
|
||||||
|
int? b = maybeGet(false);
|
||||||
|
if (b == null) {
|
||||||
|
print(0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// burada b non-null (guard geçti)
|
||||||
|
int y = b;
|
||||||
|
print(y);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
E003
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
// ADR-021: nullable → non-null atama hata vermeli
|
||||||
|
int main() {
|
||||||
|
int? a = 5;
|
||||||
|
int b = a; // HATA: int? → int atanamaz
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
E003
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
// ADR-021: nullable operand aritmetikte hata vermeli
|
||||||
|
int main() {
|
||||||
|
int? a = 5;
|
||||||
|
int b = a + 1; // HATA: a nullable
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
Merhaba Dünya
|
||||||
|
foobar
|
||||||
|
1
|
||||||
|
1
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// ADR-024: string birleştirme (+, +=) — immutable değer-tipi, içerik ==
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
string a = "Merhaba";
|
||||||
|
string b = " Dünya";
|
||||||
|
string c = a + b;
|
||||||
|
print(c);
|
||||||
|
|
||||||
|
string s = "foo";
|
||||||
|
s += "bar";
|
||||||
|
print(s);
|
||||||
|
|
||||||
|
// İki farklı değişken, aynı içerik → true (ADR-023/024)
|
||||||
|
string x = "abc";
|
||||||
|
string y = "abc";
|
||||||
|
print(x == y);
|
||||||
|
|
||||||
|
// Birleştirme sonucu eşitlik
|
||||||
|
string p = "Hello" + " World";
|
||||||
|
string q = "Hello World";
|
||||||
|
print(p == q);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue