feat(struct+null): struct runtime + ValueKind::Null + E010 ADR-020 revizyonu

- value.hpp: ValueKind::Nil → ValueKind::Null (saQut'ta null anahtar sözcüğü)
- object.hpp: StructObject : Object eklendi (alanlar sıralı Value[] olarak)
- instruction.hpp: STRUCT_NEW, FIELD_GET, FIELD_SET opcodes
- ir_generator: struct layout haritası (sembol tablosundan), VariableDecl struct init,
  MemberAccess okuma/yazma codegen
- interpreter: STRUCT_NEW/FIELD_GET/FIELD_SET + referans semantiği
- ir_function: STRUCT_NEW/FIELD_GET/FIELD_SET dump
- symbol_table: structLayouts haritası + getFieldIndex/getFieldType yardımcıları
- symbol_collector: StructDecl her zaman structFields_ girişi açar; structLayouts doldurur
- E010 devre dışı — ADR-020: struct alanları referans semantiği taşır, by-value döngü yok
- type_checker: MemberAccess struct alan tipi çözümlendi; IndexExpression array eleman tipi
- parser: parseStatement'a "TypeName VarName" → parseVariableDecl desteği (struct değişkeni)
- golden test: tests/golden/struct/basic.sqt (21 test geçiyor)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
saqut 2026-06-20 16:33:51 +03:00
parent f1cb983b69
commit 435c8bcb96
13 changed files with 238 additions and 68 deletions

View File

@ -82,6 +82,11 @@ enum class Opcode {
RETURN, // Bu frame'i kapat, slots[src]'yi caller'a ilet.
// --- Struct (ADR-020: referans semantiği) ---
STRUCT_NEW, // slots[dest] = yeni StructObject(intValue alan sayısı); functionName = struct tipi adı
FIELD_GET, // slots[dest] = slots[src].fields[intValue] (src=nesne, intValue=alan indeksi)
FIELD_SET, // slots[dest].fields[intValue] = slots[right] (dest=nesne, intValue=alan indeksi, right=değer)
// --- 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ü
@ -117,6 +122,9 @@ inline const char* opcodeName(Opcode op) {
case Opcode::SHL: return "SHL";
case Opcode::SHR: return "SHR";
case Opcode::BNOT: return "BNOT";
case Opcode::STRUCT_NEW: return "STRUCT_NEW";
case Opcode::FIELD_GET: return "FIELD_GET";
case Opcode::FIELD_SET: return "FIELD_SET";
case Opcode::ARRAY_NEW: return "ARRAY_NEW";
case Opcode::ARRAY_GET: return "ARRAY_GET";
case Opcode::ARRAY_SET: return "ARRAY_SET";

View File

@ -121,6 +121,15 @@ void IRFunction::dump() const {
} else if (ins.opcode == Opcode::BNOT) {
std::cout << slot(ins.dest) << " = ~" << slot(ins.src);
} else if (ins.opcode == Opcode::STRUCT_NEW) {
std::cout << slot(ins.dest) << " = struct<" << ins.functionName << ">[" << ins.intValue << " alan]";
} else if (ins.opcode == Opcode::FIELD_GET) {
std::cout << slot(ins.dest) << " = " << slot(ins.src) << "." << ins.intValue;
} else if (ins.opcode == Opcode::FIELD_SET) {
std::cout << slot(ins.dest) << "." << ins.intValue << " = " << slot(ins.right);
} else if (ins.opcode == Opcode::ARRAY_NEW) {
std::cout << slot(ins.dest) << " = array[" << ins.intValue << "]";

View File

@ -14,9 +14,12 @@
// generate — Ana giriş noktası
// ─────────────────────────────────────────────────────────────────────────────
IRProgram IRGenerator::generate(ASTNode* programNode, SymbolTable& /*symbolTable*/) {
IRProgram IRGenerator::generate(ASTNode* programNode, SymbolTable& symbolTable) {
IRProgram program;
// 0. Geçiş: struct layout haritasını sembol tablosundan al
structLayouts_ = symbolTable.structLayouts;
// 1. Geçiş: modül-düzeyi VariableDecl'leri topla ve kayıt et
// "Global" değil — bu dosyanın (modülün) kendi değişkenleri.
std::vector<VariableDeclNode*> globalVars;
@ -97,7 +100,7 @@ void IRGenerator::generateStatement(ASTNode* node) {
break;
}
// ── Değişken bildirimi: int x = <ifade> ──────────────────────────────
// ── Değişken bildirimi: int x = <ifade> / Point p; ────────────────
case ASTKind::VariableDecl: {
auto* vd = (VariableDeclNode*)node;
@ -106,14 +109,12 @@ void IRGenerator::generateStatement(ASTNode* node) {
registerVariable(vd->name, varSlot);
if (vd->initExpr) {
// Başlatma ifadesini üret, sonucu bir slotta al
int initSlot = generateExpression(vd->initExpr);
if (initSlot != varSlot) {
// Sonuç başka bir slotta, değişkenin slotuna kopyala
emitLoadSlot(varSlot, initSlot);
}
// initSlot == varSlot: LOAD_CONST doğrudan varSlot'a yazıldı, kopya gerekmez
if (initSlot != varSlot) emitLoadSlot(varSlot, initSlot);
} else if (structLayouts_.count(vd->varType)) {
// Struct değişkeni: init ifadesi yoksa boş StructObject oluştur
int fc = getStructFieldCount(vd->varType);
emitStructNew(varSlot, vd->varType, fc);
}
// Sibling VariableDecl'ler: int a, b; → children'da diğer VariableDecl'ler
@ -389,6 +390,18 @@ int IRGenerator::generateExpression(ASTNode* node) {
return rhsSlot;
}
// p.field = val → FIELD_SET
if (bin->Left && bin->Left->kind == ASTKind::MemberAccess) {
auto* ma = (MemberAccessNode*)bin->Left;
int objSlot = generateExpression(ma->object);
std::string structName;
if (auto* exprObj = dynamic_cast<ExpressionNode*>(ma->object))
structName = exprObj->resolvedType.structName;
int idx2 = getStructFieldIndex(structName, ma->member);
if (idx2 >= 0) emitFieldSet(objSlot, idx2, rhsSlot);
return rhsSlot;
}
auto* lhsId = (IdentifierNode*)bin->Left;
std::string varName = lhsId->parserToken.token->token;
@ -589,7 +602,19 @@ int IRGenerator::generateExpression(ASTNode* node) {
return resultSlot; // artırmadan önceki değer
}
// ── Array literali: [1, 2, 3] ─────────────────────────────────────────
// ── Üye erişimi okuma: p.x ───────────────────────────────────────────
case ASTKind::MemberAccess: {
auto* ma = (MemberAccessNode*)node;
int objSlot = generateExpression(ma->object);
int destSlot = freshSlot();
// Nesnenin struct adını resolvedType üstünden al (tip denetleyici yazdı)
std::string structName;
if (auto* exprObj = dynamic_cast<ExpressionNode*>(ma->object))
structName = exprObj->resolvedType.structName;
int idx = getStructFieldIndex(structName, ma->member);
if (idx >= 0) emitFieldGet(destSlot, objSlot, idx);
return destSlot;
}
case ASTKind::ArrayLiteral: {
auto* al = (ArrayLiteralNode*)node;
int arrSlot = freshSlot();
@ -685,6 +710,30 @@ void IRGenerator::emitStoreGlobal(int srcSlot, int globalIndex) {
currentFunction_->instructions.push_back(std::move(ins));
}
void IRGenerator::emitStructNew(int destSlot, const std::string& structType, int fieldCount) {
Instruction ins(Opcode::STRUCT_NEW);
ins.dest = destSlot;
ins.intValue = fieldCount;
ins.functionName = structType; // struct tip adı
currentFunction_->instructions.push_back(std::move(ins));
}
void IRGenerator::emitFieldGet(int destSlot, int objSlot, int fieldIdx) {
Instruction ins(Opcode::FIELD_GET);
ins.dest = destSlot;
ins.src = objSlot;
ins.intValue = fieldIdx;
currentFunction_->instructions.push_back(std::move(ins));
}
void IRGenerator::emitFieldSet(int objSlot, int fieldIdx, int valSlot) {
Instruction ins(Opcode::FIELD_SET);
ins.dest = objSlot;
ins.intValue = fieldIdx;
ins.right = valSlot;
currentFunction_->instructions.push_back(std::move(ins));
}
void IRGenerator::emitArrayNew(int destSlot, int capacity) {
Instruction ins(Opcode::ARRAY_NEW);
ins.dest = destSlot;
@ -715,6 +764,20 @@ void IRGenerator::emitArrayLen(int destSlot, int arrSlot) {
currentFunction_->instructions.push_back(std::move(ins));
}
int IRGenerator::getStructFieldIndex(const std::string& structType, const std::string& fieldName) const {
auto it = structLayouts_.find(structType);
if (it == structLayouts_.end()) return -1;
for (int i = 0; i < (int)it->second.size(); i++)
if (it->second[i].first == fieldName) return i;
return -1;
}
int IRGenerator::getStructFieldCount(const std::string& structType) const {
auto it = structLayouts_.find(structType);
if (it == structLayouts_.end()) return 0;
return (int)it->second.size();
}
bool IRGenerator::isGlobal(const std::string& name) const {
return nameToGlobal_.count(name) > 0;
}

View File

@ -22,8 +22,11 @@
#include <string>
#include <unordered_map>
#include <vector>
#include <utility>
#include "ir/ir_program.hpp"
#include "symbol/symbol_table.hpp"
#include "core/type.hpp"
#include "parser/ast_node.hpp"
class IRGenerator {
@ -58,6 +61,9 @@ private:
void emitLoadSlot(int destSlot, int srcSlot);
void emitLoadGlobal(int destSlot, int globalIndex);
void emitStoreGlobal(int srcSlot, int globalIndex);
void emitStructNew(int destSlot, const std::string& structType, int fieldCount);
void emitFieldGet(int destSlot, int objSlot, int fieldIdx);
void emitFieldSet(int objSlot, int fieldIdx, int valSlot);
void emitArrayNew(int destSlot, int capacity);
void emitArrayGet(int destSlot, int arrSlot, int idxSlot);
void emitArraySet(int arrSlot, int idxSlot, int valSlot);
@ -101,6 +107,13 @@ private:
std::unordered_map<std::string, int> nameToGlobal_;
int globalCount_ = 0;
// Struct alan düzeni: struct adı → sıralı [(alan adı, Type)] listesi
// Sembol tablosundan generate() başında kopyalanır.
std::unordered_map<std::string, std::vector<std::pair<std::string, Type>>> structLayouts_;
int getStructFieldIndex(const std::string& structType, const std::string& fieldName) const;
int getStructFieldCount(const std::string& structType) const;
bool isGlobal(const std::string& name) const;
int getGlobalIndex(const std::string& name) const;
};

View File

@ -497,6 +497,15 @@ ASTNode* Parser::parseStatement() {
if (ct.type == TokenType::KW_STRUCT)
return parseStructDecl();
// Kullanıcı tanımlı struct tipiyle değişken bildirimi: Point p; veya Point p = ...;
if (ct.type == TokenType::IDENTIFIER) {
auto la1 = lookahead(1);
// "TypeName varName" veya "TypeName varName = ..." → değişken bildirimi
// (TypeName LPAREN → ifade; o durumda parseExpressionStatement devam eder)
if (la1.type == TokenType::IDENTIFIER)
return parseVariableDecl();
}
return parseExpressionStatement();
}

View File

@ -107,6 +107,8 @@ void TypeChecker::checkStmt(ASTNode* node) {
case ASTKind::VariableDecl: {
auto* vd = (VariableDeclNode*)node;
Type targetType = Type::fromName(vd->varType);
if (targetType.isError() && table_.structLayouts.count(vd->varType))
targetType = Type::structType(vd->varType);
if (vd->initExpr) {
Type srcType = checkExpr(vd->initExpr, targetType);
bool isLit = vd->initExpr->kind == ASTKind::Literal;
@ -374,15 +376,26 @@ Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) {
// ── MemberAccess / IndexExpression ─────────────────────────────────────
case ASTKind::MemberAccess: {
auto* ma = (MemberAccessNode*)node;
checkExpr(ma->object);
result = Type::error(); // TODO(faz3+): struct alan çözümü
Type objType = checkExpr(ma->object);
if (objType.isStruct()) {
result = table_.getFieldType(objType.structName, ma->member);
if (result.isError())
diag_.report("E001", node->loc,
"'" + objType.structName + "' struct'ında '" + ma->member + "' alanı yok");
} else {
result = Type::error();
}
break;
}
case ASTKind::IndexExpression: {
auto* ie = (IndexExpressionNode*)node;
checkExpr(ie->object);
Type objType = checkExpr(ie->object);
if (ie->index) checkExpr(ie->index);
result = Type::error(); // TODO(faz3+): array eleman tipi
// array eleman tipi
if (objType.isArray() && objType.elementType)
result = *objType.elementType;
else
result = Type::Int(); // varsayılan (tip çıkarımı tam değil)
break;
}

View File

@ -77,14 +77,15 @@ void SymbolCollector::pass1Globals(ASTNode* program) {
"'" + st->name + "' zaten bu kapsamda tanımlı");
break;
}
// struct alan isimlerini cycle check için kaydet
// structFields_'e her zaman bir giriş aç (typeFromName için gerekli)
structFields_[st->name]; // boş vektör oluşturur; by-value döngü artık referans semantiğiyle meşru (ADR-020)
// structLayouts: tüm alanlar (isim + tip) sırayla — IR üreteci ve tip denetleyici için
for (ASTNode* fieldNode : st->getChildren()) {
if (fieldNode->kind == ASTKind::VariableDecl) {
auto* vd = (VariableDeclNode*)fieldNode;
// yalnızca struct tipindeki alanları izle
Type ft = Type::fromName(vd->varType);
if (ft.isError()) // primitif değilse struct tipi olabilir
structFields_[st->name].push_back(vd->varType);
Type ft = typeFromName(vd->varType, vd->loc);
table_.structLayouts[st->name].push_back({vd->name, ft});
}
}
break;
@ -124,44 +125,10 @@ void SymbolCollector::pass1Globals(ASTNode* program) {
// ─────────────────────────────────────────────────────────────────────────────
void SymbolCollector::checkStructCycles() {
// white=0 / gray=1 / black=2
std::unordered_map<std::string, int> color;
for (auto& kv : structFields_) color[kv.first] = 0;
std::function<bool(const std::string&)> dfs = [&](const std::string& name) -> bool {
auto it = color.find(name);
if (it == color.end()) return false; // primitif / bilinmeyen → çevrim değil
if (it->second == 1) return true; // gray → back-edge → çevrim!
if (it->second == 2) return false; // black → zaten işlendi
it->second = 1; // gri yap
auto fit = structFields_.find(name);
if (fit != structFields_.end()) {
for (const std::string& dep : fit->second) {
if (dfs(dep)) return true;
}
}
it->second = 2; // siyah yap
return false;
};
for (auto& kv : structFields_) {
if (color[kv.first] == 0) {
// DFS başlat
color[kv.first] = 1;
for (const std::string& dep : kv.second) {
if (dfs(dep)) {
// tanımlama konumunu bulmak için global scope'ta ara
Symbol* s = table_.global()->lookupLocal(kv.first);
SourceLocation loc = s ? s->definitionLoc : SourceLocation{};
diag_.report("E010", loc,
"Döngüsel struct: '" + kv.first + "' by-value sonsuz boyut oluşturur");
break;
}
}
color[kv.first] = 2;
}
}
// ADR-020: Struct alanları referans semantiği taşır (Object* pointer).
// By-value gömme yok → sonsuz-boyut döngüsü imkânsız.
// E010 artık üretilmez; bu metot koşullu olarak devre dışı.
// TODO(gelecek): Primitive tipler için by-value gömme eklenirse E010 geri açılır.
}
// ─────────────────────────────────────────────────────────────────────────────

View File

@ -3,6 +3,7 @@
#include <memory>
#include <vector>
#include <unordered_map>
#include "symbol/scope.hpp"
class SymbolTable {
@ -50,6 +51,26 @@ public:
return result;
}
// Struct alan düzeni: struct adı → sıralı [(alan adı, tip)] listesi
// Sembol toplayıcı doldurur; tip denetleyici ve IR üreteci okur.
std::unordered_map<std::string, std::vector<std::pair<std::string, Type>>> structLayouts;
int getFieldIndex(const std::string& structName, const std::string& fieldName) const {
auto it = structLayouts.find(structName);
if (it == structLayouts.end()) return -1;
for (int i = 0; i < (int)it->second.size(); i++)
if (it->second[i].first == fieldName) return i;
return -1;
}
Type getFieldType(const std::string& structName, const std::string& fieldName) const {
auto it = structLayouts.find(structName);
if (it == structLayouts.end()) return Type::error();
for (auto& p : it->second)
if (p.first == fieldName) return p.second;
return Type::error();
}
private:
Scope* newScope(Scope* p) {
scopes_.push_back(std::make_unique<Scope>(p));

View File

@ -193,6 +193,35 @@ int Interpreter::run() {
continue;
}
// ── Struct (ADR-020: referans semantiği) ──────────────────────────
case Opcode::STRUCT_NEW: {
StructObject* obj = heap_.allocStruct(instr.intValue);
frame.slots[instr.dest] = Value::fromRef(obj);
break;
}
case Opcode::FIELD_GET: {
Value& objVal = frame.slots[instr.src];
if (objVal.kind != ValueKind::Ref || !objVal.ref)
throw std::runtime_error("Çalışma hatası: struct değil");
auto* obj = (StructObject*)objVal.ref;
int idx = instr.intValue;
if (idx < 0 || idx >= (int)obj->fields.size())
throw std::runtime_error("Çalışma hatası: geçersiz struct alan indeksi " + std::to_string(idx));
frame.slots[instr.dest] = obj->fields[idx];
break;
}
case Opcode::FIELD_SET: {
Value& objVal = frame.slots[instr.dest];
if (objVal.kind != ValueKind::Ref || !objVal.ref)
throw std::runtime_error("Çalışma hatası: struct değil");
auto* obj = (StructObject*)objVal.ref;
int idx = instr.intValue;
if (idx < 0 || idx >= (int)obj->fields.size())
throw std::runtime_error("Çalışma hatası: geçersiz struct alan indeksi " + std::to_string(idx));
obj->fields[idx] = frame.slots[instr.right];
break;
}
// ── Array (ADR-020: referans semantiği) ───────────────────────────
case Opcode::ARRAY_NEW: {
ArrayObject* arr = heap_.allocArray(instr.intValue);

View File

@ -8,7 +8,7 @@
// 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) */ };
enum class ObjectType { Array, Struct };
struct Object {
ObjectType type;
@ -17,7 +17,6 @@ struct Object {
};
// 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 {
@ -28,6 +27,15 @@ struct ArrayObject : Object {
}
};
// ADR-020: Struct = referans semantiği. Alanlar sıra indeksiyle erişilir.
struct StructObject : Object {
std::vector<Value> fields;
explicit StructObject(int fieldCount = 0) {
type = ObjectType::Struct;
fields.resize(fieldCount); // Value::fromInt(0) ile başlatılır (varsayılan)
}
};
// ── 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.
@ -41,7 +49,15 @@ struct Heap {
head = obj;
allocCount++;
return obj;
// TODO(#56): mark-sweep kök taraması buradan — GC eşiği aşılınca tetikle
}
StructObject* allocStruct(int fieldCount) {
auto* obj = new StructObject(fieldCount);
obj->next = head;
head = obj;
allocCount++;
return obj;
// TODO(#56): mark-sweep kök taraması buradan
}
// v1: process exit'te OS toplar; yıkıcı tüm nesneleri siler.

View File

@ -8,14 +8,14 @@
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).
// ADR-021: Null = nullable referansların null değeri (saQut'ta `null` anahtar sözcüğü).
// 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,
Ref, // ADR-020: array/struct nesnesine Object* referansı
Nil, // ADR-021: nullable referansın null değeri
Null, // ADR-021: nullable referansın null değeri (saQut kaynağında `null`)
// Float, // TODO(#44)
};
@ -37,17 +37,17 @@ struct Value {
Value v; v.kind = ValueKind::Ref; v.ref = obj; return v;
}
static Value nil() {
Value v; v.kind = ValueKind::Nil; return v;
static Value null() {
Value v; v.kind = ValueKind::Null; return v;
}
// JIF_FALSE: int 0 / boş string / nil = yanlış; Ref her zaman doğru
// JIF_FALSE: int 0 / boş string / null = 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;
case ValueKind::Null: return false;
}
return false;
}
@ -57,7 +57,7 @@ struct Value {
case ValueKind::Int: return std::to_string(intValue);
case ValueKind::String: return stringValue;
case ValueKind::Ref: return "<array>";
case ValueKind::Nil: return "nil";
case ValueKind::Null: return "null";
}
return "?";
}
@ -67,7 +67,7 @@ struct Value {
case ValueKind::Int: return "int";
case ValueKind::String: return "string";
case ValueKind::Ref: return "array";
case ValueKind::Nil: return "nil";
case ValueKind::Null: return "null";
}
return "?";
}

View File

@ -0,0 +1,3 @@
10
20
99

View File

@ -0,0 +1,19 @@
struct Point {
int x;
int y;
}
void setX(Point p, int val) {
p.x = val;
}
int main() {
Point p;
p.x = 10;
p.y = 20;
print(p.x);
print(p.y);
setX(p, 99);
print(p.x);
return 0;
}