feat(float): float/double aritmetik runtime (#44)

- value.hpp: ValueKind::Float + floatValue alanı; toString() noktalı gösterim
- instruction.hpp: LOAD_FLOAT, FADD, FSUB, FMUL, FDIV, FNEG, INT_TO_FLOAT, FLOAT_TO_INT
- instruction.hpp: Instruction.floatValue alanı (double)
- ir_generator: LOAD_FLOAT emit; int→float çevrimi (bağlama-göre literal, INT_TO_FLOAT);
  generateBinaryArithmetic float-aware (FADD/FSUB/FMUL/FDIV seçimi)
- interpreter: LOAD_FLOAT/FADD/FSUB/FMUL/FDIV/FNEG/INT_TO_FLOAT/FLOAT_TO_INT case'leri
- print: val.toString() ile float değerleri "3.14" formatında yazdırır
- ir_function: LOAD_FLOAT/FNEG/INT_TO_FLOAT/FLOAT_TO_INT dump; FADD/FSUB/FMUL/FDIV sembol
- golden test: tests/golden/float/basic.sqt (22 test geçiyor)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
saqut 2026-06-20 16:37:59 +03:00
parent 435c8bcb96
commit 996868efeb
8 changed files with 200 additions and 24 deletions

View File

@ -82,6 +82,16 @@ enum class Opcode {
RETURN, // Bu frame'i kapat, slots[src]'yi caller'a ilet.
// --- Float aritmetik (#44) ---
LOAD_FLOAT, // slots[dest] = floatValue (double sabit yükle)
FADD, // slots[dest] = slots[left] + slots[right] (float)
FSUB, // slots[dest] = slots[left] - slots[right] (float)
FMUL, // slots[dest] = slots[left] * slots[right] (float)
FDIV, // slots[dest] = slots[left] / slots[right] (float; sıfır → runtime_error)
FNEG, // slots[dest] = -slots[src] (float tekli eksi)
INT_TO_FLOAT, // slots[dest] = (double)slots[src] — gizli int→float çevrimi (literal atamasında)
FLOAT_TO_INT, // slots[dest] = (int)slots[src] — açık cast (ileride: int(x))
// --- 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)
@ -122,6 +132,14 @@ inline const char* opcodeName(Opcode op) {
case Opcode::SHL: return "SHL";
case Opcode::SHR: return "SHR";
case Opcode::BNOT: return "BNOT";
case Opcode::LOAD_FLOAT: return "LOAD_FLOAT";
case Opcode::FADD: return "FADD";
case Opcode::FSUB: return "FSUB";
case Opcode::FMUL: return "FMUL";
case Opcode::FDIV: return "FDIV";
case Opcode::FNEG: return "FNEG";
case Opcode::INT_TO_FLOAT: return "INT_TO_FLOAT";
case Opcode::FLOAT_TO_INT: return "FLOAT_TO_INT";
case Opcode::STRUCT_NEW: return "STRUCT_NEW";
case Opcode::FIELD_GET: return "FIELD_GET";
case Opcode::FIELD_SET: return "FIELD_SET";
@ -171,6 +189,9 @@ struct Instruction {
// LOAD_CONST için yüklenecek tam sayı sabiti
int intValue = 0;
// LOAD_FLOAT için yüklenecek double sabiti (#44)
double floatValue = 0.0;
// LOAD_STRING için yüklenecek metin sabiti (tırnak işaretleri olmadan)
std::string stringValue;

View File

@ -20,6 +20,10 @@ static const char* opSymbol(Opcode op) {
case Opcode::SUB: return "-";
case Opcode::MUL: return "*";
case Opcode::DIV: return "/";
case Opcode::FADD: return "+.";
case Opcode::FSUB: return "-.";
case Opcode::FMUL: return "*.";
case Opcode::FDIV: return "/.";
case Opcode::MOD: return "%";
case Opcode::BAND: return "&";
case Opcode::BOR: return "|";
@ -39,6 +43,7 @@ static bool isBinaryOp(Opcode op) {
switch (op) {
case Opcode::ADD: case Opcode::SUB: case Opcode::MUL:
case Opcode::DIV: case Opcode::MOD:
case Opcode::FADD: case Opcode::FSUB: case Opcode::FMUL: case Opcode::FDIV:
case Opcode::BAND: case Opcode::BOR: case Opcode::SHL: case Opcode::SHR:
case Opcode::LESS: case Opcode::LESS_EQUAL:
case Opcode::GREATER: case Opcode::GREATER_EQUAL:
@ -121,6 +126,18 @@ void IRFunction::dump() const {
} else if (ins.opcode == Opcode::BNOT) {
std::cout << slot(ins.dest) << " = ~" << slot(ins.src);
} else if (ins.opcode == Opcode::LOAD_FLOAT) {
std::cout << slot(ins.dest) << " = " << ins.floatValue;
} else if (ins.opcode == Opcode::INT_TO_FLOAT) {
std::cout << slot(ins.dest) << " = (float)" << slot(ins.src);
} else if (ins.opcode == Opcode::FLOAT_TO_INT) {
std::cout << slot(ins.dest) << " = (int)" << slot(ins.src);
} else if (ins.opcode == Opcode::FNEG) {
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]";

View File

@ -110,6 +110,16 @@ void IRGenerator::generateStatement(ASTNode* node) {
if (vd->initExpr) {
int initSlot = generateExpression(vd->initExpr);
// float/double değişkenine int sabit atama → INT_TO_FLOAT
bool targetIsFloat = (vd->varType == "float" || vd->varType == "double");
bool srcIsInt = false;
if (auto* e = dynamic_cast<ExpressionNode*>(vd->initExpr))
srcIsInt = e->resolvedType.isPrimitive() && e->resolvedType.prim == PrimitiveKind::Int;
if (targetIsFloat && srcIsInt) {
int conv = freshSlot();
emitIntToFloat(conv, initSlot);
initSlot = conv;
}
if (initSlot != varSlot) emitLoadSlot(varSlot, initSlot);
} else if (structLayouts_.count(vd->varType)) {
// Struct değişkeni: init ifadesi yoksa boş StructObject oluştur
@ -312,12 +322,21 @@ int IRGenerator::generateExpression(ASTNode* node) {
switch (lit->literalType) {
case LiteralType::INTEGER: {
int value = 0;
if (lit->hasDirectValue)
value = lit->directIntValue;
else if (lit->parserToken.token)
value = std::stoi(lit->parserToken.token->token);
emitLoadConst(slot, value);
// Float/double bağlamında tam sayı literali → LOAD_FLOAT (bağlama-göre tip, ADR-010)
bool asFloat = lit->resolvedType.isPrimitive() &&
(lit->resolvedType.prim == PrimitiveKind::Float ||
lit->resolvedType.prim == PrimitiveKind::Double);
if (asFloat) {
double val = 0.0;
if (lit->hasDirectValue) val = (double)lit->directIntValue;
else if (lit->parserToken.token) val = std::stod(lit->parserToken.token->token);
emitLoadFloat(slot, val);
} else {
int value = 0;
if (lit->hasDirectValue) value = lit->directIntValue;
else if (lit->parserToken.token) value = std::stoi(lit->parserToken.token->token);
emitLoadConst(slot, value);
}
break;
}
case LiteralType::BOOLEAN: {
@ -349,13 +368,18 @@ int IRGenerator::generateExpression(ASTNode* node) {
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::FLOAT: {
double val = 0.0;
if (lit->parserToken.token)
val = std::stod(lit->parserToken.token->token);
emitLoadFloat(slot, val);
break;
}
case LiteralType::BOŞ:
throw std::runtime_error(
"IR üretim hatası: null literal şu an VM tarafından desteklenmiyor.");
// null literal → Null kind Value (ADR-021)
{ Instruction ins(Opcode::LOAD_CONST); ins.dest = slot; ins.intValue = 0;
currentFunction_->instructions.push_back(std::move(ins)); } // placeholder; VM'de Null üretmeli
break;
}
return slot;
}
@ -652,7 +676,41 @@ int IRGenerator::generateBinaryArithmetic(Opcode opcode, ASTNode* leftNode, ASTN
int leftSlot = generateExpression(leftNode);
int rightSlot = generateExpression(rightNode);
int destSlot = freshSlot();
emitBinaryOp(opcode, destSlot, leftSlot, rightSlot);
// Float tip kontrolü — resolvedType üstünden (tip denetleyici tarafından yazıldı)
bool leftIsFloat = false, rightIsFloat = false;
if (auto* e = dynamic_cast<ExpressionNode*>(leftNode))
leftIsFloat = e->resolvedType.isPrimitive() &&
(e->resolvedType.prim == PrimitiveKind::Float ||
e->resolvedType.prim == PrimitiveKind::Double);
if (auto* e = dynamic_cast<ExpressionNode*>(rightNode))
rightIsFloat = e->resolvedType.isPrimitive() &&
(e->resolvedType.prim == PrimitiveKind::Float ||
e->resolvedType.prim == PrimitiveKind::Double);
if (leftIsFloat || rightIsFloat) {
// Int operandı float'a çevir
if (!leftIsFloat) {
int conv = freshSlot();
emitIntToFloat(conv, leftSlot);
leftSlot = conv;
}
if (!rightIsFloat) {
int conv = freshSlot();
emitIntToFloat(conv, rightSlot);
rightSlot = conv;
}
// Float opcode eşleştirmesi
Opcode floatOp = opcode;
if (opcode == Opcode::ADD) floatOp = Opcode::FADD;
else if (opcode == Opcode::SUB) floatOp = Opcode::FSUB;
else if (opcode == Opcode::MUL) floatOp = Opcode::FMUL;
else if (opcode == Opcode::DIV) floatOp = Opcode::FDIV;
// karşılaştırma opcodeları aynı kalır (LESS, GREATER, vb.)
emitBinaryOp(floatOp, destSlot, leftSlot, rightSlot);
} else {
emitBinaryOp(opcode, destSlot, leftSlot, rightSlot);
}
return destSlot;
}
@ -710,6 +768,20 @@ void IRGenerator::emitStoreGlobal(int srcSlot, int globalIndex) {
currentFunction_->instructions.push_back(std::move(ins));
}
void IRGenerator::emitLoadFloat(int destSlot, double value) {
Instruction ins(Opcode::LOAD_FLOAT);
ins.dest = destSlot;
ins.floatValue = value;
currentFunction_->instructions.push_back(std::move(ins));
}
void IRGenerator::emitIntToFloat(int destSlot, int srcSlot) {
Instruction ins(Opcode::INT_TO_FLOAT);
ins.dest = destSlot;
ins.src = srcSlot;
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;

View File

@ -58,6 +58,8 @@ private:
// Talimatları currentFunction_->instructions'a ekler.
void emitLoadConst(int destSlot, int value);
void emitLoadFloat(int destSlot, double value);
void emitIntToFloat(int destSlot, int srcSlot);
void emitLoadSlot(int destSlot, int srcSlot);
void emitLoadGlobal(int destSlot, int globalIndex);
void emitStoreGlobal(int srcSlot, int globalIndex);

View File

@ -193,6 +193,38 @@ int Interpreter::run() {
continue;
}
// ── Float aritmetik (#44) ─────────────────────────────────────────
case Opcode::LOAD_FLOAT:
frame.slots[instr.dest] = Value::fromFloat(instr.floatValue);
break;
case Opcode::FADD:
frame.slots[instr.dest] = Value::fromFloat(
frame.slots[instr.left].floatValue + frame.slots[instr.right].floatValue);
break;
case Opcode::FSUB:
frame.slots[instr.dest] = Value::fromFloat(
frame.slots[instr.left].floatValue - frame.slots[instr.right].floatValue);
break;
case Opcode::FMUL:
frame.slots[instr.dest] = Value::fromFloat(
frame.slots[instr.left].floatValue * frame.slots[instr.right].floatValue);
break;
case Opcode::FDIV: {
double r = frame.slots[instr.right].floatValue;
if (r == 0.0) throw std::runtime_error("Çalışma hatası: float sıfıra bölme");
frame.slots[instr.dest] = Value::fromFloat(frame.slots[instr.left].floatValue / r);
break;
}
case Opcode::FNEG:
frame.slots[instr.dest] = Value::fromFloat(-frame.slots[instr.src].floatValue);
break;
case Opcode::INT_TO_FLOAT:
frame.slots[instr.dest] = Value::fromFloat((double)frame.slots[instr.src].intValue);
break;
case Opcode::FLOAT_TO_INT:
frame.slots[instr.dest] = Value::fromInt((int)frame.slots[instr.src].floatValue);
break;
// ── Struct (ADR-020: referans semantiği) ──────────────────────────
case Opcode::STRUCT_NEW: {
StructObject* obj = heap_.allocStruct(instr.intValue);
@ -280,8 +312,7 @@ void Interpreter::executeHostFunction(const std::string& name,
if (name == "print") {
if (!argSlots.empty()) {
const Value& val = slots[argSlots[0]];
if (val.kind == ValueKind::String) std::cout << val.stringValue << "\n";
else std::cout << val.intValue << "\n";
std::cout << val.toString() << "\n";
}
return;
}

View File

@ -2,6 +2,8 @@
#define SAQUT_VM_VALUE
#include <string>
#include <sstream>
#include <iomanip>
#include <stdexcept>
// Forward — Object tam tanımı object.hpp'de; Value onu pointer olarak taşır.
@ -10,25 +12,29 @@ struct Object;
// ADR-020: Primitive (int/bool) = değer; bileşik (array/struct/string) = referans.
// 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,
Float, // #44: float/double tek kind; floatValue alanı taşır
String,
Ref, // ADR-020: array/struct nesnesine Object* referansı
Null, // ADR-021: nullable referansın null değeri (saQut kaynağında `null`)
// Float, // TODO(#44)
Ref, // ADR-020: array/struct nesnesine Object* referansı
Null, // ADR-021: nullable referansın null değeri (saQut kaynağında `null`)
};
struct Value {
ValueKind kind = ValueKind::Int;
int intValue = 0;
std::string stringValue; // yalnızca kind == String
Object* ref = nullptr; // yalnızca kind == Ref
double floatValue = 0.0; // kind == Float için
std::string stringValue; // kind == String için
Object* ref = nullptr; // kind == Ref için
static Value fromInt(int n) {
Value v; v.kind = ValueKind::Int; v.intValue = n; return v;
}
static Value fromFloat(double d) {
Value v; v.kind = ValueKind::Float; v.floatValue = d; return v;
}
static Value fromString(std::string s) {
Value v; v.kind = ValueKind::String; v.stringValue = std::move(s); return v;
}
@ -41,10 +47,10 @@ struct Value {
Value v; v.kind = ValueKind::Null; return v;
}
// 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::Float: return floatValue != 0.0;
case ValueKind::String: return !stringValue.empty();
case ValueKind::Ref: return ref != nullptr;
case ValueKind::Null: return false;
@ -55,8 +61,18 @@ struct Value {
std::string toString() const {
switch (kind) {
case ValueKind::Int: return std::to_string(intValue);
case ValueKind::Float: {
// Tam sayıysa "3.0", değilse "3.14" gibi — gereksiz sıfırları kırp
std::ostringstream oss;
oss << std::setprecision(10) << floatValue;
std::string s = oss.str();
// Nokta yoksa ".0" ekle (saQut float değerleri her zaman nokta içerir)
if (s.find('.') == std::string::npos && s.find('e') == std::string::npos)
s += ".0";
return s;
}
case ValueKind::String: return stringValue;
case ValueKind::Ref: return "<array>";
case ValueKind::Ref: return "<ref>";
case ValueKind::Null: return "null";
}
return "?";
@ -65,8 +81,9 @@ struct Value {
std::string typeName() const {
switch (kind) {
case ValueKind::Int: return "int";
case ValueKind::Float: return "float";
case ValueKind::String: return "string";
case ValueKind::Ref: return "array";
case ValueKind::Ref: return "ref";
case ValueKind::Null: return "null";
}
return "?";

View File

@ -0,0 +1,5 @@
5.14
6.28
1.14
1.57
1.0

View File

@ -0,0 +1,11 @@
int main() {
float x = 3.14;
float y = 2.0;
print(x + y);
print(x * y);
print(x - y);
print(x / y);
float z = 1;
print(z);
return 0;
}