diff --git a/.gitignore b/.gitignore index 65b942d..38ac63c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,4 @@ scripts/gitea.py __pycache__/ # CMake versiyona özgü dosyalar (otomatik üretilir, her güncellemede değişir) -build/CMakeFiles/[0-9]*/ -build/.cmake/ -build/compile_commands.json +build/* diff --git a/CLAUDE.md b/CLAUDE.md index 6cd3b15..bc58d9b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,12 +30,14 @@ git'te izlenir. **rezerve** (semantik ileride). `interface` **ertelendi** (ADR-018). ⚠️ Referans semantiği döngüsel-referans **sızıntısını** açtı → GC/döngü toplayıcı borcu (**#56**, `karar-gerekli`). -- **Null güvenliği (ADR-021):** varsayılan non-null; nullable açıkça `Type?` - (Kotlin/Swift modeli). `null` yalnızca `T?`'ye atanır; `T?` üstünde doğrudan - erişim derleme hatası. **Akış-duyarlı null analizi** (`if (a != null)` daraltır; - guard/early-return; `&&` sağ tarafı). Runtime maliyeti **sıfır** (compile-time). - `a!` = runtime-kontrollü non-null iddiası. İlk gerçek akış-duyarlı analiz → - yapısal akış yeter, SSA gerekmez (#2 için veri). +- **Null güvenliği (ADR-021, REVİZE):** varsayılan non-null; nullable açıkça `Type?` + (Kotlin/Swift). `null` yalnızca `T?`'ye atanır; `T?` üstünde doğrudan erişim derleme + hatası. **Atama kuralı `T <: T?`** (notnull→nullable serbest, nullable→notnull yasak). + **Katı operand kuralı:** non-null bağlamda her operand statik non-null olmalı + (`int a=b+c+d`, biri nullable → hata). Aklama **yalnızca görünür `if` narrowing** + (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 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 @@ -52,6 +54,20 @@ git'te izlenir. 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 — 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 **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ü + @@ -88,9 +104,9 @@ git'te izlenir. ## Belge haritası - `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/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 - 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 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). diff --git a/TODO.md b/TODO.md index af7aeb7..62cc727 100644 --- a/TODO.md +++ b/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) doğrultusunda uçtan uca array çalışıyor: - `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 - Array literal parser (`[1,2,3]`), `int[]` tip sözdizimi (Java/C# stili) - Referans semantiği, kimlik `==` (ADR-023), sınır kontrolü - 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 -2. **String cilası (ADR-024)** — içerik `==`, UTF-8 concat+print -3. **Null akış-analizi (ADR-021)** — `Type?`, flow-narrowing, `a!` -4. **float/double (#44)** — Value::Float + FADD/FSUB/… opcodes -5. **mark-sweep GC v2 (#56)** — header+kök kancası üstünde aç +ADR-020 doğrultusunda struct referans semantiğiyle uçtan uca çalışıyor: +- `src/vm/object.hpp` — StructObject : Object, alan erişimi (GC-hazır header) +- `src/vm/value.hpp` — ValueKind::Null eklendi (dil anahtar sözcüğü `null`) +- E010 revizyonu: referansla tutulan `struct Node { Node next; }` artık meşru + (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). --- diff --git a/docs/adr-frontend-analiz.md b/docs/adr-frontend-analiz.md index aea0564..ac1432d 100644 --- a/docs/adr-frontend-analiz.md +++ b/docs/adr-frontend-analiz.md @@ -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) +> ⚠️ **İ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 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 | 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 | | 018 | `interface` | Ertelendi (reddedilmedi); struct+fonksiyon yeter | | 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 | diff --git a/docs/sonnet-handoff.md b/docs/sonnet-handoff.md index 698bb38..d978ee1 100644 --- a/docs/sonnet-handoff.md +++ b/docs/sonnet-handoff.md @@ -121,12 +121,20 @@ func main() { (`"şğü"` 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 — zorunlu değil.) -3. **Null akış-analizi (ADR-021).** Ayrı **frontend** görevi: `Type?` tip sistemi + - akış-duyarlı narrowing (`if (a != null)`, guard, `&&`), `T?` üstünde doğrudan - erişim → derleme hatası, `a!` runtime-kontrollü iddia. CFG/yapısal akış üstünde; - SSA gerekmez. #20 (akıllı diagnostic) buradan beslenir. -4. **float/double (#44).** Bağımsız; `Value::Float` + FADD… opcode + tip denetleyici. -5. **mark-sweep GC v2 (#56).** Adım 1.1'de bırakılan header+kök kancası üstünde aç. +3. **Null akış-analizi (ADR-021 — REVİZE).** Ayrı **frontend** görevi: `Type?` tip + sistemi + `T <: T?` atama kuralı + katı operand kuralı + akış-duyarlı narrowing + (nested `if` + sıralı guard + `&&`). ⚠️ **`a!`/`??`/`?.` YASAK** — null yalnızca + görünür `if` ile aklanır. `T?` üstünde doğrudan erişim → derleme hatası. Frontend + kesin çözer (backend yeniden analiz etmez). Detay: TODO Bölüm "SIRADAKİ İŞ" + ADR-021. +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 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. + +--- + +## 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`~~, ~~`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`~~ | +| 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. diff --git a/src/core/type.hpp b/src/core/type.hpp index e7e79f7..4e8a770 100644 --- a/src/core/type.hpp +++ b/src/core/type.hpp @@ -73,6 +73,7 @@ struct Type { std::shared_ptr returnType; // kind == Function std::vector paramTypes; // kind == Function std::string structName; // kind == Struct + bool nullable = false; // ADR-021: Type? sözdizimi // ------------------------------------------------------------------ // // Factory'ler @@ -132,11 +133,26 @@ struct Type { 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) // ------------------------------------------------------------------ // + // Yapısal eşitlik — nullable dahil (ADR-021: int ≠ int?) bool equals(const Type& o) const { if (kind != o.kind) return false; + if (nullable != o.nullable) return false; switch (kind) { case TypeKind::Primitive: return prim == o.prim; @@ -154,13 +170,14 @@ struct Type { return true; } 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 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ı // ------------------------------------------------------------------ // @@ -177,10 +194,15 @@ struct Type { return "?"; } - // Bir tip adından (parser tipleri string olarak tutar) primitif Type üretir. - // Bilinen primitif değilse Error döner — bilinmeyen tip adının teşhisi - // (E007) çağıranın (Faz 2/3) işidir; bu fonksiyon sessizce Error verir. + // Bir tip adından (parser tipleri string olarak tutar) Type üretir. + // "int?" → nullable int; "int[]" → int array; bilinen değilse Error. 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 == "float") return Float(); if (n == "double") return Double(); @@ -200,27 +222,28 @@ struct Type { // toString — İnsan-okur ("int", "int[]", "fn(int,int)->int") // ------------------------------------------------------------------ // std::string toString() const { + std::string base; switch (kind) { case TypeKind::Primitive: - return primName(prim); + base = primName(prim); break; case TypeKind::Array: - return (elementType ? elementType->toString() : "") + "[]"; + base = (elementType ? elementType->toString() : "") + "[]"; break; case TypeKind::Struct: - return "struct " + structName; + base = "struct " + structName; break; case TypeKind::Function: { - std::string s = "fn("; + base = "fn("; for (size_t i = 0; i < paramTypes.size(); ++i) { - if (i) s += ","; - s += paramTypes[i].toString(); + if (i) base += ","; + base += paramTypes[i].toString(); } - s += ")->"; - s += returnType ? returnType->toString() : ""; - return s; + base += ")->"; + base += returnType ? returnType->toString() : ""; + break; } case TypeKind::Error: return ""; } - return ""; + return nullable ? base + "?" : base; } // ------------------------------------------------------------------ // diff --git a/src/ir/instruction.hpp b/src/ir/instruction.hpp index be6b360..07aaf4b 100644 --- a/src/ir/instruction.hpp +++ b/src/ir/instruction.hpp @@ -42,6 +42,7 @@ enum class Opcode { // Örnek: LOAD_CONST dest=3 val=10 → slot[3] = 10 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" 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) 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) --- CALLHOST, // Host (C++) fonksiyonunu çağır. Şu an sadece "print" destekli. // Dönüş değeri yok; sadece yan etki (stdout'a yazmak gibi). @@ -121,6 +134,7 @@ inline const char* opcodeName(Opcode op) { switch (op) { case Opcode::LOAD_CONST: return "LOAD_CONST"; case Opcode::LOAD_STRING: return "LOAD_STRING"; + case Opcode::LOAD_NULL: return "LOAD_NULL"; case Opcode::LOAD_SLOT: return "LOAD_SLOT"; case Opcode::ADD: return "ADD"; case Opcode::SUB: return "SUB"; @@ -160,6 +174,10 @@ inline const char* opcodeName(Opcode op) { case Opcode::JIF_TRUE: return "JIF_TRUE"; case Opcode::CALL: return "CALL"; 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"; } return "UNKNOWN"; diff --git a/src/ir/ir_generator.cpp b/src/ir/ir_generator.cpp index ded5a44..290fd96 100644 --- a/src/ir/ir_generator.cpp +++ b/src/ir/ir_generator.cpp @@ -10,6 +10,10 @@ #include #include +// 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ı // ───────────────────────────────────────────────────────────────────────────── @@ -301,6 +305,54 @@ void IRGenerator::generateStatement(ASTNode* node) { 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 ; (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: break; } @@ -376,9 +428,9 @@ int IRGenerator::generateExpression(ASTNode* node) { break; } case LiteralType::BOŞ: - // null literal → Null kind Value (ADR-021) - { Instruction ins(Opcode::LOAD_CONST); ins.dest = slot; ins.intValue = 0; - currentFunction_->instructions.push_back(std::move(ins)); } // placeholder; VM'de Null üretmeli + // null literal → ValueKind::Null (ADR-021) + { Instruction ins(Opcode::LOAD_NULL); ins.dest = slot; + currentFunction_->instructions.push_back(std::move(ins)); } break; } 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::RSHIFT_EQUAL) arithOp = Opcode::SHR; + // string += string → STRING_CONCAT (ADR-024) + if (bin->Operator == TokenType::PLUS_EQUAL) { + if (auto* e = dynamic_cast(bin->Right)) + if (e->resolvedType.isString()) arithOp = Opcode::STRING_CONCAT; + } + int resultSlot = freshSlot(); 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ı) bool leftIsFloat = false, rightIsFloat = false; - if (auto* e = dynamic_cast(leftNode)) + bool leftIsString = false, rightIsString = false; + if (auto* e = dynamic_cast(leftNode)) { leftIsFloat = e->resolvedType.isPrimitive() && (e->resolvedType.prim == PrimitiveKind::Float || e->resolvedType.prim == PrimitiveKind::Double); - if (auto* e = dynamic_cast(rightNode)) + leftIsString = e->resolvedType.isString(); + } + if (auto* e = dynamic_cast(rightNode)) { rightIsFloat = e->resolvedType.isPrimitive() && (e->resolvedType.prim == PrimitiveKind::Float || 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) { // Int operandı float'a çevir diff --git a/src/parser/ast_node.hpp b/src/parser/ast_node.hpp index 75f29d1..c79820f 100644 --- a/src/parser/ast_node.hpp +++ b/src/parser/ast_node.hpp @@ -87,6 +87,8 @@ enum class ASTKind { ExpressionStatement, // ifade + noktalı virgül (;) // children: [expression] // Örn: x = 5; veya foo(); + TryStatement, // try { body } catch (Error e) { handler } (ADR-025) + ThrowStatement, // throw ; (ADR-025) /* ====== İfadeler (Expressions) ====== */ BinaryExpression, // İkili işlem: sol OP sağ. diff --git a/src/parser/nodes/statements.cpp b/src/parser/nodes/statements.cpp index a395bbe..dc29dad 100644 --- a/src/parser/nodes/statements.cpp +++ b/src/parser/nodes/statements.cpp @@ -147,3 +147,36 @@ std::string ExpressionStatementNode::toJson(int depth) { obj.addRaw("location", loc.toJson()); 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(); +} diff --git a/src/parser/nodes/statements.hpp b/src/parser/nodes/statements.hpp index d3e43da..6f58612 100644 --- a/src/parser/nodes/statements.hpp +++ b/src/parser/nodes/statements.hpp @@ -85,4 +85,26 @@ public: 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 ; +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 diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 4efd18e..0350d87 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -85,8 +85,15 @@ ASTNode* Parser::parseDeclaration() { })) { auto la1 = lookahead(1); auto la2 = lookahead(2); + // int name( → fonksiyon if (la1.type == TokenType::IDENTIFIER && la2.type == TokenType::LPAREN) 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(); } @@ -99,6 +106,11 @@ ASTNode* Parser::parseDeclaration() { auto la2 = lookahead(2); if (la1.type == TokenType::IDENTIFIER && la2.type == TokenType::LPAREN) 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) return parseVariableDecl(); } @@ -316,6 +328,10 @@ ASTNode* Parser::parseFunctionDecl() { fn->returnType = currentToken().token->token; nextToken(); + // ADR-021: nullable dönüş tipi — int? f() + if (currentToken().type == TokenType::TERNARY) + { nextToken(); fn->returnType += "?"; } + fn->name = currentToken().token->token; nextToken(); @@ -339,6 +355,9 @@ ASTNode* Parser::parseFunctionDecl() { nextToken(); paramType += "[]"; } + // ADR-021: nullable parametre — int? a + if (currentToken().type == TokenType::TERNARY) + { nextToken(); paramType += "?"; } if (currentToken().type != TokenType::IDENTIFIER || !currentToken().token) break; VariableDeclNode* param = new VariableDeclNode(); param->loc = currentToken().token->loc; @@ -396,6 +415,10 @@ ASTNode* Parser::parseVariableDecl() { vd->varType += "[]"; } + // ADR-021: nullable soneki — int? x + if (currentToken().type == TokenType::TERNARY) + { nextToken(); vd->varType += "?"; } + if (currentToken().type != TokenType::IDENTIFIER) { std::cerr << "Parser hatası: değişken ismi bekleniyor\n"; return vd; @@ -486,6 +509,12 @@ ASTNode* Parser::parseStatement() { if (ct.type == TokenType::KW_CONTINUE) return parseContinueStatement(); + if (ct.type == TokenType::KW_TRY) + return parseTryStatement(); + + if (ct.type == TokenType::KW_THROW) + return parseThrowStatement(); + if (ct.is({ TokenType::KW_VOID, TokenType::KW_INT, TokenType::KW_FLOAT_TYPE, TokenType::KW_DOUBLE, TokenType::KW_BOOL, TokenType::KW_CHAR, @@ -670,3 +699,43 @@ ASTNode* Parser::parseExpressionStatement() { 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 ; +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; +} diff --git a/src/parser/parser_base.hpp b/src/parser/parser_base.hpp index 838910f..96e9864 100644 --- a/src/parser/parser_base.hpp +++ b/src/parser/parser_base.hpp @@ -51,6 +51,8 @@ private: ASTNode* parseBreakStatement(); ASTNode* parseContinueStatement(); ASTNode* parseExpressionStatement(); + ASTNode* parseTryStatement(); + ASTNode* parseThrowStatement(); // --- İfadeler (Pratt parser) --- ASTNode* parseExpression(); diff --git a/src/semantic/type_checker.cpp b/src/semantic/type_checker.cpp index ee4e77f..61f94f3 100644 --- a/src/semantic/type_checker.cpp +++ b/src/semantic/type_checker.cpp @@ -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 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ı // ───────────────────────────────────────────────────────────────────────────── @@ -62,21 +106,41 @@ bool TypeChecker::checkAssign(const Type& target, const Type& src, const SourceLocation& loc, const std::string& ctx) { 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 sRank = numericRank(src); if (tRank >= 0 && sRank >= 0) { if (tRank > sRank) { - // Genişletme (widening): int→float, int→double, float→double - if (srcIsLiteral) return true; // literal bağlama-göre tiplenir, uyarısız + if (srcIsLiteral) return true; diag_.report("W004", loc, "'" + ctx + "': " + src.toString() + " → " + target.toString() + " örtük genişletme"); return true; } else { - // Daraltma (narrowing): float→int, double→float, vb. diag_.report("E003", loc, "'" + ctx + "': " + src.toString() + " → " + 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, "'" + ctx + "': " + src.toString() + " tipi " + target.toString() + " tipine atanamaz"); @@ -100,9 +163,26 @@ void TypeChecker::checkStmt(ASTNode* node) { switch (node->kind) { - case ASTKind::Block: - for (ASTNode* child : node->getChildren()) checkStmt(child); + case ASTKind::Block: { + // ADR-021: guard/sıralı narrowing — if (a == null) return; → sonrasında a non-null + std::vector 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; + } case ASTKind::VariableDecl: { auto* vd = (VariableDeclNode*)node; @@ -144,9 +224,23 @@ void TypeChecker::checkStmt(ASTNode* node) { case ASTKind::IfStatement: { 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 (!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 (!narrowVar.empty() && !isNotNull) + narrowedNonNull_.erase(narrowVar); + break; } @@ -180,6 +274,21 @@ void TypeChecker::checkStmt(ASTNode* node) { case ASTKind::ContinueStatement: 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 ; — unchecked, herhangi bir değer atılabilir + case ASTKind::ThrowStatement: { + auto* th = (ThrowStatementNode*)node; + if (th->value) checkExpr(th->value); + break; + } + default: break; } @@ -222,7 +331,14 @@ Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) { break; case LiteralType::BOOLEAN: result = Type::Bool(); 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; } @@ -231,6 +347,12 @@ Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) { case ASTKind::Identifier: { auto* id = (IdentifierNode*)node; 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; } @@ -267,11 +389,23 @@ Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) { } 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); - // Mantıksal - if (bin->Operator == TokenType::AMPERSAND_AMPERSAND || - bin->Operator == TokenType::PIPE_PIPE) { + // Mantıksal (||) + if (bin->Operator == TokenType::PIPE_PIPE) { result = Type::Bool(); break; } @@ -302,6 +436,24 @@ Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) { 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: +, -, *, /, % int lRank = numericRank(leftType); int rRank = numericRank(rightType); @@ -377,6 +529,14 @@ Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) { case ASTKind::MemberAccess: { auto* ma = (MemberAccessNode*)node; 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()) { result = table_.getFieldType(objType.structName, ma->member); if (result.isError()) diff --git a/src/semantic/type_checker.hpp b/src/semantic/type_checker.hpp index cc7febf..47552b6 100644 --- a/src/semantic/type_checker.hpp +++ b/src/semantic/type_checker.hpp @@ -1,6 +1,8 @@ #ifndef SAQUT_SEMANTIC_TYPE_CHECKER #define SAQUT_SEMANTIC_TYPE_CHECKER +#include +#include #include "symbol/symbol_table.hpp" #include "diagnostic/diagnostic_engine.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. 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 extractNullCheck(ASTNode* cond); + // Bir statement her zaman çıkış yapıyor mu? (return/throw/break/continue) + static bool alwaysExits(ASTNode* stmt); + SymbolTable& table_; DiagnosticEngine& diag_; Type currentReturnType_; // aktif fonksiyonun beklenen dönüş tipi bool inFunction_ = false; + + // ADR-021: akış-duyarlı null daraltma — bu kapsamda non-null olduğu bilinen değişkenler + std::unordered_set narrowedNonNull_; }; #endif // SAQUT_SEMANTIC_TYPE_CHECKER diff --git a/src/symbol/symbol_collector.cpp b/src/symbol/symbol_collector.cpp index 2ac9721..a7af8be 100644 --- a/src/symbol/symbol_collector.cpp +++ b/src/symbol/symbol_collector.cpp @@ -29,6 +29,18 @@ void SymbolCollector::seedBuiltins() { Type::function(Type::Void(), {}), SourceLocation{}); 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: 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 ; + case ASTKind::ThrowStatement: { + auto* th = (ThrowStatementNode*)node; + if (th->value) walkExpr(th->value); + break; + } + default: break; } diff --git a/src/vm/interpreter.cpp b/src/vm/interpreter.cpp index 30edaf4..60071b2 100644 --- a/src/vm/interpreter.cpp +++ b/src/vm/interpreter.cpp @@ -3,6 +3,20 @@ #include #include +// ── 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() { // Global slot'ları sıfırla globalSlots_.assign(program_.globalCount, Value::fromInt(0)); @@ -42,6 +56,10 @@ int Interpreter::run() { frame.slots[instr.dest] = Value::fromString(instr.stringValue); break; + case Opcode::LOAD_NULL: + frame.slots[instr.dest] = Value::null(); + break; + case Opcode::LOAD_SLOT: frame.slots[instr.dest] = frame.slots[instr.src]; break; @@ -63,13 +81,13 @@ int Interpreter::run() { break; case Opcode::DIV: { 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); break; } case Opcode::MOD: { 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); break; } @@ -211,7 +229,7 @@ int Interpreter::run() { break; case Opcode::FDIV: { 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); break; } @@ -263,27 +281,33 @@ int Interpreter::run() { } case Opcode::ARRAY_GET: { Value& arrVal = frame.slots[instr.left]; - if (arrVal.kind != ValueKind::Ref || !arrVal.ref) - throw std::runtime_error("Çalışma hatası: dizi değil"); + if (arrVal.kind != ValueKind::Ref || !arrVal.ref) { + pendingThrow_ = makeErrorValue("Dizi beklendi, farklı tip alındı", "E_TYPE"); break; + } auto* arr = (ArrayObject*)arrVal.ref; int idx = frame.slots[instr.right].intValue; - if (idx < 0 || idx >= (int)arr->elements.size()) - throw std::runtime_error( - "Çalışma hatası: dizi sınır dışı (indeks=" + std::to_string(idx) + - ", uzunluk=" + std::to_string(arr->elements.size()) + ")"); + if (idx < 0 || idx >= (int)arr->elements.size()) { + pendingThrow_ = makeErrorValue( + "Dizi sınır dışı (indeks=" + std::to_string(idx) + + ", uzunluk=" + std::to_string(arr->elements.size()) + ")", "E_OOB"); + break; + } frame.slots[instr.dest] = arr->elements[idx]; break; } case Opcode::ARRAY_SET: { Value& arrVal = frame.slots[instr.dest]; - if (arrVal.kind != ValueKind::Ref || !arrVal.ref) - throw std::runtime_error("Çalışma hatası: dizi değil"); + if (arrVal.kind != ValueKind::Ref || !arrVal.ref) { + pendingThrow_ = makeErrorValue("Dizi beklendi, farklı tip alındı", "E_TYPE"); break; + } auto* arr = (ArrayObject*)arrVal.ref; int idx = frame.slots[instr.left].intValue; - if (idx < 0 || idx >= (int)arr->elements.size()) - throw std::runtime_error( - "Çalışma hatası: dizi sınır dışı (indeks=" + std::to_string(idx) + - ", uzunluk=" + std::to_string(arr->elements.size()) + ")"); + if (idx < 0 || idx >= (int)arr->elements.size()) { + pendingThrow_ = makeErrorValue( + "Dizi sınır dışı (indeks=" + std::to_string(idx) + + ", uzunluk=" + std::to_string(arr->elements.size()) + ")", "E_OOB"); + break; + } arr->elements[idx] = frame.slots[instr.right]; break; } @@ -296,11 +320,61 @@ int Interpreter::run() { 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 ─────────────────────────────────────────────────────────── case Opcode::CALLHOST: executeHostFunction(instr.functionName, frame.slots, instr.argSlots); 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(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; diff --git a/src/vm/interpreter.hpp b/src/vm/interpreter.hpp index 6b02107..b2f8bb2 100644 --- a/src/vm/interpreter.hpp +++ b/src/vm/interpreter.hpp @@ -14,10 +14,18 @@ #define SAQUT_VM_INTERPRETER #include +#include #include "ir/ir_program.hpp" #include "vm/call_frame.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 { public: explicit Interpreter(IRProgram& program) : program_(program) {} @@ -27,10 +35,17 @@ public: int run(); private: - IRProgram& program_; + IRProgram& program_; std::vector callStack_; - std::vector globalSlots_; - Heap heap_; + std::vector globalSlots_; + Heap heap_; + std::vector tryStack_; // ADR-025: aktif try çerçeveleri + std::optional 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 void executeHostFunction(const std::string& name, diff --git a/tests/golden/error/basic_catch.expected b/tests/golden/error/basic_catch.expected new file mode 100644 index 0000000..b32216e --- /dev/null +++ b/tests/golden/error/basic_catch.expected @@ -0,0 +1,3 @@ +yakalandi +Sıfıra bölme +E_DIVZERO diff --git a/tests/golden/error/basic_catch.sqt b/tests/golden/error/basic_catch.sqt new file mode 100644 index 0000000..a9c3830 --- /dev/null +++ b/tests/golden/error/basic_catch.sqt @@ -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; +} diff --git a/tests/golden/error/throw_and_nested.expected b/tests/golden/error/throw_and_nested.expected new file mode 100644 index 0000000..e40ef42 --- /dev/null +++ b/tests/golden/error/throw_and_nested.expected @@ -0,0 +1,4 @@ +OOB yakalandi +E_OOB +throw yakalandi +devam diff --git a/tests/golden/error/throw_and_nested.sqt b/tests/golden/error/throw_and_nested.sqt new file mode 100644 index 0000000..f32fa60 --- /dev/null +++ b/tests/golden/error/throw_and_nested.sqt @@ -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; +} diff --git a/tests/golden/null/and_narrowing.expected b/tests/golden/null/and_narrowing.expected new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/tests/golden/null/and_narrowing.expected @@ -0,0 +1 @@ +1 diff --git a/tests/golden/null/and_narrowing.sqt b/tests/golden/null/and_narrowing.sqt new file mode 100644 index 0000000..5841534 --- /dev/null +++ b/tests/golden/null/and_narrowing.sqt @@ -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; +} diff --git a/tests/golden/null/narrowing.expected b/tests/golden/null/narrowing.expected new file mode 100644 index 0000000..a8e497c --- /dev/null +++ b/tests/golden/null/narrowing.expected @@ -0,0 +1,2 @@ +42 +0 diff --git a/tests/golden/null/narrowing.sqt b/tests/golden/null/narrowing.sqt new file mode 100644 index 0000000..02d4e88 --- /dev/null +++ b/tests/golden/null/narrowing.sqt @@ -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; +} diff --git a/tests/golden/null/nullable_assign_error.compile_error b/tests/golden/null/nullable_assign_error.compile_error new file mode 100644 index 0000000..ea41fcc --- /dev/null +++ b/tests/golden/null/nullable_assign_error.compile_error @@ -0,0 +1 @@ +E003 \ No newline at end of file diff --git a/tests/golden/null/nullable_assign_error.sqt b/tests/golden/null/nullable_assign_error.sqt new file mode 100644 index 0000000..9897cb2 --- /dev/null +++ b/tests/golden/null/nullable_assign_error.sqt @@ -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; +} diff --git a/tests/golden/null/nullable_operand_error.compile_error b/tests/golden/null/nullable_operand_error.compile_error new file mode 100644 index 0000000..ea41fcc --- /dev/null +++ b/tests/golden/null/nullable_operand_error.compile_error @@ -0,0 +1 @@ +E003 \ No newline at end of file diff --git a/tests/golden/null/nullable_operand_error.sqt b/tests/golden/null/nullable_operand_error.sqt new file mode 100644 index 0000000..e11a41e --- /dev/null +++ b/tests/golden/null/nullable_operand_error.sqt @@ -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; +} diff --git a/tests/golden/string/concat.expected b/tests/golden/string/concat.expected new file mode 100644 index 0000000..e33a3b7 --- /dev/null +++ b/tests/golden/string/concat.expected @@ -0,0 +1,4 @@ +Merhaba Dünya +foobar +1 +1 diff --git a/tests/golden/string/concat.sqt b/tests/golden/string/concat.sqt new file mode 100644 index 0000000..76e7a53 --- /dev/null +++ b/tests/golden/string/concat.sqt @@ -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; +}