feat(array): GC-hazır nesne modeli + array runtime (ADR-020/022/023)

- 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 <noreply@anthropic.com>
This commit is contained in:
saqut 2026-06-20 16:18:23 +03:00
parent c5f62a3a9d
commit f1cb983b69
18 changed files with 542 additions and 32 deletions

View File

@ -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 04 → fibonacci).
- `docs/transkript-frontend-tasarim.md` — tasarım oturumu transkripti.
- `examples/fibonacci.sqt` — geçerli referans program.

23
TODO.md
View File

@ -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ç
ı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:**

154
docs/sonnet-handoff.md Normal file
View File

@ -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
> <noreply@anthropic.com>`; 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. |
ı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<Value> 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<Value> 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ıı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.

View File

@ -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();
}

View File

@ -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";

View File

@ -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 << "]";

View File

@ -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;
}

View File

@ -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).

View File

@ -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, ...]
};
// ============================================================================

View File

@ -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) {

View File

@ -44,4 +44,13 @@ public:
std::string toJson(int depth = 0) override;
};
class ArrayLiteralNode : public ExpressionNode {
public:
std::vector<ASTNode*> elements;
ArrayLiteralNode();
~ArrayLiteralNode() override { for (auto* e : elements) delete e; }
void log(int indent = 0) override;
std::string toJson(int depth = 0) override;
};
#endif

View File

@ -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) {

View File

@ -1,4 +1,5 @@
#include "vm/interpreter.hpp"
#include "vm/object.hpp"
#include <iostream>
#include <stdexcept>
@ -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);

View File

@ -16,6 +16,7 @@
#include <vector>
#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<CallFrame> callStack_;
std::vector<Value> globalSlots_;
Heap heap_;
// Host (C++) fonksiyon çağrısı — şu an sadece "print" destekli
void executeHostFunction(const std::string& name,

62
src/vm/object.hpp Normal file
View File

@ -0,0 +1,62 @@
#ifndef SAQUT_VM_OBJECT
#define SAQUT_VM_OBJECT
#include <vector>
// 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<Value> 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

View File

@ -4,59 +4,70 @@
#include <string>
#include <stdexcept>
// Ç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 "<array>";
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 "?";
}

View File

@ -0,0 +1,4 @@
99
1
0
2

View File

@ -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;
}