From f1cb983b6978421c67dbf1da561b266411226446 Mon Sep 17 00:00:00 2001 From: saqut Date: Sat, 20 Jun 2026 16:18:23 +0300 Subject: [PATCH] =?UTF-8?q?feat(array):=20GC-haz=C4=B1r=20nesne=20modeli?= =?UTF-8?q?=20+=20array=20runtime=20(ADR-020/022/023)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/vm/object.hpp: Object, ArrayObject, Heap (v1: toplama yok, mark-sweep kancaları hazır) - src/vm/value.hpp: ValueKind::Ref + Nil eklendi (ADR-020/021) - src/ir/instruction.hpp: ARRAY_NEW/GET/SET/LEN opcodes - src/ir/ir_generator: array literal + a[i] okuma/yazma codegen - src/parser: [1,2,3] array literal, int[] tip sözdizimi (Java/C# stili) - src/vm/interpreter: ARRAY_NEW/GET/SET/LEN, sınır kontrolü, referans semantiği - EQUAL_EQUAL/NOT_EQUAL: Ref türü için kimlik karşılaştırması (ADR-023) - type.hpp: Type::fromName "int[]" vb. array tipleri destekliyor - golden test: tests/golden/array/ref_semantics.sqt (referans semantiği, kimlik ==) - 20 golden test geçiyor Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 47 +++++-- TODO.md | 23 ++++ docs/sonnet-handoff.md | 154 ++++++++++++++++++++++ src/core/type.hpp | 5 + src/ir/instruction.hpp | 10 ++ src/ir/ir_function.cpp | 12 ++ src/ir/ir_generator.cpp | 66 +++++++++- src/ir/ir_generator.hpp | 4 + src/parser/ast_node.hpp | 2 + src/parser/nodes/expressions.cpp | 17 +++ src/parser/nodes/expressions.hpp | 9 ++ src/parser/parser.cpp | 34 +++++ src/vm/interpreter.cpp | 63 ++++++++- src/vm/interpreter.hpp | 2 + src/vm/object.hpp | 62 +++++++++ src/vm/value.hpp | 45 ++++--- tests/golden/array/ref_semantics.expected | 4 + tests/golden/array/ref_semantics.sqt | 15 +++ 18 files changed, 542 insertions(+), 32 deletions(-) create mode 100644 docs/sonnet-handoff.md create mode 100644 src/vm/object.hpp create mode 100644 tests/golden/array/ref_semantics.expected create mode 100644 tests/golden/array/ref_semantics.sqt diff --git a/CLAUDE.md b/CLAUDE.md index f02cba6..6cd3b15 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,18 +20,46 @@ git'te izlenir. ham hız değil). C'ye transpile ileride geçerli 2. backend. İleride makine kodu gerekirse libgccjit/LLVM'e bağlanılır (çok uzak). Bellek = host C++ heap; özel allocator yok. (ADR-015) -- **Dil kimliği:** prosedürel, C-ailesi sözdizimi, value semantics, zorunlu - class/main boilerplate yok. **Yok:** class/OOP, closure, generic, kullanıcı - pointer'ı (`*`/`&`), auto/tip çıkarımı, gizli int↔float (tek istisna sabit - folding). **Var:** struct, tipli fonksiyonlar, array (`int[]`). `interface` - **ertelendi** (reddedilmedi, ADR-018). +- **Dil kimliği:** prosedürel, C-ailesi sözdizimi, zorunlu class/main boilerplate + yok. **Semantik (ADR-020):** primitive (`int`/`float`/`bool`) = **değer**; + bileşik (`struct`/`array`/`string`) = **referans** (JS/Java/C# modeli). "Pointer + yok" = kullanıcıya `&`/`*` **sözdizimi** verilmez; derleyici/runtime içeride ve + çalışma zamanında referansı sonuna kadar kullanır. **Yok:** OOP, closure, + generic, auto/tip çıkarımı, gizli int↔float (tek istisna sabit folding). **Var:** + struct, tipli fonksiyonlar, array (`int[]`). `class`/`function` sözdizimsel + **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). +- **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 + GC-hazır** kur (header: tip+mark biti+liste; VM kök sayar; nesne çocuk + referansları sayar). v1: toplamasız (arena); v2: aynı model üstünde mark-sweep. + Asıl perf-katili GC kararı → basit tutarak de-risk edildi. +- **Eşitlik (ADR-023):** `==` primitive'de değer, referans (struct/array) **kimlik** + (aynı nesne). Derin/yapısal eşitlik **asla** `==`'e bağlanmaz → ayrı görünür + `deepEquals()` (PHP `==`/`===`/`clone` ailesi gibi). **String istisnası:** `==` + **içerik** olmalı (Java gotcha'sından kaçın) → string'i immutable değer-tipi + modelle (#40). `obj==obj`'i hata yapmak + kullanıcı-tanımlı eşitlik = uzak gelecek. +- **String (ADR-024):** **immutable değer-tipi, iç temsil UTF-8.** `s = s + "x"` + yeni string üretir; `==` içerik (ADR-023). Bayt/scalar/grapheme erişimi açıkça + 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. - **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ü + iterasyon tavanı (`maxFixpointRounds`, ADR-009). - **Literal/tip kuralı:** tamsayı literali bağlama-göre tiplenir (`float x = 1;` geçerli; `int y = 1.5;`→E003; değişken→değişken gizli dönüşüm yok). Döngüsel - by-value struct → E010. (ADR-010/011) + by-value struct → E010 (⚠️ ADR-020 ile revize: referansla tutulan struct alanı + artık döngü kurabilir, `Node next` meşru). (ADR-010/011) - **FFI seam:** kasıtlı "host fonksiyonu çağır" mekanizması (`callhost`); `print` ilk müşteri (ADR-016). Batteries = sınır/FFI problemi, "zlib'i yeniden yaz" değil; kripto asla elle yazılmaz (ADR-017). @@ -60,8 +88,11 @@ 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…019 (frontend, analiz/optimizasyon, - çalıştırma modeli, FFI, interface, bellek). +- `docs/adr-frontend-analiz.md` — ADR-006…024 (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**). +- `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). - `docs/transkript-frontend-tasarim.md` — tasarım oturumu transkripti. - `examples/fibonacci.sqt` — geçerli referans program. diff --git a/TODO.md b/TODO.md index fba602e..af7aeb7 100644 --- a/TODO.md +++ b/TODO.md @@ -5,6 +5,29 @@ tasarım noktalarını tutar. Her giriş hangi issue'ya bağlı olduğunu belirt --- +## ✅ TAMAMLANDI — GC-hazır nesne modeli + array runtime (2026-06-20) + +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/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) + +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ç + +Açık mimari borç: **#56** (döngüsel referans sızıntısı → mark-sweep GC v2). + +--- + ## #modül-scope — IRFunction.moduleId: Modül-düzeyi değişken izolasyonu **Etkilenen dosyalar:** diff --git a/docs/sonnet-handoff.md b/docs/sonnet-handoff.md new file mode 100644 index 0000000..698bb38 --- /dev/null +++ b/docs/sonnet-handoff.md @@ -0,0 +1,154 @@ +# Sonnet Uygulama Promptu — Bileşik Tipler Runtime'ı (ADR-020…024) + +> Bu dosya bir **devir teslim promptu**. Opus ile yapılan mimari oturumda 5 karar +> alındı (ADR-020…024). Bu plan o kararları **koda döker.** Kararları yeniden +> sorma — uygula. İletişim Türkçe; commit sonu `Co-Authored-By: Claude Opus 4.8 +> `; dal `0.1.0`. + +--- + +## 0. Nerede kaldık (bağlam) + +Bu oturumda kilitlenen mimari kararlar (tümü `docs/adr-frontend-analiz.md`): + +| ADR | Karar (özet) | +|---|---| +| **020** | Primitive (`int`/`float`/`bool`) = **değer**; bileşik (`struct`/`array`/`string`) = **referans** (JS/Java/C# modeli). "Pointer yok" = kullanıcıya `&`/`*` *sözdizimi* yok; runtime referansı kullanır. | +| **021** | Null güvenliği: varsayılan **non-null**; nullable açıkça `Type?`. Akış-duyarlı null analizi (compile-time, runtime sıfır). `a!` = runtime-kontrollü iddia. | +| **022** | GC: basit **taşımasız, stop-the-world, deterministik mark-sweep**. Nesne modeli **baştan GC-hazır** (header + kök sayımı + çocuk sayımı). v1: toplama YOK. **`shared_ptr`'ı kalıcı sahiplik modeli YAPMA.** | +| **023** | `==`: primitive değer; referans (struct/array) **kimlik** (aynı nesne); string **içerik**. Derin eşitlik asla `==`'e bağlanmaz → ayrı `deepEquals()`. | +| **024** | String = **immutable değer-tipi, iç temsil UTF-8**. `s+"x"` yeni string üretir; `==` içerik. | + +Açık mimari borç: **#56** (döngüsel referans → mark-sweep GC v2). Şimdilik toplama yok, bilinçli. + +**Mevcut kod durumu:** +- int-only pipeline uçtan uca çalışıyor (`examples/fibonacci.sqt`). +- `src/vm/value.hpp` → `Value` = `{ ValueKind kind; int intValue; std::string stringValue; }`. String **inline** (immutable olduğu için yeterli — taşıma zorunlu değil). +- `src/ir/` → 3-adresli IR, slot tabanlı. `instruction.hpp`, `ir_generator.cpp`, `ir_program.hpp`. +- `src/vm/interpreter.cpp` → bytecode yorumlayıcı döngü. `call_frame.hpp` çağrı çerçevesi. +- struct/array: parser + semantik **var**, IR/VM codegen **YOK**. + +--- + +## 1. İlk görev — GC-hazır nesne modeli + ARRAY runtime (dikey dilim) + +**Neden array, string değil:** string zaten immutable-değer olarak inline çalışıyor; +asıl kararları (referans semantiği + GC-hazır nesne modeli + kimlik eşitliği) +**doğrulayan** ilk gerçek referans-tipi array'dir. Bu görev, struct'ın da oturacağı +temeli atar. + +### KAPSAM DIŞI (bu görevde YAPMA — sonraki görevler) +- ❌ Gerçek çöp toplama (mark-sweep). ADR-022 v1 = toplama yok. Sadece header + + all-objects listesi + kök-sayımı *kancasını* kur. +- ❌ Tam null akış-analizi (ADR-021). `int[]?` parse edilebilir ama flow-narrowing + bu görevde değil. +- ❌ struct codegen, string UTF-8 cilası, builder. Ayrı görevler (Bölüm 2). +- ❌ `shared_ptr` ile nesne sahipliği. Düz `Object*` + intrusive liste. + +### Adım 1.1 — Nesne modeli temeli (`src/vm/object.hpp` veya benzeri) +```cpp +enum class ObjectType { Array /*, Struct, String (ileride) */ }; + +struct Object { + ObjectType type; + bool marked = false; // mark-sweep için (v2); şimdilik kullanılmaz + Object* next = nullptr; // intrusive "tüm nesneler" listesi (mark-sweep tarayışı için) +}; +``` +- Bir **Heap/allocator**: `Object* allocate(...)` her yeni nesneyi `next` zincirine + ekler. **Free yok** (v1). Yıkıcıda/process exit'te toptan bırak. +- **Kök sayımı kancası:** VM'in operand stack + frame local'leri + global slot'lar + üstünden referansları gezebileceği bir yol bırak (şimdilik imza/TODO yeterli; içini + doldurmak v2). `// TODO(#56): mark-sweep kök taraması buradan` ile işaretle. + +### Adım 1.2 — `Value`'yu referans taşıyacak şekilde genişlet (`src/vm/value.hpp`) +- `ValueKind`'a `Ref` ekle (array/struct nesnelerine `Object*`). +- `Value`'ya `Object* ref = nullptr;` alanı + `static Value fromRef(Object*)`. +- `Nil` kind ekle (ADR-021 null: `Type?` null değeri ve referans varsayılanı). +- `isTruthy`/`toString`/`typeName`'i yeni kind'lar için genişlet. +- String inline kalsın (`ValueKind::String`), dokunma. + +### Adım 1.3 — ArrayObject +```cpp +struct ArrayObject : Object { // type = ObjectType::Array + std::vector elements; // homojen (int[] → hepsi Int) +}; +``` +- Array literali (`[1,2,3]` — **parser'ın mevcut sözdizimini doğrula**, Bölüm 4'e bak) + → ArrayObject tahsis, `elements` doldur, `Value::fromRef` ile slot'a koy. + +### Adım 1.4 — IR opcode'ları + IR üreteci +- `ARRAY_NEW` (literal/boyuttan array oluştur), `ARRAY_GET dest, arr, idx`, + `ARRAY_SET arr, idx, val`, `ARRAY_LEN dest, arr`. (`instruction.hpp` + üreteç.) +- **Referans semantiği:** array bir fonksiyona geçince/atanınca `Value` (içindeki + `Object*`) **kopyalanır ama nesne paylaşılır** → `func(arr)` içindeki `ARRAY_SET` + çağıranı etkiler. (Bu, kararın doğrulandığı kritik testtir.) +- **`==` (ADR-023):** array'de **kimlik** → iki `Value`'nun `ref`'i aynı `Object*` mı? + (İçerik DEĞİL.) Mevcut EQ opcode'unu tip-bazlı dallandır veya `REF_EQ` ekle. + +### Adım 1.5 — Sınır kontrolü (kafes felsefesi) +- `ARRAY_GET`/`ARRAY_SET` index sınır dışıysa → **temiz runtime hatası** (sessiz UB + değil). saQut "cage" — koruma şart. + +### Doğrulama (golden test — `tests/` veya `examples/`) +```c +func degistir(int[] a) { + a[0] = 99; +} +func main() { + int[] x = [1, 2, 3]; + degistir(x); + print(x[0]); // 99 ← referans semantiği (ADR-020) + int[] y = x; + print(y == x); // true ← kimlik (aynı nesne, ADR-023) + int[] z = [1, 2, 3]; + print(z == x); // false ← içerikçe aynı ama farklı nesne + print(x[1]); // 2 + // x[5] → runtime sınır hatası +} +``` +`saqut run` ile beklenen çıktı doğrulanmalı. `saqut ir` çıktısı yeni opcode'ları göstermeli. + +--- + +## 2. Sonraki görevler (sıralı — ilk görev bitince) + +1. **Struct runtime.** Alanlar (isimli), `StructObject : Object { std::vector fields; }`. + `struct Node { Node next; }` artık **meşru** (referans → sonlu boyut). **E010 + revizyonu:** referansla tutulan struct alanı için döngü artık hata DEĞİL (ADR-020). + Bu görev #56'yı *canlı* hale getirir (döngü kurulabilir, henüz toplanmaz). +2. **String cilası (ADR-024).** İçerik `==`'i doğrula; UTF-8 çok-baytlı pass-through + (`"şğü"` 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. Uyman gereken kararlar (sorma, uygula) +- Referans semantiği: bileşik = paylaşılan nesne; primitive = kopya (ADR-020). +- Array/struct `==` = **kimlik**; string `==` = **içerik** (ADR-023). +- Nesne modeli GC-hazır ama v1 toplama YOK (ADR-022); `shared_ptr` sahiplik modeli kurma. +- Sınır kontrolü + temiz runtime hataları (kafes felsefesi). +- Erken soyutlama yok: önce çalışan dikey dilim, framework sonra (CLAUDE.md ilkesi). + +## 4. Bloklamayan açık noktalar (ilk göreve engel değil — gerekirse Opus'a sor) +- **Array literal sözdizimi:** parser'da `[1,2,3]` mi `{0,1,2}` mi kabul ediliyor? + Mevcut parser'ı **doğrula**; tutarsızsa `[...]` tercih (tip `int[]`). Karar gerekirse işaretle. +- Array sabit-boyut mu büyüyebilir mi: readme "dinamik" der; ilk görevde literal+indeks + yeter, büyütme API'si (`push`/`len`) sonra. +- `int[]` non-null varsayılan (ADR-021) → `int[] a;` init zorunlu; nullable `int[]?`. +- #42 (cast modeli) ve #41 (stdlib politikası) henüz karara bağlanmadı — ilk görevi + bloklamaz; sıraları gelince Opus ile. + +--- + +## 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 +kontrolüyle uçtan uca çalıştır; struct ve null-analizi sonraki görevler. diff --git a/src/core/type.hpp b/src/core/type.hpp index d8e07af..e7e79f7 100644 --- a/src/core/type.hpp +++ b/src/core/type.hpp @@ -188,6 +188,11 @@ struct Type { if (n == "string") return String(); if (n == "bool") return Bool(); if (n == "void") return Void(); + // "int[]", "float[]" vb. — suffix [] ile dizi tipi + if (n.size() > 2 && n.substr(n.size() - 2) == "[]") { + Type elem = fromName(n.substr(0, n.size() - 2)); + if (!elem.isError()) return array(elem); + } return error(); } diff --git a/src/ir/instruction.hpp b/src/ir/instruction.hpp index c59253a..4f4da68 100644 --- a/src/ir/instruction.hpp +++ b/src/ir/instruction.hpp @@ -82,6 +82,12 @@ enum class Opcode { RETURN, // Bu frame'i kapat, slots[src]'yi caller'a ilet. + // --- Array (ADR-020: referans semantiği) --- + ARRAY_NEW, // slots[dest] = yeni ArrayObject(intValue eleman kapasitesi) + ARRAY_GET, // slots[dest] = slots[left][slots[right]] — sınır kontrolü + ARRAY_SET, // slots[dest][slots[left]] = slots[right] — sınır kontrolü (dest=dizi, left=idx, right=değer) + ARRAY_LEN, // slots[dest] = slots[src].uzunluk() + // --- Modül-düzeyi değişken erişimi --- // "Global" değil: her değişken kendi dosyasına (modülüne) aittir. // Başka modüller bu alana doğrudan erişemez; yalnızca export/import ile ulaşabilir. @@ -111,6 +117,10 @@ inline const char* opcodeName(Opcode op) { case Opcode::SHL: return "SHL"; case Opcode::SHR: return "SHR"; case Opcode::BNOT: return "BNOT"; + case Opcode::ARRAY_NEW: return "ARRAY_NEW"; + case Opcode::ARRAY_GET: return "ARRAY_GET"; + case Opcode::ARRAY_SET: return "ARRAY_SET"; + case Opcode::ARRAY_LEN: return "ARRAY_LEN"; case Opcode::LOAD_GLOBAL: return "LOAD_GLOBAL"; case Opcode::STORE_GLOBAL: return "STORE_GLOBAL"; case Opcode::LESS: return "LESS"; diff --git a/src/ir/ir_function.cpp b/src/ir/ir_function.cpp index 481f5e2..0ecb8ad 100644 --- a/src/ir/ir_function.cpp +++ b/src/ir/ir_function.cpp @@ -121,6 +121,18 @@ void IRFunction::dump() const { } else if (ins.opcode == Opcode::BNOT) { std::cout << slot(ins.dest) << " = ~" << slot(ins.src); + } else if (ins.opcode == Opcode::ARRAY_NEW) { + std::cout << slot(ins.dest) << " = array[" << ins.intValue << "]"; + + } else if (ins.opcode == Opcode::ARRAY_GET) { + std::cout << slot(ins.dest) << " = " << slot(ins.left) << "[" << slot(ins.right) << "]"; + + } else if (ins.opcode == Opcode::ARRAY_SET) { + std::cout << slot(ins.dest) << "[" << slot(ins.left) << "] = " << slot(ins.right); + + } else if (ins.opcode == Opcode::ARRAY_LEN) { + std::cout << slot(ins.dest) << " = len(" << slot(ins.src) << ")"; + } else if (ins.opcode == Opcode::LOAD_GLOBAL) { std::cout << slot(ins.dest) << " = global[" << ins.intValue << "]"; diff --git a/src/ir/ir_generator.cpp b/src/ir/ir_generator.cpp index b98593d..2f0e0bc 100644 --- a/src/ir/ir_generator.cpp +++ b/src/ir/ir_generator.cpp @@ -376,9 +376,19 @@ int IRGenerator::generateExpression(ASTNode* node) { case ASTKind::BinaryExpression: { auto* bin = (BinaryExpressionNode*)node; - // Atama operatörleri: x = expr + // Atama operatörleri: x = expr ve a[i] = expr if (bin->Operator == TokenType::EQUAL) { int rhsSlot = generateExpression(bin->Right); + + // a[i] = val → ARRAY_SET + if (bin->Left && bin->Left->kind == ASTKind::IndexExpression) { + auto* idx = (IndexExpressionNode*)bin->Left; + int arrSlot = generateExpression(idx->object); + int idxSlot = generateExpression(idx->index); + emitArraySet(arrSlot, idxSlot, rhsSlot); + return rhsSlot; + } + auto* lhsId = (IdentifierNode*)bin->Left; std::string varName = lhsId->parserToken.token->token; @@ -579,6 +589,30 @@ int IRGenerator::generateExpression(ASTNode* node) { return resultSlot; // artırmadan önceki değer } + // ── Array literali: [1, 2, 3] ───────────────────────────────────────── + case ASTKind::ArrayLiteral: { + auto* al = (ArrayLiteralNode*)node; + int arrSlot = freshSlot(); + emitArrayNew(arrSlot, (int)al->elements.size()); + for (int i = 0; i < (int)al->elements.size(); i++) { + int idxSlot = freshSlot(); + emitLoadConst(idxSlot, i); + int valSlot = generateExpression(al->elements[i]); + emitArraySet(arrSlot, idxSlot, valSlot); + } + return arrSlot; + } + + // ── Index erişimi okuma: a[i] ───────────────────────────────────────── + case ASTKind::IndexExpression: { + auto* idx = (IndexExpressionNode*)node; + int arrSlot = generateExpression(idx->object); + int idxSlot = generateExpression(idx->index); + int destSlot = freshSlot(); + emitArrayGet(destSlot, arrSlot, idxSlot); + return destSlot; + } + default: // Bilinmeyen ifade türü return freshSlot(); // boş slot (0 değeriyle) @@ -651,6 +685,36 @@ void IRGenerator::emitStoreGlobal(int srcSlot, int globalIndex) { currentFunction_->instructions.push_back(std::move(ins)); } +void IRGenerator::emitArrayNew(int destSlot, int capacity) { + Instruction ins(Opcode::ARRAY_NEW); + ins.dest = destSlot; + ins.intValue = capacity; + currentFunction_->instructions.push_back(std::move(ins)); +} + +void IRGenerator::emitArrayGet(int destSlot, int arrSlot, int idxSlot) { + Instruction ins(Opcode::ARRAY_GET); + ins.dest = destSlot; + ins.left = arrSlot; + ins.right = idxSlot; + currentFunction_->instructions.push_back(std::move(ins)); +} + +void IRGenerator::emitArraySet(int arrSlot, int idxSlot, int valSlot) { + Instruction ins(Opcode::ARRAY_SET); + ins.dest = arrSlot; + ins.left = idxSlot; + ins.right = valSlot; + currentFunction_->instructions.push_back(std::move(ins)); +} + +void IRGenerator::emitArrayLen(int destSlot, int arrSlot) { + Instruction ins(Opcode::ARRAY_LEN); + ins.dest = destSlot; + ins.src = arrSlot; + currentFunction_->instructions.push_back(std::move(ins)); +} + bool IRGenerator::isGlobal(const std::string& name) const { return nameToGlobal_.count(name) > 0; } diff --git a/src/ir/ir_generator.hpp b/src/ir/ir_generator.hpp index 4f24ce1..a6e1034 100644 --- a/src/ir/ir_generator.hpp +++ b/src/ir/ir_generator.hpp @@ -58,6 +58,10 @@ private: void emitLoadSlot(int destSlot, int srcSlot); void emitLoadGlobal(int destSlot, int globalIndex); void emitStoreGlobal(int srcSlot, int globalIndex); + void emitArrayNew(int destSlot, int capacity); + void emitArrayGet(int destSlot, int arrSlot, int idxSlot); + void emitArraySet(int arrSlot, int idxSlot, int valSlot); + void emitArrayLen(int destSlot, int arrSlot); void emitBinaryOp(Opcode op, int destSlot, int leftSlot, int rightSlot); void emitReturn(int srcSlot); // Koşulsuz atlama yazar; instruction indeksini döndürür (backpatch için). diff --git a/src/parser/ast_node.hpp b/src/parser/ast_node.hpp index c3091a6..75f29d1 100644 --- a/src/parser/ast_node.hpp +++ b/src/parser/ast_node.hpp @@ -107,6 +107,8 @@ enum class ASTKind { // children: [object], [member] IndexExpression, // Dizi/indeks erişimi: a[i]. // children: [object], [index] + ArrayLiteral, // Dizi literali: [1, 2, 3]. + // children: [element0, element1, ...] }; // ============================================================================ diff --git a/src/parser/nodes/expressions.cpp b/src/parser/nodes/expressions.cpp index 9c68517..5b56d7b 100644 --- a/src/parser/nodes/expressions.cpp +++ b/src/parser/nodes/expressions.cpp @@ -53,6 +53,23 @@ std::string MemberAccessNode::toJson(int depth) { return obj.str(); } +// ArrayLiteralNode +ArrayLiteralNode::ArrayLiteralNode() { kind = ASTKind::ArrayLiteral; } +void ArrayLiteralNode::log(int indent) { + std::cout << jsonIndent(indent) << "ArrayLiteral [" << elements.size() << " eleman]\n"; + for (auto* e : elements) e->log(indent + 1); +} +std::string ArrayLiteralNode::toJson(int depth) { + JsonObject obj(depth); + obj.add("kind", "ArrayLiteral"); + obj.addArray("elements", [&]() { + for (auto* e : elements) obj.addItem(e->toJson(depth + 2)); + }); + obj.addRaw("resolvedType", resolvedTypeJson()); + obj.addRaw("location", loc.toJson()); + return obj.str(); +} + // IndexExpressionNode IndexExpressionNode::IndexExpressionNode() { kind = ASTKind::IndexExpression; } void IndexExpressionNode::log(int indent) { diff --git a/src/parser/nodes/expressions.hpp b/src/parser/nodes/expressions.hpp index 446f091..8cdad0d 100644 --- a/src/parser/nodes/expressions.hpp +++ b/src/parser/nodes/expressions.hpp @@ -44,4 +44,13 @@ public: std::string toJson(int depth = 0) override; }; +class ArrayLiteralNode : public ExpressionNode { +public: + std::vector elements; + ArrayLiteralNode(); + ~ArrayLiteralNode() override { for (auto* e : elements) delete e; } + 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 714ef48..d9a19f9 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -150,6 +150,23 @@ ASTNode* Parser::parseNullDenotation() { return expr; } + // Array literal: [expr, expr, ...] + if (ct.type == TokenType::LBRACKET) { + nextToken(); + ArrayLiteralNode* arr = new ArrayLiteralNode(); + arr->loc = ct.token ? ct.token->loc : SourceLocation{}; + if (currentToken().type != TokenType::RBRACKET) { + arr->elements.push_back(parseExpression(0)); + while (currentToken().type == TokenType::COMMA) { + nextToken(); + arr->elements.push_back(parseExpression(0)); + } + } + if (currentToken().type == TokenType::RBRACKET) + nextToken(); + return arr; + } + if (ct.is({ TokenType::PLUS_PLUS, TokenType::MINUS_MINUS, TokenType::PLUS, TokenType::MINUS, @@ -315,6 +332,13 @@ ASTNode* Parser::parseFunctionDecl() { if (!isTypeKw || !typeTok.token) break; std::string paramType = typeTok.token->token; nextToken(); + // int[] a — tip sonrasında [] varsa array tipi + if (currentToken().type == TokenType::LBRACKET) { + nextToken(); + if (currentToken().type == TokenType::RBRACKET) + nextToken(); + paramType += "[]"; + } if (currentToken().type != TokenType::IDENTIFIER || !currentToken().token) break; VariableDeclNode* param = new VariableDeclNode(); param->loc = currentToken().token->loc; @@ -364,6 +388,14 @@ ASTNode* Parser::parseVariableDecl() { vd->varType = currentToken().token->token; nextToken(); + // Java/C# stili: int[] x — tip adından hemen sonra [] gelir + if (currentToken().type == TokenType::LBRACKET) { + nextToken(); + if (currentToken().type == TokenType::RBRACKET) + nextToken(); + vd->varType += "[]"; + } + if (currentToken().type != TokenType::IDENTIFIER) { std::cerr << "Parser hatası: değişken ismi bekleniyor\n"; return vd; @@ -372,6 +404,7 @@ ASTNode* Parser::parseVariableDecl() { vd->name = currentToken().token->token; nextToken(); + // C stili: int x[] — geriye dönük uyumluluk (postfix []) if (currentToken().type == TokenType::LBRACKET) { nextToken(); while (currentToken().type != TokenType::RBRACKET && @@ -380,6 +413,7 @@ ASTNode* Parser::parseVariableDecl() { nextToken(); if (currentToken().type == TokenType::RBRACKET) nextToken(); + if (vd->varType.back() != ']') vd->varType += "[]"; } if (currentToken().type == TokenType::EQUAL) { diff --git a/src/vm/interpreter.cpp b/src/vm/interpreter.cpp index a55bf00..97b9e8b 100644 --- a/src/vm/interpreter.cpp +++ b/src/vm/interpreter.cpp @@ -1,4 +1,5 @@ #include "vm/interpreter.hpp" +#include "vm/object.hpp" #include #include @@ -121,17 +122,25 @@ int Interpreter::run() { break; case Opcode::EQUAL_EQUAL: { auto& lv = frame.slots[instr.left]; auto& rv = frame.slots[instr.right]; - int r = (lv.kind == ValueKind::String) - ? (lv.stringValue == rv.stringValue ? 1 : 0) - : (lv.intValue == rv.intValue ? 1 : 0); + int r; + if (lv.kind == ValueKind::Ref || rv.kind == ValueKind::Ref) + r = (lv.ref == rv.ref ? 1 : 0); // ADR-023: array/struct kimlik karşılaştırması + else if (lv.kind == ValueKind::String) + r = (lv.stringValue == rv.stringValue ? 1 : 0); + else + r = (lv.intValue == rv.intValue ? 1 : 0); frame.slots[instr.dest] = Value::fromInt(r); break; } case Opcode::NOT_EQUAL: { auto& lv = frame.slots[instr.left]; auto& rv = frame.slots[instr.right]; - int r = (lv.kind == ValueKind::String) - ? (lv.stringValue != rv.stringValue ? 1 : 0) - : (lv.intValue != rv.intValue ? 1 : 0); + int r; + if (lv.kind == ValueKind::Ref || rv.kind == ValueKind::Ref) + r = (lv.ref != rv.ref ? 1 : 0); + else if (lv.kind == ValueKind::String) + r = (lv.stringValue != rv.stringValue ? 1 : 0); + else + r = (lv.intValue != rv.intValue ? 1 : 0); frame.slots[instr.dest] = Value::fromInt(r); break; } @@ -184,6 +193,48 @@ int Interpreter::run() { continue; } + // ── Array (ADR-020: referans semantiği) ─────────────────────────── + case Opcode::ARRAY_NEW: { + ArrayObject* arr = heap_.allocArray(instr.intValue); + arr->elements.resize(instr.intValue, Value::fromInt(0)); + frame.slots[instr.dest] = Value::fromRef(arr); + break; + } + 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"); + 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()) + ")"); + 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"); + 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()) + ")"); + arr->elements[idx] = frame.slots[instr.right]; + break; + } + case Opcode::ARRAY_LEN: { + Value& arrVal = frame.slots[instr.src]; + if (arrVal.kind != ValueKind::Ref || !arrVal.ref) + throw std::runtime_error("Çalışma hatası: dizi değil"); + auto* arr = (ArrayObject*)arrVal.ref; + frame.slots[instr.dest] = Value::fromInt((int)arr->elements.size()); + break; + } + // ── FFI ─────────────────────────────────────────────────────────── case Opcode::CALLHOST: executeHostFunction(instr.functionName, frame.slots, instr.argSlots); diff --git a/src/vm/interpreter.hpp b/src/vm/interpreter.hpp index d0748d3..6b02107 100644 --- a/src/vm/interpreter.hpp +++ b/src/vm/interpreter.hpp @@ -16,6 +16,7 @@ #include #include "ir/ir_program.hpp" #include "vm/call_frame.hpp" +#include "vm/object.hpp" class Interpreter { public: @@ -29,6 +30,7 @@ private: IRProgram& program_; std::vector callStack_; std::vector globalSlots_; + Heap heap_; // Host (C++) fonksiyon çağrısı — şu an sadece "print" destekli void executeHostFunction(const std::string& name, diff --git a/src/vm/object.hpp b/src/vm/object.hpp new file mode 100644 index 0000000..13517a6 --- /dev/null +++ b/src/vm/object.hpp @@ -0,0 +1,62 @@ +#ifndef SAQUT_VM_OBJECT +#define SAQUT_VM_OBJECT + +#include + +// ADR-022: GC-hazır nesne modeli (v1: toplama yok, sadece header + liste kancası). +// Her heap nesnesi bu yapıdan türer. +// marked + next: mark-sweep için hazır; v1'de kullanılmaz. +// TODO(#56): mark-sweep v2 — Heap::collect() bu header'ı kullanacak. + +enum class ObjectType { Array /*, Struct, String (ileride) */ }; + +struct Object { + ObjectType type; + bool marked = false; // mark-sweep için (v2); şimdilik kullanılmaz + Object* next = nullptr; // "tüm nesneler" intrusive listesi +}; + +// Forward declare — Value, Object*'ı taşır; Object, Value içerir. +// Gerçek tanım value.hpp'den sonra gelir; burada sadece forward. +struct Value; + +struct ArrayObject : Object { + std::vector elements; + explicit ArrayObject(int capacity = 0) { + type = ObjectType::Array; + if (capacity > 0) elements.reserve(capacity); + } +}; + +// ── Heap ───────────────────────────────────────────────────────────────────── +// Tüm nesneleri intrusive listede tutar. v1'de serbest bırakma yok. +// TODO(#56): mark-sweep v2 — collect() kök taraması yapacak, ölü nesneleri silecek. +struct Heap { + Object* head = nullptr; + int allocCount = 0; + + ArrayObject* allocArray(int capacity = 0) { + auto* obj = new ArrayObject(capacity); + obj->next = head; + head = obj; + allocCount++; + return obj; + // TODO(#56): mark-sweep kök taraması buradan — GC eşiği aşılınca tetikle + } + + // v1: process exit'te OS toplar; yıkıcı tüm nesneleri siler. + ~Heap() { + Object* cur = head; + while (cur) { + Object* nxt = cur->next; + delete cur; + cur = nxt; + } + } + + Heap() = default; + Heap(const Heap&) = delete; + Heap& operator=(const Heap&) = delete; +}; + +#endif // SAQUT_VM_OBJECT diff --git a/src/vm/value.hpp b/src/vm/value.hpp index 7ee8a33..e0d4822 100644 --- a/src/vm/value.hpp +++ b/src/vm/value.hpp @@ -4,59 +4,70 @@ #include #include -// Çalışma zamanı değer tipi. -// -// Bool ayrı bir kind değil — boolean sonuçlar int olarak saklanır -// (0 = yanlış, sıfır-dışı = doğru; C geleneği, JIF_FALSE buna dayanır). +// Forward — Object tam tanımı object.hpp'de; Value onu pointer olarak taşır. +struct Object; + +// ADR-020: Primitive (int/bool) = değer; bileşik (array/struct/string) = referans. +// ADR-021: Nil = nullable referansların null değeri (Type? için). +// Bool ayrı kind değil — boolean sonuçlar int olarak saklanır (0=yanlış, sıfır-dışı=doğru). // Float henüz implement edilmedi — IR'de float opcode yok. enum class ValueKind { Int, String, - // Float, // TODO: float literal + aritmetik eklenince + Ref, // ADR-020: array/struct nesnesine Object* referansı + Nil, // ADR-021: nullable referansın null değeri + // Float, // TODO(#44) }; struct Value { ValueKind kind = ValueKind::Int; int intValue = 0; - std::string stringValue; // yalnızca kind == String için geçerli + std::string stringValue; // yalnızca kind == String + Object* ref = nullptr; // yalnızca kind == Ref static Value fromInt(int n) { - Value v; - v.kind = ValueKind::Int; - v.intValue = n; - return v; + Value v; v.kind = ValueKind::Int; v.intValue = n; return v; } static Value fromString(std::string s) { - Value v; - v.kind = ValueKind::String; - v.stringValue = std::move(s); - return v; + Value v; v.kind = ValueKind::String; v.stringValue = std::move(s); return v; } - // JIF_FALSE için: int 0 = yanlış, boş string = yanlış, diğer = doğru + static Value fromRef(Object* obj) { + Value v; v.kind = ValueKind::Ref; v.ref = obj; return v; + } + + static Value nil() { + Value v; v.kind = ValueKind::Nil; return v; + } + + // JIF_FALSE: int 0 / boş string / nil = yanlış; Ref her zaman doğru bool isTruthy() const { switch (kind) { case ValueKind::Int: return intValue != 0; case ValueKind::String: return !stringValue.empty(); + case ValueKind::Ref: return ref != nullptr; + case ValueKind::Nil: return false; } return false; } - // Yazdırma ve hata mesajları için okunabilir temsil std::string toString() const { switch (kind) { case ValueKind::Int: return std::to_string(intValue); case ValueKind::String: return stringValue; + case ValueKind::Ref: return ""; + case ValueKind::Nil: return "nil"; } return "?"; } - // Tip adı — hata mesajları için std::string typeName() const { switch (kind) { case ValueKind::Int: return "int"; case ValueKind::String: return "string"; + case ValueKind::Ref: return "array"; + case ValueKind::Nil: return "nil"; } return "?"; } diff --git a/tests/golden/array/ref_semantics.expected b/tests/golden/array/ref_semantics.expected new file mode 100644 index 0000000..d4789db --- /dev/null +++ b/tests/golden/array/ref_semantics.expected @@ -0,0 +1,4 @@ +99 +1 +0 +2 diff --git a/tests/golden/array/ref_semantics.sqt b/tests/golden/array/ref_semantics.sqt new file mode 100644 index 0000000..d14d5dd --- /dev/null +++ b/tests/golden/array/ref_semantics.sqt @@ -0,0 +1,15 @@ +void degistir(int[] a) { + a[0] = 99; +} + +int main() { + int[] x = [1, 2, 3]; + degistir(x); + print(x[0]); + int[] y = x; + print(y == x); + int[] z = [1, 2, 3]; + print(z == x); + print(x[1]); + return 0; +}