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:
parent
e72a62ae2e
commit
3c76eab932
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 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;
|
||||
if (!ast) {
|
||||
std::cerr << "Hata: AST üretilemedi\n";
|
||||
for (auto* t : tokens) delete t;
|
||||
return 1;
|
||||
}
|
||||
std::cout << " (" << op.arg1.value.index() << ")\n";
|
||||
|
||||
// ── 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 + "'");
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue