feat(ir-vm): fibonacci.sqt çalışıyor — IR üretici + bytecode VM

IR instruction seti (14 opcode): LOAD_CONST, LOAD_SLOT, ADD/SUB/MUL/DIV/MOD,
LESS/LEQ/GT/GEQ/EQ/NEQ, JMP, JIF_FALSE, CALL, RETURN, CALLHOST

IRGenerator (AST → IR):
  - Slot tabanlı register: parametreler slot 0..n, lokaller/geçiciler sonrası
  - freshSlot() monoton sayaç — slot asla geri verilmez
  - Backpatch: ileri-jump için emitJumpIfFalse(-1) → patchJump(idx)
  - Geri-jump: loopStart = currentInstrIndex() → emitJumpUnconditional(loopStart)
  - Builtin tespiti: resolvedSymbol->isBuiltin → CALLHOST

Interpreter (bytecode VM):
  - Her iterasyonda callStack.back() taze alınır (referans güvenliği)
  - CALL: yeni frame + argüman kopyası + continue
  - RETURN: değer caller slotuna, frame pop + continue
  - ip CALL/RETURN'den önce ilerler — caller doğru noktadan devam eder
  - DIV: sıfıra bölme → runtime_error

Doğrulama:
  build/saqut run file:examples/fibonacci.sqt → 55 / 55 ✓
  tests/run.sh → TUM TESTLER GECTI ✓
This commit is contained in:
saqut 2026-06-18 19:17:30 +03:00
parent e72a62ae2e
commit 3c76eab932
16 changed files with 1395 additions and 73 deletions

Binary file not shown.

View File

@ -1,37 +1,23 @@
# ninja log v7
1 6106 1781788886608927872 CMakeFiles/saqut.dir/src/core/sourcefile.cpp.o da6f5fc90e87e6b1
1 5577 1781794548935793844 CMakeFiles/saqut.dir/src/lexer/lexer.cpp.o 90eeec811f2137e6
2 1784 1781794795003026937 CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o a01677f8bb4f4dbc
6 7118 1781794548940793848 CMakeFiles/saqut.dir/src/parser/parser.cpp.o 2c65b7be26cead32
1 7781 1781794548937485082 CMakeFiles/saqut.dir/src/parser/nodes/program.cpp.o ac5bbcd74d87561a
1 7846 1781794548937277813 CMakeFiles/saqut.dir/src/parser/nodes/identifier.cpp.o eb96bb4b1eb4ad80
1 7401 1781794548937164958 CMakeFiles/saqut.dir/src/parser/nodes/expressions.cpp.o 4057e3d63c63a1ab
1 8355 1781794548937386639 CMakeFiles/saqut.dir/src/parser/nodes/literal.cpp.o 78f2c4da7c9b2281
5 7795 1781794548939793847 CMakeFiles/saqut.dir/src/parser/nodes/statements.cpp.o b5c20724bbf3648c
1 8702 1781794548936968496 CMakeFiles/saqut.dir/src/parser/nodes/binary_expr.cpp.o d2e2bb2f8a63c6d2
1 8623 1781794548937059964 CMakeFiles/saqut.dir/src/parser/nodes/declarations.cpp.o b6c56f04a257f685
1 9568 1781794548936880430 CMakeFiles/saqut.dir/src/main.cpp.o 110c26cb1d0c3a23
1784 2001 1781794796785028624 saqut 77cf84e33c34ab02
0 22 1781795949575516449 build.ninja 1876a59d627a585
0 22 1781795949575428471 /home/saqut/Masaüstü/saqutcompiler/build/cmake_install.cmake 1876a59d627a585
7401 11835 1781794556335800853 CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o 3348f498f369213d
5577 10556 1781794554511799126 CMakeFiles/saqut.dir/src/semantic/structural_validator.cpp.o 4bfec8abc0e9893e
7118 11087 1781794556053800586 CMakeFiles/saqut.dir/src/semantic/type_checker.cpp.o 15f44776b9c3e26d
13 4576 1781796718440262330 CMakeFiles/saqut.dir/src/core/sourcefile.cpp.o 20f68631dd335029
13 4805 1781796718441539505 CMakeFiles/saqut.dir/src/lexer/lexer.cpp.o 5fd259c0401f3e22
16 6700 1781796718443848488 CMakeFiles/saqut.dir/src/parser/nodes/identifier.cpp.o e3b5b38d75fcd2ca
15 6733 1781796718442847556 CMakeFiles/saqut.dir/src/parser/nodes/declarations.cpp.o c3d262615ede4c95
14 6864 1781796718442362341 CMakeFiles/saqut.dir/src/parser/nodes/binary_expr.cpp.o 5cc8b697133bcf64
21 6876 1781796718448262357 CMakeFiles/saqut.dir/src/parser/nodes/program.cpp.o 9313cba8d8daffed
21 7502 1781796718449424977 CMakeFiles/saqut.dir/src/parser/nodes/statements.cpp.o 3c8869307381c930
16 7589 1781796718444498682 CMakeFiles/saqut.dir/src/parser/nodes/literal.cpp.o 55743f37408c5f
15 7814 1781796718443331391 CMakeFiles/saqut.dir/src/parser/nodes/expressions.cpp.o 5f5bb01381a3c3ad
14 8751 1781796718441949529 CMakeFiles/saqut.dir/src/main.cpp.o 3cfef7a665d5bf87
1 2117 1781796771817437208 CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o fbabe80dcb141239
4580 9322 1781796723007277591 CMakeFiles/saqut.dir/src/parser/parser.cpp.o 59cdb5935c541b26
6864 9502 1781796725291285201 CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o fbabe80dcb141239
21 6876 1781796718448262357 CMakeFiles/saqut.dir/src/parser/nodes/program.cpp.o 9313cba8d8daffed
16 6700 1781796718443848488 CMakeFiles/saqut.dir/src/parser/nodes/identifier.cpp.o e3b5b38d75fcd2ca
15 7814 1781796718443331391 CMakeFiles/saqut.dir/src/parser/nodes/expressions.cpp.o 5f5bb01381a3c3ad
16 7589 1781796718444498682 CMakeFiles/saqut.dir/src/parser/nodes/literal.cpp.o 55743f37408c5f
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 4655 1781799345769869911 CMakeFiles/saqut.dir/src/main.cpp.o 3cfef7a665d5bf87
4655 4925 1781799350422111935 saqut f2e198803c4dbffb
0 22 1781799395548855747 build.ninja 1876a59d627a585
0 22 1781799395548855747 /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
6733 11112 1781796725160284765 CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o ec4e483b8ddb4007
11112 11349 1781796729539299317 saqut a8f6b7bef23ca761
1 2117 1781796771817437208 CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o fbabe80dcb141239
2117 2374 1781796773933443989 saqut a8f6b7bef23ca761
2 795 1781799345769990010 CMakeFiles/saqut.dir/src/vm/interpreter.cpp.o b7dd80e002d68a1d
1 958 1781799106946635459 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

View File

@ -20,7 +20,8 @@ CMAKE_ADDR2LINE:FILEPATH=/usr/bin/addr2line
//Path to a program.
CMAKE_AR:FILEPATH=/usr/bin/ar
//No help, variable specified on the command line.
//Choose the type of build, options are: None Debug Release RelWithDebInfo
// MinSizeRel ...
CMAKE_BUILD_TYPE:STRING=Debug
//No help, variable specified on the command line.

View File

@ -58,6 +58,33 @@ build CMakeFiles/saqut.dir/src/core/sourcefile.cpp.o: CXX_COMPILER__saqut_unscan
OBJECT_FILE_DIR = CMakeFiles/saqut.dir/src/core
TARGET_SUPPORT_DIR = CMakeFiles/saqut.dir
build CMakeFiles/saqut.dir/src/ir/ir_function.cpp.o: CXX_COMPILER__saqut_unscanned_Debug /home/saqut/Masaüstü/saqutcompiler/src/ir/ir_function.cpp || cmake_object_order_depends_target_saqut
CONFIG = Debug
DEP_FILE = CMakeFiles/saqut.dir/src/ir/ir_function.cpp.o.d
FLAGS = -g -std=gnu++20 -Wall -Wextra -g -O0
INCLUDES = -I/home/saqut/Masaüstü/saqutcompiler/src
OBJECT_DIR = CMakeFiles/saqut.dir
OBJECT_FILE_DIR = CMakeFiles/saqut.dir/src/ir
TARGET_SUPPORT_DIR = CMakeFiles/saqut.dir
build CMakeFiles/saqut.dir/src/ir/ir_generator.cpp.o: CXX_COMPILER__saqut_unscanned_Debug /home/saqut/Masaüstü/saqutcompiler/src/ir/ir_generator.cpp || cmake_object_order_depends_target_saqut
CONFIG = Debug
DEP_FILE = CMakeFiles/saqut.dir/src/ir/ir_generator.cpp.o.d
FLAGS = -g -std=gnu++20 -Wall -Wextra -g -O0
INCLUDES = -I/home/saqut/Masaüstü/saqutcompiler/src
OBJECT_DIR = CMakeFiles/saqut.dir
OBJECT_FILE_DIR = CMakeFiles/saqut.dir/src/ir
TARGET_SUPPORT_DIR = CMakeFiles/saqut.dir
build CMakeFiles/saqut.dir/src/ir/ir_program.cpp.o: CXX_COMPILER__saqut_unscanned_Debug /home/saqut/Masaüstü/saqutcompiler/src/ir/ir_program.cpp || cmake_object_order_depends_target_saqut
CONFIG = Debug
DEP_FILE = CMakeFiles/saqut.dir/src/ir/ir_program.cpp.o.d
FLAGS = -g -std=gnu++20 -Wall -Wextra -g -O0
INCLUDES = -I/home/saqut/Masaüstü/saqutcompiler/src
OBJECT_DIR = CMakeFiles/saqut.dir
OBJECT_FILE_DIR = CMakeFiles/saqut.dir/src/ir
TARGET_SUPPORT_DIR = CMakeFiles/saqut.dir
build CMakeFiles/saqut.dir/src/lexer/lexer.cpp.o: CXX_COMPILER__saqut_unscanned_Debug /home/saqut/Masaüstü/saqutcompiler/src/lexer/lexer.cpp || cmake_object_order_depends_target_saqut
CONFIG = Debug
DEP_FILE = CMakeFiles/saqut.dir/src/lexer/lexer.cpp.o.d
@ -184,6 +211,15 @@ build CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o: CXX_COMPILER__saqut_un
OBJECT_FILE_DIR = CMakeFiles/saqut.dir/src/tokenizer
TARGET_SUPPORT_DIR = CMakeFiles/saqut.dir
build CMakeFiles/saqut.dir/src/vm/interpreter.cpp.o: CXX_COMPILER__saqut_unscanned_Debug /home/saqut/Masaüstü/saqutcompiler/src/vm/interpreter.cpp || cmake_object_order_depends_target_saqut
CONFIG = Debug
DEP_FILE = CMakeFiles/saqut.dir/src/vm/interpreter.cpp.o.d
FLAGS = -g -std=gnu++20 -Wall -Wextra -g -O0
INCLUDES = -I/home/saqut/Masaüstü/saqutcompiler/src
OBJECT_DIR = CMakeFiles/saqut.dir
OBJECT_FILE_DIR = CMakeFiles/saqut.dir/src/vm
TARGET_SUPPORT_DIR = CMakeFiles/saqut.dir
# =============================================================================
# Link build statements for EXECUTABLE target saqut
@ -192,7 +228,7 @@ build CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o: CXX_COMPILER__saqut_un
#############################################
# Link the executable saqut
build saqut: CXX_EXECUTABLE_LINKER__saqut_Debug CMakeFiles/saqut.dir/src/core/sourcefile.cpp.o CMakeFiles/saqut.dir/src/lexer/lexer.cpp.o CMakeFiles/saqut.dir/src/main.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/binary_expr.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/declarations.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/expressions.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/identifier.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/literal.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/program.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/statements.cpp.o CMakeFiles/saqut.dir/src/parser/parser.cpp.o CMakeFiles/saqut.dir/src/semantic/structural_validator.cpp.o CMakeFiles/saqut.dir/src/semantic/type_checker.cpp.o CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o
build saqut: CXX_EXECUTABLE_LINKER__saqut_Debug CMakeFiles/saqut.dir/src/core/sourcefile.cpp.o CMakeFiles/saqut.dir/src/ir/ir_function.cpp.o CMakeFiles/saqut.dir/src/ir/ir_generator.cpp.o CMakeFiles/saqut.dir/src/ir/ir_program.cpp.o CMakeFiles/saqut.dir/src/lexer/lexer.cpp.o CMakeFiles/saqut.dir/src/main.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/binary_expr.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/declarations.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/expressions.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/identifier.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/literal.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/program.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/statements.cpp.o CMakeFiles/saqut.dir/src/parser/parser.cpp.o CMakeFiles/saqut.dir/src/semantic/structural_validator.cpp.o CMakeFiles/saqut.dir/src/semantic/type_checker.cpp.o CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o CMakeFiles/saqut.dir/src/vm/interpreter.cpp.o
CONFIG = Debug
DEP_FILE = CMakeFiles/saqut.dir/link.d
FLAGS = -g

View File

@ -1,10 +1,13 @@
// ============================================================================
// saQut CLI — run komutu (pipeline: token → AST → IR debug)
// ============================================================================
// saQut CLI — run komutu
//
// TODO: İleride `saqut -` ile stdin'den okuyup anında çalıştıracak
// interpreter modu bu komutun altına gelecek.
// Tam derleme + çalıştırma pipeline'ı:
// tokenize → parse → sembol topla → IR üret → VM çalıştır
//
// Başarı kriteri:
// build/saqut run file:examples/fibonacci.sqt
// → 55
// → 55
// ============================================================================
#ifndef SAQUT_CLI_RUN
@ -14,53 +17,61 @@
#include "cli/args.hpp"
#include "tokenizer/tokenizer.hpp"
#include "parser/parser.hpp"
#include "ir/ir.hpp"
#include "symbol/symbol_table.hpp"
#include "symbol/symbol_collector.hpp"
#include "diagnostic/diagnostic_engine.hpp"
#include "ir/ir_generator.hpp"
#include "vm/interpreter.hpp"
inline int cmdRun(const CliArgs& args) {
std::string source = readSource(args);
std::string filePath = inputFilePath(args);
std::string source = readSource(args);
if (source.empty()) return 1;
// ── Aşama 1: Tokenize ────────────────────────────────────────────────
Tokenizer tokenizer;
auto tokens = tokenizer.scan(source, inputFilePath(args));
std::cout << "=== saQut Compiler ===\n";
std::cout << "Kaynak kod:\n" << source << "\n\n";
std::cout << "Tokenler (" << tokens.size() << " adet):\n";
for (auto* t : tokens) {
std::cout << " [" << t->gettype() << "] \"" << t->token << "\"\n";
}
std::cout << "\n";
auto tokens = tokenizer.scan(source, filePath);
// ── Aşama 2: Parse ───────────────────────────────────────────────────
Parser parser;
ASTNode* ast = parser.parse(tokens);
if (ast) {
std::cout << "AST:\n";
ast->log(0);
std::cout << "\n";
CodeGenerator cg;
cg.parse(ast);
std::cout << "IR (" << cg.IROpDatas.size() << " komut):\n";
for (size_t i = 0; i < cg.IROpDatas.size(); i++) {
auto& op = cg.IROpDatas[i];
std::cout << " [" << i << "] reg" << op.targetReg << " = ";
switch (op.op) {
case OPCode::mathadd: std::cout << "add"; break;
case OPCode::mathsub: std::cout << "sub"; break;
case OPCode::mathmul: std::cout << "mul"; break;
case OPCode::mathdiv: std::cout << "div"; break;
case OPCode::declare: std::cout << "literal"; break;
}
std::cout << " (" << op.arg1.value.index() << ")\n";
}
delete ast;
if (!ast) {
std::cerr << "Hata: AST üretilemedi\n";
for (auto* t : tokens) delete t;
return 1;
}
// ── Aşama 3: Sembol toplama ───────────────────────────────────────────
// Identifier'ların resolvedSymbol'ü doldurulur — IR generator buna ihtiyaç duyar.
SymbolTable symbolTable;
DiagnosticEngine diag;
SymbolCollector(symbolTable, diag).collect(ast);
if (diag.hasErrors()) {
std::cerr << "Derleme hataları var, program çalıştırılamaz:\n";
diag.printAll(std::cerr);
delete ast;
for (auto* t : tokens) delete t;
return 1;
}
// ── Aşama 4: IR üretimi ───────────────────────────────────────────────
IRGenerator irGenerator;
IRProgram program = irGenerator.generate(ast, symbolTable);
// ── Aşama 5: VM çalıştırma ────────────────────────────────────────────
int exitCode = 0;
try {
Interpreter vm(program);
exitCode = vm.run();
} catch (const std::exception& e) {
std::cerr << "Çalışma zamanı hatası: " << e.what() << "\n";
exitCode = 1;
}
delete ast;
for (auto* t : tokens) delete t;
return 0;
return exitCode;
}
#endif // SAQUT_CLI_RUN

143
src/ir/instruction.hpp Normal file
View File

@ -0,0 +1,143 @@
// ============================================================================
// saQut IR — Instruction (Tek Talimat)
//
// Sanal makine bu talimatlara bakarak ne yapacağını anlar.
// Her talimatın bir "opcode"u (ne iş yapacağı) ve birkaç operandı vardır.
// Operandlar ya slot numarasıdır (fonksiyonun yerel değişken/geçici depoları)
// ya da doğrudan bir sayı/isim değeridir.
//
// SLOT NEDİR?
// Her fonksiyon çağrısı kendi "frame"ini açar.
// Frame içinde numaralı kutucuklar vardır: slot[0], slot[1], ...
// Parametreler slot 0'dan başlar. Sonrasında lokal değişkenler
// ve hesaplama sırasında oluşan geçici değerler gelir.
// "slots[5] = 42" demek "5 numaralı kutucuğa 42 değerini koy" demektir.
//
// HANGİ OPCODE HANGİ ALANI KULLANIR?
// LOAD_CONST : dest, intValue
// LOAD_SLOT : dest, src
// ADD/SUB/... : dest, left, right
// LESS/LEQ/... : dest, left, right (sonuç: 1=doğru, 0=yanlış)
// JMP : jumpTarget
// JIF_FALSE : cond, jumpTarget
// CALL : dest, functionName, argSlots
// RETURN : src
// CALLHOST : functionName, argSlots
// ============================================================================
#ifndef SAQUT_IR_INSTRUCTION
#define SAQUT_IR_INSTRUCTION
#include <string>
#include <vector>
// ----------------------------------------------------------------------------
// Opcode — Sanal Makinenin Anlayacağı İşlem Kodları
// ----------------------------------------------------------------------------
enum class Opcode {
// --- Değer yükleme ---
LOAD_CONST, // slots[dest] = intValue
// Örnek: LOAD_CONST dest=3 val=10 → slot[3] = 10
LOAD_SLOT, // slots[dest] = slots[src]
// Bir slotun değerini başka bir slota kopyalar.
// Atama işlemlerinde (x = y) kullanılır.
// --- Aritmetik (tümü: slots[dest] = slots[left] OP slots[right]) ---
ADD,
SUB,
MUL,
DIV, // UYARI: sıfıra bölme → runtime_error fırlatılır
MOD,
// --- Karşılaştırma (sonuç: 1 = doğru, 0 = yanlış) ---
LESS, // slots[left] < slots[right]
LESS_EQUAL, // slots[left] <= slots[right]
GREATER, // slots[left] > slots[right]
GREATER_EQUAL, // slots[left] >= slots[right]
EQUAL_EQUAL, // slots[left] == slots[right]
NOT_EQUAL, // slots[left] != slots[right]
// --- Kontrol akışı ---
JMP, // Koşulsuz atlama: ip = jumpTarget
JIF_FALSE, // Koşullu atlama: slots[cond] == 0 ise ip = jumpTarget
// --- Fonksiyon çağrısı ---
CALL, // Başka bir saQut fonksiyonunu çağır.
// Yeni frame açılır, argümanlar parametre slotlarına kopyalanır.
// Fonksiyon RETURN ile bitince sonuç slots[dest]'e yazılır.
RETURN, // Bu frame'i kapat, slots[src]'yi caller'a ilet.
// --- Dış dünya (FFI — Foreign Function Interface) ---
CALLHOST, // Host (C++) fonksiyonunu çağır. Şu an sadece "print" destekli.
// Dönüş değeri yok; sadece yan etki (stdout'a yazmak gibi).
};
// Hata ayıklama ve IR dump için okunabilir isim
inline const char* opcodeName(Opcode op) {
switch (op) {
case Opcode::LOAD_CONST: return "LOAD_CONST";
case Opcode::LOAD_SLOT: return "LOAD_SLOT";
case Opcode::ADD: return "ADD";
case Opcode::SUB: return "SUB";
case Opcode::MUL: return "MUL";
case Opcode::DIV: return "DIV";
case Opcode::MOD: return "MOD";
case Opcode::LESS: return "LESS";
case Opcode::LESS_EQUAL: return "LESS_EQUAL";
case Opcode::GREATER: return "GREATER";
case Opcode::GREATER_EQUAL: return "GREATER_EQUAL";
case Opcode::EQUAL_EQUAL: return "EQUAL_EQUAL";
case Opcode::NOT_EQUAL: return "NOT_EQUAL";
case Opcode::JMP: return "JMP";
case Opcode::JIF_FALSE: return "JIF_FALSE";
case Opcode::CALL: return "CALL";
case Opcode::RETURN: return "RETURN";
case Opcode::CALLHOST: return "CALLHOST";
}
return "UNKNOWN";
}
// ----------------------------------------------------------------------------
// Instruction — Tek bir IR talimatı
//
// Okunabilirlik öncelikli bir tasarım: her talimat TÜM alanları içerir,
// kullanılmayanlar varsayılan değerde (-1 veya boş) kalır.
// Bu yaklaşım bellek israfeder ama her talimatın hangi veriyle çalıştığı
// açıkça görünür — karmaşık union/variant yapısı gerekmez.
// ----------------------------------------------------------------------------
struct Instruction {
Opcode opcode;
// Hedef slot — sonucun yazılacağı yer (LOAD_CONST, ADD, CALL vb.)
int dest = -1;
// Kaynak slot — kopyalama veya döndürme için (LOAD_SLOT, RETURN)
int src = -1;
// Aritmetik/karşılaştırma operandları
int left = -1;
int right = -1;
// LOAD_CONST için yüklenecek sabit değer
int intValue = 0;
// JMP / JIF_FALSE için hedef instruction indeksi
// Üretim sırasında bilinmiyorsa -1 bırakılır, sonradan doldurulur (backpatch).
int jumpTarget = -1;
// JIF_FALSE için kontrol edilecek koşul slotu
int cond = -1;
// CALL / CALLHOST için çağrılacak fonksiyonun adı
std::string functionName;
// CALL / CALLHOST için argüman slot indeksleri (sırayla)
std::vector<int> argSlots;
explicit Instruction(Opcode op) : opcode(op) {}
};
#endif // SAQUT_IR_INSTRUCTION

63
src/ir/ir_function.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "ir/ir_function.hpp"
#include <iomanip>
#include <iostream>
// Her instruction'ı "indeks: OPCODE operandlar" formatında yazdır.
// Bu çıktı hem insanın okuduğu hem de birim testlerin karşılaştırdığı formattır.
void IRFunction::dump() const {
std::cout << "=== " << name
<< " (paramCount=" << paramCount
<< ", slotCount=" << slotCount << ") ===\n";
for (int i = 0; i < (int)instructions.size(); i++) {
const Instruction& ins = instructions[i];
std::cout << " " << std::setw(3) << i << ": "
<< std::left << std::setw(14) << opcodeName(ins.opcode);
switch (ins.opcode) {
case Opcode::LOAD_CONST:
std::cout << "slot[" << ins.dest << "] = " << ins.intValue;
break;
case Opcode::LOAD_SLOT:
std::cout << "slot[" << ins.dest << "] = slot[" << ins.src << "]";
break;
case Opcode::ADD: case Opcode::SUB: case Opcode::MUL:
case Opcode::DIV: case Opcode::MOD:
case Opcode::LESS: case Opcode::LESS_EQUAL:
case Opcode::GREATER: case Opcode::GREATER_EQUAL:
case Opcode::EQUAL_EQUAL: case Opcode::NOT_EQUAL:
std::cout << "slot[" << ins.dest << "] = "
<< "slot[" << ins.left << "] op slot[" << ins.right << "]";
break;
case Opcode::JMP:
std::cout << "" << ins.jumpTarget;
break;
case Opcode::JIF_FALSE:
std::cout << "if !slot[" << ins.cond << "] → " << ins.jumpTarget;
break;
case Opcode::CALL: {
std::cout << "slot[" << ins.dest << "] = " << ins.functionName << "(";
for (int j = 0; j < (int)ins.argSlots.size(); j++) {
if (j) std::cout << ", ";
std::cout << "slot[" << ins.argSlots[j] << "]";
}
std::cout << ")";
break;
}
case Opcode::RETURN:
std::cout << "slot[" << ins.src << "]";
break;
case Opcode::CALLHOST: {
std::cout << ins.functionName << "(";
for (int j = 0; j < (int)ins.argSlots.size(); j++) {
if (j) std::cout << ", ";
std::cout << "slot[" << ins.argSlots[j] << "]";
}
std::cout << ")";
break;
}
}
std::cout << "\n";
}
std::cout << "\n";
}

37
src/ir/ir_function.hpp Normal file
View File

@ -0,0 +1,37 @@
// ============================================================================
// saQut IR — IRFunction (Tek Fonksiyonun IR Karşılığı)
//
// Bir IRFunction, kaynak koddaki tek bir fonksiyonun "pişmiş" halidir.
// IRGenerator bu yapıyı doldurur, Interpreter bu yapıyı çalıştırır.
//
// SLOT DÜZENI:
// slot[0 .. paramCount-1] → parametreler (soldan sağa)
// slot[paramCount ..] → lokal değişkenler ve geçici sonuçlar
// slotCount → toplam kaç slot lazım (frame boyutu)
//
// Örnek — fibonacci(int n):
// paramCount = 1 → slot[0] = n
// slotCount = 11 → slot[0..10] (0'ı parametre, 1-10 hesaplamalar)
// ============================================================================
#ifndef SAQUT_IR_FUNCTION
#define SAQUT_IR_FUNCTION
#include <string>
#include <vector>
#include "ir/instruction.hpp"
struct IRFunction {
std::string name; // kaynak koddaki fonksiyon adı
int paramCount; // kaç parametresi var
int slotCount; // frame boyutu (üretim sonunda doldurulur)
std::vector<Instruction> instructions; // bu fonksiyonun talimat listesi
IRFunction(std::string name, int paramCount)
: name(std::move(name)), paramCount(paramCount), slotCount(0) {}
// Okunabilir IR dump — "saqut run" hata ayıklaması veya inceleme için
void dump() const;
};
#endif // SAQUT_IR_FUNCTION

551
src/ir/ir_generator.cpp Normal file
View File

@ -0,0 +1,551 @@
#include "ir/ir_generator.hpp"
#include "parser/nodes/program.hpp"
#include "parser/nodes/declarations.hpp"
#include "parser/nodes/statements.hpp"
#include "parser/nodes/expressions.hpp"
#include "parser/nodes/binary_expr.hpp"
#include "parser/nodes/identifier.hpp"
#include "parser/nodes/literal.hpp"
#include <stdexcept>
#include <string>
// ─────────────────────────────────────────────────────────────────────────────
// generate — Ana giriş noktası
// ─────────────────────────────────────────────────────────────────────────────
IRProgram IRGenerator::generate(ASTNode* programNode, SymbolTable& /*symbolTable*/) {
IRProgram program;
// ProgramNode'un her çocuğunu gez.
// Bizi ilgilendiren: FunctionDecl. StructDecl/GlobalVar → TODO.
for (ASTNode* child : programNode->getChildren()) {
if (child->kind == ASTKind::FunctionDecl) {
// Her fonksiyon üretimi için sıfırla
nameToSlot_.clear();
nextSlot_ = 0;
// IRFunction oluştur, currentFunction_ olarak işaretle
auto* fnDecl = (FunctionDeclNode*)child;
IRFunction irFn(fnDecl->name, (int)fnDecl->params.size());
program.addFunction(std::move(irFn));
// addFunction std::move yaptığı için pointer'ı haritadan alalım
currentFunction_ = program.findFunction(fnDecl->name);
generateFunction(child);
// Fonksiyon bitti — toplam slot sayısını kaydet
currentFunction_->slotCount = nextSlot_;
}
}
return program;
}
// ─────────────────────────────────────────────────────────────────────────────
// generateFunction — Tek bir fonksiyonu IR'a çevirir
// ─────────────────────────────────────────────────────────────────────────────
void IRGenerator::generateFunction(ASTNode* functionDeclNode) {
auto* fn = (FunctionDeclNode*)functionDeclNode;
// Parametreler slot 0, 1, 2, ... sırasıyla alır.
// Interpreter, CALL sırasında bu slotlara argümanları kopyalar.
for (auto* param : fn->params) {
int slot = freshSlot();
registerVariable(param->name, slot);
}
// Fonksiyon gövdesi — children[0] her zaman BlockNode
auto& children = fn->getChildren();
if (!children.empty()) {
generateStatement(children[0]);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// generateStatement — Deyim türlerine göre talimat üret
// ─────────────────────────────────────────────────────────────────────────────
void IRGenerator::generateStatement(ASTNode* node) {
if (!node) return;
switch (node->kind) {
// ── Blok: içindeki her deyimi sırayla üret ───────────────────────────
case ASTKind::Block: {
for (ASTNode* child : node->getChildren()) {
generateStatement(child);
}
break;
}
// ── Değişken bildirimi: int x = <ifade> ──────────────────────────────
case ASTKind::VariableDecl: {
auto* vd = (VariableDeclNode*)node;
// Bu değişken için yeni bir slot ayır
int varSlot = freshSlot();
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
}
// Sibling VariableDecl'ler: int a, b; → children'da diğer VariableDecl'ler
for (ASTNode* sib : node->getChildren()) {
if (sib->kind == ASTKind::VariableDecl) {
generateStatement(sib);
}
}
break;
}
// ── return <ifade> ───────────────────────────────────────────────────
case ASTKind::ReturnStatement: {
auto* rs = (ReturnStatementNode*)node;
int returnSlot = 0; // varsayılan: slot[0] (void fonksiyon / boş return)
if (rs->value) {
returnSlot = generateExpression(rs->value);
}
emitReturn(returnSlot);
break;
}
// ── if (koşul) { ... } [else { ... }] ───────────────────────────────
case ASTKind::IfStatement: {
auto* ifn = (IfStatementNode*)node;
// Koşulu hesapla
int condSlot = generateExpression(ifn->condition);
// "Koşul yanlışsa atla" → hedef henüz bilinmiyor, backpatch bekliyor
int jumpToElse = emitJumpIfFalse(condSlot);
// Then bloğu
if (ifn->thenBranch) generateStatement(ifn->thenBranch);
if (ifn->elseBranch) {
// Then bitti, else'i atla (then içinde çalışanlar else'e girmemeli)
int jumpOverElse = emitJumpUnconditional(-1);
// Şimdi else'in başlangıç konumunu biliyoruz → jumpToElse'i doldur
patchJump(jumpToElse);
generateStatement(ifn->elseBranch);
// Else bitti → jumpOverElse'i doldur
patchJump(jumpOverElse);
} else {
// Else yok → jumpToElse doğrudan if sonrasına atlıyor
patchJump(jumpToElse);
}
break;
}
// ── while (koşul) { gövde } ──────────────────────────────────────────
case ASTKind::WhileStatement: {
auto* ws = (WhileStatementNode*)node;
// Döngü başının konumu — geri-jump buraya gelecek
int loopStart = currentInstrIndex();
int condSlot = generateExpression(ws->condition);
int exitJump = emitJumpIfFalse(condSlot); // ileri, backpatch bekliyor
if (ws->body) generateStatement(ws->body);
// Geri-jump: hedef zaten biliniyor (loopStart)
emitJumpUnconditional(loopStart);
// Döngü çıkış noktası → exitJump'ı doldur
patchJump(exitJump);
break;
}
// ── for (init; koşul; güncelleme) { gövde } ─────────────────────────
//
// Üretilen IR yapısı:
// [init]
// LOOP_START:
// [koşul] → condSlot
// JIF_FALSE condSlot → LOOP_END (ileri-jump, backpatch)
// [gövde]
// [güncelleme]
// JMP → LOOP_START (geri-jump, hedef biliniyor)
// LOOP_END:
// ─────────────────────────────────────────────────────────────────────
case ASTKind::ForStatement: {
auto* fs = (ForStatementNode*)node;
// Init: genellikle "int i = 0" gibi bir VariableDecl
if (fs->init) generateStatement(fs->init);
// Döngü başı konumu — geri-jump'ın hedefi
int loopStart = currentInstrIndex();
// Koşul
int condSlot = fs->condition ? generateExpression(fs->condition) : -1;
int exitJump = (condSlot != -1) ? emitJumpIfFalse(condSlot) : -1;
// Gövde
if (fs->body) generateStatement(fs->body);
// Güncelleme (ör: i = i + 1) — ifade deyimi, sonuç önemsiz
if (fs->update) generateExpression(fs->update);
// Geri-jump: hedef loopStart, zaten biliniyor
emitJumpUnconditional(loopStart);
// Döngü çıkışı → exitJump'ı doldur
if (exitJump != -1) patchJump(exitJump);
break;
}
// ── do { gövde } while (koşul) ───────────────────────────────────────
case ASTKind::DoWhileStatement: {
auto* dw = (DoWhileStatementNode*)node;
int loopStart = currentInstrIndex();
if (dw->body) generateStatement(dw->body);
int condSlot = generateExpression(dw->condition);
// Koşul doğruysa geri atla (1 = doğru → atla; 0 = yanlış → devam)
// JIF_FALSE koşul yanlışsa atlar; biz doğruysa atlamak istiyoruz.
// Bu yüzden JIF_FALSE yerine "doğruysa atla" mantığı lazım.
// Basit çözüm: koşulun tersini al (0→1, diğer→0) ve JIF_FALSE kullan.
// NOT: saQut'ta "!" operatörü yok henüz; NOT talimatı eklenebilir.
// Şimdilik: koşul slotuna bak, sıfır değilse geri atla.
// TODO(vm-genişletme): JIF_TRUE talimatı ekle
// Geçici çözüm: sabit 1 ile karşılaştır (condSlot != 0 → geri)
int oneSlot = freshSlot();
emitLoadConst(oneSlot, 1);
int eqSlot = freshSlot();
emitBinaryOp(Opcode::EQUAL_EQUAL, eqSlot, condSlot, oneSlot);
int skipJump = emitJumpIfFalse(eqSlot); // koşul yanlışsa döngüden çık
emitJumpUnconditional(loopStart); // geri atla
patchJump(skipJump);
break;
}
// ── İfade deyimi: bir ifadeyi değerlendirip sonucu at ────────────────
// Örnek: print(x) çağrısı, veya x = 5 ataması
case ASTKind::ExpressionStatement: {
auto* es = (ExpressionStatementNode*)node;
if (es->expression) {
generateExpression(es->expression); // sonucu kullanmıyoruz
}
break;
}
case ASTKind::BreakStatement:
case ASTKind::ContinueStatement:
// TODO(vm-genişletme): break/continue için JMP + label mekanizması gerekir
break;
default:
break;
}
}
// ─────────────────────────────────────────────────────────────────────────────
// generateExpression — İfadeyi IR'a çevirir, sonucu içeren slot'u döndürür
// ─────────────────────────────────────────────────────────────────────────────
int IRGenerator::generateExpression(ASTNode* node) {
if (!node) return 0;
switch (node->kind) {
// ── Sabit değer: 42, 3.14, true ... ──────────────────────────────────
case ASTKind::Literal: {
auto* lit = (LiteralNode*)node;
int slot = freshSlot();
switch (lit->literalType) {
case LiteralType::INTEGER: {
// Sayı metnini int'e çevir
int value = 0;
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);
break;
}
return slot;
}
// ── Değişken ismi: n, first, second ... ──────────────────────────────
// Bu değişkenin değeri zaten bir slotta. O slotu döndür.
case ASTKind::Identifier: {
auto* id = (IdentifierNode*)node;
std::string name = id->parserToken.token ? id->parserToken.token->token : "";
// Önce builtin mi? (print gibi) — identifier olarak gelen builtin fonksiyon
// çağrıları CallExpression içinde yakalanıyor, burada sadece değişken kalır
return lookupVariable(name);
}
// ── İkili ifade: x + y, x = y, x < y ... ────────────────────────────
case ASTKind::BinaryExpression: {
auto* bin = (BinaryExpressionNode*)node;
// Atama operatörleri: x = expr, x += expr ...
// Sol taraf bir değişken, sağ taraf hesaplanır ve o değişkene yazılır.
if (bin->Operator == TokenType::EQUAL) {
// Sağ tarafı hesapla
int rhsSlot = generateExpression(bin->Right);
// Sol taraf değişkenin slotunu bul
auto* lhsId = (IdentifierNode*)bin->Left;
std::string varName = lhsId->parserToken.token->token;
int varSlot = lookupVariable(varName);
// Sonucu değişkenin slotuna kopyala
if (rhsSlot != varSlot) {
emitLoadSlot(varSlot, rhsSlot);
}
return varSlot;
}
// Birleşik atama: += -= *= /=
// x += y ≡ x = x + y
if (bin->Operator == TokenType::PLUS_EQUAL ||
bin->Operator == TokenType::MINUS_EQUAL ||
bin->Operator == TokenType::STAR_EQUAL ||
bin->Operator == TokenType::SLASH_EQUAL) {
auto* lhsId = (IdentifierNode*)bin->Left;
std::string varName = lhsId->parserToken.token->token;
int varSlot = lookupVariable(varName);
int rhsSlot = generateExpression(bin->Right);
Opcode arithOp = Opcode::ADD;
if (bin->Operator == TokenType::MINUS_EQUAL) arithOp = Opcode::SUB;
else if (bin->Operator == TokenType::STAR_EQUAL) arithOp = Opcode::MUL;
else if (bin->Operator == TokenType::SLASH_EQUAL) arithOp = Opcode::DIV;
int resultSlot = freshSlot();
emitBinaryOp(arithOp, resultSlot, varSlot, rhsSlot);
emitLoadSlot(varSlot, resultSlot);
return varSlot;
}
// Unary prefix: Left = nullptr (ör: -x, !x)
if (!bin->Left) {
int operandSlot = generateExpression(bin->Right);
int resultSlot = freshSlot();
if (bin->Operator == TokenType::MINUS) {
// -x → 0 - x
int zeroSlot = freshSlot();
emitLoadConst(zeroSlot, 0);
emitBinaryOp(Opcode::SUB, resultSlot, zeroSlot, operandSlot);
} else {
// Diğer unary operatörler → TODO
emitLoadSlot(resultSlot, operandSlot);
}
return resultSlot;
}
// Aritmetik operatörler
switch (bin->Operator) {
case TokenType::PLUS: return generateBinaryArithmetic(Opcode::ADD, bin->Left, bin->Right);
case TokenType::MINUS: return generateBinaryArithmetic(Opcode::SUB, bin->Left, bin->Right);
case TokenType::STAR: return generateBinaryArithmetic(Opcode::MUL, bin->Left, bin->Right);
case TokenType::SLASH: return generateBinaryArithmetic(Opcode::DIV, bin->Left, bin->Right);
case TokenType::PERCENT: return generateBinaryArithmetic(Opcode::MOD, bin->Left, bin->Right);
// Karşılaştırma operatörleri
case TokenType::LESS: return generateBinaryArithmetic(Opcode::LESS, bin->Left, bin->Right);
case TokenType::LESS_EQUAL: return generateBinaryArithmetic(Opcode::LESS_EQUAL, bin->Left, bin->Right);
case TokenType::GREATER: return generateBinaryArithmetic(Opcode::GREATER, bin->Left, bin->Right);
case TokenType::GREATER_EQUAL: return generateBinaryArithmetic(Opcode::GREATER_EQUAL, bin->Left, bin->Right);
case TokenType::EQUAL_EQUAL: return generateBinaryArithmetic(Opcode::EQUAL_EQUAL, bin->Left, bin->Right);
case TokenType::BANG_EQUAL: return generateBinaryArithmetic(Opcode::NOT_EQUAL, bin->Left, bin->Right);
default: {
// Bilinmeyen operatör — boş slot döndür
int slot = freshSlot();
emitLoadConst(slot, 0);
return slot;
}
}
}
// ── Fonksiyon çağrısı: fibonacci(n-1), print(x) ... ─────────────────
case ASTKind::Call: {
auto* call = (CallExpressionNode*)node;
// Hangi fonksiyon çağrılıyor? Callee bir Identifier
std::string fnName;
bool isBuiltin = false;
if (call->callee && call->callee->kind == ASTKind::Identifier) {
auto* calleeId = (IdentifierNode*)call->callee;
if (calleeId->parserToken.token) {
fnName = calleeId->parserToken.token->token;
}
// Builtin kontrolü: resolvedSymbol->isBuiltin
if (calleeId->resolvedSymbol && calleeId->resolvedSymbol->isBuiltin) {
isBuiltin = true;
}
}
// Her argümanı hesapla, sonuçların slot numaralarını topla
std::vector<int> argSlots;
for (ASTNode* arg : call->arguments) {
argSlots.push_back(generateExpression(arg));
}
if (isBuiltin) {
// CALLHOST: host (C++) fonksiyonu çağır (print gibi), dönüş değeri yok
Instruction ins(Opcode::CALLHOST);
ins.functionName = fnName;
ins.argSlots = argSlots;
currentFunction_->instructions.push_back(std::move(ins));
return -1; // Dönüş değeri yok
} else {
// CALL: saQut fonksiyonu çağır, sonucu yeni slota yaz
int destSlot = freshSlot();
Instruction ins(Opcode::CALL);
ins.dest = destSlot;
ins.functionName = fnName;
ins.argSlots = argSlots;
currentFunction_->instructions.push_back(std::move(ins));
return destSlot;
}
}
// ── Postfix: i++, i-- ────────────────────────────────────────────────
case ASTKind::Postfix: {
auto* pf = (PostfixNode*)node;
// Şu anki değeri döndür, sonra artır/azalt
int operandSlot = generateExpression(pf->operand);
int resultSlot = freshSlot(); // dönüş değeri (artırmadan önceki)
emitLoadSlot(resultSlot, operandSlot);
int oneSlot = freshSlot();
emitLoadConst(oneSlot, 1);
int newSlot = freshSlot();
if (pf->Operator == TokenType::PLUS_PLUS) {
emitBinaryOp(Opcode::ADD, newSlot, operandSlot, oneSlot);
} else {
emitBinaryOp(Opcode::SUB, newSlot, operandSlot, oneSlot);
}
emitLoadSlot(operandSlot, newSlot); // orijinal değişkeni güncelle
return resultSlot; // artırmadan önceki değer
}
default:
// Bilinmeyen ifade türü
return freshSlot(); // boş slot (0 değeriyle)
}
}
// ─────────────────────────────────────────────────────────────────────────────
// generateBinaryArithmetic — İkili op için sol+sağ üret, talimat ekle
// ─────────────────────────────────────────────────────────────────────────────
int IRGenerator::generateBinaryArithmetic(Opcode opcode, ASTNode* leftNode, ASTNode* rightNode) {
int leftSlot = generateExpression(leftNode);
int rightSlot = generateExpression(rightNode);
int destSlot = freshSlot();
emitBinaryOp(opcode, destSlot, leftSlot, rightSlot);
return destSlot;
}
// ─────────────────────────────────────────────────────────────────────────────
// Slot yönetimi
// ─────────────────────────────────────────────────────────────────────────────
int IRGenerator::freshSlot() {
return nextSlot_++;
}
void IRGenerator::registerVariable(const std::string& name, int slot) {
nameToSlot_[name] = slot;
}
int IRGenerator::lookupVariable(const std::string& name) {
auto it = nameToSlot_.find(name);
if (it == nameToSlot_.end()) {
// Bu noktaya normalde gelinmemeli; sembol toplayıcı E001 üretmiş olur.
// Yine de çökmemek için 0 döndür.
return 0;
}
return it->second;
}
// ─────────────────────────────────────────────────────────────────────────────
// Talimat yazma yardımcıları
// ─────────────────────────────────────────────────────────────────────────────
void IRGenerator::emitLoadConst(int destSlot, int value) {
Instruction ins(Opcode::LOAD_CONST);
ins.dest = destSlot;
ins.intValue = value;
currentFunction_->instructions.push_back(std::move(ins));
}
void IRGenerator::emitLoadSlot(int destSlot, int srcSlot) {
Instruction ins(Opcode::LOAD_SLOT);
ins.dest = destSlot;
ins.src = srcSlot;
currentFunction_->instructions.push_back(std::move(ins));
}
void IRGenerator::emitBinaryOp(Opcode op, int destSlot, int leftSlot, int rightSlot) {
Instruction ins(op);
ins.dest = destSlot;
ins.left = leftSlot;
ins.right = rightSlot;
currentFunction_->instructions.push_back(std::move(ins));
}
void IRGenerator::emitReturn(int srcSlot) {
Instruction ins(Opcode::RETURN);
ins.src = srcSlot;
currentFunction_->instructions.push_back(std::move(ins));
}
int IRGenerator::emitJumpUnconditional(int targetInstrIndex) {
Instruction ins(Opcode::JMP);
ins.jumpTarget = targetInstrIndex;
currentFunction_->instructions.push_back(std::move(ins));
return (int)currentFunction_->instructions.size() - 1;
}
int IRGenerator::emitJumpIfFalse(int condSlot) {
Instruction ins(Opcode::JIF_FALSE);
ins.cond = condSlot;
ins.jumpTarget = -1; // henüz bilinmiyor — patchJump() bekliyor
currentFunction_->instructions.push_back(std::move(ins));
// Bu instruction'ın indeksini döndür (backpatch için)
return (int)currentFunction_->instructions.size() - 1;
}
void IRGenerator::patchJump(int instrIndex) {
// instrIndex'teki JMP veya JIF_FALSE'un hedefini şu anki konuma doldur
currentFunction_->instructions[instrIndex].jumpTarget = currentInstrIndex();
}
int IRGenerator::currentInstrIndex() const {
return (int)currentFunction_->instructions.size();
}

84
src/ir/ir_generator.hpp Normal file
View File

@ -0,0 +1,84 @@
// ============================================================================
// saQut IR — IRGenerator (AST → IR Dönüşümü)
//
// AST'yi (parse edilmiş kaynak kodu) Instruction listelerine çevirir.
// Her fonksiyon için bir IRFunction üretir, hepsini IRProgram'a toplar.
//
// SLOT ATAMA STRATEJİSİ:
// - Her fonksiyon üretiminde nextSlot_ sıfırdan başlar.
// - Parametreler 0, 1, 2, ... slotlarına sırayla atanır.
// - Sonraki her değişken veya geçici sonuç freshSlot() ile yeni slot alır.
// - Slotlar asla geri verilmez (basitlik öncelikli).
// - Fonksiyon bitince nextSlot_ = slotCount.
//
// SINIRLAMALAR (fibonacci için yeterli, genel dil için TODO):
// - Aynı isimli iki değişken farklı iç kapsamlarda olsa bile çakışır.
// (fibonacci.sqt'de bu durum yok; gelecekte scope-aware slot atama gerekir.)
// - Sadece int değerler desteklenir (Value.kind şu an hep Int).
// ============================================================================
#ifndef SAQUT_IR_GENERATOR
#define SAQUT_IR_GENERATOR
#include <string>
#include <unordered_map>
#include "ir/ir_program.hpp"
#include "symbol/symbol_table.hpp"
#include "parser/ast_node.hpp"
class IRGenerator {
public:
// Ana giriş noktası: programNode = ProgramNode, tablo = sembol tablosu
IRProgram generate(ASTNode* programNode, SymbolTable& symbolTable);
private:
// ── Fonksiyon üretimi ─────────────────────────────────────────────────
void generateFunction(ASTNode* functionDeclNode);
// ── Deyim (statement) üretimi — talimat listesine yazar ──────────────
void generateStatement(ASTNode* node);
// ── İfade (expression) üretimi — sonucun slotunu döndürür ────────────
// Sonuç her zaman bir slotta bulunur. Identifier zaten bir slotta,
// hesaplamalar freshSlot() ile yeni slot alır.
int generateExpression(ASTNode* node);
// ── İkili operatör (binary op) için ortak yardımcı ───────────────────
int generateBinaryArithmetic(Opcode opcode, ASTNode* leftNode, ASTNode* rightNode);
// ── Slot yönetimi ─────────────────────────────────────────────────────
int freshSlot(); // Yeni slot numarası al (nextSlot_++)
void registerVariable(const std::string& name, int slot); // name → slot kaydı
int lookupVariable(const std::string& name); // name → slot (bulunamazsa hata)
// ── Talimat yazma yardımcıları ────────────────────────────────────────
// Talimatları currentFunction_->instructions'a ekler.
void emitLoadConst(int destSlot, int value);
void emitLoadSlot(int destSlot, int srcSlot);
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).
// Hedef bilinmiyorsa -1 geçilir, patchJump() ile doldurulur.
int emitJumpUnconditional(int targetInstrIndex);
// JIF_FALSE talimatını -1 hedefle yazar, instruction indeksini döndürür.
// Döndürülen indeks ileride patchJump() ile doldurulur (backpatch).
int emitJumpIfFalse(int condSlot);
// Daha önce -1 hedefle yazılan jump'ın hedefini şu anki pozisyona doldur.
void patchJump(int instrIndex);
// Şu an kaç talimat üretildi? (jump hedefi belirlemek için)
int currentInstrIndex() const;
// ── Per-function üretim durumu ────────────────────────────────────────
IRFunction* currentFunction_ = nullptr; // şu an üretilen fonksiyon
int nextSlot_ = 0; // sıradaki boş slot numarası
// Değişken ismi → slot numarası.
// Sınırlama: aynı isimdeki farklı scope değişkenleri çakışır (TODO).
std::unordered_map<std::string, int> nameToSlot_;
};
#endif // SAQUT_IR_GENERATOR

11
src/ir/ir_program.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "ir/ir_program.hpp"
#include <iostream>
void IRProgram::dump() const {
std::cout << "========== IR DUMP ==========\n\n";
for (const auto& name : functionOrder) {
auto it = functions.find(name);
if (it != functions.end()) it->second.dump();
}
std::cout << "=============================\n";
}

48
src/ir/ir_program.hpp Normal file
View File

@ -0,0 +1,48 @@
// ============================================================================
// saQut IR — IRProgram (Bir .sqt Dosyasının Tüm IR İçeriği)
//
// IRProgram, üretilen tüm fonksiyonları tutar.
// Interpreter programı çalıştırmak için bu yapıyı kullanır.
//
// NEDEN İKİ YAPIDA TUTUYORUZ?
// - functionOrder: fonksiyonları tanımlandıkları sırayla tutar (dump için)
// - functions (unordered_map): CALL instruction'larında isimle hızlı arama için
//
// NOT: unordered_map değer semantiğiyle (IRFunction by value) tutar.
// findFunction() bir pointer döndürür — bu pointer tüm addFunction() çağrıları
// bittikten sonra alınmalıdır. Interpreter program üretildikten sonra çalıştığı
// için bu kural otomatik olarak sağlanır.
// ============================================================================
#ifndef SAQUT_IR_PROGRAM
#define SAQUT_IR_PROGRAM
#include <string>
#include <unordered_map>
#include <vector>
#include "ir/ir_function.hpp"
struct IRProgram {
// Fonksiyon adı → IRFunction (hızlı arama için)
std::unordered_map<std::string, IRFunction> functions;
// Ekleme sırası (dump'ta orijinal sırayla göstermek için)
std::vector<std::string> functionOrder;
// Yeni fonksiyon ekle
void addFunction(IRFunction fn) {
functionOrder.push_back(fn.name);
functions.emplace(fn.name, std::move(fn));
}
// İsimle ara — bulunamazsa nullptr döner
IRFunction* findFunction(const std::string& name) {
auto it = functions.find(name);
return (it != functions.end()) ? &it->second : nullptr;
}
// Tüm fonksiyonları ekleme sırasıyla yazdır
void dump() const;
};
#endif // SAQUT_IR_PROGRAM

46
src/vm/call_frame.hpp Normal file
View File

@ -0,0 +1,46 @@
// ============================================================================
// saQut VM — CallFrame (Tek Fonksiyon Çağrısının Çalışma Alanı)
//
// fibonacci(5) çağrıldığında bir CallFrame açılır.
// fibonacci(4) çağrıldığında AYRI bir CallFrame daha açılır.
// Her frame kendi slot dizisine sahiptir — üst frame'e asla dokunmaz.
//
// FRAME YAŞAM DÖNGÜSÜ:
// 1. CALL instruction'ı çalışır → yeni CallFrame oluşturulur, callStack'e eklenir
// 2. Interpreter bu frame'in instruction'larını çalıştırır
// 3. RETURN instruction'ı çalışır → frame callStack'ten çıkarılır,
// dönüş değeri caller'ın `returnDestSlot`'una yazılır
//
// REFERANS GÜVENLİĞİ:
// Interpreter döngüsü her iterasyonda callStack.back() ile frame'i TAZELER.
// CALL ve RETURN'den sonra `continue` ile döngü başına dönülür.
// Bu sayede vector büyüyüp referansı geçersiz kılsa bile sorun olmaz.
// ============================================================================
#ifndef SAQUT_VM_CALL_FRAME
#define SAQUT_VM_CALL_FRAME
#include <vector>
#include "ir/ir_function.hpp"
#include "vm/value.hpp"
struct CallFrame {
// Hangi fonksiyonun instruction'larını çalıştırıyoruz?
// Pointer — IRProgram sahibi, frame sahibi değil.
const IRFunction* function = nullptr;
// Sıradaki çalıştırılacak instruction'ın indeksi.
// Döngü her adımda önce bu indeksteki instruction'ı alır,
// SONRA ip'yi artırır. CALL/RETURN ip'ye dokunmaz.
int instructionPointer = 0;
// Bu frame'in değer depoları: parametreler + lokaller + geçiciler.
// Boyut = function->slotCount (frame oluşturulurken ayarlanır).
std::vector<Value> slots;
// RETURN olunca dönüş değeri CALLER'ın hangi slotuna yazılacak?
// -1 = main fonksiyonu (caller yok, değer kullanılmaz).
int returnDestSlot = -1;
};
#endif // SAQUT_VM_CALL_FRAME

224
src/vm/interpreter.cpp Normal file
View File

@ -0,0 +1,224 @@
#include "vm/interpreter.hpp"
#include <iostream>
#include <stdexcept>
// ─────────────────────────────────────────────────────────────────────────────
// run — Ana yorumlayıcı döngüsü
// ─────────────────────────────────────────────────────────────────────────────
int Interpreter::run() {
// "main" fonksiyonunu bul
IRFunction* mainFunction = program_.findFunction("main");
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
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) {
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_SLOT:
frame.slots[instr.dest] = frame.slots[instr.src];
break;
// ── Aritmetik ────────────────────────────────────────────────────
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);
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);
break;
}
// ── Karşılaştırma (sonuç: 1=doğru, 0=yanlış) ────────────────────
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);
break;
case Opcode::NOT_EQUAL:
frame.slots[instr.dest] = Value::fromInt(
frame.slots[instr.left].intValue != frame.slots[instr.right].intValue ? 1 : 0);
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()) {
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) {
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
// Argümanları parametre slotlarına kopyala (slot 0, 1, 2, ...)
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
}
// ── 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) {
callStack_.back().slots[returnDestSlot] = returnValue;
}
// main fonksiyonu döndü → program bitti
if (callStack_.empty()) {
return returnValue.intValue;
}
continue; // ← bir sonraki iterasyonda caller frame tazeden alınır
}
// ── FFI: Host fonksiyon çağrısı ───────────────────────────────────
case Opcode::CALLHOST:
executeHostFunction(instr.functionName, frame.slots, instr.argSlots);
break;
}
}
return 0; // Normal çıkış
}
// ─────────────────────────────────────────────────────────────────────────────
// 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";
}
return;
}
// Bilinmeyen host fonksiyon
throw std::runtime_error("Çalışma hatası: bilinmeyen host fonksiyonu '" + name + "'");
}

38
src/vm/interpreter.hpp Normal file
View File

@ -0,0 +1,38 @@
// ============================================================================
// saQut VM — Interpreter (Bytecode Yorumlayıcı)
//
// IRProgram içindeki talimatları çalıştırır.
// "main" fonksiyonundan başlar, RETURN ile biten frame'leri kapatır.
//
// DÖNGÜ GÜVENLİĞİ (referans invalidation):
// Her iterasyonun başında callStack.back() tazeden alınır.
// CALL ve RETURN'den sonra `continue` ile döngü başına dönülür;
// böylece vector büyümesinden kaynaklanan dangling pointer sorunu olmaz.
// ============================================================================
#ifndef SAQUT_VM_INTERPRETER
#define SAQUT_VM_INTERPRETER
#include <vector>
#include "ir/ir_program.hpp"
#include "vm/call_frame.hpp"
class Interpreter {
public:
explicit Interpreter(IRProgram& program) : program_(program) {}
// "main" fonksiyonunu bul ve çalıştır.
// Tamamlandığında main'in dönüş değerini (int) döndürür.
int run();
private:
IRProgram& program_;
std::vector<CallFrame> callStack_;
// Host (C++) fonksiyon çağrısı — şu an sadece "print" destekli
void executeHostFunction(const std::string& name,
const std::vector<Value>& slots,
const std::vector<int>& argSlots);
};
#endif // SAQUT_VM_INTERPRETER

43
src/vm/value.hpp Normal file
View File

@ -0,0 +1,43 @@
// ============================================================================
// saQut VM — Value (Çalışma Zamanı Değer)
//
// Bir saQut değerinin bellekteki temsilidir.
//
// ŞU AN SADECE INT:
// fibonacci.sqt tamamen int kullanır, bu dikey dilim için int yeterli.
// İleride float, bool, string eklenmesi için "kind" alanı iskelet olarak bırakıldı.
//
// BOOLEAN OLARAK KULLANIM:
// JIF_FALSE talimatı değerin 0 olup olmadığına bakar.
// 0 = yanlış, sıfır-dışı = doğru. C geleneği.
// ============================================================================
#ifndef SAQUT_VM_VALUE
#define SAQUT_VM_VALUE
// Gelecekte float/bool/string eklendiğinde burası genişleyecek.
// Şimdilik sadece int.
enum class ValueKind {
Int,
// Float, // TODO(vm-genişletme)
// Bool, // TODO(vm-genişletme)
// String, // TODO(vm-genişletme)
};
struct Value {
ValueKind kind = ValueKind::Int;
int intValue = 0;
// Kolay oluşturma
static Value fromInt(int n) {
Value v;
v.kind = ValueKind::Int;
v.intValue = n;
return v;
}
// JIF_FALSE için: 0 = yanlış, diğer = doğru
bool isTruthy() const { return intValue != 0; }
};
#endif // SAQUT_VM_VALUE