feat(vm): string veri tipi + run pipeline'ına TypeChecker eklendi

- Value: ValueKind::String + stringValue alanı eklendi
- instruction: LOAD_STRING opcode'u eklendi
- ir_generator: STRING literal → LOAD_STRING; desteklenmeyen tipler
  (FLOAT, null) IR üretim aşamasında hata fırlatır
- interpreter: runtime tip kontrolleri kaldırıldı (TypeChecker zaten
  derleme zamanında tipleri doğruluyor); sıfıra bölme kontrolü kaldı
  (gerçek çalışma zamanı koşulu); print() string/int ayırt eder
- run.hpp: TypeChecker + StructuralValidator pipeline'a eklendi

Test:
  build/saqut run file:examples/merhaba.sqt → Merhaba / saQut calisiyor
  build/saqut run file:examples/fibonacci.sqt → 55 / 55

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
abdussamedulutas 2026-06-18 19:46:48 +03:00
parent a4bd5110b3
commit 539e08e521
9 changed files with 130 additions and 125 deletions

Binary file not shown.

View File

@ -10,19 +10,14 @@
21 7502 1781796718449424977 CMakeFiles/saqut.dir/src/parser/nodes/statements.cpp.o 3c8869307381c930
14 6864 1781796718442362341 CMakeFiles/saqut.dir/src/parser/nodes/binary_expr.cpp.o 5cc8b697133bcf64
15 6733 1781796718442847556 CMakeFiles/saqut.dir/src/parser/nodes/declarations.cpp.o c3d262615ede4c95
2 4490 1781799598563879630 CMakeFiles/saqut.dir/src/main.cpp.o 3cfef7a665d5bf87
4490 4758 1781799603051859470 saqut f2e198803c4dbffb
0 22 1781799611852960564 build.ninja 1876a59d627a585
0 22 1781799611852960564 /home/saqut/Masaüstü/saqutcompiler/build/cmake_install.cmake 1876a59d627a585
1 4629 1781801148234045650 CMakeFiles/saqut.dir/src/main.cpp.o 3cfef7a665d5bf87
4629 4900 1781801152862380672 saqut f2e198803c4dbffb
0 22 1781801180493522122 build.ninja 1876a59d627a585
0 22 1781801180493522122 /home/saqut/Masaüstü/saqutcompiler/build/cmake_install.cmake 1876a59d627a585
6733 11112 1781796725160284765 CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o ec4e483b8ddb4007
4805 9685 1781796723232278341 CMakeFiles/saqut.dir/src/semantic/structural_validator.cpp.o 248faa3675024351
6700 10405 1781796725127284655 CMakeFiles/saqut.dir/src/semantic/type_checker.cpp.o b29c133293d988b0
2 795 1781799345769990010 CMakeFiles/saqut.dir/src/vm/interpreter.cpp.o b7dd80e002d68a1d
1 668 1781799598562879634 CMakeFiles/saqut.dir/src/ir/ir_function.cpp.o 10f5e8dfd1461d69
1 1001 1781799106947865509 CMakeFiles/saqut.dir/src/ir/ir_program.cpp.o 9518231d970828da
2 3078 1781799345769137653 CMakeFiles/saqut.dir/src/ir/ir_generator.cpp.o 10a1ed4e1f52e530
1 636 1781799663202595202 CMakeFiles/saqut.dir/src/ir/ir_function.cpp.o 10f5e8dfd1461d69
636 892 1781799663837592468 saqut f2e198803c4dbffb
1 653 1781800137590930314 CMakeFiles/saqut.dir/src/ir/ir_program.cpp.o 9518231d970828da
1 658 1781800137589789659 CMakeFiles/saqut.dir/src/ir/ir_function.cpp.o 10f5e8dfd1461d69
658 919 1781800138246787400 saqut f2e198803c4dbffb
1 850 1781801148235507662 CMakeFiles/saqut.dir/src/vm/interpreter.cpp.o b7dd80e002d68a1d
2 957 1781800866770475511 CMakeFiles/saqut.dir/src/ir/ir_function.cpp.o 10f5e8dfd1461d69
2 718 1781800866771246136 CMakeFiles/saqut.dir/src/ir/ir_program.cpp.o 9518231d970828da
2 3169 1781800866771136888 CMakeFiles/saqut.dir/src/ir/ir_generator.cpp.o 10a1ed4e1f52e530

5
examples/merhaba.sqt Normal file
View File

@ -0,0 +1,5 @@
int main() {
print("Merhaba");
print("saQut calisiyor");
return 0;
}

View File

@ -19,6 +19,8 @@
#include "parser/parser.hpp"
#include "symbol/symbol_table.hpp"
#include "symbol/symbol_collector.hpp"
#include "semantic/type_checker.hpp"
#include "semantic/structural_validator.hpp"
#include "diagnostic/diagnostic_engine.hpp"
#include "ir/ir_generator.hpp"
#include "vm/interpreter.hpp"
@ -46,6 +48,8 @@ inline int cmdRun(const CliArgs& args) {
SymbolTable symbolTable;
DiagnosticEngine diag;
SymbolCollector(symbolTable, diag).collect(ast);
TypeChecker(symbolTable, diag).check(ast);
StructuralValidator(diag).validate(ast);
if (diag.hasErrors()) {
std::cerr << "Derleme hataları var, program çalıştırılamaz:\n";

View File

@ -37,9 +37,12 @@
enum class Opcode {
// --- Değer yükleme ---
LOAD_CONST, // slots[dest] = intValue
LOAD_CONST, // slots[dest] = intValue (tam sayı sabitini slota yükle)
// Örnek: LOAD_CONST dest=3 val=10 → slot[3] = 10
LOAD_STRING, // slots[dest] = stringValue (metin sabitini slota yükle)
// Örnek: LOAD_STRING dest=2 val="Merhaba" → slot[2] = "Merhaba"
LOAD_SLOT, // slots[dest] = slots[src]
// Bir slotun değerini başka bir slota kopyalar.
// Atama işlemlerinde (x = y) kullanılır.
@ -79,6 +82,7 @@ enum class Opcode {
inline const char* opcodeName(Opcode op) {
switch (op) {
case Opcode::LOAD_CONST: return "LOAD_CONST";
case Opcode::LOAD_STRING: return "LOAD_STRING";
case Opcode::LOAD_SLOT: return "LOAD_SLOT";
case Opcode::ADD: return "ADD";
case Opcode::SUB: return "SUB";
@ -121,9 +125,12 @@ struct Instruction {
int left = -1;
int right = -1;
// LOAD_CONST için yüklenecek sabit değer
// LOAD_CONST için yüklenecek tam sayı sabiti
int intValue = 0;
// LOAD_STRING için yüklenecek metin sabiti (tırnak işaretleri olmadan)
std::string stringValue;
// JMP / JIF_FALSE için hedef instruction indeksi
// Üretim sırasında bilinmiyorsa -1 bırakılır, sonradan doldurulur (backpatch).
int jumpTarget = -1;

View File

@ -80,6 +80,9 @@ void IRFunction::dump() const {
if (ins.opcode == Opcode::LOAD_CONST) {
std::cout << slot(ins.dest) << " = " << ins.intValue;
} else if (ins.opcode == Opcode::LOAD_STRING) {
std::cout << slot(ins.dest) << " = \"" << ins.stringValue << "\"";
} else if (ins.opcode == Opcode::LOAD_SLOT) {
std::cout << slot(ins.dest) << " = " << slot(ins.src);

View File

@ -1,4 +1,5 @@
#include "ir/ir_generator.hpp"
#include "tokenizer/token.hpp"
#include "parser/nodes/program.hpp"
#include "parser/nodes/declarations.hpp"
#include "parser/nodes/statements.hpp"
@ -269,26 +270,45 @@ int IRGenerator::generateExpression(ASTNode* node) {
switch (lit->literalType) {
case LiteralType::INTEGER: {
// Sayı metnini int'e çevir
int value = 0;
if (lit->parserToken.token) {
if (lit->parserToken.token)
value = std::stoi(lit->parserToken.token->token);
}
emitLoadConst(slot, value);
break;
}
case LiteralType::BOOLEAN: {
// true → 1, false → 0
int value = (lit->parserToken.token &&
lit->parserToken.token->token == "true") ? 1 : 0;
emitLoadConst(slot, value);
break;
}
default:
// float, string vb. → TODO(vm-genişletme)
emitLoadConst(slot, 0);
case LiteralType::STRING: {
// StringToken::context tırnak işaretleri olmadan içeriği tutar
std::string content;
if (auto* st = dynamic_cast<StringToken*>(lit->lexerToken))
content = st->context;
else if (lit->parserToken.token) {
// Fallback: token'ın başındaki ve sonundaki " işaretlerini sıyır
std::string raw = lit->parserToken.token->token;
if (raw.size() >= 2 && raw.front() == '"' && raw.back() == '"')
content = raw.substr(1, raw.size() - 2);
else
content = raw;
}
Instruction ins(Opcode::LOAD_STRING);
ins.dest = slot;
ins.stringValue = std::move(content);
currentFunction_->instructions.push_back(std::move(ins));
break;
}
case LiteralType::FLOAT:
throw std::runtime_error(
"IR üretim hatası: float literal şu an VM tarafından desteklenmiyor. "
"Tam sayı kullanın veya float desteği eklenene kadar bekleyin.");
case LiteralType::BOŞ:
throw std::runtime_error(
"IR üretim hatası: null literal şu an VM tarafından desteklenmiyor.");
}
return slot;
}

View File

@ -2,223 +2,172 @@
#include <iostream>
#include <stdexcept>
// ─────────────────────────────────────────────────────────────────────────────
// run — Ana yorumlayıcı döngüsü
// ─────────────────────────────────────────────────────────────────────────────
int Interpreter::run() {
// "main" fonksiyonunu bul
IRFunction* mainFunction = program_.findFunction("main");
if (!mainFunction) {
if (!mainFunction)
throw std::runtime_error("Çalışma hatası: 'main' fonksiyonu bulunamadı");
}
// main için ilk frame'i oluştur ve stack'e ekle
CallFrame mainFrame;
mainFrame.function = mainFunction;
mainFrame.instructionPointer = 0;
mainFrame.slots.resize(mainFunction->slotCount, Value::fromInt(0));
mainFrame.returnDestSlot = -1; // caller yok
mainFrame.returnDestSlot = -1;
callStack_.push_back(std::move(mainFrame));
// ── Ana döngü ─────────────────────────────────────────────────────────
// Her iterasyonun başında mevcut frame'i TAZEDEN alırız.
// CALL ve RETURN callStack'i değiştirir; `continue` ile döngü başına
// dönülür ve frame yeniden alınır — dangling pointer sorunu olmaz.
while (!callStack_.empty()) {
// Her iterasyonda taze referans al (CALL sonrası vector büyüyebilir)
CallFrame& frame = callStack_.back();
// Tüm instruction'lar tükendi mi? (RETURN olmadan biten fonksiyon)
if (frame.instructionPointer >= (int)frame.function->instructions.size()) {
// void fonksiyon gibi davran — 0 döndür
int destSlot = frame.returnDestSlot;
callStack_.pop_back();
if (!callStack_.empty() && destSlot != -1) {
if (!callStack_.empty() && destSlot != -1)
callStack_.back().slots[destSlot] = Value::fromInt(0);
}
continue;
}
// Sıradaki talimatı al ve ip'yi ÖNCE İLERLET.
// Neden önce? CALL veya RETURN ip'ye dokunmaz. Böylece:
// - CALL: yeni frame ip=0 ile açılır, caller'ın ip'si zaten ilerletilmiş.
// - RETURN sonrası caller kaldığı yerden (ip zaten doğru) devam eder.
const Instruction& instr = frame.function->instructions[frame.instructionPointer];
frame.instructionPointer++;
// ── Talimat switch'i ──────────────────────────────────────────────
switch (instr.opcode) {
// slots[dest] = sabit değer
case Opcode::LOAD_CONST:
frame.slots[instr.dest] = Value::fromInt(instr.intValue);
break;
// slots[dest] = slots[src] (kopyala)
case Opcode::LOAD_STRING:
frame.slots[instr.dest] = Value::fromString(instr.stringValue);
break;
case Opcode::LOAD_SLOT:
frame.slots[instr.dest] = frame.slots[instr.src];
break;
// ── Aritmetik ────────────────────────────────────────────────────
// ── Aritmetik ─────────────────────────────────────────────────────
// TypeChecker derleme zamanında tipleri doğruladı — burada sadece hesap yapılır.
// İstisna: sıfıra bölme gerçek bir çalışma zamanı koşuludur, kontrol edilir.
case Opcode::ADD:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue + frame.slots[instr.right].intValue);
break;
case Opcode::SUB:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue - frame.slots[instr.right].intValue);
break;
case Opcode::MUL:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue * frame.slots[instr.right].intValue);
break;
case Opcode::DIV: {
int divisor = frame.slots[instr.right].intValue;
if (divisor == 0) {
throw std::runtime_error("Çalışma hatası: sıfıra bölme");
}
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue / divisor);
int d = frame.slots[instr.right].intValue;
if (d == 0) throw std::runtime_error("Çalışma hatası: sıfıra bölme");
frame.slots[instr.dest] = Value::fromInt(frame.slots[instr.left].intValue / d);
break;
}
case Opcode::MOD: {
int divisor = frame.slots[instr.right].intValue;
if (divisor == 0) {
throw std::runtime_error("Çalışma hatası: sıfıra bölme (mod)");
}
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue % divisor);
int d = frame.slots[instr.right].intValue;
if (d == 0) throw std::runtime_error("Çalışma hatası: sıfıra bölme (mod)");
frame.slots[instr.dest] = Value::fromInt(frame.slots[instr.left].intValue % d);
break;
}
// ── Karşılaştırma (sonuç: 1=doğru, 0=yanlış) ────────────────────
// ── Karşılaştırma ─────────────────────────────────────────────────
case Opcode::LESS:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue < frame.slots[instr.right].intValue ? 1 : 0);
break;
case Opcode::LESS_EQUAL:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue <= frame.slots[instr.right].intValue ? 1 : 0);
break;
case Opcode::GREATER:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue > frame.slots[instr.right].intValue ? 1 : 0);
break;
case Opcode::GREATER_EQUAL:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue >= frame.slots[instr.right].intValue ? 1 : 0);
break;
case Opcode::EQUAL_EQUAL:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue == frame.slots[instr.right].intValue ? 1 : 0);
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);
frame.slots[instr.dest] = Value::fromInt(r);
break;
case Opcode::NOT_EQUAL:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue != frame.slots[instr.right].intValue ? 1 : 0);
}
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);
frame.slots[instr.dest] = Value::fromInt(r);
break;
}
// ── Kontrol akışı ─────────────────────────────────────────────────
// Koşulsuz atlama
case Opcode::JMP:
frame.instructionPointer = instr.jumpTarget;
break;
// Koşullu atlama: slot[cond] == 0 (yanlış) ise atla
case Opcode::JIF_FALSE:
if (!frame.slots[instr.cond].isTruthy()) {
if (!frame.slots[instr.cond].isTruthy())
frame.instructionPointer = instr.jumpTarget;
}
break;
// ── Fonksiyon çağrısı ─────────────────────────────────────────────
// Yeni frame oluştur, argümanları parametre slotlarına kopyala,
// stack'e ekle. `continue` ile döngü başına dön — yeni frame çalışmaya başlar.
case Opcode::CALL: {
IRFunction* callee = program_.findFunction(instr.functionName);
if (!callee) {
if (!callee)
throw std::runtime_error(
"Çalışma hatası: '" + instr.functionName + "' fonksiyonu bulunamadı");
}
// Yeni frame hazırla
CallFrame newFrame;
newFrame.function = callee;
newFrame.instructionPointer = 0;
newFrame.slots.resize(callee->slotCount, Value::fromInt(0));
newFrame.returnDestSlot = instr.dest; // sonuç bu slota yazılacak
newFrame.returnDestSlot = instr.dest;
// Argümanları parametre slotlarına kopyala (slot 0, 1, 2, ...)
for (int i = 0; i < (int)instr.argSlots.size(); i++) {
for (int i = 0; i < (int)instr.argSlots.size(); i++)
newFrame.slots[i] = frame.slots[instr.argSlots[i]];
}
// Frame'i stack'e ekle — SONRA `continue` ile döngü başına dön.
// Böylece bir sonraki iterasyonda bu yeni frame çalışmaya başlar.
callStack_.push_back(std::move(newFrame));
continue; // ← frame referansı burada yenilenir, dangling pointer yok
continue;
}
// ── Dönüş ─────────────────────────────────────────────────────────
// Dönüş değerini caller'ın beklediği slota yaz, bu frame'i kapat.
case Opcode::RETURN: {
Value returnValue = frame.slots[instr.src];
int returnDestSlot = frame.returnDestSlot;
// Bu frame'i kapat
callStack_.pop_back();
// Caller varsa dönüş değerini onun slotuna yaz
if (!callStack_.empty() && returnDestSlot != -1) {
if (!callStack_.empty() && returnDestSlot != -1)
callStack_.back().slots[returnDestSlot] = returnValue;
}
// main fonksiyonu döndü → program bitti
if (callStack_.empty()) {
if (callStack_.empty())
return returnValue.intValue;
continue;
}
continue; // ← bir sonraki iterasyonda caller frame tazeden alınır
}
// ── FFI: Host fonksiyon çağrısı ───────────────────────────────────
// ── FFI ───────────────────────────────────────────────────────────
case Opcode::CALLHOST:
executeHostFunction(instr.functionName, frame.slots, instr.argSlots);
break;
}
}
return 0; // Normal çıkış
return 0;
}
// ─────────────────────────────────────────────────────────────────────────────
// executeHostFunction — C++ tarafında tanımlı fonksiyonları çağır
// ─────────────────────────────────────────────────────────────────────────────
void Interpreter::executeHostFunction(const std::string& name,
const std::vector<Value>& slots,
const std::vector<int>& argSlots) {
if (name == "print") {
// print(değer) — stdout'a değeri yazdır
if (!argSlots.empty()) {
std::cout << slots[argSlots[0]].intValue << "\n";
const Value& val = slots[argSlots[0]];
if (val.kind == ValueKind::String) std::cout << val.stringValue << "\n";
else std::cout << val.intValue << "\n";
}
return;
}
// Bilinmeyen host fonksiyon
throw std::runtime_error("Çalışma hatası: bilinmeyen host fonksiyonu '" + name + "'");
}

View File

@ -15,20 +15,22 @@
#ifndef SAQUT_VM_VALUE
#define SAQUT_VM_VALUE
#include <string>
// Gelecekte float/bool/string eklendiğinde burası genişleyecek.
// Şimdilik sadece int.
enum class ValueKind {
Int,
String,
// Float, // TODO(vm-genişletme)
// Bool, // TODO(vm-genişletme)
// String, // TODO(vm-genişletme)
};
struct Value {
ValueKind kind = ValueKind::Int;
int intValue = 0;
std::string stringValue; // yalnızca kind == String için geçerli
// Kolay oluşturma
static Value fromInt(int n) {
Value v;
v.kind = ValueKind::Int;
@ -36,8 +38,28 @@ struct Value {
return v;
}
// JIF_FALSE için: 0 = yanlış, diğer = doğru
bool isTruthy() const { return intValue != 0; }
static Value fromString(std::string s) {
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
bool isTruthy() const {
if (kind == ValueKind::Int) return intValue != 0;
if (kind == ValueKind::String) return !stringValue.empty();
return false;
}
// Okunabilir metin — dump ve hata mesajları için
std::string typeName() const {
switch (kind) {
case ValueKind::Int: return "int";
case ValueKind::String: return "string";
}
return "?";
}
};
#endif // SAQUT_VM_VALUE