From 435c8bcb963d09dbd4060282e64eaeec97bf7765 Mon Sep 17 00:00:00 2001 From: saqut Date: Sat, 20 Jun 2026 16:33:51 +0300 Subject: [PATCH] feat(struct+null): struct runtime + ValueKind::Null + E010 ADR-020 revizyonu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/ir/instruction.hpp | 8 +++ src/ir/ir_function.cpp | 9 ++++ src/ir/ir_generator.cpp | 83 ++++++++++++++++++++++++++---- src/ir/ir_generator.hpp | 13 +++++ src/parser/parser.cpp | 9 ++++ src/semantic/type_checker.cpp | 21 ++++++-- src/symbol/symbol_collector.cpp | 53 ++++--------------- src/symbol/symbol_table.hpp | 21 ++++++++ src/vm/interpreter.cpp | 29 +++++++++++ src/vm/object.hpp | 22 ++++++-- src/vm/value.hpp | 16 +++--- tests/golden/struct/basic.expected | 3 ++ tests/golden/struct/basic.sqt | 19 +++++++ 13 files changed, 238 insertions(+), 68 deletions(-) create mode 100644 tests/golden/struct/basic.expected create mode 100644 tests/golden/struct/basic.sqt diff --git a/src/ir/instruction.hpp b/src/ir/instruction.hpp index 4f4da68..a1969ed 100644 --- a/src/ir/instruction.hpp +++ b/src/ir/instruction.hpp @@ -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"; diff --git a/src/ir/ir_function.cpp b/src/ir/ir_function.cpp index 0ecb8ad..8dab1e2 100644 --- a/src/ir/ir_function.cpp +++ b/src/ir/ir_function.cpp @@ -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 << "]"; diff --git a/src/ir/ir_generator.cpp b/src/ir/ir_generator.cpp index 2f0e0bc..d33ba63 100644 --- a/src/ir/ir_generator.cpp +++ b/src/ir/ir_generator.cpp @@ -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 globalVars; @@ -97,7 +100,7 @@ void IRGenerator::generateStatement(ASTNode* node) { break; } - // ── Değişken bildirimi: int x = ────────────────────────────── + // ── Değişken bildirimi: int x = / 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(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(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; } diff --git a/src/ir/ir_generator.hpp b/src/ir/ir_generator.hpp index a6e1034..ea2b90f 100644 --- a/src/ir/ir_generator.hpp +++ b/src/ir/ir_generator.hpp @@ -22,8 +22,11 @@ #include #include +#include +#include #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 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>> 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; }; diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index d9a19f9..4efd18e 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -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(); } diff --git a/src/semantic/type_checker.cpp b/src/semantic/type_checker.cpp index 3e75649..ee4e77f 100644 --- a/src/semantic/type_checker.cpp +++ b/src/semantic/type_checker.cpp @@ -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; } diff --git a/src/symbol/symbol_collector.cpp b/src/symbol/symbol_collector.cpp index c4295b6..2ac9721 100644 --- a/src/symbol/symbol_collector.cpp +++ b/src/symbol/symbol_collector.cpp @@ -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 color; - for (auto& kv : structFields_) color[kv.first] = 0; - - std::function 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. } // ───────────────────────────────────────────────────────────────────────────── diff --git a/src/symbol/symbol_table.hpp b/src/symbol/symbol_table.hpp index 8ff6624..996c441 100644 --- a/src/symbol/symbol_table.hpp +++ b/src/symbol/symbol_table.hpp @@ -3,6 +3,7 @@ #include #include +#include #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>> 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(p)); diff --git a/src/vm/interpreter.cpp b/src/vm/interpreter.cpp index 97b9e8b..63cc0c9 100644 --- a/src/vm/interpreter.cpp +++ b/src/vm/interpreter.cpp @@ -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); diff --git a/src/vm/object.hpp b/src/vm/object.hpp index 13517a6..800c176 100644 --- a/src/vm/object.hpp +++ b/src/vm/object.hpp @@ -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 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. diff --git a/src/vm/value.hpp b/src/vm/value.hpp index e0d4822..5304a9f 100644 --- a/src/vm/value.hpp +++ b/src/vm/value.hpp @@ -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 ""; - 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 "?"; } diff --git a/tests/golden/struct/basic.expected b/tests/golden/struct/basic.expected new file mode 100644 index 0000000..df4a4a1 --- /dev/null +++ b/tests/golden/struct/basic.expected @@ -0,0 +1,3 @@ +10 +20 +99 diff --git a/tests/golden/struct/basic.sqt b/tests/golden/struct/basic.sqt new file mode 100644 index 0000000..92a15c8 --- /dev/null +++ b/tests/golden/struct/basic.sqt @@ -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; +}