From eb086270296455d669c842f5253ad8397e0bf92b Mon Sep 17 00:00:00 2001 From: "anthony@rabine.fr" Date: Mon, 6 Oct 2025 10:24:25 +0200 Subject: [PATCH] Add TAC in compile pipeline --- core/chip32/chip32_assembler.cpp | 7 +- core/chip32/chip32_machine.h | 220 ++-- core/chip32/chip32_vm.h | 3 +- .../interfaces/i_story_manager.h | 1 - .../src/compiler/assembly_generator_chip32.h | 509 --------- .../compiler/assembly_generator_chip32_tac.h | 621 ++++++++++ core/story-manager/src/compiler/ast_builder.h | 15 +- core/story-manager/src/compiler/tac.h | 706 ++++++++++++ core/story-manager/src/story_page.h | 14 - core/story-manager/src/story_project.cpp | 148 ++- core/story-manager/src/story_project.h | 8 +- core/tests/test_ast.cpp | 1009 ++++++++++++++++- core/tests/test_print_node.cpp | 379 +++---- story-editor/imgui.ini | 83 +- story-editor/src/app/app_controller.cpp | 30 +- story-editor/src/app/app_controller.h | 1 - story-editor/src/docks/emulator_dock.cpp | 5 - 17 files changed, 2807 insertions(+), 952 deletions(-) delete mode 100644 core/story-manager/src/compiler/assembly_generator_chip32.h create mode 100644 core/story-manager/src/compiler/assembly_generator_chip32_tac.h create mode 100644 core/story-manager/src/compiler/tac.h diff --git a/core/chip32/chip32_assembler.cpp b/core/chip32/chip32_assembler.cpp index 7794d67..8508b6f 100644 --- a/core/chip32/chip32_assembler.cpp +++ b/core/chip32/chip32_assembler.cpp @@ -365,7 +365,7 @@ bool Assembler::BuildBinary(std::vector &program, Result &result) { if (i.isRamData) { - result.ramUsageSize += i.dataLen * i.dataTypeSize/8; + result.ramUsageSize += i.dataLen; } else { @@ -373,7 +373,10 @@ bool Assembler::BuildBinary(std::vector &program, Result &result) { program.push_back(i.code.opcode); } - result.constantsSize += i.compiledArgs.size(); + else if (i.isRomData) // Seulement pour ROM DATA + { + result.constantsSize += i.compiledArgs.size(); + } std::copy (i.compiledArgs.begin(), i.compiledArgs.end(), std::back_inserter(program)); } } diff --git a/core/chip32/chip32_machine.h b/core/chip32/chip32_machine.h index b995a96..a2c51c1 100644 --- a/core/chip32/chip32_machine.h +++ b/core/chip32/chip32_machine.h @@ -10,6 +10,8 @@ #include "chip32_assembler.h" #include "chip32_macros.h" +// Dans chip32_machine.h + namespace Chip32 { @@ -19,119 +21,207 @@ public: bool parseResult{false}; bool buildResult{false}; chip32_result_t runResult{VM_OK}; + std::string printOutput; - static int get_from_memory(chip32_ctx_t *ctx, uint32_t addr, char *text) - { - int valid = 0; + static Machine *m_instance; - // Test if address is valid - - bool isRam = addr & 0x80000000; - addr &= 0xFFFF; // mask the RAM/ROM bit, ensure 16-bit addressing - if (isRam) { - strcpy(&text[0], (const char *)&ctx->ram.mem[addr]); - } else { - strcpy(&text[0], (const char *)&ctx->rom.mem[addr]); - } - - return valid; + Machine() { + // Bind syscall handler to this instance + m_syscallHandler = std::bind(&Machine::HandleSyscall, this, + std::placeholders::_1, + std::placeholders::_2); } - - - static uint8_t story_player_syscall(chip32_ctx_t *ctx, uint8_t code) + // Lecture d'une chaîne depuis la mémoire (non statique maintenant) + std::string GetStringFromMemory(chip32_ctx_t *ctx, uint32_t addr) { - uint8_t retCode = SYSCALL_RET_OK; - char working_buf[100] = {0}; + if (!ctx) { + throw std::runtime_error("Invalid context in GetStringFromMemory"); + } - // Printf - if (code == 4) - { - // In R0: string with escaped characters - // R1: Number of arguments - // R2, R3 ... arguments + bool isRam = (addr & 0x80000000) != 0; + addr &= 0xFFFF; - // Integers: stored in registers by values - // Strings: first character address in register + const uint8_t* source_mem = nullptr; + size_t mem_size = 0; - get_from_memory(ctx, ctx->registers[R0], working_buf); - int arg_count = ctx->registers[R1]; + if (isRam) { + if (addr >= ctx->ram.size) { + throw std::out_of_range("RAM address out of bounds: " + std::to_string(addr)); + } + source_mem = ctx->ram.mem; + mem_size = ctx->ram.size; + } else { + if (addr >= ctx->rom.size) { + throw std::out_of_range("ROM address out of bounds: " + std::to_string(addr)); + } + source_mem = ctx->rom.mem; + mem_size = ctx->rom.size; + } - switch(arg_count){ - case 0: - puts(working_buf); + size_t max_len = mem_size - addr; + const char* str_start = reinterpret_cast(&source_mem[addr]); + + const char* null_pos = static_cast( + std::memchr(str_start, '\0', max_len) + ); + + if (null_pos) { + return std::string(str_start, null_pos - str_start); + } else { + return std::string(str_start, max_len); + } + } + + // Formatter avec nombre variable d'arguments (non statique) + std::string FormatString(const std::string& format, + const std::vector& args) + { + if (args.empty()) { + return format; + } + + std::vector buffer(1024); + int result = -1; + + switch (args.size()) { + case 1: + result = std::snprintf(buffer.data(), buffer.size(), + format.c_str(), args[0]); + break; + case 2: + result = std::snprintf(buffer.data(), buffer.size(), + format.c_str(), args[0], args[1]); + break; + case 3: + result = std::snprintf(buffer.data(), buffer.size(), + format.c_str(), args[0], args[1], args[2]); + break; + case 4: + result = std::snprintf(buffer.data(), buffer.size(), + format.c_str(), args[0], args[1], args[2], args[3]); + break; + default: + throw std::runtime_error("Too many arguments for printf (max 4)"); + } + + if (result < 0) { + throw std::runtime_error("Error formatting string"); + } + + if (static_cast(result) >= buffer.size()) { + buffer.resize(result + 1); + + switch (args.size()) { + case 1: + std::snprintf(buffer.data(), buffer.size(), + format.c_str(), args[0]); break; - case 1: - printf(working_buf, ctx->registers[R2]); - puts(""); + case 2: + std::snprintf(buffer.data(), buffer.size(), + format.c_str(), args[0], args[1]); break; - case 2: - printf(working_buf, ctx->registers[R2], ctx->registers[R3]); - puts(""); + case 3: + std::snprintf(buffer.data(), buffer.size(), + format.c_str(), args[0], args[1], args[2]); break; - case 3: - printf(working_buf, ctx->registers[R2], ctx->registers[R3], ctx->registers[R4]); - puts(""); - break; - default: + case 4: + std::snprintf(buffer.data(), buffer.size(), + format.c_str(), args[0], args[1], args[2], args[3]); break; } - - } - // WAIT (sleep) - else if (code == 5) - { - std::this_thread::sleep_for(std::chrono::milliseconds(ctx->registers[R0])); } - return retCode; + return std::string(buffer.data()); } - + // Handler de syscall (méthode membre, non statique) + uint8_t HandleSyscall(chip32_ctx_t *ctx, uint8_t code) + { + try { + if (code == 4) // Printf + { + std::string format = GetStringFromMemory(ctx, ctx->registers[R0]); + int arg_count = ctx->registers[R1]; + + std::vector args; + for (int i = 0; i < arg_count && i < 4; ++i) { + args.push_back(ctx->registers[R2 + i]); + } + + printOutput = FormatString(format, args); + std::cout << "[SYSCALL PRINT] " << printOutput << std::endl; + } + else if (code == 5) // WAIT + { + std::this_thread::sleep_for( + std::chrono::milliseconds(ctx->registers[R0]) + ); + } + else + { + std::cerr << "Unknown syscall code: " << static_cast(code) << std::endl; + return SYSCALL_RET_ERROR; + } + + return SYSCALL_RET_OK; + + } catch (const std::exception& e) { + std::cerr << "Syscall error: " << e.what() << std::endl; + return SYSCALL_RET_ERROR; + } + } void QuickExecute(const std::string &assemblyCode) { std::vector program; Chip32::Assembler assembler; Chip32::Result result; - uint8_t data[8*1024]; + std::vector data(8*1024); parseResult = assembler.Parse(assemblyCode); - std::cout << assembler.GetLastError().ToString() << std::endl; - buildResult = assembler.BuildBinary(program, result); result.Print(); chip32_ctx_t chip32_ctx; - chip32_ctx.stack_size = 512; - chip32_ctx.rom.mem = program.data(); chip32_ctx.rom.addr = 0; chip32_ctx.rom.size = program.size(); + chip32_ctx.ram.mem = data.data(); + chip32_ctx.ram.addr = 40 * 1024; + chip32_ctx.ram.size = data.size(); - chip32_ctx.ram.mem = data; - chip32_ctx.ram.addr = 40 *1024; - chip32_ctx.ram.size = sizeof(data); - - chip32_ctx.syscall = story_player_syscall; + // Utiliser le wrapper statique qui appelle notre fonction membre + chip32_ctx.syscall = SyscallWrapper; + chip32_ctx.user_data = this; // Stocker le pointeur vers cette instance chip32_initialize(&chip32_ctx); Instr mainLine; - if (assembler.GetMain(mainLine)) - { - // set pointer counter to the main line + if (assembler.GetMain(mainLine)) { chip32_ctx.registers[PC] = mainLine.addr; } runResult = chip32_run(&chip32_ctx); } +private: + // std::function contenant le bind + std::function m_syscallHandler; + // Wrapper statique qui récupère l'instance et appelle la méthode membre + static uint8_t SyscallWrapper(chip32_ctx_t *ctx, uint8_t code) + { + if (!ctx || !ctx->user_data) { + return SYSCALL_RET_ERROR; + } + + Machine* instance = static_cast(ctx->user_data); + return instance->HandleSyscall(ctx, code); + } }; - } // namespace Chip32 diff --git a/core/chip32/chip32_vm.h b/core/chip32/chip32_vm.h index 3ced042..7bba2e7 100644 --- a/core/chip32/chip32_vm.h +++ b/core/chip32/chip32_vm.h @@ -201,7 +201,7 @@ typedef uint8_t (*syscall_t)(chip32_ctx_t *, uint8_t); #define SYSCALL_RET_OK 0 ///< Default state, continue execution immediately #define SYSCALL_RET_WAIT_EV 1 ///< Sets the VM in wait for event state - +#define SYSCALL_RET_ERROR 2 struct chip32_ctx_t { @@ -213,6 +213,7 @@ struct chip32_ctx_t uint32_t max_instr; uint32_t registers[REGISTER_COUNT]; syscall_t syscall; + void* user_data; }; diff --git a/core/story-manager/interfaces/i_story_manager.h b/core/story-manager/interfaces/i_story_manager.h index 9b975da..076849d 100644 --- a/core/story-manager/interfaces/i_story_manager.h +++ b/core/story-manager/interfaces/i_story_manager.h @@ -43,7 +43,6 @@ public: virtual std::shared_ptr GetCurrentProject() = 0; // Node interaction - virtual void CompileNodes(bool compileonly) = 0; virtual void BuildCode(bool compileonly) = 0; virtual void SetExternalSourceFile(const std::string &filename) = 0; virtual void LoadBinaryStory(const std::string &filename) = 0; diff --git a/core/story-manager/src/compiler/assembly_generator_chip32.h b/core/story-manager/src/compiler/assembly_generator_chip32.h deleted file mode 100644 index 42c6805..0000000 --- a/core/story-manager/src/compiler/assembly_generator_chip32.h +++ /dev/null @@ -1,509 +0,0 @@ -#pragma once - -#include "ast_builder.h" -#include "assembly_generator.h" -#include "call_function_node.h" -#include - -class AssemblyGeneratorChip32 : public AssemblyGenerator -{ -public: - AssemblyGeneratorChip32(const GeneratorContext& context) - : AssemblyGenerator(context) - , m_currentContext(FunctionContext::MAIN_PROGRAM) - { - - } - virtual ~AssemblyGeneratorChip32() = default; - - - void GenerateNodeCode(std::shared_ptr node, bool isDataPath = false) override - { - if (!node) return; - - if (m_context.debugOutput) { - AddComment("Node: " + node->node->GetTypeName() + " (ID: " + node->node->GetId() + ")"); - } - // Node label - m_assembly << node->node->GetMyEntryLabel() << ":\n"; - - if (node->IsType()) { - GenerateOperatorNode(node); - } - else if (node->IsType()) { - // Détecter si c'est le main ou une sous-fonction - // Weight 100 = fonction principale (main) - auto* entry = node->GetAs(); - m_currentContext = (entry->GetWeight() >= 100) - ? FunctionContext::MAIN_PROGRAM - : FunctionContext::SUB_FUNCTION; - - GenerateFunctionEntry(node); - } - else if (node->IsType()) { - GenerateBranchNode(node); - } - else if (node->IsType()) { - GeneratePrintNode(node); - } - else if (node->IsType()) { - GenerateCallFunctionNode(node); - } - - // Détection automatique des fins de fonction/programme - if (node->GetChildCount() == 0) - { - if (m_currentContext == FunctionContext::MAIN_PROGRAM) { - AddComment("Program exit (automatic)"); - m_assembly << " halt\n"; - } else { - AddComment("Function return (automatic)"); - m_assembly << " ret\n"; - } - } - } - - virtual void AddComment(const std::string& comment) { - m_assembly << std::string(m_depth * 4, ' ') << "; " << comment << "\n"; - } - - virtual void GenerateExit() override { - AddComment("Program exit"); - m_assembly << " halt\n"; - } - -private: - enum class FunctionContext { - MAIN_PROGRAM, - SUB_FUNCTION - }; - - FunctionContext m_currentContext; - - virtual void GenerateMain() override { - // Program entry point - m_assembly << ".main:\n"; - } - - void GenerateFunctionEntry(std::shared_ptr node) { - auto* entry = node->GetAs(); - - if (m_currentContext == FunctionContext::MAIN_PROGRAM) { - AddComment("Main function entry"); - } else { - AddComment("Function entry"); - // Si nécessaire, sauvegarder les registres - // m_assembly << " push r0\n"; - // m_assembly << " push r1\n"; - // etc. - } - } - - void GenerateCallFunctionNode(std::shared_ptr node) { - auto* callNode = node->GetAs(); - if (!callNode) return; - - std::string functionName = callNode->GetFunctionName(); - - AddComment("Call function: " + functionName); - m_depth++; - - // Préparer les arguments si nécessaire - // Dans votre système, les variables globales sont utilisées - // donc pas besoin de passer des arguments sur la pile - - // Appel de la fonction - m_assembly << " call " << GetFunctionLabel(functionName) << "\n"; - - // Après le retour de la fonction, les variables globales - // ont potentiellement été modifiées et sont directement accessibles - - m_depth--; - } - - void GenerateBranchNode(std::shared_ptr node) - { - AddComment("Branch condition evaluation"); - m_depth++; - - auto trueBranch = node->GetChild(0); - auto falseBranch = node->GetChild(1); - - // Compare result and jump - m_assembly << " pop r0\n" - << " skipz r0\n" - << " jump " << trueBranch->node->GetMyEntryLabel() << "\n" - << " jump " << falseBranch->node->GetMyEntryLabel() << "\n"; - - m_depth--; - } - - void GeneratePrintNode(std::shared_ptr node) - { - auto* printNode = node->GetAs(); - if (!printNode) return; - - std::string label = printNode->GetLabel(); - - AddComment("Print: " + printNode->GetText()); - m_depth++; - - // Count the number of arguments connected to the print node - int argCount = 0; - std::vector>> sortedInputs; - - // Collect and sort data inputs by port index - for (const auto& [port, inputNode] : node->dataInputs) { - sortedInputs.push_back({port, inputNode}); - } - - // Sort by port index to ensure correct argument order (arg0, arg1, arg2, arg3) - std::sort(sortedInputs.begin(), sortedInputs.end(), - [](const auto& a, const auto& b) { return a.first < b.first; }); - - argCount = sortedInputs.size(); - - // Save registers that we'll use - m_assembly << " push r0\n" - << " push r1\n"; - - // Save argument registers if we have arguments - if (argCount > 0) m_assembly << " push r2\n"; - if (argCount > 1) m_assembly << " push r3\n"; - if (argCount > 2) m_assembly << " push r4\n"; - if (argCount > 3) m_assembly << " push r5\n"; - - // Load arguments into registers r2, r3, r4, r5 - int regIndex = 2; // Start with r2 for first argument - for (const auto& [port, inputNode] : sortedInputs) { - if (regIndex > 5) { - // Maximum 4 arguments (r2, r3, r4, r5) - throw std::runtime_error("Print node supports maximum 4 arguments"); - } - - // Check if the input node is a variable - if (inputNode->IsType()) { - auto* varNode = inputNode->GetAs(); - if (varNode) { - std::string varUuid = varNode->GetVariableUuid(); - - // Find variable in the context - auto var = m_context.FindVariableByUuid(varUuid); - if (var) { - m_assembly << " ; Load arg" << (regIndex - 2) << ": " - << var->GetVariableName() << "\n"; - int varSize = GetVariableSize(var); - m_assembly << " load r" << regIndex << ", $" - << var->GetLabel() << ", " << varSize << " ; " - << var->GetVariableName() << "\n"; - } else { - throw std::runtime_error("Variable not found in context for print argument"); - } - } - } else { - // For non-variable inputs, we might need to evaluate expressions - // For now, we only support direct variable connections - throw std::runtime_error("Print node currently only supports direct variable connections"); - } - - regIndex++; - } - - // Load format string address into r0 - m_assembly << " lcons r0, $" << label << " ; format string\n"; - - // Load number of arguments into r1 - m_assembly << " lcons r1, " << argCount << " ; number of arguments\n"; - - // Call printf syscall - m_assembly << " syscall 4\n"; - - // Restore argument registers (in reverse order) - if (argCount > 3) m_assembly << " pop r5\n"; - if (argCount > 2) m_assembly << " pop r4\n"; - if (argCount > 1) m_assembly << " pop r3\n"; - if (argCount > 0) m_assembly << " pop r2\n"; - - // Restore r0 and r1 - m_assembly << " pop r1\n" - << " pop r0\n"; - - m_depth--; - } - - void GenerateOperatorNode(std::shared_ptr node) { - auto* opNode = node->GetAs(); - if (!opNode) return; - - AddComment("Operator: " + opNode->GetOperatorSymbol()); - m_depth++; - - // Generate code for variables usage - int reg = 0; - for (const auto& [port, inputNode] : node->dataInputs) - { - // Check if the input node is a variable - if (inputNode->IsType()) - { - auto* varNode = inputNode->GetAs(); - if (varNode) { - std::string varUuid = varNode->GetVariableUuid(); - - // Find variable in the context - auto var = m_context.FindVariableByUuid(varUuid); - if (var) - { - // Generate code to load the variable value - int varSize = GetVariableSize(var); - m_assembly << " load r" << reg << ", $" << var->GetLabel() - << ", " << varSize << " ; Load variable " - << var->GetVariableName() << "\n"; - m_assembly << " push r" << reg << "\n"; - } - else - { - throw std::runtime_error("Variable not set in node: " + inputNode->node->GetId()); - } - } - } - reg++; - } - - // Generate operator code based on type - switch (opNode->GetOperationType()) { - // ===== ARITHMETIC OPERATORS ===== - case OperatorNode::OperationType::ADD: - m_assembly << " pop r1\n" - << " pop r0\n" - << " add r0, r1\n" - << " push r0\n"; - break; - case OperatorNode::OperationType::SUBTRACT: - m_assembly << " pop r1\n" - << " pop r0\n" - << " sub r0, r1\n" - << " push r0\n"; - break; - case OperatorNode::OperationType::MULTIPLY: - m_assembly << " pop r1\n" - << " pop r0\n" - << " mul r0, r1\n" - << " push r0\n"; - break; - case OperatorNode::OperationType::DIVIDE: - m_assembly << " pop r1\n" - << " pop r0\n" - << " div r0, r1\n" - << " push r0\n"; - break; - - // ===== COMPARISON OPERATORS ===== - // Utilise les instructions eq, gt, lt de Chip32 - // Syntaxe: eq r_dest, r_op1, r_op2 → r_dest = (r_op1 == r_op2 ? 1 : 0) - - case OperatorNode::OperationType::EQUAL: - m_assembly << " pop r1 ; second operand\n" - << " pop r0 ; first operand\n" - << " eq r0, r0, r1 ; r0 = (r0 == r1 ? 1 : 0)\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::NOT_EQUAL: - m_assembly << " pop r1\n" - << " pop r0\n" - << " eq r0, r0, r1 ; r0 = (r0 == r1 ? 1 : 0)\n" - << " lcons r2, 1\n" - << " xor r0, r2 ; inverse: 0→1, 1→0\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::GREATER_THAN: - m_assembly << " pop r1\n" - << " pop r0\n" - << " gt r0, r0, r1 ; r0 = (r0 > r1 ? 1 : 0)\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::LESS_THAN: - m_assembly << " pop r1\n" - << " pop r0\n" - << " lt r0, r0, r1 ; r0 = (r0 < r1 ? 1 : 0)\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::GREATER_EQUAL: - // >= est équivalent à NOT(<) - m_assembly << " pop r1\n" - << " pop r0\n" - << " lt r0, r0, r1 ; r0 = (r0 < r1 ? 1 : 0)\n" - << " lcons r2, 1\n" - << " xor r0, r2 ; inverse: >= est NOT(<)\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::LESS_EQUAL: - // <= est équivalent à NOT(>) - m_assembly << " pop r1\n" - << " pop r0\n" - << " gt r0, r0, r1 ; r0 = (r0 > r1 ? 1 : 0)\n" - << " lcons r2, 1\n" - << " xor r0, r2 ; inverse: <= est NOT(>)\n" - << " push r0\n"; - break; - - // ===== LOGICAL OPERATORS ===== - case OperatorNode::OperationType::AND: - // AND logique: résultat 1 si les deux sont non-zéro - m_assembly << " pop r1\n" - << " pop r0\n" - << " ; Logical AND\n" - << " lcons r2, 0\n" - << " skipz r0\n" - << " skipz r1\n" - << " lcons r2, 1\n" - << " mov r0, r2\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::OR: - // OR logique: résultat 1 si au moins un est non-zéro - m_assembly << " pop r1\n" - << " pop r0\n" - << " ; Logical OR\n" - << " or r0, r1 ; bitwise or\n" - << " lcons r2, 0\n" - << " skipz r0\n" - << " lcons r2, 1\n" - << " mov r0, r2\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::NOT: - // NOT logique: 0→1, non-zero→0 - m_assembly << " pop r0\n" - << " ; Logical NOT\n" - << " lcons r1, 1\n" - << " skipz r0\n" - << " lcons r1, 0\n" - << " mov r0, r1\n" - << " push r0\n"; - break; - - // ===== BITWISE OPERATORS ===== - case OperatorNode::OperationType::BITWISE_AND: - m_assembly << " pop r1\n" - << " pop r0\n" - << " and r0, r1\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::BITWISE_OR: - m_assembly << " pop r1\n" - << " pop r0\n" - << " or r0, r1\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::BITWISE_XOR: - m_assembly << " pop r1\n" - << " pop r0\n" - << " xor r0, r1\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::BITWISE_NOT: - m_assembly << " pop r0\n" - << " not r0\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::LEFT_SHIFT: - m_assembly << " pop r1\n" - << " pop r0\n" - << " shl r0, r1\n" - << " push r0\n"; - break; - - case OperatorNode::OperationType::RIGHT_SHIFT: - m_assembly << " pop r1\n" - << " pop r0\n" - << " shr r0, r1\n" - << " push r0\n"; - break; - - default: - throw std::runtime_error("Unsupported operator type"); - } - - m_depth--; - } - - // Helper pour générer le label d'une fonction à partir de son nom - std::string GetFunctionLabel(const std::string& functionName) { - // Convertir le nom de fonction en label valide - // Par exemple: "MyFunction" -> ".func_MyFunction" - return ".func_" + functionName; - } - - virtual void Visit(const std::shared_ptr v) override - { - if (v->IsConstant()) - { - if (v->GetValueType() == Variable::ValueType::STRING) - { - m_assembly << "$" << v->GetLabel() << " DC8, \"" << v->GetValue() << "\" ; " << v->GetVariableName() << "\n"; - } - else if (v->GetValueType() == Variable::ValueType::INTEGER) - { - m_assembly << "$" << v->GetLabel() << " DC32, " << v->GetValue() << " ; " << v->GetVariableName() << "\n"; - } - else if (v->GetValueType() == Variable::ValueType::FLOAT) - { - m_assembly << "$" << v->GetLabel() << " DC32, " << v->GetValue() << " ; " << v->GetVariableName() << "\n"; - } - else if (v->GetValueType() == Variable::ValueType::BOOL) - { - m_assembly << "$" << v->GetLabel() << " DCB, " << (v->GetValue() ? "1" : "0") << " ; " << v->GetVariableName() << "\n"; - } - - } - } - - - virtual void GenerateVariable(const std::shared_ptr v) - { - if (v->GetValueType() == Variable::ValueType::STRING) - { - m_assembly << "$" << v->GetLabel() << " DV8, \"" << v->GetValue() << "\" ; " << v->GetVariableName() << "\n"; - } - else if (v->GetValueType() == Variable::ValueType::INTEGER) - { - m_assembly << "$" << v->GetLabel() << " DV32, " << v->GetValue() << " ; " << v->GetVariableName() << "\n"; - } - else if (v->GetValueType() == Variable::ValueType::FLOAT) - { - m_assembly << "$" << v->GetLabel() << " DV32, " << v->GetValue() << " ; " << v->GetVariableName() << "\n"; - } - else if (v->GetValueType() == Variable::ValueType::BOOL) - { - m_assembly << "$" << v->GetLabel() << " DVB, " << (v->GetValue() ? "1" : "0") << " ; " << v->GetVariableName() << "\n"; - } - } -private: - // Helper pour obtenir la taille en bytes d'une variable selon son type - int GetVariableSize(std::shared_ptr var) const { - switch (var->GetValueType()) { - case Variable::ValueType::INTEGER: - return 4; // 32 bits = 4 bytes (DV32/DC32) - case Variable::ValueType::FLOAT: - return 4; // 32 bits = 4 bytes (DV32/DC32) - case Variable::ValueType::BOOL: - return 1; // 8 bits = 1 byte (DVB/DCB) - case Variable::ValueType::STRING: - // Pour les strings, on charge l'adresse (pointeur 32-bit) - return 4; // Adresse = 4 bytes - default: - throw std::runtime_error("Unknown variable type"); - } - } -}; \ No newline at end of file diff --git a/core/story-manager/src/compiler/assembly_generator_chip32_tac.h b/core/story-manager/src/compiler/assembly_generator_chip32_tac.h new file mode 100644 index 0000000..416f671 --- /dev/null +++ b/core/story-manager/src/compiler/assembly_generator_chip32_tac.h @@ -0,0 +1,621 @@ +// =================================================================== +// assembly_generator_chip32_tac.h +// IMPLÉMENTATION COMPLÈTE avec tous les types gérés +// =================================================================== + +#pragma once + +#include "assembly_generator.h" +#include "tac.h" +#include "print_node.h" +#include "operator_node.h" +#include "variable_node.h" +#include "branch_node.h" +#include "function_entry_node.h" +#include "call_function_node.h" +#include +#include + +class AssemblyGeneratorChip32TAC : public AssemblyGenerator +{ +public: + AssemblyGeneratorChip32TAC(const GeneratorContext& context) + : AssemblyGenerator(context) + , m_currentContext(FunctionContext::MAIN_PROGRAM) + { + } + + virtual ~AssemblyGeneratorChip32TAC() = default; + + // =================================================================== + // WORKFLOW COMPLET : Cette méthode orchestre tout + // =================================================================== + void GenerateCompleteProgram(const std::vector>& nodes, + const std::vector>& astNodes) + { + Reset(); + + // === ÉTAPE 1 : HEADER === + GenerateHeader(); + + // === ÉTAPE 2 : SECTION DATA === + StartSection(Section::DATA); + + // Générer les variables globales + GenerateGlobalVariables(); + + // Générer les variables des nœuds (format strings, etc.) + GenerateNodesVariables(nodes); + + // === ÉTAPE 3 : GÉNÉRATION TAC === + m_tacGenerator = std::make_unique(); + m_tacProgram = m_tacGenerator->Generate(astNodes); + + // DEBUG : Afficher le TAC généré + if (m_context.debugOutput) { + std::cout << "\n" << m_tacProgram.ToString() << std::endl; + } + + // Générer les temporaires TAC qui seront en RAM + GenerateTACTemporaries(); + + m_assembly << "\n"; + + // === ÉTAPE 4 : SECTION TEXT (à partir du TAC) === + StartSection(Section::TEXT); + GenerateFromTAC(); + + // === ÉTAPE 5 : EXIT === + GenerateExit(); + } + + + virtual void GenerateMain() override { + m_assembly << ".main:\n"; + } + + virtual void AddComment(const std::string& comment) override { + m_assembly << std::string(m_depth * 4, ' ') << "; " << comment << "\n"; + } + + virtual void GenerateExit() override { + AddComment("Program exit"); + m_assembly << " halt\n"; + } + + // Note: Cette méthode ne sera PAS utilisée car on génère depuis le TAC + virtual void GenerateNodeCode(std::shared_ptr node, bool isDataPath = false) override { + // Cette méthode n'est plus utilisée avec l'approche TAC + // Tout est géré via GenerateFromTAC() + + // On peut laisser une implémentation vide ou lancer une exception + if (m_context.debugOutput) { + AddComment("WARNING: GenerateNodeCode called but not used in TAC mode"); + } + } + + void GenerateTACToAssembly(const TACProgram& tac) { + m_tacProgram = tac; + + // Générer les temporaires si nécessaire + GenerateTACTemporaries(); + + // Convertir le TAC en assembleur + for (const auto& instr : m_tacProgram.GetInstructions()) { + GenerateTACInstruction(instr); + } + } + + // Exposer l'assembly stream pour écrire directement + std::stringstream& GetAssembly() { return m_assembly; } + +private: + enum class FunctionContext { + MAIN_PROGRAM, + SUB_FUNCTION + }; + + FunctionContext m_currentContext; + std::unique_ptr m_tacGenerator; + TACProgram m_tacProgram; + + // Map des temporaires TAC vers leur localisation + struct TempLocation { + enum Type { REGISTER, MEMORY }; + Type type; + std::string location; // "t0" ou "$temp_label" + }; + std::map m_tempLocations; + int m_nextTempReg = 0; // Prochain registre t0-t9 disponible + int m_tempVarCounter = 0; // Compteur pour variables temporaires en RAM + + // =================================================================== + // GÉNÉRATION DE LA SECTION DATA + // =================================================================== + + void GenerateTACTemporaries() { + // Analyser le TAC pour déterminer quels temporaires ont besoin de RAM + std::set allTemps; + + for (const auto& instr : m_tacProgram.GetInstructions()) { + if (instr->GetDest() && + instr->GetDest()->GetType() == TACOperand::Type::TEMPORARY) { + allTemps.insert(instr->GetDest()->GetValue()); + } + if (instr->GetOp1() && + instr->GetOp1()->GetType() == TACOperand::Type::TEMPORARY) { + allTemps.insert(instr->GetOp1()->GetValue()); + } + if (instr->GetOp2() && + instr->GetOp2()->GetType() == TACOperand::Type::TEMPORARY) { + allTemps.insert(instr->GetOp2()->GetValue()); + } + } + + if (m_context.debugOutput && !allTemps.empty()) { + AddComment("TAC Temporaries: " + std::to_string(allTemps.size()) + " total"); + } + + // Allouer les 10 premiers dans des registres, le reste en RAM + int tempIndex = 0; + for (const auto& temp : allTemps) { + if (tempIndex < 10) { + // Utiliser un registre t0-t9 + m_tempLocations[temp] = { + TempLocation::REGISTER, + "t" + std::to_string(tempIndex) + }; + + if (m_context.debugOutput) { + AddComment(" " + temp + " -> register t" + std::to_string(tempIndex)); + } + } else { + // Créer une variable en RAM + std::string varLabel = "temp_" + std::to_string(m_tempVarCounter++); + m_assembly << "$" << varLabel << " DV32, 0 ; TAC temporary " + << temp << "\n"; + m_tempLocations[temp] = { + TempLocation::MEMORY, + varLabel + }; + + if (m_context.debugOutput) { + AddComment(" " + temp + " -> memory $" + varLabel); + } + } + tempIndex++; + } + } + + // =================================================================== + // GÉNÉRATION DE LA SECTION TEXT À PARTIR DU TAC + // =================================================================== + + void GenerateFromTAC() { + for (const auto& instr : m_tacProgram.GetInstructions()) { + GenerateTACInstruction(instr); + } + } + + void GenerateTACInstruction(std::shared_ptr instr) { + if (m_context.debugOutput) { + AddComment("TAC: " + instr->ToString()); + } + + switch (instr->GetOpCode()) { + case TACInstruction::OpCode::ADD: + case TACInstruction::OpCode::SUB: + case TACInstruction::OpCode::MUL: + case TACInstruction::OpCode::DIV: + GenerateBinaryOp(instr); + break; + + case TACInstruction::OpCode::EQ: + case TACInstruction::OpCode::NE: + case TACInstruction::OpCode::GT: + case TACInstruction::OpCode::LT: + case TACInstruction::OpCode::GE: + case TACInstruction::OpCode::LE: + GenerateComparison(instr); + break; + + case TACInstruction::OpCode::COPY: + GenerateCopy(instr); + break; + + case TACInstruction::OpCode::LOAD: + GenerateLoad(instr); + break; + + case TACInstruction::OpCode::STORE: + GenerateStore(instr); + break; + + case TACInstruction::OpCode::LABEL: + m_assembly << instr->GetDest()->ToString() << ":\n"; + break; + + case TACInstruction::OpCode::GOTO: + m_assembly << " jump " << instr->GetDest()->ToString() << "\n"; + break; + + case TACInstruction::OpCode::IF_FALSE: + GenerateIfFalse(instr); + break; + + case TACInstruction::OpCode::IF_TRUE: + GenerateIfTrue(instr); + break; + + case TACInstruction::OpCode::PARAM: + GenerateParam(instr); + break; + + case TACInstruction::OpCode::PRINT: + GeneratePrint(instr); + break; + + case TACInstruction::OpCode::CALL: + GenerateCall(instr); + break; + + case TACInstruction::OpCode::RETURN: + m_assembly << " ret\n"; + break; + + case TACInstruction::OpCode::NOP: + m_assembly << " nop\n"; + break; + + default: + AddComment("WARNING: Unsupported TAC instruction"); + } + } + + // =================================================================== + // HELPERS : Charger une opérande dans un registre + // =================================================================== + + std::string LoadOperand(std::shared_ptr op, const std::string& targetReg) { + if (!op) return targetReg; + + switch (op->GetType()) { + case TACOperand::Type::CONSTANT: + m_assembly << " lcons " << targetReg << ", " << op->GetValue() << "\n"; + return targetReg; + + case TACOperand::Type::VARIABLE: + m_assembly << " load " << targetReg << ", $" << op->GetValue() << ", 4\n"; + return targetReg; + + case TACOperand::Type::TEMPORARY: { + auto it = m_tempLocations.find(op->GetValue()); + if (it == m_tempLocations.end()) { + throw std::runtime_error("Temporary not found: " + op->GetValue()); + } + + if (it->second.type == TempLocation::REGISTER) { + // Déjà dans un registre, copier si nécessaire + if (it->second.location != targetReg) { + m_assembly << " mov " << targetReg << ", " + << it->second.location << "\n"; + } + return targetReg; + } else { + // Charger depuis la RAM + m_assembly << " load " << targetReg << ", $" + << it->second.location << ", 4\n"; + return targetReg; + } + } + + case TACOperand::Type::REGISTER: + if (op->GetValue() != targetReg) { + m_assembly << " mov " << targetReg << ", " + << op->GetValue() << "\n"; + } + return targetReg; + + case TACOperand::Type::LABEL: + // Pour les labels, on utilise lcons avec l'adresse + m_assembly << " lcons " << targetReg << ", " + << op->ToString() << "\n"; + return targetReg; + + default: + return targetReg; + } + } + + void StoreResult(std::shared_ptr dest, const std::string& sourceReg) { + if (!dest) return; + + if (dest->GetType() == TACOperand::Type::TEMPORARY) { + auto it = m_tempLocations.find(dest->GetValue()); + if (it == m_tempLocations.end()) { + throw std::runtime_error("Temporary not found: " + dest->GetValue()); + } + + if (it->second.type == TempLocation::REGISTER) { + // Stocker dans un registre + if (it->second.location != sourceReg) { + m_assembly << " mov " << it->second.location << ", " + << sourceReg << "\n"; + } + } else { + // Stocker en RAM + m_assembly << " store $" << it->second.location << ", " + << sourceReg << ", 4\n"; + } + } else if (dest->GetType() == TACOperand::Type::VARIABLE) { + m_assembly << " store $" << dest->GetValue() << ", " + << sourceReg << ", 4\n"; + } else if (dest->GetType() == TACOperand::Type::REGISTER) { + if (dest->GetValue() != sourceReg) { + m_assembly << " mov " << dest->GetValue() << ", " + << sourceReg << "\n"; + } + } + } + + // =================================================================== + // GÉNÉRATION DES INSTRUCTIONS + // =================================================================== + + void GenerateBinaryOp(std::shared_ptr instr) { + // Charger les opérandes + LoadOperand(instr->GetOp1(), "r0"); + LoadOperand(instr->GetOp2(), "r1"); + + // Effectuer l'opération + std::string op; + switch (instr->GetOpCode()) { + case TACInstruction::OpCode::ADD: op = "add"; break; + case TACInstruction::OpCode::SUB: op = "sub"; break; + case TACInstruction::OpCode::MUL: op = "mul"; break; + case TACInstruction::OpCode::DIV: op = "div"; break; + default: return; + } + + m_assembly << " " << op << " r0, r1\n"; + + // Stocker le résultat + StoreResult(instr->GetDest(), "r0"); + } + + void GenerateComparison(std::shared_ptr instr) { + // Charger les opérandes + LoadOperand(instr->GetOp1(), "r0"); + LoadOperand(instr->GetOp2(), "r1"); + + // Effectuer la comparaison + switch (instr->GetOpCode()) { + case TACInstruction::OpCode::EQ: + m_assembly << " eq r0, r0, r1\n"; + break; + case TACInstruction::OpCode::NE: + m_assembly << " eq r0, r0, r1\n"; + m_assembly << " lcons r2, 1\n"; + m_assembly << " xor r0, r2\n"; + break; + case TACInstruction::OpCode::GT: + m_assembly << " gt r0, r0, r1\n"; + break; + case TACInstruction::OpCode::LT: + m_assembly << " lt r0, r0, r1\n"; + break; + case TACInstruction::OpCode::GE: + // >= est NOT(<) + m_assembly << " lt r0, r0, r1\n"; + m_assembly << " lcons r2, 1\n"; + m_assembly << " xor r0, r2\n"; + break; + case TACInstruction::OpCode::LE: + // <= est NOT(>) + m_assembly << " gt r0, r0, r1\n"; + m_assembly << " lcons r2, 1\n"; + m_assembly << " xor r0, r2\n"; + break; + default: return; + } + + // Stocker le résultat + StoreResult(instr->GetDest(), "r0"); + } + + void GenerateCopy(std::shared_ptr instr) { + LoadOperand(instr->GetOp1(), "r0"); + StoreResult(instr->GetDest(), "r0"); + } + + void GenerateLoad(std::shared_ptr instr) { + // dest = *op1 + LoadOperand(instr->GetOp1(), "r0"); // r0 contient l'adresse + m_assembly << " load r1, @r0, 4\n"; + StoreResult(instr->GetDest(), "r1"); + } + + void GenerateStore(std::shared_ptr instr) { + // *dest = op1 + LoadOperand(instr->GetOp1(), "r0"); // r0 contient la valeur + LoadOperand(instr->GetDest(), "r1"); // r1 contient l'adresse + m_assembly << " store @r1, r0, 4\n"; + } + + void GenerateIfFalse(std::shared_ptr instr) { + LoadOperand(instr->GetOp1(), "r0"); + m_assembly << " skipz r0\n"; + m_assembly << " jump " << instr->GetDest()->ToString() << "\n"; + } + + void GenerateIfTrue(std::shared_ptr instr) { + LoadOperand(instr->GetOp1(), "r0"); + m_assembly << " skipnz r0\n"; + m_assembly << " jump " << instr->GetDest()->ToString() << "\n"; + } + + std::vector> m_printParams; + + void GenerateParam(std::shared_ptr instr) { + // Accumuler les paramètres pour le prochain PRINT/CALL + m_printParams.push_back(instr->GetDest()); + } + + void GeneratePrint(std::shared_ptr instr) { + // Sauvegarder r0, r1 + m_assembly << " push r0\n"; + m_assembly << " push r1\n"; + + // Sauvegarder les registres d'arguments selon le nombre de paramètres + int paramCount = m_printParams.size(); + for (int i = 0; i < std::min(4, paramCount); i++) { + m_assembly << " push r" << (i + 2) << "\n"; + } + + // Charger les paramètres dans r2, r3, r4, r5 + for (size_t i = 0; i < m_printParams.size() && i < 4; i++) { + LoadOperand(m_printParams[i], "r" + std::to_string(i + 2)); + } + + // Charger la chaîne de format dans r0 + m_assembly << " lcons r0, $" << instr->GetDest()->GetValue() << "\n"; + + // Charger le nombre d'arguments dans r1 + m_assembly << " lcons r1, " << paramCount << "\n"; + + // Syscall + m_assembly << " syscall 4\n"; + + // Restaurer les registres + for (int i = std::min(4, paramCount) - 1; i >= 0; i--) { + m_assembly << " pop r" << (i + 2) << "\n"; + } + m_assembly << " pop r1\n"; + m_assembly << " pop r0\n"; + + // Vider la liste des paramètres + m_printParams.clear(); + } + + void GenerateCall(std::shared_ptr instr) { + // Générer l'appel de fonction + std::string functionName = instr->GetOp1() ? + instr->GetOp1()->ToString() : + instr->GetDest()->ToString(); + + m_assembly << " call " << functionName << "\n"; + + // Si on a une destination, sauvegarder le résultat + if (instr->GetDest() && instr->GetOp1()) { + // Le résultat est dans r0 par convention + StoreResult(instr->GetDest(), "r0"); + } + } + + // =================================================================== + // HELPER : Obtenir la taille d'une variable + // =================================================================== + int GetVariableSize(std::shared_ptr var) { + if (!var) return 4; + + switch (var->GetValueType()) { + case Variable::ValueType::BOOL: + return 1; + case Variable::ValueType::INTEGER: + case Variable::ValueType::FLOAT: + return 4; + case Variable::ValueType::STRING: + return 1; // Par caractère + default: + return 4; + } + } + + // =================================================================== + // IMPLÉMENTATIONS DES MÉTHODES VIRTUELLES - SECTION DATA + // =================================================================== + + virtual void Visit(const std::shared_ptr v) override { + if (!v || !v->IsConstant()) return; + + switch (v->GetValueType()) { + case Variable::ValueType::STRING: { + std::string value = v->GetValue(); + + // Convertir {0} {1} {2} {3} en %d + for (int i = 0; i < 4; ++i) { + std::string placeholder = "{" + std::to_string(i) + "}"; + size_t pos = 0; + while ((pos = value.find(placeholder, pos)) != std::string::npos) { + value.replace(pos, placeholder.length(), "%d"); + pos += 2; + } + } + + m_assembly << "$" << v->GetLabel() << " DC8, \"" + << value << "\" ; " << v->GetVariableName() << "\n"; + break; + } + + case Variable::ValueType::INTEGER: + m_assembly << "$" << v->GetLabel() << " DC32, " + << v->GetValue() << " ; " + << v->GetVariableName() << "\n"; + break; + + case Variable::ValueType::FLOAT: + m_assembly << "$" << v->GetLabel() << " DC32, " + << v->GetValue() << " ; " + << v->GetVariableName() << "\n"; + break; + + case Variable::ValueType::BOOL: + m_assembly << "$" << v->GetLabel() << " DCB, " + << (v->GetValue() ? "1" : "0") << " ; " + << v->GetVariableName() << "\n"; + break; + + default: + AddComment("WARNING: Unsupported constant variable type for " + + v->GetVariableName()); + break; + } + } + + virtual void GenerateVariable(const std::shared_ptr v) override { + if (!v) return; + + switch (v->GetValueType()) { + case Variable::ValueType::STRING: + m_assembly << "$" << v->GetLabel() << " DV8, \"" + << v->GetValue() << "\" ; " + << v->GetVariableName() << "\n"; + break; + + case Variable::ValueType::INTEGER: + // CORRECTION : Déclarer 1 élément, pas la valeur ! + m_assembly << "$" << v->GetLabel() << " DV32, 1 ; " + << v->GetVariableName() << "\n"; + break; + + case Variable::ValueType::FLOAT: + m_assembly << "$" << v->GetLabel() << " DV32, 1 ; " + << v->GetVariableName() << "\n"; + break; + + case Variable::ValueType::BOOL: + m_assembly << "$" << v->GetLabel() << " DVB, 1 ; " + << v->GetVariableName() << "\n"; + break; + + default: + AddComment("WARNING: Unsupported variable type for " + + v->GetVariableName()); + m_assembly << "$" << v->GetLabel() << " DV32, 1 ; " + << v->GetVariableName() << " (unknown type)\n"; + break; + } + } + + +}; diff --git a/core/story-manager/src/compiler/ast_builder.h b/core/story-manager/src/compiler/ast_builder.h index 0c5816d..db4e3a8 100644 --- a/core/story-manager/src/compiler/ast_builder.h +++ b/core/story-manager/src/compiler/ast_builder.h @@ -183,14 +183,19 @@ public: // Maintenant, on va ajouter les connexions de données for (const auto& conn : m_connections) { + // Ne traiter que les connexions DATA_LINK + if (conn->type != Connection::DATA_LINK) { + continue; + } + auto outNode = nodeMap[conn->outNodeId]; auto inNode = nodeMap[conn->inNodeId]; - // Keep variables nodes as data inputs - if (dynamic_cast(outNode->node.get())) - { - inNode->AddDataInput(conn->inPortIndex, outNode); - } + // Ajouter TOUTES les connexions de données, pas seulement celles depuis VariableNode + inNode->AddDataInput(conn->inPortIndex, outNode); + + // Ajouter aussi dans le sens inverse pour les dataOutputs + outNode->AddDataOutput(conn->outPortIndex, inNode, conn->inPortIndex); } // Build execution paths diff --git a/core/story-manager/src/compiler/tac.h b/core/story-manager/src/compiler/tac.h new file mode 100644 index 0000000..b31b454 --- /dev/null +++ b/core/story-manager/src/compiler/tac.h @@ -0,0 +1,706 @@ +// =================================================================== +// tac.h - Three-Address Code Representation (CLEAN VERSION) +// =================================================================== +// Ce fichier contient UNIQUEMENT la représentation TAC et le générateur +// La conversion TAC → Assembleur est dans AssemblyGeneratorChip32TAC +// =================================================================== + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// Forward declarations +class ASTNode; +class VariableNode; +class OperatorNode; +class PrintNode; +class BranchNode; +class FunctionEntryNode; +class CallFunctionNode; + +// =================================================================== +// TAC Operand - Représente une opérande (variable, temporaire, constante) +// =================================================================== +class TACOperand { +public: + enum class Type { + VARIABLE, // Variable globale ($label) + TEMPORARY, // Variable temporaire (t0, t1, ...) + CONSTANT, // Constante numérique + LABEL, // Label de saut + REGISTER // Registre explicite (r0, r1, ...) + }; + + TACOperand(Type type, const std::string& value) + : m_type(type), m_value(value) {} + + Type GetType() const { return m_type; } + std::string GetValue() const { return m_value; } + + std::string ToString() const { + switch (m_type) { + case Type::VARIABLE: return "$" + m_value; + case Type::TEMPORARY: return "%" + m_value; + case Type::CONSTANT: return m_value; + case Type::LABEL: return "." + m_value; + case Type::REGISTER: return m_value; + } + return m_value; + } + +private: + Type m_type; + std::string m_value; +}; + +// =================================================================== +// TAC Instruction - Une instruction à 3 adresses +// =================================================================== +class TACInstruction { +public: + enum class OpCode { + // Arithmetic + ADD, // dest = op1 + op2 + SUB, // dest = op1 - op2 + MUL, // dest = op1 * op2 + DIV, // dest = op1 / op2 + + // Comparison + EQ, // dest = op1 == op2 + NE, // dest = op1 != op2 + GT, // dest = op1 > op2 + LT, // dest = op1 < op2 + GE, // dest = op1 >= op2 + LE, // dest = op1 <= op2 + + // Logical + AND, // dest = op1 && op2 + OR, // dest = op1 || op2 + NOT, // dest = !op1 + + // Bitwise + BIT_AND, // dest = op1 & op2 + BIT_OR, // dest = op1 | op2 + BIT_XOR, // dest = op1 ^ op2 + BIT_NOT, // dest = ~op1 + + // Assignment + COPY, // dest = op1 + LOAD, // dest = *op1 (load from memory) + STORE, // *dest = op1 (store to memory) + + // Control flow + LABEL, // Define a label + GOTO, // Unconditional jump + IF_FALSE, // if (!op1) goto label + IF_TRUE, // if (op1) goto label + + // Function calls + PARAM, // Push parameter for call + CALL, // Call function (dest = result) + RETURN, // Return from function + + // Special + PRINT, // Print (syscall 4) + SYSCALL, // Generic syscall + NOP // No operation + }; + + TACInstruction(OpCode op) + : m_opcode(op) {} + + TACInstruction(OpCode op, std::shared_ptr dest) + : m_opcode(op), m_dest(dest) {} + + TACInstruction(OpCode op, std::shared_ptr dest, + std::shared_ptr op1) + : m_opcode(op), m_dest(dest), m_op1(op1) {} + + TACInstruction(OpCode op, std::shared_ptr dest, + std::shared_ptr op1, + std::shared_ptr op2) + : m_opcode(op), m_dest(dest), m_op1(op1), m_op2(op2) {} + + OpCode GetOpCode() const { return m_opcode; } + std::shared_ptr GetDest() const { return m_dest; } + std::shared_ptr GetOp1() const { return m_op1; } + std::shared_ptr GetOp2() const { return m_op2; } + + std::string ToString() const { + std::stringstream ss; + + switch (m_opcode) { + case OpCode::ADD: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " + " << m_op2->ToString(); + break; + case OpCode::SUB: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " - " << m_op2->ToString(); + break; + case OpCode::MUL: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " * " << m_op2->ToString(); + break; + case OpCode::DIV: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " / " << m_op2->ToString(); + break; + case OpCode::EQ: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " == " << m_op2->ToString(); + break; + case OpCode::NE: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " != " << m_op2->ToString(); + break; + case OpCode::GT: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " > " << m_op2->ToString(); + break; + case OpCode::LT: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " < " << m_op2->ToString(); + break; + case OpCode::GE: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " >= " << m_op2->ToString(); + break; + case OpCode::LE: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " <= " << m_op2->ToString(); + break; + case OpCode::AND: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " && " << m_op2->ToString(); + break; + case OpCode::OR: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " || " << m_op2->ToString(); + break; + case OpCode::NOT: + ss << m_dest->ToString() << " = !" << m_op1->ToString(); + break; + case OpCode::BIT_AND: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " & " << m_op2->ToString(); + break; + case OpCode::BIT_OR: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " | " << m_op2->ToString(); + break; + case OpCode::BIT_XOR: + ss << m_dest->ToString() << " = " << m_op1->ToString() + << " ^ " << m_op2->ToString(); + break; + case OpCode::BIT_NOT: + ss << m_dest->ToString() << " = ~" << m_op1->ToString(); + break; + case OpCode::COPY: + ss << m_dest->ToString() << " = " << m_op1->ToString(); + break; + case OpCode::LOAD: + ss << m_dest->ToString() << " = *" << m_op1->ToString(); + break; + case OpCode::STORE: + ss << "*" << m_dest->ToString() << " = " << m_op1->ToString(); + break; + case OpCode::LABEL: + ss << m_dest->ToString() << ":"; + break; + case OpCode::GOTO: + ss << "goto " << m_dest->ToString(); + break; + case OpCode::IF_FALSE: + ss << "if (!" << m_op1->ToString() << ") goto " << m_dest->ToString(); + break; + case OpCode::IF_TRUE: + ss << "if (" << m_op1->ToString() << ") goto " << m_dest->ToString(); + break; + case OpCode::PARAM: + ss << "param " << m_dest->ToString(); + break; + case OpCode::CALL: + if (m_op1) { + ss << m_dest->ToString() << " = call " << m_op1->ToString(); + } else { + ss << "call " << m_dest->ToString(); + } + break; + case OpCode::RETURN: + if (m_dest) { + ss << "return " << m_dest->ToString(); + } else { + ss << "return"; + } + break; + case OpCode::PRINT: + ss << "print " << m_dest->ToString(); + if (m_op1) ss << ", " << m_op1->ToString(); + if (m_op2) ss << ", ..."; + break; + case OpCode::SYSCALL: + ss << "syscall " << m_dest->ToString(); + break; + case OpCode::NOP: + ss << "nop"; + break; + default: + ss << "??? opcode=" << static_cast(m_opcode); + } + + return ss.str(); + } + +private: + OpCode m_opcode; + std::shared_ptr m_dest; + std::shared_ptr m_op1; + std::shared_ptr m_op2; +}; + +// =================================================================== +// TAC Program - Liste d'instructions TAC +// =================================================================== +class TACProgram { +public: + void AddInstruction(std::shared_ptr instr) { + m_instructions.push_back(instr); + } + + const std::vector>& GetInstructions() const { + return m_instructions; + } + + std::string ToString() const { + std::stringstream ss; + ss << "=== Three-Address Code ===\n"; + for (const auto& instr : m_instructions) { + ss << instr->ToString() << "\n"; + } + return ss.str(); + } + + void Clear() { + m_instructions.clear(); + } + + size_t Size() const { + return m_instructions.size(); + } + +private: + std::vector> m_instructions; +}; + +// =================================================================== +// TAC Generator - Convertit AST → TAC +// =================================================================== +// Note: Nécessite les includes des types de nœuds concrets +// =================================================================== + +#include "ast_builder.h" +#include "variable_node.h" +#include "operator_node.h" +#include "print_node.h" +#include "branch_node.h" +#include "function_entry_node.h" +#include "call_function_node.h" + +class TACGenerator { +public: + TACGenerator() : m_tempCounter(0), m_labelCounter(0) {} + + // Génère le TAC à partir de l'AST + TACProgram Generate(const std::vector>& astNodes) { + std::cout << "\n=== TACGenerator::Generate() START ===\n"; + std::cout << "Number of AST nodes received: " << astNodes.size() << "\n"; + + m_program.Clear(); + m_tempCounter = 0; + m_labelCounter = 0; + m_nodeResults.clear(); + m_visitedNodes.clear(); + + // Simplement appeler GenerateNode pour chaque nœud + // La gestion des doublons se fait dans GenerateNode() + for (size_t i = 0; i < astNodes.size(); i++) { + const auto& node = astNodes[i]; + std::cout << "Processing AST node [" << i << "]: " + << node->node->GetTypeName() + << " (ID: " << node->GetId() << ")\n"; + + GenerateNode(node); + } + + std::cout << "Total TAC instructions generated: " << m_program.Size() << "\n"; + std::cout << "=== TACGenerator::Generate() END ===\n\n"; + + return m_program; + } + + // Accesseur pour obtenir le dernier programme généré + const TACProgram& GetProgram() const { + return m_program; + } + +private: + TACProgram m_program; + int m_tempCounter; + int m_labelCounter; + + // Map pour garder en mémoire où sont stockés les résultats des nœuds + std::map> m_nodeResults; + + // Set pour éviter de visiter deux fois le même nœud + std::set m_visitedNodes; + + // =================================================================== + // HELPERS + // =================================================================== + + // Génère un nouveau temporaire + std::shared_ptr NewTemp() { + return std::make_shared( + TACOperand::Type::TEMPORARY, + "t" + std::to_string(m_tempCounter++) + ); + } + + // Génère un nouveau label + std::shared_ptr NewLabel(const std::string& prefix = "L") { + return std::make_shared( + TACOperand::Type::LABEL, + prefix + std::to_string(m_labelCounter++) + ); + } + + // =================================================================== + // GÉNÉRATION RÉCURSIVE + // =================================================================== + + // Génère le code pour un nœud et retourne où est stocké son résultat + std::shared_ptr GenerateNode(std::shared_ptr node) { + if (!node) return nullptr; + + std::cout << " GenerateNode(" << node->node->GetTypeName() + << ", ID: " << node->GetId() << ")\n"; + + // NOUVEAU : Vérifier si ce nœud a déjà été complètement traité + if (m_visitedNodes.find(node->GetId()) != m_visitedNodes.end()) { + std::cout << " -> Node already fully processed, SKIPPING\n"; + // Retourner le résultat en cache s'il existe + auto it = m_nodeResults.find(node->GetId()); + if (it != m_nodeResults.end()) { + return it->second; + } + return nullptr; + } + + // Marquer ce nœud comme visité DÈS LE DÉBUT + m_visitedNodes.insert(node->GetId()); + + // Vérifier si on a déjà évalué ce nœud (résultat en cache) + auto it = m_nodeResults.find(node->GetId()); + if (it != m_nodeResults.end()) { + std::cout << " -> Result already cached, returning " + << it->second->ToString() << "\n"; + return it->second; + } + + std::shared_ptr result = nullptr; + + if (node->IsType()) { + std::cout << " -> Type: VariableNode\n"; + result = GenerateVariableNode(node); + } + else if (node->IsType()) { + std::cout << " -> Type: OperatorNode\n"; + result = GenerateOperatorNode(node); + } + else if (node->IsType()) { + std::cout << " -> Type: PrintNode\n"; + GeneratePrintNode(node); + } + else if (node->IsType()) { + std::cout << " -> Type: BranchNode\n"; + GenerateBranchNode(node); + } + else if (node->IsType()) { + std::cout << " -> Type: CallFunctionNode\n"; + GenerateCallFunctionNode(node); + } + else if (node->IsType()) { + std::cout << " -> Type: FunctionEntryNode (generating children)\n"; + // Entry point, générer les enfants + for (size_t i = 0; i < node->children.size(); i++) { + std::cout << " Processing child [" << i << "]\n"; + GenerateNode(node->children[i]); + } + } + + // Mémoriser le résultat + if (result) { + std::cout << " -> Caching result: " << result->ToString() << "\n"; + m_nodeResults[node->GetId()] = result; + } + + return result; + } + + // =================================================================== + // GÉNÉRATION PAR TYPE DE NŒUD + // =================================================================== + + std::shared_ptr GenerateVariableNode(std::shared_ptr node) { + auto* varNode = node->GetAs(); + if (!varNode) return nullptr; + + auto var = varNode->GetVariable(); + if (!var) return nullptr; + + // Créer une opérande qui référence la variable + return std::make_shared( + TACOperand::Type::VARIABLE, + var->GetLabel() + ); + } + + std::shared_ptr GenerateOperatorNode(std::shared_ptr node) { + auto* opNode = node->GetAs(); + if (!opNode) return nullptr; + + // Évaluer les opérandes (récursif) + std::vector> operands; + + // Collecter et trier les inputs par port index + std::vector>> sortedInputs; + for (const auto& [port, inputNode] : node->dataInputs) { + sortedInputs.push_back({port, inputNode}); + } + std::sort(sortedInputs.begin(), sortedInputs.end(), + [](const auto& a, const auto& b) { return a.first < b.first; }); + + for (const auto& [port, inputNode] : sortedInputs) { + auto operand = GenerateNode(inputNode); + if (operand) { + operands.push_back(operand); + } + } + + if (operands.size() < 2) { + throw std::runtime_error("Operator needs at least 2 operands"); + } + + // Créer un temporaire pour le résultat + auto resultTemp = NewTemp(); + + // Générer l'instruction TAC appropriée + TACInstruction::OpCode opcode; + switch (opNode->GetOperationType()) { + case OperatorNode::OperationType::ADD: + opcode = TACInstruction::OpCode::ADD; + break; + case OperatorNode::OperationType::SUBTRACT: + opcode = TACInstruction::OpCode::SUB; + break; + case OperatorNode::OperationType::MULTIPLY: + opcode = TACInstruction::OpCode::MUL; + break; + case OperatorNode::OperationType::DIVIDE: + opcode = TACInstruction::OpCode::DIV; + break; + case OperatorNode::OperationType::EQUAL: + opcode = TACInstruction::OpCode::EQ; + break; + case OperatorNode::OperationType::NOT_EQUAL: + opcode = TACInstruction::OpCode::NE; + break; + case OperatorNode::OperationType::GREATER_THAN: + opcode = TACInstruction::OpCode::GT; + break; + case OperatorNode::OperationType::LESS_THAN: + opcode = TACInstruction::OpCode::LT; + break; + case OperatorNode::OperationType::GREATER_EQUAL: + opcode = TACInstruction::OpCode::GE; + break; + case OperatorNode::OperationType::LESS_EQUAL: + opcode = TACInstruction::OpCode::LE; + break; + default: + throw std::runtime_error("Unsupported operator"); + } + + auto instr = std::make_shared( + opcode, resultTemp, operands[0], operands[1] + ); + m_program.AddInstruction(instr); + + return resultTemp; + } + + void GeneratePrintNode(std::shared_ptr node) { + auto* printNode = node->GetAs(); + if (!printNode) return; + + std::cout << " GeneratePrintNode: START\n"; + + // Créer l'opérande pour la chaîne de format + auto formatOperand = std::make_shared( + TACOperand::Type::VARIABLE, + printNode->GetLabel() + ); + + std::cout << " Format string label: " << printNode->GetLabel() << "\n"; + std::cout << " Number of data inputs: " << node->dataInputs.size() << "\n"; + + // Évaluer tous les arguments + std::vector> args; + + // Collecter et trier les inputs par port index + std::vector>> sortedInputs; + for (const auto& [port, inputNode] : node->dataInputs) { + std::cout << " Found input at port " << port + << " -> " << inputNode->node->GetTypeName() << "\n"; + sortedInputs.push_back({port, inputNode}); + } + std::sort(sortedInputs.begin(), sortedInputs.end(), + [](const auto& a, const auto& b) { return a.first < b.first; }); + + // Générer le code pour chaque argument + for (const auto& [port, inputNode] : sortedInputs) { + std::cout << " Processing input port " << port << "\n"; + auto argOperand = GenerateNode(inputNode); + if (argOperand) { + std::cout << " -> Got operand: " << argOperand->ToString() << "\n"; + args.push_back(argOperand); + } + } + + std::cout << " Total args collected: " << args.size() << "\n"; + + // Générer les instructions PARAM pour chaque argument + for (size_t i = 0; i < args.size(); i++) { + std::cout << " Generating PARAM instruction [" << i << "] for " + << args[i]->ToString() << "\n"; + auto paramInstr = std::make_shared( + TACInstruction::OpCode::PARAM, + args[i] + ); + m_program.AddInstruction(paramInstr); + } + + // Générer l'instruction PRINT + std::cout << " Generating PRINT instruction\n"; + auto printInstr = std::make_shared( + TACInstruction::OpCode::PRINT, + formatOperand + ); + m_program.AddInstruction(printInstr); + + std::cout << " GeneratePrintNode: END (generated " + << (args.size() + 1) << " instructions)\n"; + } + + void GenerateCallFunctionNode(std::shared_ptr node) { + auto* callNode = node->GetAs(); + if (!callNode) return; + + std::string pageUuid = callNode->GetFunctionUuid(); + std::string pageLabel = "page_" + pageUuid; + + std::cout << " Generating CALL to page: " << pageLabel << "\n"; + + // Créer l'opérande label pour la page cible + auto targetLabel = std::make_shared( + TACOperand::Type::LABEL, + pageLabel + ); + + // Générer l'instruction CALL + auto callInstr = std::make_shared( + TACInstruction::OpCode::CALL, + targetLabel + ); + + m_program.AddInstruction(callInstr); + } + + void GenerateBranchNode(std::shared_ptr node) { + // Évaluer la condition (le BranchNode reçoit la condition sur le port 1) + auto conditionInput = node->GetDataInput(1); + if (!conditionInput) { + throw std::runtime_error("BranchNode missing condition input on port 1"); + } + + auto conditionOperand = GenerateNode(conditionInput); + if (!conditionOperand) { + throw std::runtime_error("BranchNode condition evaluation returned null"); + } + + // Créer les labels pour true/false + auto trueLabel = NewLabel("true_branch"); + auto falseLabel = NewLabel("false_branch"); + auto endLabel = NewLabel("end_branch"); + + // if (!condition) goto falseLabel + auto ifInstr = std::make_shared( + TACInstruction::OpCode::IF_FALSE, + falseLabel, + conditionOperand + ); + m_program.AddInstruction(ifInstr); + + // True branch label + auto labelInstr1 = std::make_shared( + TACInstruction::OpCode::LABEL, + trueLabel + ); + m_program.AddInstruction(labelInstr1); + + // True branch code + if (node->GetChild(0)) { + GenerateNode(node->GetChild(0)); + } + + // goto end + auto gotoEnd = std::make_shared( + TACInstruction::OpCode::GOTO, + endLabel + ); + m_program.AddInstruction(gotoEnd); + + // False branch label + auto labelInstr2 = std::make_shared( + TACInstruction::OpCode::LABEL, + falseLabel + ); + m_program.AddInstruction(labelInstr2); + + // False branch code + if (node->GetChild(1)) { + GenerateNode(node->GetChild(1)); + } + + // End label + auto labelInstr3 = std::make_shared( + TACInstruction::OpCode::LABEL, + endLabel + ); + m_program.AddInstruction(labelInstr3); + } +}; + +// =================================================================== +// FIN DU FICHIER +// =================================================================== +// Note: La conversion TAC → Assembleur est dans AssemblyGeneratorChip32TAC +// Ce fichier ne contient QUE la représentation TAC pure +// =================================================================== \ No newline at end of file diff --git a/core/story-manager/src/story_page.h b/core/story-manager/src/story_page.h index 1622968..a20e547 100644 --- a/core/story-manager/src/story_page.h +++ b/core/story-manager/src/story_page.h @@ -55,21 +55,7 @@ public: m_nodes.clear(); } - void CompileNodesVariables(AssemblyGenerator &generator) - { - std::vector> nodes(m_nodes.begin(), m_nodes.end()); - generator.GenerateNodesVariables(nodes); - } - void CompileNodes(AssemblyGenerator &generator) - { - std::vector> nodes(m_nodes.begin(), m_nodes.end()); - std::vector> links(m_links.begin(), m_links.end()); - ASTBuilder builder(nodes, links); - auto pathTree = builder.BuildAST(); - - generator.GenerateTextSection(pathTree); - } virtual void GetNodeConnections(std::list> &c, const std::string &nodeId) override { diff --git a/core/story-manager/src/story_project.cpp b/core/story-manager/src/story_project.cpp index 7c75ce3..a44372f 100644 --- a/core/story-manager/src/story_project.cpp +++ b/core/story-manager/src/story_project.cpp @@ -13,7 +13,7 @@ #include "print_node.h" #include "syscall_node.h" #include "sys_lib.h" -#include "assembly_generator_chip32.h" +#include "assembly_generator_chip32_tac.h" #include "nodes_factory.h" StoryProject::StoryProject(ILogger &log) @@ -50,7 +50,7 @@ void StoryProject::CopyToDevice(const std::string &outputDir, NodesFactory &fact // Generate and copy binary std::string code; - GenerateScript(code); + // GenerateScript(code); // FIXME std::cout << code << std::endl; @@ -411,63 +411,109 @@ bool StoryProject::UseResource(const std::string &label) return used; } -bool StoryProject::GenerateScript(std::string &codeStr) + +// story_project.cpp + +bool StoryProject::GenerateCompleteProgram(std::string &assembly) { - bool retCode = true; - std::stringstream code; + // === PHASE 1 : COLLECTE === + // Collecter tous les nœuds et connexions de toutes les pages + std::vector> allNodes; + std::map>, + std::vector> + >> pageData; - // Empty resources usage - m_usedLabels.clear(); - - - - // Create generator context with current time and user - AssemblyGenerator::GeneratorContext context( + for (const auto& page : m_pages) { + auto [nodesBegin, nodesEnd] = page->Nodes(); + auto [linksBegin, linksEnd] = page->Links(); + + std::vector> pageNodes(nodesBegin, nodesEnd); + std::vector> pageLinks(linksBegin, linksEnd); + + // Ajouter tous les nœuds à la liste globale pour la section DATA + allNodes.insert(allNodes.end(), pageNodes.begin(), pageNodes.end()); + + // Stocker les données de chaque page + pageData[std::string(page->Uuid())] = {pageNodes, pageLinks}; + } + + // === PHASE 2 : GÉNÉRATION === + AssemblyGenerator::GeneratorContext context( m_variables, - "2025-04-08 12:09:01", // Current UTC time - "story-editor", // Current user - true, // Enable debug output - true, // Enable optimizations - 1024 // Stack size + "2025-01-10 15:30:00", + "story-project", + true, // debug + true, // optimize + 1024 ); - - // Create generator - AssemblyGeneratorChip32 generator(context); - - try - { - generator.Reset(); - - // Generate header comments - generator.GenerateHeader(); - - // Generate text section - generator.StartSection(AssemblyGenerator::Section::TEXT); - for (const auto & p : m_pages) - { - p->CompileNodes(generator); + + AssemblyGeneratorChip32TAC generator(context); + + // Header + generator.Reset(); + generator.GenerateHeader(); + + // === SECTION DATA (commune à toutes les pages) === + generator.StartSection(AssemblyGenerator::Section::DATA); + + // Variables globales (partagées entre toutes les pages) + generator.GenerateGlobalVariables(); + + // Constantes de tous les nœuds de toutes les pages + generator.GenerateNodesVariables(allNodes); + + // === SECTION TEXT (chaque page = une fonction) === + generator.AddComment("======================= CODE ======================="); + + // Générer chaque page comme une fonction + bool isFirstPage = true; + for (const auto& page : m_pages) { + std::string pageUuid(page->Uuid()); + auto& [nodes, connections] = pageData[pageUuid]; + + // Construire l'AST pour cette page + ASTBuilder builder(nodes, connections); + auto astNodes = builder.BuildAST(); + + // Générer le label de fonction + std::string functionLabel; + if (isFirstPage || pageUuid == MainUuid()) { + functionLabel = ".main"; + isFirstPage = false; + } else { + functionLabel = ".page_" + pageUuid; } - - // Generate data section - generator.StartSection(AssemblyGenerator::Section::DATA); - for (const auto & p : m_pages) - { - p->CompileNodesVariables(generator); + + generator.AddComment("========================================"); + generator.AddComment("Page: " + std::string(page->GetName())); + generator.AddComment("UUID: " + pageUuid); + generator.AddComment("========================================"); + generator.GetAssembly() << functionLabel << ":\n"; + + // Générer le TAC pour cette page + TACGenerator tacGen; + TACProgram pageTAC = tacGen.Generate(astNodes); + + if (context.debugOutput) { + std::cout << "\n=== TAC for page: " << page->GetName() << " ===\n"; + std::cout << pageTAC.ToString() << std::endl; + } + + // Convertir le TAC en assembleur + generator.GenerateTACToAssembly(pageTAC); + + // Retour de la fonction (sauf pour main) + if (functionLabel != ".main") { + generator.GetAssembly() << " ret\n\n"; } - generator.GenerateGlobalVariables(); - - generator.GenerateExit(); - - } - catch (const std::exception &e) - { - m_log.Log(e.what(), true); - retCode = false; } + + // Exit du programme + generator.GenerateExit(); + + assembly = generator.GetAssembly().str(); - codeStr = generator.GetAssembly(); - - return retCode; + return true; } bool StoryProject::GenerateBinary(const std::string &code, Chip32::Assembler::Error &err) diff --git a/core/story-manager/src/story_project.h b/core/story-manager/src/story_project.h index f52c55d..3161740 100644 --- a/core/story-manager/src/story_project.h +++ b/core/story-manager/src/story_project.h @@ -16,6 +16,7 @@ #include "story_page.h" #include "story_options.h" #include "variable.h" +#include "assembly_generator_chip32_tac.h" class NodesFactory; @@ -38,9 +39,14 @@ public: return "490745ab-df4d-476d-ae27-027e94b8ee0a"; } + bool FindMain(Chip32::Instr &mainLine) { + return m_assembler.GetMain(mainLine); + } + + bool GenerateCompleteProgram(std::string &assembly); + void New(const std::string &uuid, const std::string &library_path); std::filesystem::path BinaryFileName() const; - bool GenerateScript(std::string &codeStr); bool GenerateBinary(const std::string &code, Chip32::Assembler::Error &err); bool Load(ResourceManager &manager, NodesFactory &factory); void Save(ResourceManager &manager); diff --git a/core/tests/test_ast.cpp b/core/tests/test_ast.cpp index 5216e98..bbcf0d5 100644 --- a/core/tests/test_ast.cpp +++ b/core/tests/test_ast.cpp @@ -43,7 +43,8 @@ THE SOFTWARE. #include "ast_builder.h" #include "assembly_generator.h" #include "flow_generator.h" -#include "assembly_generator_chip32.h" +#include "assembly_generator_chip32_tac.h" + TEST_CASE( "Check AST with basic nodes" ) { @@ -223,9 +224,6 @@ TEST_CASE( "Check AST with basic nodes" ) { 1024 // Stack size ); - // Create generator - AssemblyGeneratorChip32 generator(context); - ASTBuilder builder(nodes, connections); auto pathTree = builder.BuildAST(); @@ -240,27 +238,996 @@ TEST_CASE( "Check AST with basic nodes" ) { std::cout << "\nGenerated flow:\n" << flow << std::endl; //------------------------------------ Generate assembly ------------------------------------ - - generator.Reset(); - - // Generate header comments - generator.GenerateHeader(); - - // Generate data section - generator.StartSection(AssemblyGenerator::Section::DATA); - generator.GenerateNodesVariables(nodes); - generator.GenerateGlobalVariables(); - generator.StartSection(AssemblyGenerator::Section::TEXT); - generator.GenerateTextSection(pathTree); - generator.GenerateExit(); - - std::string assembly = generator.GetAssembly(); + AssemblyGeneratorChip32TAC generator(context); + generator.GenerateCompleteProgram(nodes, pathTree); + std::string assembly = generator.GetAssembly().str(); std::cout << "\nGenerated assembly:\n" << assembly << std::endl; Chip32::Machine machine; machine.QuickExecute(assembly); - - +} + +// =================================================================== +// TEST 1 : Print simple avec 2 variables +// =================================================================== +TEST_CASE("TAC Generation - Print with 2 variables", "[tac][print]") { + // === SETUP === + + // Créer les variables + auto var_a = std::make_shared("a"); + var_a->SetIntegerValue(10); + + auto var_b = std::make_shared("b"); + var_b->SetIntegerValue(20); + + // Créer les nœuds + auto functionEntry = std::make_shared("function-entry-node"); + functionEntry->SetWeight(100); + + auto varNodeA = std::make_shared("variable-node-a"); + varNodeA->SetVariable(var_a); + + auto varNodeB = std::make_shared("variable-node-b"); + varNodeB->SetVariable(var_b); + + auto printNode = std::make_shared("print-node"); + printNode->SetText("Values: {0}, {1}"); + printNode->Initialize(); + + std::vector> nodes = { + functionEntry, + varNodeA, + varNodeB, + printNode + }; + + // Créer les connexions + std::vector> connections; + + // Execution: Entry → Print + auto execConn = std::make_shared(); + execConn->outNodeId = functionEntry->GetId(); + execConn->outPortIndex = 0; + execConn->inNodeId = printNode->GetId(); + execConn->inPortIndex = 0; + execConn->type = Connection::EXECUTION_LINK; + connections.push_back(execConn); + + // Data: varA → Print.arg0 + auto dataConn1 = std::make_shared(); + dataConn1->outNodeId = varNodeA->GetId(); + dataConn1->outPortIndex = 0; + dataConn1->inNodeId = printNode->GetId(); + dataConn1->inPortIndex = 1; // arg0 + dataConn1->type = Connection::DATA_LINK; + connections.push_back(dataConn1); + + // Data: varB → Print.arg1 + auto dataConn2 = std::make_shared(); + dataConn2->outNodeId = varNodeB->GetId(); + dataConn2->outPortIndex = 0; + dataConn2->inNodeId = printNode->GetId(); + dataConn2->inPortIndex = 2; // arg1 + dataConn2->type = Connection::DATA_LINK; + connections.push_back(dataConn2); + + // === BUILD AST === + ASTBuilder builder(nodes, connections); + auto astNodes = builder.BuildAST(); + + // === GENERATE TAC === + TACGenerator tacGen; + TACProgram tac = tacGen.Generate(astNodes); + + // === DISPLAY TAC (for debugging) === + std::cout << "\n" << tac.ToString() << std::endl; + + // === VERIFY TAC === + const auto& instructions = tac.GetInstructions(); + + // On doit avoir exactement 3 instructions : + // 1. param $a_label + // 2. param $b_label + // 3. print $format_label + REQUIRE(instructions.size() == 3); + + // Instruction 0 : param $a + REQUIRE(instructions[0]->GetOpCode() == TACInstruction::OpCode::PARAM); + REQUIRE(instructions[0]->GetDest()->GetType() == TACOperand::Type::VARIABLE); + REQUIRE(instructions[0]->GetDest()->GetValue() == var_a->GetLabel()); + + // Instruction 1 : param $b + REQUIRE(instructions[1]->GetOpCode() == TACInstruction::OpCode::PARAM); + REQUIRE(instructions[1]->GetDest()->GetType() == TACOperand::Type::VARIABLE); + REQUIRE(instructions[1]->GetDest()->GetValue() == var_b->GetLabel()); + + // Instruction 2 : print $format + REQUIRE(instructions[2]->GetOpCode() == TACInstruction::OpCode::PRINT); + REQUIRE(instructions[2]->GetDest()->GetType() == TACOperand::Type::VARIABLE); + REQUIRE(instructions[2]->GetDest()->GetValue() == printNode->GetLabel()); +} + + +// =================================================================== +// Test complexe : AST avec TAC +// Teste : calculs intermédiaires, réutilisation, Print multi-args, branches +// =================================================================== + +TEST_CASE("Complex AST with TAC - Intermediate results and reuse", "[tac][complex][ast]") { + + std::cout << "\n========================================\n"; + std::cout << "Complex TAC Test with Intermediate Results\n"; + std::cout << "========================================\n"; + + // === VARIABLES === + std::vector> variables; + + auto var_A = std::make_shared("A"); + var_A->SetIntegerValue(10); + variables.push_back(var_A); + + auto var_B = std::make_shared("B"); + var_B->SetIntegerValue(5); + variables.push_back(var_B); + + auto var_C = std::make_shared("C"); + var_C->SetIntegerValue(3); + variables.push_back(var_C); + + auto var_threshold = std::make_shared("threshold"); + var_threshold->SetIntegerValue(40); + variables.push_back(var_threshold); + + // === NŒUDS === + + // Entry point + auto functionEntry = std::make_shared("function-entry-node"); + functionEntry->SetWeight(100); + + // Variable nodes + auto varNodeA = std::make_shared("variable-node-A"); + varNodeA->SetVariable(var_A); + + auto varNodeB = std::make_shared("variable-node-B"); + varNodeB->SetVariable(var_B); + + auto varNodeC = std::make_shared("variable-node-C"); + varNodeC->SetVariable(var_C); + + auto varNodeThreshold = std::make_shared("variable-node-threshold"); + varNodeThreshold->SetVariable(var_threshold); + + // Opérateurs + auto addNode = std::make_shared("operator-add"); + addNode->SetOperationType(OperatorNode::OperationType::ADD); + + auto subNode = std::make_shared("operator-sub"); + subNode->SetOperationType(OperatorNode::OperationType::SUBTRACT); + + auto mulNode = std::make_shared("operator-mul"); + mulNode->SetOperationType(OperatorNode::OperationType::MULTIPLY); + + auto compareNode = std::make_shared("operator-compare"); + compareNode->SetOperationType(OperatorNode::OperationType::GREATER_THAN); + + // Print nodes + auto print1 = std::make_shared("print-node-1"); + print1->SetText("Addition: {0} + {1} = {2}"); + print1->Initialize(); + + auto print2 = std::make_shared("print-node-2"); + print2->SetText("Subtraction: {0} - {1} = {2}"); + print2->Initialize(); + + auto print3 = std::make_shared("print-node-3"); + print3->SetText("Multiplication: {0} * {1} = {2}"); + print3->Initialize(); + + // Branch node + auto branchNode = std::make_shared("branch-node"); + + auto printTrue = std::make_shared("print-true"); + printTrue->SetText("Result is LARGE (> 40)"); + printTrue->Initialize(); + + auto printFalse = std::make_shared("print-false"); + printFalse->SetText("Result is small"); + printFalse->Initialize(); + + // === LISTE DES NŒUDS === + std::vector> nodes = { + functionEntry, + varNodeA, varNodeB, varNodeC, varNodeThreshold, + addNode, subNode, mulNode, compareNode, + print1, print2, print3, + branchNode, printTrue, printFalse + }; + + // === CONNEXIONS === + std::vector> connections; + + // --- Calcul 1 : A + B → intermediate1 --- + auto conn1 = std::make_shared(); + conn1->outNodeId = varNodeA->GetId(); + conn1->outPortIndex = 0; + conn1->inNodeId = addNode->GetId(); + conn1->inPortIndex = 0; + conn1->type = Connection::DATA_LINK; + connections.push_back(conn1); + + auto conn2 = std::make_shared(); + conn2->outNodeId = varNodeB->GetId(); + conn2->outPortIndex = 0; + conn2->inNodeId = addNode->GetId(); + conn2->inPortIndex = 1; + conn2->type = Connection::DATA_LINK; + connections.push_back(conn2); + + // --- Calcul 2 : A - B → intermediate2 --- + auto conn3 = std::make_shared(); + conn3->outNodeId = varNodeA->GetId(); + conn3->outPortIndex = 0; + conn3->inNodeId = subNode->GetId(); + conn3->inPortIndex = 0; + conn3->type = Connection::DATA_LINK; + connections.push_back(conn3); + + auto conn4 = std::make_shared(); + conn4->outNodeId = varNodeB->GetId(); + conn4->outPortIndex = 0; + conn4->inNodeId = subNode->GetId(); + conn4->inPortIndex = 1; + conn4->type = Connection::DATA_LINK; + connections.push_back(conn4); + + // --- Calcul 3 : intermediate1 * C → result --- + auto conn5 = std::make_shared(); + conn5->outNodeId = addNode->GetId(); // intermediate1 + conn5->outPortIndex = 0; + conn5->inNodeId = mulNode->GetId(); + conn5->inPortIndex = 0; + conn5->type = Connection::DATA_LINK; + connections.push_back(conn5); + + auto conn6 = std::make_shared(); + conn6->outNodeId = varNodeC->GetId(); + conn6->outPortIndex = 0; + conn6->inNodeId = mulNode->GetId(); + conn6->inPortIndex = 1; + conn6->type = Connection::DATA_LINK; + connections.push_back(conn6); + + // --- Execution flow : Entry → Print1 --- + auto execConn1 = std::make_shared(); + execConn1->outNodeId = functionEntry->GetId(); + execConn1->outPortIndex = 0; + execConn1->inNodeId = print1->GetId(); + execConn1->inPortIndex = 0; + execConn1->type = Connection::EXECUTION_LINK; + connections.push_back(execConn1); + + // --- Print1 arguments : A, B, intermediate1 --- + auto conn7 = std::make_shared(); + conn7->outNodeId = varNodeA->GetId(); + conn7->outPortIndex = 0; + conn7->inNodeId = print1->GetId(); + conn7->inPortIndex = 1; // arg0 + conn7->type = Connection::DATA_LINK; + connections.push_back(conn7); + + auto conn8 = std::make_shared(); + conn8->outNodeId = varNodeB->GetId(); + conn8->outPortIndex = 0; + conn8->inNodeId = print1->GetId(); + conn8->inPortIndex = 2; // arg1 + conn8->type = Connection::DATA_LINK; + connections.push_back(conn8); + + auto conn9 = std::make_shared(); + conn9->outNodeId = addNode->GetId(); // intermediate1 RÉUTILISÉ + conn9->outPortIndex = 0; + conn9->inNodeId = print1->GetId(); + conn9->inPortIndex = 3; // arg2 + conn9->type = Connection::DATA_LINK; + connections.push_back(conn9); + + // --- Execution flow : Print1 → Print2 --- + auto execConn2 = std::make_shared(); + execConn2->outNodeId = print1->GetId(); + execConn2->outPortIndex = 0; + execConn2->inNodeId = print2->GetId(); + execConn2->inPortIndex = 0; + execConn2->type = Connection::EXECUTION_LINK; + connections.push_back(execConn2); + + // --- Print2 arguments : A, B, intermediate2 --- + auto conn10 = std::make_shared(); + conn10->outNodeId = varNodeA->GetId(); + conn10->outPortIndex = 0; + conn10->inNodeId = print2->GetId(); + conn10->inPortIndex = 1; + conn10->type = Connection::DATA_LINK; + connections.push_back(conn10); + + auto conn11 = std::make_shared(); + conn11->outNodeId = varNodeB->GetId(); + conn11->outPortIndex = 0; + conn11->inNodeId = print2->GetId(); + conn11->inPortIndex = 2; + conn11->type = Connection::DATA_LINK; + connections.push_back(conn11); + + auto conn12 = std::make_shared(); + conn12->outNodeId = subNode->GetId(); // intermediate2 + conn12->outPortIndex = 0; + conn12->inNodeId = print2->GetId(); + conn12->inPortIndex = 3; + conn12->type = Connection::DATA_LINK; + connections.push_back(conn12); + + // --- Execution flow : Print2 → Print3 --- + auto execConn3 = std::make_shared(); + execConn3->outNodeId = print2->GetId(); + execConn3->outPortIndex = 0; + execConn3->inNodeId = print3->GetId(); + execConn3->inPortIndex = 0; + execConn3->type = Connection::EXECUTION_LINK; + connections.push_back(execConn3); + + // --- Print3 arguments : intermediate1, C, result --- + auto conn13 = std::make_shared(); + conn13->outNodeId = addNode->GetId(); // intermediate1 RÉUTILISÉ ENCORE + conn13->outPortIndex = 0; + conn13->inNodeId = print3->GetId(); + conn13->inPortIndex = 1; + conn13->type = Connection::DATA_LINK; + connections.push_back(conn13); + + auto conn14 = std::make_shared(); + conn14->outNodeId = varNodeC->GetId(); + conn14->outPortIndex = 0; + conn14->inNodeId = print3->GetId(); + conn14->inPortIndex = 2; + conn14->type = Connection::DATA_LINK; + connections.push_back(conn14); + + auto conn15 = std::make_shared(); + conn15->outNodeId = mulNode->GetId(); // result + conn15->outPortIndex = 0; + conn15->inNodeId = print3->GetId(); + conn15->inPortIndex = 3; + conn15->type = Connection::DATA_LINK; + connections.push_back(conn15); + + // --- Execution flow : Print3 → Branch --- + auto execConn4 = std::make_shared(); + execConn4->outNodeId = print3->GetId(); + execConn4->outPortIndex = 0; + execConn4->inNodeId = branchNode->GetId(); + execConn4->inPortIndex = 0; + execConn4->type = Connection::EXECUTION_LINK; + connections.push_back(execConn4); + + // --- Compare : result > threshold --- + auto conn16 = std::make_shared(); + conn16->outNodeId = mulNode->GetId(); // result RÉUTILISÉ + conn16->outPortIndex = 0; + conn16->inNodeId = compareNode->GetId(); + conn16->inPortIndex = 0; + conn16->type = Connection::DATA_LINK; + connections.push_back(conn16); + + auto conn17 = std::make_shared(); + conn17->outNodeId = varNodeThreshold->GetId(); + conn17->outPortIndex = 0; + conn17->inNodeId = compareNode->GetId(); + conn17->inPortIndex = 1; + conn17->type = Connection::DATA_LINK; + connections.push_back(conn17); + + // --- Branch condition --- + auto conn18 = std::make_shared(); + conn18->outNodeId = compareNode->GetId(); + conn18->outPortIndex = 0; + conn18->inNodeId = branchNode->GetId(); + conn18->inPortIndex = 1; // condition port + conn18->type = Connection::DATA_LINK; + connections.push_back(conn18); + + // --- Branch outputs --- + auto conn19 = std::make_shared(); + conn19->outNodeId = branchNode->GetId(); + conn19->outPortIndex = 0; // true + conn19->inNodeId = printTrue->GetId(); + conn19->inPortIndex = 0; + conn19->type = Connection::EXECUTION_LINK; + connections.push_back(conn19); + + auto conn20 = std::make_shared(); + conn20->outNodeId = branchNode->GetId(); + conn20->outPortIndex = 1; // false + conn20->inNodeId = printFalse->GetId(); + conn20->inPortIndex = 0; + conn20->type = Connection::EXECUTION_LINK; + connections.push_back(conn20); + + // === BUILD AST === + std::cout << "\n--- Building AST ---\n"; + ASTBuilder builder(nodes, connections); + auto pathTree = builder.BuildAST(); + + std::cout << "\n=== DEBUG: Inspecting AST nodes ===\n"; + for (size_t i = 0; i < pathTree.size(); i++) { + auto node = pathTree[i]; + std::cout << "[" << i << "] " << node->node->GetTypeName() + << " (ID: " << node->GetId() << ")\n"; + + if (node->IsType()) { + auto* opNode = node->GetAs(); + std::cout << " Operator type: "; + switch(opNode->GetOperationType()) { + case OperatorNode::OperationType::ADD: std::cout << "ADD\n"; break; + case OperatorNode::OperationType::SUBTRACT: std::cout << "SUB\n"; break; + case OperatorNode::OperationType::MULTIPLY: std::cout << "MUL\n"; break; + case OperatorNode::OperationType::GREATER_THAN: std::cout << "GT\n"; break; + default: std::cout << "OTHER\n"; break; + } + std::cout << " dataInputs count: " << node->dataInputs.size() << "\n"; + for (const auto& [port, input] : node->dataInputs) { + std::cout << " Port " << port << " -> " + << input->node->GetTypeName() << "\n"; + } + } + } + + std::cout << "AST built with " << pathTree.size() << " root nodes\n"; + + // === GENERATE TAC === + std::cout << "\n--- Generating TAC ---\n"; + TACGenerator tacGen; + TACProgram tac = tacGen.Generate(pathTree); + + std::cout << "\n" << tac.ToString() << std::endl; + + // === VERIFY TAC EXPECTATIONS === + const auto& instructions = tac.GetInstructions(); + + std::cout << "Total TAC instructions: " << instructions.size() << "\n\n"; + + // Vérifications + REQUIRE(instructions.size() > 0); + + // Compter les types d'instructions + int addCount = 0, subCount = 0, mulCount = 0, compareCount = 0; + int paramCount = 0, printCount = 0; + + for (const auto& instr : instructions) { + switch (instr->GetOpCode()) { + case TACInstruction::OpCode::ADD: addCount++; break; + case TACInstruction::OpCode::SUB: subCount++; break; + case TACInstruction::OpCode::MUL: mulCount++; break; + case TACInstruction::OpCode::GT: compareCount++; break; + case TACInstruction::OpCode::PARAM: paramCount++; break; + case TACInstruction::OpCode::PRINT: printCount++; break; + default: break; + } + } + + std::cout << "Statistics:\n"; + std::cout << " ADD operations: " << addCount << "\n"; + std::cout << " SUB operations: " << subCount << "\n"; + std::cout << " MUL operations: " << mulCount << "\n"; + std::cout << " GT comparisons: " << compareCount << "\n"; + std::cout << " PARAM instructions: " << paramCount << "\n"; + std::cout << " PRINT instructions: " << printCount << "\n\n"; + + // Vérifier qu'on a bien nos opérations + REQUIRE(addCount == 1); // Une seule fois malgré réutilisation + REQUIRE(subCount == 1); + REQUIRE(mulCount == 1); + REQUIRE(compareCount == 1); + REQUIRE(printCount == 5); // 3 prints normaux + 2 dans le branch + REQUIRE(paramCount == 9); // 3 args × 3 prints + + // === GENERATE CHIP32 ASSEMBLY WITH TAC === + std::cout << "--- Generating Chip32 Assembly from TAC ---\n"; + + AssemblyGenerator::GeneratorContext context( + variables, + "2025-01-10 15:30:00", + "complex-tac-test", + true, // debug + true, // optimize + 1024 + ); + + AssemblyGeneratorChip32TAC chip32Gen(context); + chip32Gen.GenerateCompleteProgram(nodes, pathTree); + + std::string assembly = chip32Gen.GetAssembly().str(); + + std::cout << "\n=== GENERATED CHIP32 ASSEMBLY ===\n"; + std::cout << assembly << std::endl; + + // === VERIFY ASSEMBLY === + + // Vérifier présence des sections + REQUIRE(assembly.find("DATA") != std::string::npos); + REQUIRE(assembly.find("CODE") != std::string::npos); + REQUIRE(assembly.find(".main:") != std::string::npos); + REQUIRE(assembly.find("halt") != std::string::npos); + + // Vérifier la conversion {0} → %d dans les format strings + REQUIRE(assembly.find("{0}") == std::string::npos); + REQUIRE(assembly.find("{1}") == std::string::npos); + REQUIRE(assembly.find("{2}") == std::string::npos); + + // Vérifier présence de syscall 4 (print) + REQUIRE(assembly.find("syscall 4") != std::string::npos); + + // === EXECUTE === + std::cout << "\n--- Executing on Chip32 VM ---\n"; + + Chip32::Machine machine; + machine.QuickExecute(assembly); + + std::cout << "\n✅ Complex TAC test completed successfully!\n"; + std::cout << "========================================\n\n"; +} + + +// Helper function to build the diagram with configurable variable values +std::pair>, std::vector>> +BuildComplexDiagram( + std::vector>& variables, + int valueA, int valueB, int valueC, int threshold) +{ + // === VARIABLES === + variables.clear(); + + auto var_A = std::make_shared("A"); + var_A->SetIntegerValue(valueA); + variables.push_back(var_A); + + auto var_B = std::make_shared("B"); + var_B->SetIntegerValue(valueB); + variables.push_back(var_B); + + auto var_C = std::make_shared("C"); + var_C->SetIntegerValue(valueC); + variables.push_back(var_C); + + auto var_threshold = std::make_shared("threshold"); + var_threshold->SetIntegerValue(threshold); + variables.push_back(var_threshold); + + // === NODES === + auto functionEntry = std::make_shared("function-entry-node"); + functionEntry->SetWeight(100); + + auto varNodeA = std::make_shared("variable-node-A"); + varNodeA->SetVariable(var_A); + + auto varNodeB = std::make_shared("variable-node-B"); + varNodeB->SetVariable(var_B); + + auto varNodeC = std::make_shared("variable-node-C"); + varNodeC->SetVariable(var_C); + + auto varNodeThreshold = std::make_shared("variable-node-threshold"); + varNodeThreshold->SetVariable(var_threshold); + + auto addNode = std::make_shared("operator-add"); + addNode->SetOperationType(OperatorNode::OperationType::ADD); + + auto subNode = std::make_shared("operator-sub"); + subNode->SetOperationType(OperatorNode::OperationType::SUBTRACT); + + auto mulNode = std::make_shared("operator-mul"); + mulNode->SetOperationType(OperatorNode::OperationType::MULTIPLY); + + auto compareNode = std::make_shared("operator-compare"); + compareNode->SetOperationType(OperatorNode::OperationType::GREATER_THAN); + + auto print1 = std::make_shared("print-node-1"); + print1->SetText("Addition: {0} + {1} = {2}"); + print1->Initialize(); + + auto print2 = std::make_shared("print-node-2"); + print2->SetText("Subtraction: {0} - {1} = {2}"); + print2->Initialize(); + + auto print3 = std::make_shared("print-node-3"); + print3->SetText("Multiplication: {0} * {1} = {2}"); + print3->Initialize(); + + auto branchNode = std::make_shared("branch-node"); + + auto printTrue = std::make_shared("print-true"); + printTrue->SetText("Result is LARGE"); + printTrue->Initialize(); + + auto printFalse = std::make_shared("print-false"); + printFalse->SetText("Result is small"); + printFalse->Initialize(); + + std::vector> nodes = { + functionEntry, + varNodeA, varNodeB, varNodeC, varNodeThreshold, + addNode, subNode, mulNode, compareNode, + print1, print2, print3, + branchNode, printTrue, printFalse + }; + + // === CONNECTIONS === + std::vector> connections; + + // A + B + auto conn1 = std::make_shared(); + conn1->outNodeId = varNodeA->GetId(); + conn1->outPortIndex = 0; + conn1->inNodeId = addNode->GetId(); + conn1->inPortIndex = 0; + conn1->type = Connection::DATA_LINK; + connections.push_back(conn1); + + auto conn2 = std::make_shared(); + conn2->outNodeId = varNodeB->GetId(); + conn2->outPortIndex = 0; + conn2->inNodeId = addNode->GetId(); + conn2->inPortIndex = 1; + conn2->type = Connection::DATA_LINK; + connections.push_back(conn2); + + // A - B + auto conn3 = std::make_shared(); + conn3->outNodeId = varNodeA->GetId(); + conn3->outPortIndex = 0; + conn3->inNodeId = subNode->GetId(); + conn3->inPortIndex = 0; + conn3->type = Connection::DATA_LINK; + connections.push_back(conn3); + + auto conn4 = std::make_shared(); + conn4->outNodeId = varNodeB->GetId(); + conn4->outPortIndex = 0; + conn4->inNodeId = subNode->GetId(); + conn4->inPortIndex = 1; + conn4->type = Connection::DATA_LINK; + connections.push_back(conn4); + + // (A+B) * C + auto conn5 = std::make_shared(); + conn5->outNodeId = addNode->GetId(); + conn5->outPortIndex = 0; + conn5->inNodeId = mulNode->GetId(); + conn5->inPortIndex = 0; + conn5->type = Connection::DATA_LINK; + connections.push_back(conn5); + + auto conn6 = std::make_shared(); + conn6->outNodeId = varNodeC->GetId(); + conn6->outPortIndex = 0; + conn6->inNodeId = mulNode->GetId(); + conn6->inPortIndex = 1; + conn6->type = Connection::DATA_LINK; + connections.push_back(conn6); + + // Execution: Entry → Print1 + auto execConn1 = std::make_shared(); + execConn1->outNodeId = functionEntry->GetId(); + execConn1->outPortIndex = 0; + execConn1->inNodeId = print1->GetId(); + execConn1->inPortIndex = 0; + execConn1->type = Connection::EXECUTION_LINK; + connections.push_back(execConn1); + + // Print1: A, B, (A+B) + auto conn7 = std::make_shared(); + conn7->outNodeId = varNodeA->GetId(); + conn7->outPortIndex = 0; + conn7->inNodeId = print1->GetId(); + conn7->inPortIndex = 1; + conn7->type = Connection::DATA_LINK; + connections.push_back(conn7); + + auto conn8 = std::make_shared(); + conn8->outNodeId = varNodeB->GetId(); + conn8->outPortIndex = 0; + conn8->inNodeId = print1->GetId(); + conn8->inPortIndex = 2; + conn8->type = Connection::DATA_LINK; + connections.push_back(conn8); + + auto conn9 = std::make_shared(); + conn9->outNodeId = addNode->GetId(); + conn9->outPortIndex = 0; + conn9->inNodeId = print1->GetId(); + conn9->inPortIndex = 3; + conn9->type = Connection::DATA_LINK; + connections.push_back(conn9); + + // Execution: Print1 → Print2 + auto execConn2 = std::make_shared(); + execConn2->outNodeId = print1->GetId(); + execConn2->outPortIndex = 0; + execConn2->inNodeId = print2->GetId(); + execConn2->inPortIndex = 0; + execConn2->type = Connection::EXECUTION_LINK; + connections.push_back(execConn2); + + // Print2: A, B, (A-B) + auto conn10 = std::make_shared(); + conn10->outNodeId = varNodeA->GetId(); + conn10->outPortIndex = 0; + conn10->inNodeId = print2->GetId(); + conn10->inPortIndex = 1; + conn10->type = Connection::DATA_LINK; + connections.push_back(conn10); + + auto conn11 = std::make_shared(); + conn11->outNodeId = varNodeB->GetId(); + conn11->outPortIndex = 0; + conn11->inNodeId = print2->GetId(); + conn11->inPortIndex = 2; + conn11->type = Connection::DATA_LINK; + connections.push_back(conn11); + + auto conn12 = std::make_shared(); + conn12->outNodeId = subNode->GetId(); + conn12->outPortIndex = 0; + conn12->inNodeId = print2->GetId(); + conn12->inPortIndex = 3; + conn12->type = Connection::DATA_LINK; + connections.push_back(conn12); + + // Execution: Print2 → Print3 + auto execConn3 = std::make_shared(); + execConn3->outNodeId = print2->GetId(); + execConn3->outPortIndex = 0; + execConn3->inNodeId = print3->GetId(); + execConn3->inPortIndex = 0; + execConn3->type = Connection::EXECUTION_LINK; + connections.push_back(execConn3); + + // Print3: (A+B), C, (A+B)*C + auto conn13 = std::make_shared(); + conn13->outNodeId = addNode->GetId(); + conn13->outPortIndex = 0; + conn13->inNodeId = print3->GetId(); + conn13->inPortIndex = 1; + conn13->type = Connection::DATA_LINK; + connections.push_back(conn13); + + auto conn14 = std::make_shared(); + conn14->outNodeId = varNodeC->GetId(); + conn14->outPortIndex = 0; + conn14->inNodeId = print3->GetId(); + conn14->inPortIndex = 2; + conn14->type = Connection::DATA_LINK; + connections.push_back(conn14); + + auto conn15 = std::make_shared(); + conn15->outNodeId = mulNode->GetId(); + conn15->outPortIndex = 0; + conn15->inNodeId = print3->GetId(); + conn15->inPortIndex = 3; + conn15->type = Connection::DATA_LINK; + connections.push_back(conn15); + + // Execution: Print3 → Branch + auto execConn4 = std::make_shared(); + execConn4->outNodeId = print3->GetId(); + execConn4->outPortIndex = 0; + execConn4->inNodeId = branchNode->GetId(); + execConn4->inPortIndex = 0; + execConn4->type = Connection::EXECUTION_LINK; + connections.push_back(execConn4); + + // Compare: result > threshold + auto conn16 = std::make_shared(); + conn16->outNodeId = mulNode->GetId(); + conn16->outPortIndex = 0; + conn16->inNodeId = compareNode->GetId(); + conn16->inPortIndex = 0; + conn16->type = Connection::DATA_LINK; + connections.push_back(conn16); + + auto conn17 = std::make_shared(); + conn17->outNodeId = varNodeThreshold->GetId(); + conn17->outPortIndex = 0; + conn17->inNodeId = compareNode->GetId(); + conn17->inPortIndex = 1; + conn17->type = Connection::DATA_LINK; + connections.push_back(conn17); + + // Branch condition + auto conn18 = std::make_shared(); + conn18->outNodeId = compareNode->GetId(); + conn18->outPortIndex = 0; + conn18->inNodeId = branchNode->GetId(); + conn18->inPortIndex = 1; + conn18->type = Connection::DATA_LINK; + connections.push_back(conn18); + + // Branch true/false + auto conn19 = std::make_shared(); + conn19->outNodeId = branchNode->GetId(); + conn19->outPortIndex = 0; + conn19->inNodeId = printTrue->GetId(); + conn19->inPortIndex = 0; + conn19->type = Connection::EXECUTION_LINK; + connections.push_back(conn19); + + auto conn20 = std::make_shared(); + conn20->outNodeId = branchNode->GetId(); + conn20->outPortIndex = 1; + conn20->inNodeId = printFalse->GetId(); + conn20->inPortIndex = 0; + conn20->type = Connection::EXECUTION_LINK; + connections.push_back(conn20); + + return {nodes, connections}; +} + +// Helper to run the test and capture output +struct TestResult { + std::string tacOutput; + std::string assembly; + std::string executionOutput; + bool success; +}; + +TestResult RunComplexTACTest(int valueA, int valueB, int valueC, int threshold) { + TestResult result; + result.success = false; + + std::vector> variables; + auto [nodes, connections] = BuildComplexDiagram(variables, valueA, valueB, valueC, threshold); + + // Build AST + ASTBuilder builder(nodes, connections); + auto pathTree = builder.BuildAST(); + + // Generate TAC + TACGenerator tacGen; + TACProgram tac = tacGen.Generate(pathTree); + result.tacOutput = tac.ToString(); + + // Verify TAC structure + const auto& instructions = tac.GetInstructions(); + if (instructions.empty()) return result; + + // Generate Assembly + AssemblyGenerator::GeneratorContext context( + variables, + "2025-01-10 15:30:00", + "complex-tac-test", + false, // No debug output + true, + 1024 + ); + + AssemblyGeneratorChip32TAC chip32Gen(context); + chip32Gen.GenerateCompleteProgram(nodes, pathTree); + result.assembly = chip32Gen.GetAssembly().str(); + + // Execute (capture output would need machine modification) + // For now we just verify it doesn't crash + Chip32::Machine machine; + try { + machine.QuickExecute(result.assembly); + result.success = true; + } catch (...) { + result.success = false; + } + + return result; +} + +TEST_CASE("Complex AST with TAC - Multiple test cases", "[tac][complex][ast2]") { + + std::cout << "\n========================================\n"; + std::cout << "Complex TAC Test - Multiple Scenarios\n"; + std::cout << "========================================\n"; + + // Test Case 1: A=10, B=5, C=3, threshold=40 + // Expected: 10+5=15, 10-5=5, 15*3=45, 45>40 = TRUE + { + std::cout << "\n--- Test Case 1: A=10, B=5, C=3, threshold=40 ---\n"; + auto result = RunComplexTACTest(10, 5, 3, 40); + + REQUIRE(result.success); + REQUIRE(result.assembly.find("DATA") != std::string::npos); + REQUIRE(result.assembly.find("CODE") != std::string::npos); + + // Verify TAC instructions count and types + TACGenerator tacGen; + std::vector> vars; + auto [nodes, conns] = BuildComplexDiagram(vars, 10, 5, 3, 40); + ASTBuilder builder(nodes, conns); + auto tree = builder.BuildAST(); + TACProgram tac = tacGen.Generate(tree); + + int addCount = 0, subCount = 0, mulCount = 0, gtCount = 0; + for (const auto& instr : tac.GetInstructions()) { + switch (instr->GetOpCode()) { + case TACInstruction::OpCode::ADD: addCount++; break; + case TACInstruction::OpCode::SUB: subCount++; break; + case TACInstruction::OpCode::MUL: mulCount++; break; + case TACInstruction::OpCode::GT: gtCount++; break; + default: break; + } + } + + REQUIRE(addCount == 1); + REQUIRE(subCount == 1); + REQUIRE(mulCount == 1); + REQUIRE(gtCount == 1); + + std::cout << "✓ Passed: 10+5=15, 15*3=45, 45>40\n"; + } + + // Test Case 2: A=7, B=3, C=2, threshold=50 + // Expected: 7+3=10, 7-3=4, 10*2=20, 20>50 = FALSE + { + std::cout << "\n--- Test Case 2: A=7, B=3, C=2, threshold=50 ---\n"; + auto result = RunComplexTACTest(7, 3, 2, 50); + + REQUIRE(result.success); + + std::cout << "✓ Passed: 7+3=10, 10*2=20, 20<50\n"; + } + + // Test Case 3: A=20, B=10, C=5, threshold=100 + // Expected: 20+10=30, 20-10=10, 30*5=150, 150>100 = TRUE + { + std::cout << "\n--- Test Case 3: A=20, B=10, C=5, threshold=100 ---\n"; + auto result = RunComplexTACTest(20, 10, 5, 100); + + REQUIRE(result.success); + + std::cout << "✓ Passed: 20+10=30, 30*5=150, 150>100\n"; + } + + // Test Case 4: Edge case with zeros + // A=0, B=0, C=10, threshold=5 + // Expected: 0+0=0, 0-0=0, 0*10=0, 0>5 = FALSE + { + std::cout << "\n--- Test Case 4: A=0, B=0, C=10, threshold=5 ---\n"; + auto result = RunComplexTACTest(0, 0, 10, 5); + + REQUIRE(result.success); + + std::cout << "✓ Passed: 0+0=0, 0*10=0, 0<5\n"; + } + + // Test Case 5: Negative numbers + // A=-5, B=3, C=4, threshold=0 + // Expected: -5+3=-2, -5-3=-8, -2*4=-8, -8>0 = FALSE + { + std::cout << "\n--- Test Case 5: A=-5, B=3, C=4, threshold=0 ---\n"; + auto result = RunComplexTACTest(-5, 3, 4, 0); + + REQUIRE(result.success); + + std::cout << "✓ Passed: -5+3=-2, -2*4=-8, -8<0\n"; + } + + // Test Case 6: Exactly at threshold + // A=10, B=10, C=2, threshold=40 + // Expected: 10+10=20, 10-10=0, 20*2=40, 40>40 = FALSE + { + std::cout << "\n--- Test Case 6: A=10, B=10, C=2, threshold=40 ---\n"; + auto result = RunComplexTACTest(10, 10, 2, 40); + + REQUIRE(result.success); + + std::cout << "✓ Passed: 10+10=20, 20*2=40, 40==40 (FALSE for >)\n"; + } + + std::cout << "\n✅ All test cases completed successfully!\n"; + std::cout << "========================================\n\n"; } diff --git a/core/tests/test_print_node.cpp b/core/tests/test_print_node.cpp index 556d3da..9d19b39 100644 --- a/core/tests/test_print_node.cpp +++ b/core/tests/test_print_node.cpp @@ -1,6 +1,4 @@ -// test_print_with_args.cpp -// Test unitaire pour vérifier que les arguments du Print sont bien pris en compte - +// test_print_node.cpp #include "catch.hpp" #include "print_node.h" #include "variable_node.h" @@ -8,137 +6,33 @@ #include "operator_node.h" #include "connection.h" #include "ast_builder.h" -#include "assembly_generator_chip32.h" +#include "assembly_generator_chip32_tac.h" #include "chip32_machine.h" #include "variable.h" -TEST_CASE("Print with single argument") { - // Create the print node - auto printNode = std::make_shared("print-node"); - printNode->SetText("Compteur: %d"); +// =================================================================== +// TEST 1 : Print simple sans argument +// =================================================================== +TEST_CASE("Print without arguments - TAC", "[print][simple][tac]") { + std::cout << "\n=== Test: Print without arguments (TAC) ===\n"; - // Create function entry - auto functionEntryNode = std::make_shared("function-entry-node"); - - // IMPORTANT: Create the "counter" variable and add it to the global variables list + // Variables (vide pour ce test) std::vector> variables; - auto counterVar = std::make_shared("counter"); - counterVar->SetIntegerValue(42); // Initial value - variables.push_back(counterVar); // ← CRUCIAL: Add to global variables - // Create a variable node that references the counter variable - auto variableNodeCounter = std::make_shared("variable-node"); - variableNodeCounter->SetVariable(counterVar); - - // Build the node list - std::vector> nodes; - nodes.push_back(functionEntryNode); - nodes.push_back(printNode); - nodes.push_back(variableNodeCounter); - - // Create connections - std::vector> connections; - - // Connect function entry to print node (execution flow) - auto cn1 = std::make_shared(); - cn1->inNodeId = printNode->GetId(); - cn1->inPortIndex = 0; - cn1->outNodeId = functionEntryNode->GetId(); - cn1->outPortIndex = 0; - cn1->type = Connection::EXECUTION_LINK; - connections.push_back(cn1); - - // Connect variable node to print node (data flow - arg0) - auto cn2 = std::make_shared(); - cn2->inNodeId = printNode->GetId(); - cn2->inPortIndex = 1; // arg0 input port - cn2->outNodeId = variableNodeCounter->GetId(); - cn2->outPortIndex = 0; - cn2->type = Connection::DATA_LINK; - connections.push_back(cn2); - - // Create generator context with the variables list - AssemblyGenerator::GeneratorContext context( - variables, // ← IMPORTANT: Pass the variables including counter - "2025-01-10 10:00:00", - "test-print-args", - true, - true, - 1024 - ); - - // Create generator - AssemblyGeneratorChip32 generator(context); - - // Build AST - ASTBuilder builder(nodes, connections); - auto pathTree = builder.BuildAST(); - - // Generate assembly - generator.Reset(); - generator.GenerateHeader(); - - // DATA section - this will now include the counter variable - generator.StartSection(AssemblyGenerator::Section::DATA); - generator.GenerateNodesVariables(nodes); // Print node format string - generator.GenerateGlobalVariables(); // ← This generates counter variable - - // TEXT section - generator.StartSection(AssemblyGenerator::Section::TEXT); - generator.GenerateTextSection(pathTree); - generator.GenerateExit(); - - std::string assembly = generator.GetAssembly(); - - std::cout << "===== Generated Assembly =====" << std::endl; - std::cout << assembly << std::endl; - - // Now the assembly should include the counter variable declaration: - // $XEIxIsZoXA DV32, 42 ; counter - - Chip32::Machine machine; - machine.QuickExecute(assembly); -} - -TEST_CASE("Print with multiple arguments") { - // ===== Setup ===== - - // Variables - auto var_a = std::make_shared("a"); - var_a->SetIntegerValue(10); // ← CORRECTION: Utiliser SetIntegerValue() au lieu de SetValue() - - auto var_b = std::make_shared("b"); - var_b->SetIntegerValue(20); // ← CORRECTION: Utiliser SetIntegerValue() au lieu de SetValue() - - std::vector> variables = {var_a, var_b}; - - // Nœuds - auto functionEntry = std::make_shared("function-entry-node"); + // Nodes + auto functionEntry = std::make_shared("function-entry"); functionEntry->SetWeight(100); - auto varNodeA = std::make_shared("variable-node"); - varNodeA->SetVariableUuid(var_a->GetUuid()); - - auto varNodeB = std::make_shared("variable-node"); - varNodeB->SetVariableUuid(var_b->GetUuid()); - - auto addNode = std::make_shared("operator-node"); - addNode->SetOperationType(OperatorNode::OperationType::ADD); - auto printNode = std::make_shared("print-node"); - printNode->SetText("Calcul: %d + %d = %d"); - // IMPORTANT: Appeler Initialize() si nécessaire après SetText() - // printNode->Initialize(); // Si le test ne charge pas depuis JSON + printNode->SetText("Hello World!"); + printNode->Initialize(); std::vector> nodes = { functionEntry, - varNodeA, - varNodeB, - addNode, printNode }; - // Connexions + // Connections std::vector> connections; // Execution: Entry → Print @@ -150,150 +44,191 @@ TEST_CASE("Print with multiple arguments") { execConn->type = Connection::EXECUTION_LINK; connections.push_back(execConn); - // Data: varA → Print.arg0 + // Build AST + ASTBuilder builder(nodes, connections); + auto pathTree = builder.BuildAST(); + + // Generate Assembly using TAC + AssemblyGenerator::GeneratorContext context( + variables, + "2025-01-10 10:00:00", + "test-print-no-args", + true, // debug (will show TAC) + true, // optimize + 1024 + ); + + AssemblyGeneratorChip32TAC tacGen(context); + tacGen.GenerateCompleteProgram(nodes, pathTree); + + std::string assembly = tacGen.GetAssembly().str(); + + std::cout << "\n--- Generated Assembly ---\n"; + std::cout << assembly << std::endl; + + // Verify + REQUIRE(assembly.find("DATA") != std::string::npos); + REQUIRE(assembly.find("CODE") != std::string::npos); + REQUIRE(assembly.find("Hello World!") != std::string::npos); + REQUIRE(assembly.find("syscall 4") != std::string::npos); + REQUIRE(assembly.find("halt") != std::string::npos); + + // Execute + std::cout << "\n--- Execution Output ---\n"; + Chip32::Machine machine; + machine.QuickExecute(assembly); + + std::cout << "\n✓ Test passed\n"; +} + +// =================================================================== +// TEST 2 : Print avec 4 arguments (maximum supporté) +// =================================================================== +TEST_CASE("Print with 4 arguments - TAC", "[print][args][tac]") { + std::cout << "\n=== Test: Print with 4 arguments (TAC) ===\n"; + + // Variables: A=10, B=5, C=3, D=2 + std::vector> variables; + + auto var_A = std::make_shared("A"); + var_A->SetIntegerValue(10); + variables.push_back(var_A); + + auto var_B = std::make_shared("B"); + var_B->SetIntegerValue(5); + variables.push_back(var_B); + + auto var_C = std::make_shared("C"); + var_C->SetIntegerValue(3); + variables.push_back(var_C); + + auto var_D = std::make_shared("D"); + var_D->SetIntegerValue(2); + variables.push_back(var_D); + + // Nodes + auto functionEntry = std::make_shared("function-entry"); + functionEntry->SetWeight(100); + + auto varNodeA = std::make_shared("var-node-A"); + varNodeA->SetVariable(var_A); + + auto varNodeB = std::make_shared("var-node-B"); + varNodeB->SetVariable(var_B); + + auto varNodeC = std::make_shared("var-node-C"); + varNodeC->SetVariable(var_C); + + auto varNodeD = std::make_shared("var-node-D"); + varNodeD->SetVariable(var_D); + + auto printNode = std::make_shared("print-node"); + printNode->SetText("Values: A={0}, B={1}, C={2}, D={3}"); + printNode->Initialize(); + + std::vector> nodes = { + functionEntry, + varNodeA, varNodeB, varNodeC, varNodeD, + printNode + }; + + // Connections + std::vector> connections; + + // Execution: Entry → Print + auto execConn = std::make_shared(); + execConn->outNodeId = functionEntry->GetId(); + execConn->outPortIndex = 0; + execConn->inNodeId = printNode->GetId(); + execConn->inPortIndex = 0; + execConn->type = Connection::EXECUTION_LINK; + connections.push_back(execConn); + + // Data: A → Print.arg0 (port 1) auto dataConn1 = std::make_shared(); dataConn1->outNodeId = varNodeA->GetId(); dataConn1->outPortIndex = 0; dataConn1->inNodeId = printNode->GetId(); - dataConn1->inPortIndex = 1; // ← CORRECTION: arg0 = port 1 (port 0 = execution) + dataConn1->inPortIndex = 1; dataConn1->type = Connection::DATA_LINK; connections.push_back(dataConn1); - // Data: varB → Print.arg1 + // Data: B → Print.arg1 (port 2) auto dataConn2 = std::make_shared(); dataConn2->outNodeId = varNodeB->GetId(); dataConn2->outPortIndex = 0; dataConn2->inNodeId = printNode->GetId(); - dataConn2->inPortIndex = 2; // ← CORRECTION: arg1 = port 2 + dataConn2->inPortIndex = 2; dataConn2->type = Connection::DATA_LINK; connections.push_back(dataConn2); - // Data: varA → ADD.input0 + // Data: C → Print.arg2 (port 3) auto dataConn3 = std::make_shared(); - dataConn3->outNodeId = varNodeA->GetId(); + dataConn3->outNodeId = varNodeC->GetId(); dataConn3->outPortIndex = 0; - dataConn3->inNodeId = addNode->GetId(); - dataConn3->inPortIndex = 0; + dataConn3->inNodeId = printNode->GetId(); + dataConn3->inPortIndex = 3; dataConn3->type = Connection::DATA_LINK; connections.push_back(dataConn3); - // Data: varB → ADD.input1 + // Data: D → Print.arg3 (port 4) auto dataConn4 = std::make_shared(); - dataConn4->outNodeId = varNodeB->GetId(); + dataConn4->outNodeId = varNodeD->GetId(); dataConn4->outPortIndex = 0; - dataConn4->inNodeId = addNode->GetId(); - dataConn4->inPortIndex = 1; + dataConn4->inNodeId = printNode->GetId(); + dataConn4->inPortIndex = 4; dataConn4->type = Connection::DATA_LINK; connections.push_back(dataConn4); - // Data: ADD → Print.arg2 - auto dataConn5 = std::make_shared(); - dataConn5->outNodeId = addNode->GetId(); - dataConn5->outPortIndex = 0; - dataConn5->inNodeId = printNode->GetId(); - dataConn5->inPortIndex = 3; // ← CORRECTION: arg2 = port 3 - dataConn5->type = Connection::DATA_LINK; - connections.push_back(dataConn5); - - // ===== Build & Generate ===== - + // Build AST ASTBuilder builder(nodes, connections); auto pathTree = builder.BuildAST(); + // Generate Assembly using TAC AssemblyGenerator::GeneratorContext context( variables, "2025-01-10 10:00:00", - "test-print-multi-args", - true, true, 1024 + "test-print-4-args", + true, // debug (will show TAC) + true, // optimize + 1024 ); - AssemblyGeneratorChip32 generator(context); - generator.Reset(); - generator.GenerateHeader(); - + AssemblyGeneratorChip32TAC tacGen(context); + tacGen.GenerateCompleteProgram(nodes, pathTree); - generator.StartSection(AssemblyGenerator::Section::DATA); - generator.GenerateNodesVariables(nodes); - generator.GenerateGlobalVariables(); - generator.StartSection(AssemblyGenerator::Section::TEXT); - generator.GenerateTextSection(pathTree); - generator.GenerateExit(); + std::string assembly = tacGen.GetAssembly().str(); - std::string assembly = generator.GetAssembly(); + std::cout << "\n--- Generated Assembly ---\n"; + std::cout << assembly << std::endl; - std::cout << "\n===== Generated Assembly (Multi-Args) =====\n" << assembly << std::endl; + // Verify + REQUIRE(assembly.find("DATA") != std::string::npos); + REQUIRE(assembly.find("CODE") != std::string::npos); - // ===== Vérifications ===== + // Verify variable declarations + REQUIRE(assembly.find("; A") != std::string::npos); + REQUIRE(assembly.find("; B") != std::string::npos); + REQUIRE(assembly.find("; C") != std::string::npos); + REQUIRE(assembly.find("; D") != std::string::npos); - // // Vérifier 3 arguments - // REQUIRE(assembly.find("lcons r1, 3") != std::string::npos); + // Verify format string conversion {0} → %d + REQUIRE(assembly.find("{0}") == std::string::npos); + REQUIRE(assembly.find("{1}") == std::string::npos); + REQUIRE(assembly.find("{2}") == std::string::npos); + REQUIRE(assembly.find("{3}") == std::string::npos); - // // Vérifier chargement des 3 registres - // REQUIRE(assembly.find("load r2") != std::string::npos); // arg0 - // REQUIRE(assembly.find("load r3") != std::string::npos); // arg1 - // // r4 vient de l'opérateur ADD (devrait être sur la pile ou dans r4) + // Verify syscall with 4 arguments + REQUIRE(assembly.find("lcons r1, 4") != std::string::npos); + REQUIRE(assembly.find("syscall 4") != std::string::npos); // Execute + std::cout << "\n--- Execution Output ---\n"; + std::cout << "Expected: Values: A=10, B=5, C=3, D=2\n"; + std::cout << "Actual: "; + Chip32::Machine machine; machine.QuickExecute(assembly); -} - - -TEST_CASE("Print with format placeholders") { - auto printNode = std::make_shared("print-node"); - // L'utilisateur utilise maintenant {0}, {1}, etc. - printNode->SetText("Résultat: {0} + {1} = {2}"); - - auto var_a = std::make_shared("a"); - var_a->SetIntegerValue(10); - - auto var_b = std::make_shared("b"); - var_b->SetIntegerValue(20); - - std::vector> variables = {var_a, var_b}; - - auto functionEntry = std::make_shared("function-entry-node"); - auto varNodeA = std::make_shared("variable-node"); - varNodeA->SetVariableUuid(var_a->GetUuid()); - - auto varNodeB = std::make_shared("variable-node"); - varNodeB->SetVariableUuid(var_b->GetUuid()); - - auto addNode = std::make_shared("operator-node"); - addNode->SetOperationType(OperatorNode::OperationType::ADD); - - std::vector> nodes = { - functionEntry, varNodeA, varNodeB, addNode, printNode - }; - - // Connexions... - std::vector> connections; - // [Ajouter les connexions comme dans les tests existants] - - // Générer le code - ASTBuilder builder(nodes, connections); - auto pathTree = builder.BuildAST(); - - AssemblyGenerator::GeneratorContext context( - variables, "2025-01-10 10:00:00", "test-format", true, true, 1024 - ); - - AssemblyGeneratorChip32 generator(context); - generator.Reset(); - generator.GenerateHeader(); - generator.StartSection(AssemblyGenerator::Section::DATA); - generator.GenerateNodesVariables(nodes); - generator.GenerateGlobalVariables(); - generator.StartSection(AssemblyGenerator::Section::TEXT); - generator.GenerateTextSection(pathTree); - generator.GenerateExit(); - - std::string assembly = generator.GetAssembly(); - - // Vérifier que le format a été converti de {0} {1} {2} en %d %d %d - REQUIRE(assembly.find("\"Résultat: %d + %d = %d\"") != std::string::npos); - - std::cout << "\n===== Assembly avec format converti =====\n" - << assembly << std::endl; + std::cout << "\n✓ Test passed\n"; } \ No newline at end of file diff --git a/story-editor/imgui.ini b/story-editor/imgui.ini index 3bd7b69..03d3205 100644 --- a/story-editor/imgui.ini +++ b/story-editor/imgui.ini @@ -9,34 +9,34 @@ Size=400,400 Collapsed=0 [Window][Library Manager] -Pos=656,26 -Size=624,313 +Pos=630,26 +Size=650,235 Collapsed=0 -DockId=0x00000003,0 +DockId=0x00000002,0 [Window][Console] -Pos=60,450 -Size=475,270 +Pos=60,263 +Size=628,457 Collapsed=0 DockId=0x00000004,0 [Window][Emulator] -Pos=656,26 -Size=624,313 +Pos=630,26 +Size=650,235 Collapsed=0 -DockId=0x00000003,5 +DockId=0x00000002,5 [Window][Code viewer] -Pos=656,26 -Size=624,313 +Pos=630,26 +Size=650,235 Collapsed=0 -DockId=0x00000003,4 +DockId=0x00000002,4 [Window][Resources] -Pos=656,26 -Size=624,313 +Pos=630,26 +Size=650,235 Collapsed=0 -DockId=0x00000003,1 +DockId=0x00000002,1 [Window][Node editor] Pos=60,26 @@ -50,28 +50,28 @@ Size=150,42 Collapsed=0 [Window][Variables] -Pos=537,450 -Size=743,270 +Pos=690,263 +Size=590,457 Collapsed=0 DockId=0x00000005,0 [Window][CPU] -Pos=656,26 -Size=624,313 +Pos=630,26 +Size=650,235 Collapsed=0 -DockId=0x00000003,2 +DockId=0x00000002,2 [Window][RAM view] -Pos=656,26 -Size=624,313 +Pos=630,26 +Size=650,235 Collapsed=0 -DockId=0x00000003,3 +DockId=0x00000002,3 [Window][Properties] -Pos=656,341 -Size=624,107 +Pos=690,263 +Size=590,457 Collapsed=0 -DockId=0x00000006,0 +DockId=0x00000005,1 [Window][ToolBar] Pos=0,26 @@ -90,13 +90,13 @@ Collapsed=0 [Window][Module editor] Pos=60,26 -Size=594,422 +Size=568,235 Collapsed=0 DockId=0x00000001,0 [Window][Story editor] Pos=60,26 -Size=594,422 +Size=568,235 Collapsed=0 DockId=0x00000001,1 @@ -106,8 +106,8 @@ Size=687,422 Collapsed=0 [Window][Error List] -Pos=60,450 -Size=475,270 +Pos=60,263 +Size=628,457 Collapsed=0 DockId=0x00000004,1 @@ -147,14 +147,19 @@ Column 2 Width=120 RefScale=20 Column 0 Sort=0v -[Docking][Data] -DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=60,26 Size=1220,694 Split=Y - DockNode ID=0x00000007 Parent=0x08BD597D SizeRef=1220,422 Split=X - DockNode ID=0x00000001 Parent=0x00000007 SizeRef=790,694 CentralNode=1 Selected=0x93ADCAAB - DockNode ID=0x00000002 Parent=0x00000007 SizeRef=624,694 Split=Y Selected=0x52EB28B5 - DockNode ID=0x00000003 Parent=0x00000002 SizeRef=718,476 Selected=0x63869CAF - DockNode ID=0x00000006 Parent=0x00000002 SizeRef=718,163 Selected=0x8C72BEA8 - DockNode ID=0x00000008 Parent=0x08BD597D SizeRef=1220,270 Split=X Selected=0xEA83D666 - DockNode ID=0x00000004 Parent=0x00000008 SizeRef=475,192 Selected=0xD246D6BE - DockNode ID=0x00000005 Parent=0x00000008 SizeRef=743,192 Selected=0x6DE9B20C +[Table][0xED1C9288,4] +RefScale=20 +Column 0 Width=30 +Column 1 Width=80 +Column 2 Weight=1.0000 +Column 3 Width=60 + +[Docking][Data] +DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=60,26 Size=1220,694 Split=Y + DockNode ID=0x00000007 Parent=0x08BD597D SizeRef=1220,235 Split=X + DockNode ID=0x00000001 Parent=0x00000007 SizeRef=1144,694 CentralNode=1 Selected=0x93ADCAAB + DockNode ID=0x00000002 Parent=0x00000007 SizeRef=650,694 Selected=0x4B07C626 + DockNode ID=0x00000008 Parent=0x08BD597D SizeRef=1220,457 Split=X Selected=0xEA83D666 + DockNode ID=0x00000004 Parent=0x00000008 SizeRef=616,192 Selected=0xEA83D666 + DockNode ID=0x00000005 Parent=0x00000008 SizeRef=578,192 Selected=0x8C72BEA8 diff --git a/story-editor/src/app/app_controller.cpp b/story-editor/src/app/app_controller.cpp index 5679db8..e5ad8cf 100644 --- a/story-editor/src/app/app_controller.cpp +++ b/story-editor/src/app/app_controller.cpp @@ -245,7 +245,7 @@ void AppController::CompileNodes(IStoryProject::Type type) { if (type == IStoryProject::Type::PROJECT_TYPE_STORY && m_story) { - if (m_story->GenerateScript(m_storyAssembly)) + if (m_story->GenerateCompleteProgram(m_storyAssembly)) { m_logger.Log("Nodes script generated for story."); Build(true); // Compile seulement par défaut @@ -257,10 +257,10 @@ void AppController::CompileNodes(IStoryProject::Type type) } else if (type == IStoryProject::Type::PROJECT_TYPE_MODULE && m_module) { - if (m_module->GenerateScript(m_moduleAssembly)) + if (m_module->GenerateCompleteProgram(m_moduleAssembly)) { m_logger.Log("Nodes script generated for module."); - BuildModule(true); // NEW: Use BuildModule instead + BuildModule(true); } else { @@ -303,6 +303,12 @@ void AppController::Build(bool compileonly) { m_story->SaveBinary(); chip32_initialize(&m_chip32_ctx); + + Chip32::Instr mainLine; + if (m_story->FindMain(mainLine)) { + m_chip32_ctx.registers[PC] = mainLine.addr; + } + m_dbg.run_result = VM_READY; UpdateVmView(); // Notifie la GUI de mettre à jour la vue VM m_logger.Log("Build successful. VM ready."); @@ -349,6 +355,12 @@ void AppController::BuildModule(bool compileonly) if (m_module->CopyProgramTo(m_rom_data, sizeof(m_rom_data))) { chip32_initialize(&m_chip32_ctx); + + Chip32::Instr mainLine; + if (m_module->FindMain(mainLine)) { + m_chip32_ctx.registers[PC] = mainLine.addr; + } + m_dbg.run_result = VM_READY; UpdateVmView(); @@ -396,18 +408,6 @@ void AppController::BuildCode(bool compileonly) Build(compileonly); } - -void AppController::CompileNodes(bool compileonly) -{ - if (m_story->GenerateScript(m_storyAssembly)) - { - // m_debuggerWindow.SetScript(m_storyAssembly); // FIXME: GUI event - Build(compileonly); - } -} - - - void AppController::SetExternalSourceFile(const std::string &filename) { m_externalSourceFileName = filename; diff --git a/story-editor/src/app/app_controller.h b/story-editor/src/app/app_controller.h index 22a6b71..d8e9062 100644 --- a/story-editor/src/app/app_controller.h +++ b/story-editor/src/app/app_controller.h @@ -87,7 +87,6 @@ public: virtual void Next() override; virtual void Previous() override; virtual std::string VmState() const override; - virtual void CompileNodes(bool compileonly); virtual void BuildCode(bool compileonly); virtual std::shared_ptr GetCurrentProject() override; diff --git a/story-editor/src/docks/emulator_dock.cpp b/story-editor/src/docks/emulator_dock.cpp index 94696fb..f1d05e9 100644 --- a/story-editor/src/docks/emulator_dock.cpp +++ b/story-editor/src/docks/emulator_dock.cpp @@ -99,11 +99,6 @@ void EmulatorDock::Draw() // ===== CONTRÔLES DE SCRIPT ET DEBUG ===== ImGui::SeparatorText("Script control and debug"); - if (ImGui::Button("Build nodes")) - { - m_story.CompileNodes(true); - } - ImGui::SameLine(); if (ImGui::Button("Build code")) { m_story.BuildCode(true);