From 7d13671eede70d9b5837250aa12b1907314f2a06 Mon Sep 17 00:00:00 2001 From: Tomasz Okon Date: Sun, 31 Aug 2025 17:40:09 +0000 Subject: [PATCH 1/5] IR class --- include/minic/IR.hpp | 135 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 include/minic/IR.hpp diff --git a/include/minic/IR.hpp b/include/minic/IR.hpp new file mode 100644 index 0000000..d4bf6a7 --- /dev/null +++ b/include/minic/IR.hpp @@ -0,0 +1,135 @@ +#ifndef MINIC_IR_HPP +#define MINIC_IR_HPP + +#include "AST.hpp" +#include +#include +#include + +namespace minic +{ + +/** + * @brief Intermediate Representation (IR) opcodes for miniC. + * + * Represents a compact set of operations used by the IR layer: + * arithmetic, comparisons, assignments, memory access, control flow, and labels. + */ +enum class IROpcode +{ + ADD, + SUB, + MUL, + DIV, // Arithmetic + EQ, + NEQ, + LT, + GT, + LE, + GE, // Comparisons + ASSIGN, // Assignment + LOAD, + STORE, // Variable access + JUMP, + JUMPIF, // Control flow + RETURN, // Return + LABEL // Block label +}; + +/** + * @class IRInstruction + * @brief A single IR instruction. + * + * An IR instruction has an opcode and up to two operands plus an optional + * result (used for temporary variables or label names). + */ +class IRInstruction +{ +public: + IROpcode opcode; ///< The opcode for this instruction + std::string result; ///< Destination (temp var or label) + std::string operand1; ///< First operand (or sole operand) + std::string operand2; ///< Second operand (for binary ops) + + /** + * @brief Construct an IRInstruction. + * @param op The opcode. + * @param res Optional result name. + * @param op1 Optional first operand. + * @param op2 Optional second operand. + */ + IRInstruction(IROpcode op, + const std::string& res = {}, + const std::string& op1 = {}, + const std::string& op2 = {}) + : opcode(op) + , result(res) + , operand1(op1) + , operand2(op2) + { + } +}; + +/** + * @class BasicBlock + * @brief A sequence of IR instructions with a label. + * + * Basic blocks group instructions and are the unit of control-flow in the IR. + */ +class BasicBlock +{ +public: + std::string label; ///< Unique label for the block + std::vector instructions; ///< Instructions contained in the block + + /** + * @brief Construct a BasicBlock with the given label. + * @param lbl The block label. + */ + explicit BasicBlock(const std::string& lbl) + : label(lbl) + { + } +}; + +/** + * @class IRFunction + * @brief Represents a function in the IR. + * + * Stores function name, return type, parameter list, and a sequence of basic blocks. + */ +class IRFunction +{ +public: + std::string name; ///< Function name + TokenType return_type; ///< Function return type (from AST/Token) + std::vector parameters; ///< Function parameters + std::vector> blocks; ///< Owned basic blocks + + /** + * @brief Construct an IRFunction. + * @param n Function name. + * @param rt Return type. + * @param params Parameter list (moved). + */ + IRFunction(const std::string& n, TokenType rt, std::vector params) + : name(n) + , return_type(rt) + , parameters(std::move(params)) + { + } +}; + +/** + * @class IRProgram + * @brief A container for all IR functions comprising the program. + */ +class IRProgram +{ +public: + std::vector> functions; ///< Owned functions +}; + +} // namespace minic + +#endif // MINIC_IR_HPP \ No newline at end of file From 42008a3ad278876f1b8957c2868350f8ed0ac821 Mon Sep 17 00:00:00 2001 From: Tomasz Okon Date: Sun, 31 Aug 2025 17:44:29 +0000 Subject: [PATCH 2/5] IRGenerator interface --- include/minic/IRGenerator.hpp | 106 ++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 include/minic/IRGenerator.hpp diff --git a/include/minic/IRGenerator.hpp b/include/minic/IRGenerator.hpp new file mode 100644 index 0000000..85d4a02 --- /dev/null +++ b/include/minic/IRGenerator.hpp @@ -0,0 +1,106 @@ +#ifndef MINIC_IR_GENERATOR_HPP +#define MINIC_IR_GENERATOR_HPP + +#include "ASTVisitor.hpp" +#include "IR.hpp" +#include +#include +#include + +namespace minic +{ + +/** + * @class IRGenerator + * @brief Generate an intermediate representation (IR) from the AST. + * + * IRGenerator traverses the parsed AST (via ASTVisitor) and emits a + * linear IR represented by IRProgram / IRFunction / BasicBlock / + * IRInstruction. It manages temporary names, current function and block + * context, and a mapping from source variables to IR temporaries. + */ +class IRGenerator : public ASTVisitor +{ +public: + /** + * @brief Generate an IRProgram for the given AST Program. + * @param program The AST root program to translate. + * @return A unique_ptr owning the generated IRProgram. + */ + std::unique_ptr generate(const Program& program); + + /** + * @brief Visit the top-level Program node. + * @param program The program AST node. + * + * Implements ASTVisitor::visit to traverse and translate program-level + * declarations and functions into IRFunctions. + */ + void visit(const Program& program) override; + + /** + * @brief Visit a Function AST node and produce an IRFunction. + * @param function The function AST node. + * + * Sets up function-level state (current_function_, blocks, parameter + * mapping) and emits instructions for the function body. + */ + void visit(const Function& function) override; + + /** + * @brief Visit a statement AST node and emit corresponding IR. + * @param stmt The statement AST node. + * + * Dispatches on statement kind (block, return, if, while, expr-stmt, etc.) + * and emits one or more IR instructions/blocks as needed. + */ + void visit(const Stmt& stmt) override; + + /** + * @brief Visit an expression AST node and emit IR to compute its value. + * @param expr The expression AST node. + * + * Expression visits typically produce temporaries and may emit multiple + * instructions; the produced temporary is returned by generate_expr. + */ + void visit(const Expr& expr) override; + +private: + std::unique_ptr ir_program_; ///< Owned IRProgram under construction + IRFunction* current_function_ = nullptr; ///< Function being generated (non-owning) + BasicBlock* current_block_ = nullptr; ///< Current basic block for emitting instructions + int temp_counter_ = 0; ///< Counter for generating unique temporaries + std::map var_map_; ///< Map from source var name to IR temp/slot + + /** + * @brief Create a new unique temporary variable name. + * @return A string containing the new temporary name. + */ + std::string new_temp(); + + /** + * @brief Emit an IR instruction into the current basic block. + * @param op The opcode to emit. + * @param res Optional result/target name. + * @param op1 Optional first operand. + * @param op2 Optional second operand. + * + * This helper constructs an IRInstruction and appends it to the + * current_block_->instructions. current_block_ must be non-null. + */ + void emit(IROpcode op, const std::string& res = "", const std::string& op1 = "", const std::string& op2 = ""); + + /** + * @brief Generate IR for an expression and return the result temporary. + * @param expr The expression AST node. + * @return The name of the temporary holding the expression result. + * + * This helper encapsulates expression lowering logic and returns the + * temporary (or variable name) that holds the computed value. + */ + std::string generate_expr(const Expr& expr); // Returns result temp +}; + +} // namespace minic + +#endif // MINIC_IR_GENERATOR_HPP \ No newline at end of file From 47f59912133886a515286526b10dee909de8381e Mon Sep 17 00:00:00 2001 From: Tomasz Okon Date: Sun, 31 Aug 2025 18:56:42 +0000 Subject: [PATCH 3/5] IRGenerator implementation and minor parser fix --- include/minic/IR.hpp | 3 + include/minic/IRGenerator.hpp | 108 ++++++++------- include/minic/Parser.hpp | 8 +- src/IRGenerator.cpp | 241 ++++++++++++++++++++++++++++++++++ src/Parser.cpp | 3 + 5 files changed, 317 insertions(+), 46 deletions(-) create mode 100644 src/IRGenerator.cpp diff --git a/include/minic/IR.hpp b/include/minic/IR.hpp index d4bf6a7..92bd890 100644 --- a/include/minic/IR.hpp +++ b/include/minic/IR.hpp @@ -21,6 +21,8 @@ enum class IROpcode SUB, MUL, DIV, // Arithmetic + NEG, + NOT, EQ, NEQ, LT, @@ -32,6 +34,7 @@ enum class IROpcode STORE, // Variable access JUMP, JUMPIF, // Control flow + JUMPIFNOT, // Control flow RETURN, // Return LABEL // Block label }; diff --git a/include/minic/IRGenerator.hpp b/include/minic/IRGenerator.hpp index 85d4a02..d22eadd 100644 --- a/include/minic/IRGenerator.hpp +++ b/include/minic/IRGenerator.hpp @@ -1,10 +1,11 @@ #ifndef MINIC_IR_GENERATOR_HPP #define MINIC_IR_GENERATOR_HPP -#include "ASTVisitor.hpp" -#include "IR.hpp" +#include "minic/ASTVisitor.hpp" +#include "minic/IR.hpp" #include #include +#include #include namespace minic @@ -12,93 +13,112 @@ namespace minic /** * @class IRGenerator - * @brief Generate an intermediate representation (IR) from the AST. + * @brief Generate Intermediate Representation (IR) from the AST. * - * IRGenerator traverses the parsed AST (via ASTVisitor) and emits a - * linear IR represented by IRProgram / IRFunction / BasicBlock / - * IRInstruction. It manages temporary names, current function and block - * context, and a mapping from source variables to IR temporaries. + * IRGenerator is an ASTVisitor that traverses the parsed AST and emits a + * sequence of IRFunction and BasicBlock objects inside an IRProgram. + * It maintains generation state such as the current function and block, + * temporary and label counters, and a mapping from source variable names to + * IR temporaries/variables. */ class IRGenerator : public ASTVisitor { public: /** - * @brief Generate an IRProgram for the given AST Program. - * @param program The AST root program to translate. - * @return A unique_ptr owning the generated IRProgram. + * @brief Generate an IRProgram for the given AST program. + * + * Traverses the top-level Program node and produces a unique_ptr to an + * IRProgram containing one IRFunction per source function and the + * corresponding basic blocks and instructions. + * + * @param program AST root Program node. + * @return Owned IRProgram representing the compiled IR. */ std::unique_ptr generate(const Program& program); /** - * @brief Visit the top-level Program node. - * @param program The program AST node. + * @brief Visit a Program node. * - * Implements ASTVisitor::visit to traverse and translate program-level - * declarations and functions into IRFunctions. + * Called during traversal of the AST's root program; responsible for + * visiting contained functions and populating the IRProgram. */ void visit(const Program& program) override; /** - * @brief Visit a Function AST node and produce an IRFunction. - * @param function The function AST node. + * @brief Visit a Function node. * - * Sets up function-level state (current_function_, blocks, parameter - * mapping) and emits instructions for the function body. + * Creates a corresponding IRFunction, initializes entry BasicBlock(s), + * and emits IR for the function body. */ void visit(const Function& function) override; /** - * @brief Visit a statement AST node and emit corresponding IR. - * @param stmt The statement AST node. + * @brief Visit a statement node. * - * Dispatches on statement kind (block, return, if, while, expr-stmt, etc.) - * and emits one or more IR instructions/blocks as needed. + * Emits IR instructions or control-flow modifications corresponding to + * the given statement. */ void visit(const Stmt& stmt) override; /** - * @brief Visit an expression AST node and emit IR to compute its value. - * @param expr The expression AST node. + * @brief Visit an expression node. * - * Expression visits typically produce temporaries and may emit multiple - * instructions; the produced temporary is returned by generate_expr. + * Evaluates the expression into IR by emitting necessary instructions and + * returning/recording temporaries via generate_expr. */ void visit(const Expr& expr) override; private: - std::unique_ptr ir_program_; ///< Owned IRProgram under construction - IRFunction* current_function_ = nullptr; ///< Function being generated (non-owning) - BasicBlock* current_block_ = nullptr; ///< Current basic block for emitting instructions - int temp_counter_ = 0; ///< Counter for generating unique temporaries - std::map var_map_; ///< Map from source var name to IR temp/slot + std::unique_ptr ir_program_; ///< Owned IRProgram being built + IRFunction* current_function_ = nullptr; ///< Currently emitting function (non-owning) + BasicBlock* current_block_ = nullptr; ///< Currently emitting basic block (non-owning) + int temp_counter_ = 0; ///< Counter to generate unique temporary names + int label_counter_ = 0; ///< Counter to generate unique labels + std::map var_map_; ///< Map from source var name to IR var/temp /** - * @brief Create a new unique temporary variable name. - * @return A string containing the new temporary name. + * @brief Create a fresh temporary variable name. + * + * Returns a unique temporary string (used as result names for instructions). */ std::string new_temp(); + /** + * @brief Create a fresh label with the given prefix. + * + * Useful for generating basic block labels or branch targets. + * + * @param prefix Label prefix to make generated labels more readable. + */ + std::string new_label(const std::string& prefix); + /** * @brief Emit an IR instruction into the current basic block. - * @param op The opcode to emit. - * @param res Optional result/target name. + * + * Convenience helper to append an IRInstruction with the supplied opcode + * and operands to the current_block_. If no current block is set, this + * function should handle that error state appropriately. + * + * @param op Opcode for the instruction. + * @param res Optional result destination (temp/variable/label). * @param op1 Optional first operand. * @param op2 Optional second operand. - * - * This helper constructs an IRInstruction and appends it to the - * current_block_->instructions. current_block_ must be non-null. */ void emit(IROpcode op, const std::string& res = "", const std::string& op1 = "", const std::string& op2 = ""); /** - * @brief Generate IR for an expression and return the result temporary. - * @param expr The expression AST node. - * @return The name of the temporary holding the expression result. + * @brief Generate IR for an expression and return its result name. + * + * Traverses the expression subtree, emits instructions to compute its + * value, and returns the name of the temporary or variable holding the + * computed value. * - * This helper encapsulates expression lowering logic and returns the - * temporary (or variable name) that holds the computed value. + * @param expr Expression AST node to translate. + * @return Name of the IR temporary or variable that contains the result. */ - std::string generate_expr(const Expr& expr); // Returns result temp + std::string generate_expr(const Expr& expr); // Returns result temp/var + + friend class PublicIRGenerator; // Allow testing class to access private members }; } // namespace minic diff --git a/include/minic/Parser.hpp b/include/minic/Parser.hpp index fa35512..80afcbb 100644 --- a/include/minic/Parser.hpp +++ b/include/minic/Parser.hpp @@ -1,4 +1,6 @@ -#pragma once +#ifndef MINIC_PARSER_HPP +#define MINIC_PARSER_HPP + #include "AST.hpp" /** @@ -169,4 +171,6 @@ class Parser friend class PublicParser; ///< Exposes internals for testing or controlled external access. }; -} // namespace minic \ No newline at end of file +} // namespace minic + +#endif // MINIC_PARSER_HPP \ No newline at end of file diff --git a/src/IRGenerator.cpp b/src/IRGenerator.cpp new file mode 100644 index 0000000..afb058e --- /dev/null +++ b/src/IRGenerator.cpp @@ -0,0 +1,241 @@ +#include "minic/IRGenerator.hpp" + +namespace minic +{ + +std::unique_ptr IRGenerator::generate(const Program& program) +{ + ir_program_ = std::make_unique(); + visit(program); + return std::move(ir_program_); +} + +void IRGenerator::visit(const Program& program) +{ + for (const auto& func_ptr : program.functions) + { + if (func_ptr) + visit(*func_ptr); + } +} + +void IRGenerator::visit(const Function& function) +{ + auto ir_func = std::make_unique(function.name, function.return_type, function.parameters); + current_function_ = ir_func.get(); + temp_counter_ = 0; + label_counter_ = 0; + var_map_.clear(); + + auto entry_block = std::make_unique(new_label("entry")); + current_block_ = entry_block.get(); + ir_func->blocks.push_back(std::move(entry_block)); + + // Params (treat as vars) + for (const auto& param : function.parameters) + { + var_map_[param.name] = param.name; // Use name directly + } + + // Body + for (const auto& stmt : function.body) + { + visit(*stmt); + } + + ir_program_->functions.push_back(std::move(ir_func)); +} + +void IRGenerator::visit(const Stmt& stmt) +{ + if (auto* decl = dynamic_cast(&stmt)) + { + std::string var = decl->name; + var_map_[var] = var; + if (decl->initializer) + { + std::string init_temp = generate_expr(*decl->initializer); + emit(IROpcode::ASSIGN, var, init_temp); + } + } + else if (auto* assign = dynamic_cast(&stmt)) + { + std::string value_temp = generate_expr(*assign->value); + emit(IROpcode::ASSIGN, assign->name, value_temp); + } + else if (auto* ret = dynamic_cast(&stmt)) + { + if (ret->value) + { + std::string ret_temp = generate_expr(*ret->value); + emit(IROpcode::RETURN, "", ret_temp); + } + else + { + emit(IROpcode::RETURN); + } + } + else if (auto* if_stmt = dynamic_cast(&stmt)) + { + std::string cond_temp = generate_expr(*if_stmt->condition); + std::string then_label = new_label("if_then"); + std::string else_label = new_label("if_else"); + std::string end_label = new_label("if_end"); + + emit(IROpcode::JUMPIFNOT, cond_temp, else_label); // Jump if false + + // Then branch + auto then_block = std::make_unique(then_label); + current_block_ = then_block.get(); + current_function_->blocks.push_back(std::move(then_block)); + for (const auto& s : if_stmt->then_branch) + visit(*s); + emit(IROpcode::JUMP, end_label); + + // Else branch + auto else_block = std::make_unique(else_label); + current_block_ = else_block.get(); + current_function_->blocks.push_back(std::move(else_block)); + for (const auto& s : if_stmt->else_branch) + visit(*s); + emit(IROpcode::JUMP, end_label); + + // End + auto end_block = std::make_unique(end_label); + current_block_ = end_block.get(); + current_function_->blocks.push_back(std::move(end_block)); + } + else if (auto* while_stmt = dynamic_cast(&stmt)) + { + std::string cond_label = new_label("while_cond"); + std::string body_label = new_label("while_body"); + std::string end_label = new_label("while_end"); + + emit(IROpcode::JUMP, cond_label); + + // Cond block + auto cond_block = std::make_unique(cond_label); + current_block_ = cond_block.get(); + current_function_->blocks.push_back(std::move(cond_block)); + std::string cond_temp = generate_expr(*while_stmt->condition); + emit(IROpcode::JUMPIFNOT, cond_temp, end_label); // Jump if false + + // Body block + auto body_block = std::make_unique(body_label); + current_block_ = body_block.get(); + current_function_->blocks.push_back(std::move(body_block)); + for (const auto& s : while_stmt->body) + visit(*s); + emit(IROpcode::JUMP, cond_label); + + // End block + auto end_block = std::make_unique(end_label); + current_block_ = end_block.get(); + current_function_->blocks.push_back(std::move(end_block)); + } + else + { + throw std::runtime_error("Unsupported statement in IR generation"); + } +} + +void IRGenerator::visit(const Expr& expr) +{ + generate_expr(expr); // Discard result if not used +} + +std::string IRGenerator::generate_expr(const Expr& expr) +{ + if (auto* lit = dynamic_cast(&expr)) + { + std::string temp = new_temp(); + emit(IROpcode::ASSIGN, temp, std::to_string(lit->value)); + return temp; + } + else if (auto* str_lit = dynamic_cast(&expr)) + { + std::string temp = new_temp(); + emit(IROpcode::ASSIGN, temp, str_lit->value); // Assume string literals as constants + return temp; + } + else if (auto* id = dynamic_cast(&expr)) + { + auto it = var_map_.find(id->name); + if (it == var_map_.end()) + throw std::runtime_error("Undeclared variable in IR"); + return it->second; + } + else if (auto* unary = dynamic_cast(&expr)) + { + std::string oper_temp = generate_expr(*unary->operand); + std::string result_temp = new_temp(); + IROpcode op = (unary->op == TokenType::OP_MINUS) ? IROpcode::NEG : IROpcode::NOT; + emit(op, result_temp, oper_temp); + return result_temp; + } + else if (auto* bin = dynamic_cast(&expr)) + { + std::string left_temp = generate_expr(*bin->left); + std::string right_temp = generate_expr(*bin->right); + std::string result_temp = new_temp(); + IROpcode op; + switch (bin->op) + { + case TokenType::OP_PLUS: + op = IROpcode::ADD; + break; + case TokenType::OP_MINUS: + op = IROpcode::SUB; + break; + case TokenType::OP_MULTIPLY: + op = IROpcode::MUL; + break; + case TokenType::OP_DIVIDE: + op = IROpcode::DIV; + break; + case TokenType::OP_EQUAL: + op = IROpcode::EQ; + break; + case TokenType::OP_NOT_EQUAL: + op = IROpcode::NEQ; + break; + case TokenType::OP_LESS: + op = IROpcode::LT; + break; + case TokenType::OP_GREATER: + op = IROpcode::GT; + break; + case TokenType::OP_LESS_EQ: + op = IROpcode::LE; + break; + case TokenType::OP_GREATER_EQ: + op = IROpcode::GE; + break; + default: + throw std::runtime_error("Unsupported binary operator in IR"); + } + emit(op, result_temp, left_temp, right_temp); + return result_temp; + } + else + { + throw std::runtime_error("Unsupported expression in IR generation"); + } +} + +std::string IRGenerator::new_temp() +{ + return "t" + std::to_string(temp_counter_++); +} + +std::string IRGenerator::new_label(const std::string& prefix) +{ + return prefix + "_" + std::to_string(label_counter_++); +} + +void IRGenerator::emit(IROpcode op, const std::string& res, const std::string& op1, const std::string& op2) +{ + current_block_->instructions.emplace_back(op, res, op1, op2); +} + +} // namespace minic \ No newline at end of file diff --git a/src/Parser.cpp b/src/Parser.cpp index dc05ab4..977b4f3 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -280,6 +280,9 @@ std::vector Parser::parse_parameters() std::unique_ptr Parser::parse_function() { + while (check(TokenType::NEWLINE)) + advance(); // Skip newlines + Token type; if (check(TokenType::KEYWORD_INT)) { From be8228849ae796b81710b552f6e977a17c2ca906 Mon Sep 17 00:00:00 2001 From: Tomasz Okon Date: Sun, 31 Aug 2025 18:56:58 +0000 Subject: [PATCH 4/5] IRGenerator tests --- tests/CMakeLists.txt | 3 +- tests/TestIRGenerator.cpp | 744 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 746 insertions(+), 1 deletion(-) create mode 100644 tests/TestIRGenerator.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 02c7402..72e7e4a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,7 +18,8 @@ file(GLOB TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") add_executable(minic_tests ${TEST_SOURCES} ${CMAKE_SOURCE_DIR}/src/Lexer.cpp ${CMAKE_SOURCE_DIR}/src/Parser.cpp - ${CMAKE_SOURCE_DIR}/src/SemanticAnalyzer.cpp) + ${CMAKE_SOURCE_DIR}/src/SemanticAnalyzer.cpp + ${CMAKE_SOURCE_DIR}/src/IRGenerator.cpp) # Link against Google Test and compiler sources target_link_libraries(minic_tests PRIVATE gtest gtest_main) diff --git a/tests/TestIRGenerator.cpp b/tests/TestIRGenerator.cpp new file mode 100644 index 0000000..1639248 --- /dev/null +++ b/tests/TestIRGenerator.cpp @@ -0,0 +1,744 @@ +#include "minic/IRGenerator.hpp" +#include "minic/Parser.hpp" +#include + +namespace minic +{ + +// PublicIRGenerator exposes private members for testing +class PublicIRGenerator : public IRGenerator +{ +public: + using IRGenerator::current_block_; + using IRGenerator::current_function_; + using IRGenerator::emit; + using IRGenerator::generate_expr; + using IRGenerator::ir_program_; + using IRGenerator::label_counter_; + using IRGenerator::new_label; + using IRGenerator::new_temp; + using IRGenerator::temp_counter_; + using IRGenerator::var_map_; +}; + +// Helper to build AST nodes for testing +std::unique_ptr BuildProgram(std::vector> funcs) +{ + return std::make_unique(std::move(funcs)); +} + +std::unique_ptr BuildFunction(const std::string& name, TokenType ret_type, std::vector params, std::vector> body) +{ + return std::make_unique(name, ret_type, std::move(params), std::move(body)); +} + +std::unique_ptr BuildVarDecl(TokenType type, const std::string& name, std::unique_ptr init = nullptr) +{ + return std::make_unique(type, name, std::move(init)); +} + +std::unique_ptr BuildAssign(const std::string& name, std::unique_ptr value) +{ + return std::make_unique(name, std::move(value)); +} + +std::unique_ptr BuildReturn(std::unique_ptr value = nullptr) +{ + return std::make_unique(std::move(value)); +} + +std::unique_ptr BuildIntLit(int val) +{ + return std::make_unique(val); +} + +std::unique_ptr BuildStrLit(const std::string& val) +{ + return std::make_unique(val); +} + +std::unique_ptr BuildId(const std::string& name) +{ + return std::make_unique(name); +} + +std::unique_ptr BuildUnary(TokenType op, std::unique_ptr operand) +{ + return std::make_unique(op, std::move(operand)); +} + +std::unique_ptr BuildBinary(std::unique_ptr left, TokenType op, std::unique_ptr right) +{ + return std::make_unique(std::move(left), op, std::move(right)); +} + +std::unique_ptr BuildIf(std::unique_ptr cond, std::vector> then_b, std::vector> else_b) +{ + return std::make_unique(std::move(cond), std::move(then_b), std::move(else_b)); +} + +std::unique_ptr BuildWhile(std::unique_ptr cond, std::vector> body) +{ + return std::make_unique(std::move(cond), std::move(body)); +} + +class IRGeneratorTest : public ::testing::Test +{ +protected: + minic::PublicIRGenerator generator_; + + // Helper to parse source to AST + std::unique_ptr ParseSource(const std::string& source) + { + minic::Lexer lexer(source); + auto tokens = lexer.Lex(); + minic::Parser parser(tokens); + return parser.parse(); + } + + // Helper to get instruction count in a block + size_t CountInstructions(const minic::BasicBlock* block) + { + return block ? block->instructions.size() : 0; + } + + // Helper to check if instruction exists in block + bool HasInstruction(const minic::BasicBlock* block, minic::IROpcode op, const std::string& res = "", const std::string& op1 = "", const std::string& op2 = "") + { + if (!block) + return false; + for (const auto& instr : block->instructions) + { + if (instr.opcode == op && (res.empty() || instr.result == res) && (op1.empty() || instr.operand1 == op1) && (op2.empty() || instr.operand2 == op2)) + { + return true; + } + } + return false; + } + + // Helper to find block by label prefix + const minic::BasicBlock* FindBlockByLabelPrefix(const minic::IRFunction* func, const std::string& prefix) + { + for (const auto& block : func->blocks) + { + if (block->label.find(prefix) == 0) + { + return block.get(); + } + } + return nullptr; + } +}; + +TEST_F(IRGeneratorTest, DeclWithInit) +{ + auto init = minic::BuildBinary(minic::BuildIntLit(5), TokenType::OP_PLUS, minic::BuildIntLit(3)); + auto decl = minic::BuildVarDecl(TokenType::KEYWORD_INT, "x", std::move(init)); + std::vector> body; + body.push_back(std::move(decl)); + + auto func = minic::BuildFunction("main", TokenType::KEYWORD_VOID, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + ASSERT_EQ(ir->functions.size(), 1); + ASSERT_EQ(ir->functions[0]->blocks.size(), 1); + const auto* entry = ir->functions[0]->blocks[0].get(); + ASSERT_EQ(CountInstructions(entry), 4); // ASSIGN t0=5, ASSIGN t1=3, ADD t2=t0+t1, ASSIGN x=t2 + EXPECT_TRUE(HasInstruction(entry, IROpcode::ASSIGN, "", "5")); + EXPECT_TRUE(HasInstruction(entry, IROpcode::ASSIGN, "", "3")); + EXPECT_TRUE(HasInstruction(entry, IROpcode::ADD)); + EXPECT_TRUE(HasInstruction(entry, IROpcode::ASSIGN, "x")); +} + +TEST_F(IRGeneratorTest, DeclNoInit) +{ + auto decl = minic::BuildVarDecl(TokenType::KEYWORD_INT, "x"); + std::vector> body; + body.push_back(std::move(decl)); + + auto func = minic::BuildFunction("main", TokenType::KEYWORD_VOID, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + const auto* entry = ir->functions[0]->blocks[0].get(); + EXPECT_EQ(CountInstructions(entry), 0); +} + +TEST_F(IRGeneratorTest, AssignComplexExpr) +{ + auto expr = minic::BuildBinary( + minic::BuildUnary(TokenType::OP_MINUS, minic::BuildId("y")), + TokenType::OP_MULTIPLY, + minic::BuildBinary(minic::BuildIntLit(2), TokenType::OP_DIVIDE, minic::BuildIntLit(4))); + auto assign = minic::BuildAssign("x", std::move(expr)); + std::vector> body; + body.push_back(minic::BuildVarDecl(TokenType::KEYWORD_INT, "y")); + body.push_back(std::move(assign)); + + auto func = minic::BuildFunction("main", TokenType::KEYWORD_VOID, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + const auto* entry = ir->functions[0]->blocks[0].get(); + EXPECT_GE(CountInstructions(entry), 6); // NEG, DIV, MUL, assigns + EXPECT_TRUE(HasInstruction(entry, IROpcode::NEG)); + EXPECT_TRUE(HasInstruction(entry, IROpcode::DIV)); + EXPECT_TRUE(HasInstruction(entry, IROpcode::MUL)); + EXPECT_TRUE(HasInstruction(entry, IROpcode::ASSIGN, "x")); +} + +TEST_F(IRGeneratorTest, ReturnIntLiteral) +{ + auto ret = minic::BuildReturn(minic::BuildIntLit(42)); + std::vector> body; + body.push_back(std::move(ret)); + + auto func = minic::BuildFunction("func", TokenType::KEYWORD_INT, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + const auto* entry = ir->functions[0]->blocks[0].get(); + EXPECT_EQ(CountInstructions(entry), 2); + EXPECT_TRUE(HasInstruction(entry, IROpcode::ASSIGN, "", "42")); + EXPECT_TRUE(HasInstruction(entry, IROpcode::RETURN)); +} + +TEST_F(IRGeneratorTest, ReturnVoid) +{ + auto ret = minic::BuildReturn(); + std::vector> body; + body.push_back(std::move(ret)); + + auto func = minic::BuildFunction("func", TokenType::KEYWORD_VOID, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + const auto* entry = ir->functions[0]->blocks[0].get(); + EXPECT_EQ(CountInstructions(entry), 1); + EXPECT_TRUE(HasInstruction(entry, IROpcode::RETURN)); +} + +TEST_F(IRGeneratorTest, IfWithBranches) +{ + auto cond = minic::BuildBinary(minic::BuildId("x"), TokenType::OP_GREATER, minic::BuildIntLit(0)); + auto then_assign = minic::BuildAssign("y", minic::BuildIntLit(1)); + std::vector> then_b; + then_b.push_back(std::move(then_assign)); + auto else_assign = minic::BuildAssign("y", minic::BuildIntLit(0)); + std::vector> else_b; + else_b.push_back(std::move(else_assign)); + auto if_stmt = minic::BuildIf(std::move(cond), std::move(then_b), std::move(else_b)); + + std::vector> body; + body.push_back(minic::BuildVarDecl(TokenType::KEYWORD_INT, "x")); + body.push_back(minic::BuildVarDecl(TokenType::KEYWORD_INT, "y")); + body.push_back(std::move(if_stmt)); + + auto func = minic::BuildFunction("main", TokenType::KEYWORD_VOID, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + EXPECT_EQ(ir->functions[0]->blocks.size(), 4); + const auto* entry = ir->functions[0]->blocks[0].get(); + EXPECT_TRUE(HasInstruction(entry, IROpcode::GT)); + EXPECT_TRUE(HasInstruction(entry, IROpcode::JUMPIFNOT)); + + const auto* then_block = FindBlockByLabelPrefix(ir->functions[0].get(), "if_then"); + EXPECT_TRUE(HasInstruction(then_block, IROpcode::ASSIGN, "", "1")); + EXPECT_TRUE(HasInstruction(then_block, IROpcode::ASSIGN, "y")); + EXPECT_TRUE(HasInstruction(then_block, IROpcode::JUMP)); + + const auto* else_block = FindBlockByLabelPrefix(ir->functions[0].get(), "if_else"); + EXPECT_TRUE(HasInstruction(else_block, IROpcode::ASSIGN, "", "0")); + EXPECT_TRUE(HasInstruction(else_block, IROpcode::ASSIGN, "y")); + EXPECT_TRUE(HasInstruction(else_block, IROpcode::JUMP)); + + const auto* end_block = FindBlockByLabelPrefix(ir->functions[0].get(), "if_end"); + EXPECT_EQ(CountInstructions(end_block), 0); +} + +TEST_F(IRGeneratorTest, IfNoElse) +{ + auto cond = minic::BuildIntLit(1); + auto then_assign = minic::BuildAssign("x", minic::BuildIntLit(1)); + std::vector> then_b; + then_b.push_back(std::move(then_assign)); + auto if_stmt = minic::BuildIf(std::move(cond), std::move(then_b), {}); + + std::vector> body; + body.push_back(std::move(if_stmt)); + + auto func = minic::BuildFunction("main", TokenType::KEYWORD_VOID, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + EXPECT_EQ(ir->functions[0]->blocks.size(), 4); + EXPECT_TRUE(FindBlockByLabelPrefix(ir->functions[0].get(), "if_then")); + EXPECT_TRUE(FindBlockByLabelPrefix(ir->functions[0].get(), "if_else")); + EXPECT_TRUE(FindBlockByLabelPrefix(ir->functions[0].get(), "if_end")); +} + +TEST_F(IRGeneratorTest, WhileWithBody) +{ + auto cond = minic::BuildBinary(minic::BuildId("i"), TokenType::OP_LESS, minic::BuildIntLit(10)); + auto inc = minic::BuildAssign("i", minic::BuildBinary(minic::BuildId("i"), TokenType::OP_PLUS, minic::BuildIntLit(1))); + std::vector> loop_body; + loop_body.push_back(std::move(inc)); + auto while_stmt = minic::BuildWhile(std::move(cond), std::move(loop_body)); + + std::vector> body; + body.push_back(minic::BuildVarDecl(TokenType::KEYWORD_INT, "i")); + body.push_back(std::move(while_stmt)); + + auto func = minic::BuildFunction("main", TokenType::KEYWORD_VOID, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + EXPECT_EQ(ir->functions[0]->blocks.size(), 4); + const auto* entry = ir->functions[0]->blocks[0].get(); + EXPECT_TRUE(HasInstruction(entry, IROpcode::JUMP)); + + const auto* cond_block = FindBlockByLabelPrefix(ir->functions[0].get(), "while_cond"); + EXPECT_TRUE(HasInstruction(cond_block, IROpcode::LT)); + EXPECT_TRUE(HasInstruction(cond_block, IROpcode::JUMPIFNOT)); + + const auto* body_block = FindBlockByLabelPrefix(ir->functions[0].get(), "while_body"); + EXPECT_TRUE(HasInstruction(body_block, IROpcode::ADD)); + EXPECT_TRUE(HasInstruction(body_block, IROpcode::ASSIGN, "i")); + EXPECT_TRUE(HasInstruction(body_block, IROpcode::JUMP)); + + const auto* end_block = FindBlockByLabelPrefix(ir->functions[0].get(), "while_end"); + EXPECT_EQ(CountInstructions(end_block), 0); +} + +TEST_F(IRGeneratorTest, WhileNoBody) +{ + auto cond = minic::BuildIntLit(0); + auto while_stmt = minic::BuildWhile(std::move(cond), {}); + + std::vector> body; + body.push_back(std::move(while_stmt)); + + auto func = minic::BuildFunction("main", TokenType::KEYWORD_VOID, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + EXPECT_EQ(ir->functions[0]->blocks.size(), 4); + const auto* body_block = FindBlockByLabelPrefix(ir->functions[0].get(), "while_body"); + EXPECT_EQ(CountInstructions(body_block), 1); // JUMP to cond +} + +TEST_F(IRGeneratorTest, WhileFalseCond) +{ + auto cond = minic::BuildIntLit(0); + auto while_stmt = minic::BuildWhile(std::move(cond), {}); + + std::vector> body; + body.push_back(std::move(while_stmt)); + + auto func = minic::BuildFunction("main", TokenType::KEYWORD_VOID, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + const auto* cond_block = FindBlockByLabelPrefix(ir->functions[0].get(), "while_cond"); + EXPECT_TRUE(HasInstruction(cond_block, IROpcode::ASSIGN, "", "0")); + EXPECT_TRUE(HasInstruction(cond_block, IROpcode::JUMPIFNOT)); +} + +TEST_F(IRGeneratorTest, UnaryOps) +{ + // Unary minus + auto unary = minic::BuildUnary(TokenType::OP_MINUS, minic::BuildIntLit(10)); + auto assign = minic::BuildAssign("x", std::move(unary)); + std::vector> body1; + body1.push_back(std::move(assign)); + auto func1 = minic::BuildFunction("main", TokenType::KEYWORD_VOID, {}, std::move(body1)); + std::vector> funcs1; + funcs1.push_back(std::move(func1)); + auto ir1 = generator_.generate(*minic::BuildProgram(std::move(funcs1))); + const auto* entry1 = ir1->functions[0]->blocks[0].get(); + EXPECT_TRUE(HasInstruction(entry1, IROpcode::ASSIGN, "", "10")); + EXPECT_TRUE(HasInstruction(entry1, IROpcode::NEG)); + EXPECT_TRUE(HasInstruction(entry1, IROpcode::ASSIGN, "x")); + + // Unary not + generator_ = minic::PublicIRGenerator(); // reset + auto unary_not = minic::BuildUnary(TokenType::OP_NOT, minic::BuildIntLit(0)); + auto ret = minic::BuildReturn(std::move(unary_not)); + std::vector> body2; + body2.push_back(std::move(ret)); + auto func2 = minic::BuildFunction("func", TokenType::KEYWORD_INT, {}, std::move(body2)); + std::vector> funcs2; + funcs2.push_back(std::move(func2)); + auto ir2 = generator_.generate(*minic::BuildProgram(std::move(funcs2))); + const auto* entry2 = ir2->functions[0]->blocks[0].get(); + EXPECT_TRUE(HasInstruction(entry2, IROpcode::ASSIGN, "", "0")); + EXPECT_TRUE(HasInstruction(entry2, IROpcode::NOT)); + EXPECT_TRUE(HasInstruction(entry2, IROpcode::RETURN)); +} + +TEST_F(IRGeneratorTest, StringAssign) +{ + auto assign = minic::BuildAssign("s", minic::BuildStrLit("hello")); + std::vector> body; + body.push_back(std::move(assign)); + + auto func = minic::BuildFunction("main", TokenType::KEYWORD_VOID, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + const auto* entry = ir->functions[0]->blocks[0].get(); + EXPECT_TRUE(HasInstruction(entry, IROpcode::ASSIGN, "", "hello")); + EXPECT_TRUE(HasInstruction(entry, IROpcode::ASSIGN, "s")); +} + +TEST_F(IRGeneratorTest, ParamUsageAndVarMapParam) +{ + Parameter p1(TokenType::KEYWORD_INT, "a"); + std::vector params; + params.push_back(p1); + auto assign = minic::BuildAssign("b", minic::BuildId("a")); + std::vector> body; + body.push_back(std::move(assign)); + + auto func = minic::BuildFunction("func", TokenType::KEYWORD_VOID, std::move(params), std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + const auto* entry = ir->functions[0]->blocks[0].get(); + EXPECT_TRUE(HasInstruction(entry, IROpcode::ASSIGN, "b", "a")); + + { + Parameter p(TokenType::KEYWORD_INT, "p"); + std::vector ps; + ps.push_back(p); + auto f = minic::BuildFunction("test", TokenType::KEYWORD_VOID, std::move(ps), {}); + + generator_.ir_program_ = std::make_unique(); + generator_.visit(*f); + EXPECT_EQ(generator_.var_map_["p"], "p"); + } +} + +TEST_F(IRGeneratorTest, EmptyFunctionAndFunctionNoBody) +{ + auto func = minic::BuildFunction("empty", TokenType::KEYWORD_VOID, {}, {}); + std::vector> funcs; + funcs.push_back(std::move(func)); + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + EXPECT_EQ(ir->functions[0]->blocks.size(), 1); + EXPECT_EQ(CountInstructions(ir->functions[0]->blocks[0].get()), 0); + + auto f2 = minic::BuildFunction("no_body", TokenType::KEYWORD_VOID, {}, {}); + generator_.ir_program_ = std::make_unique(); + generator_.visit(*f2); + EXPECT_EQ(generator_.current_function_->blocks.size(), 1); + EXPECT_EQ(CountInstructions(generator_.current_function_->blocks[0].get()), 0); +} + +TEST_F(IRGeneratorTest, NestedIfWhile) +{ + auto inner_cond = minic::BuildIntLit(1); + auto inner_assign = minic::BuildAssign("inner", minic::BuildIntLit(3)); + std::vector> inner_body; + inner_body.push_back(std::move(inner_assign)); + auto inner_while = minic::BuildWhile(std::move(inner_cond), std::move(inner_body)); + + std::vector> then_branch; + then_branch.push_back(std::move(inner_while)); + auto outer_cond = minic::BuildIntLit(1); + auto outer_if = minic::BuildIf(std::move(outer_cond), std::move(then_branch), {}); + + std::vector> body; + body.push_back(std::move(outer_if)); + + auto func = minic::BuildFunction("main", TokenType::KEYWORD_VOID, {}, std::move(body)); + std::vector> funcs; + funcs.push_back(std::move(func)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + EXPECT_GE(ir->functions[0]->blocks.size(), 7); + EXPECT_TRUE(FindBlockByLabelPrefix(ir->functions[0].get(), "if_then")); + EXPECT_TRUE(FindBlockByLabelPrefix(ir->functions[0].get(), "while_cond")); +} + +TEST_F(IRGeneratorTest, ErrorCases) +{ + generator_.ir_program_ = std::make_unique(); + auto irf = std::make_unique("err_test", TokenType::KEYWORD_VOID, std::vector {}); + irf->blocks.push_back(std::make_unique("entry")); + generator_.current_function_ = irf.get(); + generator_.current_block_ = irf->blocks[0].get(); + generator_.ir_program_->functions.push_back(std::move(irf)); + + class UnknownStmt : public minic::Stmt + { + }; + auto unknown = std::make_unique(); + EXPECT_THROW(generator_.visit(*unknown), std::runtime_error); + + class UnknownExpr : public minic::Expr + { + }; + auto unknownE = std::make_unique(); + EXPECT_THROW(generator_.generate_expr(*unknownE), std::runtime_error); + + auto bin = std::make_unique(minic::BuildIntLit(1), TokenType::OP_ASSIGN, minic::BuildIntLit(2)); + EXPECT_THROW(generator_.generate_expr(*bin), std::runtime_error); +} + +TEST_F(IRGeneratorTest, TempLabelAndCounters) +{ + generator_.temp_counter_ = 0; + EXPECT_EQ(generator_.new_temp(), "t0"); + EXPECT_EQ(generator_.new_temp(), "t1"); + + generator_.label_counter_ = 0; + EXPECT_EQ(generator_.new_label("test"), "test_0"); + EXPECT_EQ(generator_.new_label("test"), "test_1"); + + generator_.temp_counter_ = 5; + EXPECT_EQ(generator_.new_temp(), "t5"); + EXPECT_EQ(generator_.temp_counter_, 6); + + generator_.label_counter_ = 3; + EXPECT_EQ(generator_.new_label("p"), "p_3"); + EXPECT_EQ(generator_.label_counter_, 4); +} + +TEST_F(IRGeneratorTest, EmitInstructionAndVariations) +{ + auto block = std::make_unique("test"); + generator_.current_block_ = block.get(); + generator_.emit(minic::IROpcode::ADD, "t2", "t0", "t1"); + EXPECT_EQ(CountInstructions(block.get()), 1); + EXPECT_EQ(block->instructions[0].opcode, minic::IROpcode::ADD); + EXPECT_EQ(block->instructions[0].result, "t2"); + EXPECT_EQ(block->instructions[0].operand1, "t0"); + EXPECT_EQ(block->instructions[0].operand2, "t1"); + + // Full + generator_.emit(minic::IROpcode::ADD, "res", "op1", "op2"); + EXPECT_EQ(block->instructions.back().result, "res"); + + // No res + generator_.emit(minic::IROpcode::JUMP, "", "label"); + EXPECT_EQ(block->instructions.back().operand1, "label"); + EXPECT_TRUE(block->instructions.back().result.empty()); + + // No op2 + generator_.emit(minic::IROpcode::NEG, "res", "op1"); + EXPECT_TRUE(block->instructions.back().operand2.empty()); + + // No op1/op2 + generator_.emit(minic::IROpcode::RETURN); + EXPECT_TRUE(block->instructions.back().operand1.empty()); + EXPECT_TRUE(block->instructions.back().operand2.empty()); +} + +TEST_F(IRGeneratorTest, VarMapAndIRProgram) +{ + generator_.var_map_["var"] = "t10"; + EXPECT_EQ(generator_.var_map_["var"], "t10"); + + generator_.ir_program_ = std::make_unique(); + EXPECT_TRUE(generator_.ir_program_ != nullptr); +} + +TEST_F(IRGeneratorTest, MultipleFunctions) +{ + auto func1 = minic::BuildFunction("func1", TokenType::KEYWORD_VOID, {}, {}); + auto func2 = minic::BuildFunction("func2", TokenType::KEYWORD_INT, {}, {}); + std::vector> funcs; + funcs.push_back(std::move(func1)); + funcs.push_back(std::move(func2)); + + auto ast = minic::BuildProgram(std::move(funcs)); + auto ir = generator_.generate(*ast); + + EXPECT_EQ(ir->functions.size(), 2); + EXPECT_EQ(ir->functions[0]->name, "func1"); + EXPECT_EQ(ir->functions[1]->name, "func2"); +} + +TEST_F(IRGeneratorTest, FullProgramViaParser) +{ + std::string source = "\n" + "int main() {\n" + " int x = 5;\n" + " x = x + 1;\n if (x > 0) {\n" + " return -x;\n" + " } else {\n" + " return !x;\n" + " }\n" + "}\n"; + auto ast = ParseSource(source); + auto ir = generator_.generate(*ast); + + EXPECT_EQ(ir->functions.size(), 1); + EXPECT_GE(ir->functions[0]->blocks.size(), 4); + const auto* then_block = FindBlockByLabelPrefix(ir->functions[0].get(), "if_then"); + EXPECT_TRUE(HasInstruction(then_block, IROpcode::NEG)); + const auto* else_block = FindBlockByLabelPrefix(ir->functions[0].get(), "if_else"); + EXPECT_TRUE(HasInstruction(else_block, IROpcode::NOT)); +} + +TEST_F(IRGeneratorTest, AllArithmeticOps) +{ + struct TestCase + { + TokenType token_op; + minic::IROpcode ir_op; + }; + std::vector cases = { + { TokenType::OP_PLUS, IROpcode::ADD }, + { TokenType::OP_MINUS, IROpcode::SUB }, + { TokenType::OP_MULTIPLY, IROpcode::MUL }, + { TokenType::OP_DIVIDE, IROpcode::DIV } + }; + + generator_.ir_program_ = std::make_unique(); + auto irf = std::make_unique("arith_test", TokenType::KEYWORD_VOID, std::vector {}); + irf->blocks.push_back(std::make_unique("entry")); + generator_.current_function_ = irf.get(); + generator_.current_block_ = irf->blocks[0].get(); + generator_.ir_program_->functions.push_back(std::move(irf)); + + generator_.temp_counter_ = 0; + + for (const auto& tc : cases) + { + auto bin = minic::BuildBinary(minic::BuildIntLit(10), tc.token_op, minic::BuildIntLit(2)); + generator_.generate_expr(*bin); + } + EXPECT_GE(generator_.temp_counter_, cases.size() * 3); +} + +TEST_F(IRGeneratorTest, AllComparisonOps) +{ + struct TestCase + { + TokenType token_op; + minic::IROpcode ir_op; + }; + std::vector cases = { + { TokenType::OP_EQUAL, IROpcode::EQ }, + { TokenType::OP_NOT_EQUAL, IROpcode::NEQ }, + { TokenType::OP_LESS, IROpcode::LT }, + { TokenType::OP_GREATER, IROpcode::GT }, + { TokenType::OP_LESS_EQ, IROpcode::LE }, + { TokenType::OP_GREATER_EQ, IROpcode::GE } + }; + + generator_.ir_program_ = std::make_unique(); + auto irf = std::make_unique("cmp_test", TokenType::KEYWORD_VOID, std::vector {}); + irf->blocks.push_back(std::make_unique("entry")); + generator_.current_function_ = irf.get(); + generator_.current_block_ = irf->blocks[0].get(); + generator_.ir_program_->functions.push_back(std::move(irf)); + + generator_.temp_counter_ = 0; + + for (const auto& tc : cases) + { + auto bin = minic::BuildBinary(minic::BuildIntLit(10), tc.token_op, minic::BuildIntLit(2)); + generator_.generate_expr(*bin); + } + EXPECT_GE(generator_.temp_counter_, cases.size() * 3); +} + +// Id not mapped error +TEST_F(IRGeneratorTest, IdNotMappedError) +{ + auto id = minic::BuildId("missing"); + EXPECT_THROW(generator_.generate_expr(*id), std::runtime_error); +} + +TEST_F(IRGeneratorTest, MultipleEmitsAndNestedExpr) +{ + generator_.ir_program_ = std::make_unique(); + auto irf = std::make_unique("nested_test", TokenType::KEYWORD_VOID, std::vector {}); + irf->blocks.push_back(std::make_unique("entry")); + generator_.current_function_ = irf.get(); + generator_.current_block_ = irf->blocks[0].get(); + generator_.ir_program_->functions.push_back(std::move(irf)); + + generator_.temp_counter_ = 0; + + auto nested = minic::BuildBinary( + minic::BuildBinary(minic::BuildIntLit(1), TokenType::OP_PLUS, minic::BuildIntLit(2)), + TokenType::OP_MULTIPLY, + minic::BuildIntLit(3)); + generator_.generate_expr(*nested); + EXPECT_EQ(generator_.temp_counter_, 5); // ASSIGNs for 1,2,3 + ADD + MUL +} + +TEST_F(IRGeneratorTest, ExprDiscardAndVisit) +{ + auto expr = minic::BuildIntLit(5); + + generator_.ir_program_ = std::make_unique(); + auto irf = std::make_unique("expr_test", TokenType::KEYWORD_VOID, std::vector {}); + irf->blocks.push_back(std::make_unique("entry")); + generator_.current_function_ = irf.get(); + generator_.current_block_ = irf->blocks[0].get(); + generator_.ir_program_->functions.push_back(std::move(irf)); + + int prev_temp = generator_.temp_counter_; + generator_.visit(*expr); + EXPECT_EQ(generator_.temp_counter_, prev_temp + 1); +} + +TEST_F(IRGeneratorTest, PrivateCurrentPointers) +{ + auto block = std::make_unique("private"); + generator_.current_block_ = block.get(); + EXPECT_EQ(generator_.current_block_->label, "private"); + + auto func = std::make_unique("test", TokenType::KEYWORD_VOID, std::vector {}); + generator_.current_function_ = func.get(); + EXPECT_EQ(generator_.current_function_->name, "test"); +} + +} \ No newline at end of file From 54dd27b0feff98e3e490bf9998fff13494849b95 Mon Sep 17 00:00:00 2001 From: Tomasz Okon Date: Sun, 31 Aug 2025 19:03:36 +0000 Subject: [PATCH 5/5] Minor change to semantic analyzer tests --- tests/TestSemanticAnalyzer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/TestSemanticAnalyzer.cpp b/tests/TestSemanticAnalyzer.cpp index b729b34..d724913 100644 --- a/tests/TestSemanticAnalyzer.cpp +++ b/tests/TestSemanticAnalyzer.cpp @@ -6,12 +6,12 @@ namespace minic { // Helper to build minimal AST for isolated testing -std::unique_ptr BuildSimpleProgram(std::vector> funcs) +inline std::unique_ptr BuildSimpleProgram(std::vector> funcs) { return std::make_unique(std::move(funcs)); } -std::unique_ptr BuildFunction(const std::string& name, TokenType ret_type, +inline std::unique_ptr BuildFunction(const std::string& name, TokenType ret_type, std::vector params, std::vector> body) {