From 9682b44a7a52051658f70bf134165735198adf0e Mon Sep 17 00:00:00 2001 From: OusmBlueNinja <89956790+OusmBlueNinja@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:34:24 -0600 Subject: [PATCH] V 0.1.0 --- .gitignore | 2 + README.md | 17 +- main.c | 13 + src/gcml.h | 455 +++++++++++++++++++++++++++ src/main.cpp | 848 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1334 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 main.c create mode 100644 src/gcml.h create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a402379 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.exe +*.vscode diff --git a/README.md b/README.md index 4ff79a4..b2d0e17 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,18 @@ # C_Interpreter -A Simple Interpreter for the C Language \ No newline at end of file +A Simple Interpreter for the C Language + +## ToDo + +- Add While +- Add Functions +- Add I/O +- Add Compialer stuff like Macros and Includes +- Make a STD Library + + +## Dependencies + +This uses `GCML` for some Macros and Memory simplifications + +[GCML](https://dock-it.dev/GigabiteStudios/C-Cpp__Utilitys) \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..0060d83 --- /dev/null +++ b/main.c @@ -0,0 +1,13 @@ +int main() { + int first = 256; + int second = 265; + int third = first + second; + + if (third > 500) { + third = third - 256; + } else { + third = third + 256; + } + + return 0; +} diff --git a/src/gcml.h b/src/gcml.h new file mode 100644 index 0000000..f973c31 --- /dev/null +++ b/src/gcml.h @@ -0,0 +1,455 @@ +#ifndef GCML_H +#define GCML_H + +#include +#include +#include + +// ------------------------- +// General Utility Macros +// ------------------------- + +/** + * @brief Returns the minimum of two values. + */ +#define MIN(a, b) (( (a) < (b) ) ? (a) : (b)) + +/** + * @brief Returns the maximum of two values. + */ +#define MAX(a, b) (( (a) > (b) ) ? (a) : (b)) + +/** + * @brief Calculates the number of elements in an array. + */ +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +/** + * @brief Suppresses compiler warnings for unused variables. + */ +#define UNUSED(x) (void)(x) + +/** + * @brief Aligns a value `x` up to the nearest multiple of `align`. + */ +#define ALIGN_UP(x, align) (((x) + ((align)-1)) & ~((align)-1)) + +/** + * @brief Aligns a value `x` down to the nearest multiple of `align`. + */ +#define ALIGN_DOWN(x, align) ((x) & ~((align)-1)) + +// ------------------------- +// Debugging and Logging Macros +// ------------------------- + +#ifdef DEBUG + /** + * @brief Prints debug messages with file name, line number, and function name. + */ + #define DEBUG_PRINT(fmt, ...) \ + fprintf(stderr, "DEBUG: %s:%d:%s(): " fmt "\n", \ + __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#else + #define DEBUG_PRINT(fmt, ...) // No operation in release builds +#endif + +/** + * @brief Logs informational messages. + */ +#define LOG_INFO(fmt, ...) \ + fprintf(stdout, "INFO: " fmt "\n", ##__VA_ARGS__) + +/** + * @brief Logs warning messages. + */ +#define LOG_WARN(fmt, ...) \ + fprintf(stderr, "WARNING: " fmt "\n", ##__VA_ARGS__) + +/** + * @brief Logs error messages. + */ +#define LOG_ERROR(fmt, ...) \ + fprintf(stderr, "ERROR: " fmt "\n", ##__VA_ARGS__) + +/** + * @brief Logs fatal error messages and exits the program. + */ +#define LOG_FATAL(fmt, ...) do { \ + fprintf(stderr, "FATAL: " fmt "\n", ##__VA_ARGS__); \ + exit(EXIT_FAILURE); \ +} while (0) + +// ------------------------- +// Assertion Macros +// ------------------------- + +/** + * @brief Asserts a condition and logs an error message if the condition is false. + */ +#ifndef NDEBUG + #define ASSERT(cond, fmt, ...) do { \ + if (!(cond)) { \ + fprintf(stderr, "Assertion failed: (%s), function %s, file %s, line %d.\n", \ + #cond, __func__, __FILE__, __LINE__); \ + fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ + abort(); \ + } \ + } while (0) +#else + #define ASSERT(cond, fmt, ...) ((void)0) +#endif + +// ------------------------- +// Stringification Macros +// ------------------------- + +/** + * @brief Converts a macro argument to a string. + */ +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +// ------------------------- +// Token Pasting Macros +// ------------------------- + +/** + * @brief Concatenates two tokens. + */ +#define CONCAT(a, b) a ## b + +/** + * @brief Concatenates three tokens. + */ +#define CONCAT3(a, b, c) a ## b ## c + +// ------------------------- +// Memory Management Macros +// ------------------------- + +/** + * @brief Allocates memory and checks for allocation failure. + * @param ptr The pointer to assign the allocated memory. + * @param size The size in bytes to allocate. + */ +#define SAFE_MALLOC(ptr, size) do { \ + (ptr) = malloc(size); \ + if ((ptr) == NULL) { \ + LOG_FATAL("Memory allocation failed for size %zu", (size_t)(size)); \ + } \ +} while (0) + +/** + * @brief Allocates zero-initialized memory and checks for allocation failure. + * @param ptr The pointer to assign the allocated memory. + * @param count The number of elements to allocate. + * @param type The type of each element. + */ +#define SAFE_CALLOC(ptr, count, type) do { \ + (ptr) = calloc((count), sizeof(type)); \ + if ((ptr) == NULL) { \ + LOG_FATAL("Memory allocation (calloc) failed for count %zu of type %s", \ + (size_t)(count), #type); \ + } \ +} while (0) + +/** + * @brief Frees memory and sets the pointer to NULL. + * @param ptr The pointer to free. + */ +#define SAFE_FREE(ptr) do { \ + free(ptr); \ + ptr = NULL; \ +} while(0) + +// ------------------------- +// Type Casting Macros +// ------------------------- + +/** + * @brief Safely casts a pointer to a specific type. + * @param ptr The pointer to cast. + * @param type The target type. + */ +#define SAFE_CAST(ptr, type) ((type)(ptr)) + +// ------------------------- +// Bit Manipulation Macros +// ------------------------- + +/** + * @brief Sets a bit at a specific position. + * @param x The variable. + * @param pos The bit position. + */ +#define SET_BIT(x, pos) ((x) |= (1U << (pos))) + +/** + * @brief Clears a bit at a specific position. + * @param x The variable. + * @param pos The bit position. + */ +#define CLEAR_BIT(x, pos) ((x) &= ~(1U << (pos))) + +/** + * @brief Toggles a bit at a specific position. + * @param x The variable. + * @param pos The bit position. + */ +#define TOGGLE_BIT(x, pos) ((x) ^= (1U << (pos))) + +/** + * @brief Checks if a bit at a specific position is set. + * @param x The variable. + * @param pos The bit position. + * @return Non-zero if the bit is set, zero otherwise. + */ +#define CHECK_BIT(x, pos) (((x) >> (pos)) & 1U) + +// ------------------------- +// Compile-Time Assertion Macro +// ------------------------- + +/** + * @brief Performs a compile-time assertion. + * @param expr The expression to evaluate. + * @param msg The message to display if the assertion fails. + */ +#define STATIC_ASSERT(expr, msg) _Static_assert(expr, msg) + +// ------------------------- +// Deprecation Warning Macros +// ------------------------- + +/** + * @brief Marks a function as deprecated with a custom message. + */ +#if defined(__GNUC__) || defined(__clang__) + #define DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(_MSC_VER) + #define DEPRECATED(msg) __declspec(deprecated(msg)) +#else + #pragma message("WARNING: DEPRECATED macro is not supported for this compiler.") + #define DEPRECATED(msg) +#endif + +// ------------------------- +// Loop Macros +// ------------------------- + +/** + * @brief Iterates over each element in an array. + * @param item The loop variable. + * @param array The array to iterate over. + */ +#define FOREACH(item, array) \ + for (size_t keep = 1, \ + count = ARRAY_SIZE(array), \ + i = 0; \ + keep && i < count; \ + keep = !keep, i++) \ + for (item = (array) + i; keep; keep = !keep) + +/** + * @brief Repeats a block of code `n` times. + * @param n The number of times to repeat. + * @param block The block of code to execute. + */ +#define REPEAT(n, block) \ + for (size_t _i = 0; _i < (n); ++_i) { block; } + +// ------------------------- +// Swap Macro +// ------------------------- + +/** + * @brief Swaps two variables of the same type. + * @param a The first variable. + * @param b The second variable. + */ +#define SWAP(a, b) do { \ + typeof(a) _swap_temp = (a); \ + (a) = (b); \ + (b) = _swap_temp; \ +} while (0) + +// ------------------------- +// Execute Once Macro +// ------------------------- + +/** + * @brief Executes a block of code only once. + * @param block The block of code to execute. + */ +#define DO_ONCE(block) \ + do { \ + static int _do_once_flag = 0; \ + if (!_do_once_flag) { \ + _do_once_flag = 1; \ + block \ + } \ + } while (0) + +// ------------------------- +// Utility Macros +// ------------------------- + +/** + * @brief Calculates the offset of a member within a struct. + * @param type The struct type. + * @param member The member within the struct. + */ +#define OFFSET_OF(type, member) ((size_t) &(((type *)0)->member)) + +/** + * @brief Retrieves the containing struct from a member pointer. + * @param ptr The pointer to the member. + * @param type The type of the containing struct. + * @param member The member within the struct. + */ +#define CONTAINER_OF(ptr, type, member) \ + ((type *)((char *)(ptr) - OFFSET_OF(type, member))) + + +// ------------------------- +// Additional Utility Macros +// ------------------------- + +/** + * @brief Safely reallocates memory and checks for allocation failure. + * @param ptr The pointer to the previously allocated memory. + * @param size The new size in bytes to allocate. + */ +#define SAFE_REALLOC(ptr, size) do { \ + void* _tmp = realloc((ptr), (size)); \ + if ((_tmp) == NULL) { \ + LOG_FATAL("Memory reallocation failed for size %zu", (size_t)(size)); \ + } else { \ + (ptr) = _tmp; \ + } \ +} while (0) + +/** + * @brief Marks a function as unused to suppress compiler warnings. + */ +#define UNUSED_FUNCTION __attribute__((unused)) + +/** + * @brief Converts a value to a string at compile time. + * @param value The value to stringify. + * @return The string representation of the value. + */ +#define TO_STRING(value) TOSTRING(value) + +/** + * @brief Generates a unique identifier by appending the line number. + * @param prefix The prefix for the identifier. + * @return A unique identifier. + */ +#define UNIQUE_ID(prefix) CONCAT(prefix, __LINE__) + +/** + * @brief Forces a value to evaluate to a specific type without altering its binary representation. + * @param value The value to cast. + * @param type The target type. + * @return The value cast to the specified type. + */ +#define FORCE_CAST(value, type) (*(type*)&(value)) + +/** + * @brief Creates a do-while loop that executes exactly once. + * @param block The block of code to execute. + */ +#define EXECUTE_ONCE(block) do { block } while(0) + +/** + * @brief Checks if a pointer is aligned to a specified boundary. + * @param ptr The pointer to check. + * @param align The alignment boundary (must be a power of two). + * @return Non-zero if aligned, zero otherwise. + */ +#define IS_ALIGNED(ptr, align) ((((uintptr_t)(const void*)(ptr)) & ((align) - 1)) == 0) + +/** + * @brief Calculates the number of bits set to 1 in a variable. + * @param x The variable to count bits in. + * @return The number of bits set to 1. + */ +#define COUNT_SET_BITS(x) (__builtin_popcount(x)) + +/** + * @brief Calculates the ceiling of a division between two integers. + * @param numerator The numerator. + * @param denominator The denominator. + * @return The ceiling of the division. + */ +#define CEIL_DIV(numerator, denominator) (((numerator) + (denominator) - 1) / (denominator)) + +/** + * @brief Concatenates two tokens with an underscore. + * @param a The first token. + * @param b The second token. + * @return The concatenated token separated by an underscore. + */ +#define CONCAT_WITH_UNDERSCORE(a, b) CONCAT(a, _##b) + +/** + * @brief Swaps two variables without using a temporary variable (only for integer types). + * @param a The first variable. + * @param b The second variable. + */ +#define SWAP_INPLACE(a, b) do { \ + (a) ^= (b); \ + (b) ^= (a); \ + (a) ^= (b); \ +} while (0) + +// ------------------------- +// Safe Reallocation Macro +// ------------------------- + +/** + * @brief Safely reallocates memory and checks for allocation failure. + * @param ptr The pointer to the previously allocated memory. + * @param size The new size in bytes to allocate. + */ +#define SAFE_REALLOC(ptr, size) do { \ + void* _tmp = realloc((ptr), (size)); \ + if ((_tmp) == NULL) { \ + LOG_FATAL("Memory reallocation failed for size %zu", (size_t)(size)); \ + } else { \ + (ptr) = _tmp; \ + } \ +} while (0) + + + + + +#define MAX_OF(...) MAX_OF_IMPL(__VA_ARGS__, MAX_OF_RSEQ_N()) +#define MAX_OF_IMPL(...) MAX_OF_ARG_N(__VA_ARGS__) +#define MAX_OF_ARG_N(_1, _2, _3, _4, _5, N, ...) N +#define MAX_OF_RSEQ_N() 5,4,3,2,1,0 + +#define MIN_OF(...) MIN_OF_IMPL(__VA_ARGS__, MIN_OF_RSEQ_N()) +#define MIN_OF_IMPL(...) MIN_OF_ARG_N(__VA_ARGS__) +#define MIN_OF_ARG_N(_1, _2, _3, _4, _5, N, ...) N +#define MIN_OF_RSEQ_N() 5,4,3,2,1,0 + + +#define ZERO_STRUCT(s) memset(&(s), 0, sizeof(s)) + + +#define PRINT_VAR(var) LOG_INFO(#var " = %d", var) + + + + + + + +#endif // GCML_H + + + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..fcd9b93 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,848 @@ +// src/main.cpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Include the gcml.h header with macros +#include "gcml.h" + +// ------------------- +// Lexer Definitions +// ------------------- + +enum TokenType { + // Keywords + TOKEN_INT, + TOKEN_IF, + TOKEN_ELSE, + + // Identifiers and literals + TOKEN_ID, + TOKEN_NUMBER, + + // Operators + TOKEN_PLUS, + TOKEN_MINUS, + TOKEN_TIMES, + TOKEN_DIVIDE, + TOKEN_ASSIGN, + TOKEN_EQ, + TOKEN_NEQ, + TOKEN_LT, + TOKEN_GT, + TOKEN_LE, + TOKEN_GE, + + // Delimiters + TOKEN_SEMICOLON, + TOKEN_LPAREN, + TOKEN_RPAREN, + TOKEN_LBRACE, + TOKEN_RBRACE, + + // Function Keywords + TOKEN_RETURN, + + // End of file + TOKEN_EOF +}; + +// Struct to represent a token +struct Token { + TokenType type; + std::string value; + int lineno; + + Token(TokenType type = TOKEN_EOF, const std::string& value = "", int lineno = 0) + : type(type), value(value), lineno(lineno) {} +}; + +// Lexer class +class Lexer { +private: + std::string input; + size_t pos; + int lineno; + std::unordered_map keywords; + +public: + Lexer(const std::string& input) + : input(input), pos(0), lineno(1), + keywords({{"int", TOKEN_INT}, {"if", TOKEN_IF}, {"else", TOKEN_ELSE}, {"return", TOKEN_RETURN}}) {} + + // Function to get the next token + Token getNextToken() { + while (pos < input.length()) { + char current = input[pos]; + + // Skip whitespace + if (isspace(current)) { + if (current == '\n') { + lineno++; + } + pos++; + continue; + } + + // Identifiers or keywords + if (isalpha(current) || current == '_') { + size_t start = pos; + while (pos < input.length() && (isalnum(input[pos]) || input[pos] == '_')) { + pos++; + } + std::string word = input.substr(start, pos - start); + if (keywords.find(word) != keywords.end()) { + return Token(keywords[word], word, lineno); + } else { + return Token(TOKEN_ID, word, lineno); + } + } + + // Numbers + if (isdigit(current)) { + size_t start = pos; + while (pos < input.length() && isdigit(input[pos])) { + pos++; + } + std::string number = input.substr(start, pos - start); + return Token(TOKEN_NUMBER, number, lineno); + } + + // Operators and delimiters + switch (current) { + case '+': + pos++; + return Token(TOKEN_PLUS, "+", lineno); + case '-': + pos++; + return Token(TOKEN_MINUS, "-", lineno); + case '*': + pos++; + return Token(TOKEN_TIMES, "*", lineno); + case '/': + pos++; + return Token(TOKEN_DIVIDE, "/", lineno); + case '=': + if (pos + 1 < input.length() && input[pos + 1] == '=') { + pos += 2; + return Token(TOKEN_EQ, "==", lineno); + } else { + pos++; + return Token(TOKEN_ASSIGN, "=", lineno); + } + case '!': + if (pos + 1 < input.length() && input[pos + 1] == '=') { + pos += 2; + return Token(TOKEN_NEQ, "!=", lineno); + } else { + LOG_ERROR("Invalid token '!' at line %d", lineno); + // Exit after logging the error + exit(EXIT_FAILURE); + } + case '<': + if (pos + 1 < input.length() && input[pos + 1] == '=') { + pos += 2; + return Token(TOKEN_LE, "<=", lineno); + } else { + pos++; + return Token(TOKEN_LT, "<", lineno); + } + case '>': + if (pos + 1 < input.length() && input[pos + 1] == '=') { + pos += 2; + return Token(TOKEN_GE, ">=", lineno); + } else { + pos++; + return Token(TOKEN_GT, ">", lineno); + } + case ';': + pos++; + return Token(TOKEN_SEMICOLON, ";", lineno); + case '(': + pos++; + return Token(TOKEN_LPAREN, "(", lineno); + case ')': + pos++; + return Token(TOKEN_RPAREN, ")", lineno); + case '{': + pos++; + return Token(TOKEN_LBRACE, "{", lineno); + case '}': + pos++; + return Token(TOKEN_RBRACE, "}", lineno); + default: + LOG_ERROR("Illegal character '%c' at line %d", current, lineno); + // Exit after logging the error + exit(EXIT_FAILURE); + } + } + + return Token(TOKEN_EOF, "", lineno); + } +}; + +// ------------------- +// Parser and AST Definitions +// ------------------- + +// Forward declarations +struct ASTNode; +struct Program; +struct Function; +struct Compound; +struct Declaration; +struct Assignment; +struct IfStatement; +struct ReturnStatement; +struct BinaryOp; +struct Number; +struct Variable; + +// Base AST Node +struct ASTNode { + virtual ~ASTNode() = default; +}; + +// AST Nodes +struct Program : public ASTNode { + std::vector> functions; + + Program(std::vector> functions) + : functions(std::move(functions)) {} +}; + +struct Function : public ASTNode { + std::string return_type; + std::string name; + std::vector> parameters; // Not used in this simple interpreter + std::unique_ptr body; + + Function(const std::string& return_type, const std::string& name, + std::unique_ptr body) + : return_type(return_type), name(name), body(std::move(body)) {} +}; + +struct Compound : public ASTNode { + std::vector> statements; + + Compound(std::vector> statements) + : statements(std::move(statements)) {} +}; + +struct Declaration : public ASTNode { + std::string var_name; + std::unique_ptr value; // Can be nullptr + + Declaration(const std::string& var_name, std::unique_ptr value = nullptr) + : var_name(var_name), value(std::move(value)) {} +}; + +struct Assignment : public ASTNode { + std::string var_name; + std::unique_ptr value; + + Assignment(const std::string& var_name, std::unique_ptr value) + : var_name(var_name), value(std::move(value)) {} +}; + +struct IfStatement : public ASTNode { + std::unique_ptr condition; + std::unique_ptr then_branch; + std::unique_ptr else_branch; // Can be nullptr + + IfStatement(std::unique_ptr condition, + std::unique_ptr then_branch, + std::unique_ptr else_branch = nullptr) + : condition(std::move(condition)), + then_branch(std::move(then_branch)), + else_branch(std::move(else_branch)) {} +}; + +struct ReturnStatement : public ASTNode { + std::unique_ptr expression; // Can be nullptr + + ReturnStatement(std::unique_ptr expression = nullptr) + : expression(std::move(expression)) {} +}; + +struct BinaryOp : public ASTNode { + std::string op; + std::unique_ptr left; + std::unique_ptr right; + + BinaryOp(const std::string& op, std::unique_ptr left, std::unique_ptr right) + : op(op), left(std::move(left)), right(std::move(right)) {} +}; + +struct Number : public ASTNode { + int value; + + Number(int value) : value(value) {} +}; + +struct Variable : public ASTNode { + std::string name; + + Variable(const std::string& name) : name(name) {} +}; + +// Parser class +class Parser { +private: + Lexer lexer; + Token currentToken; + + void eat(TokenType type) { + if (currentToken.type == type) { + currentToken = lexer.getNextToken(); + } else { + std::stringstream ss; + ss << "Syntax error: Expected token type " << tokenTypeToString(type) + << " but got " << tokenTypeToString(currentToken.type) << " at line " << currentToken.lineno; + LOG_ERROR("%s", ss.str().c_str()); + // Exit after logging the error + exit(EXIT_FAILURE); + } + } + + std::string tokenTypeToString(TokenType type) { + switch (type) { + case TOKEN_INT: return "TOKEN_INT"; + case TOKEN_IF: return "TOKEN_IF"; + case TOKEN_ELSE: return "TOKEN_ELSE"; + case TOKEN_ID: return "TOKEN_ID"; + case TOKEN_NUMBER: return "TOKEN_NUMBER"; + case TOKEN_PLUS: return "TOKEN_PLUS"; + case TOKEN_MINUS: return "TOKEN_MINUS"; + case TOKEN_TIMES: return "TOKEN_TIMES"; + case TOKEN_DIVIDE: return "TOKEN_DIVIDE"; + case TOKEN_ASSIGN: return "TOKEN_ASSIGN"; + case TOKEN_EQ: return "TOKEN_EQ"; + case TOKEN_NEQ: return "TOKEN_NEQ"; + case TOKEN_LT: return "TOKEN_LT"; + case TOKEN_GT: return "TOKEN_GT"; + case TOKEN_LE: return "TOKEN_LE"; + case TOKEN_GE: return "TOKEN_GE"; + case TOKEN_SEMICOLON: return "TOKEN_SEMICOLON"; + case TOKEN_LPAREN: return "TOKEN_LPAREN"; + case TOKEN_RPAREN: return "TOKEN_RPAREN"; + case TOKEN_LBRACE: return "TOKEN_LBRACE"; + case TOKEN_RBRACE: return "TOKEN_RBRACE"; + case TOKEN_RETURN: return "TOKEN_RETURN"; + case TOKEN_EOF: return "TOKEN_EOF"; + default: return "UNKNOWN_TOKEN"; + } + } + + // Grammar rules implementations + + std::unique_ptr program() { + std::vector> functions; + while (currentToken.type != TOKEN_EOF) { + functions.emplace_back(function_definition()); + } + return std::make_unique(std::move(functions)); + } + + std::unique_ptr function_definition() { + // Assuming all functions have return type 'int' for simplicity + std::string return_type = currentToken.value; + if (currentToken.type != TOKEN_INT) { + std::stringstream ss; + ss << "Syntax error: Expected 'int' as return type at line " << currentToken.lineno; + LOG_ERROR("%s", ss.str().c_str()); + exit(EXIT_FAILURE); + } + eat(TOKEN_INT); + + if (currentToken.type != TOKEN_ID) { + std::stringstream ss; + ss << "Syntax error: Expected function name at line " << currentToken.lineno; + LOG_ERROR("%s", ss.str().c_str()); + exit(EXIT_FAILURE); + } + std::string func_name = currentToken.value; + eat(TOKEN_ID); + + eat(TOKEN_LPAREN); + // For simplicity, no parameters are handled + eat(TOKEN_RPAREN); + + auto body = compound_statement(); + return std::make_unique(return_type, func_name, std::unique_ptr(dynamic_cast(body.release()))); + } + + std::unique_ptr compound_statement() { + eat(TOKEN_LBRACE); + std::vector> statements = statement_list(); + eat(TOKEN_RBRACE); + return std::make_unique(std::move(statements)); + } + + std::vector> statement_list() { + std::vector> statements; + while (currentToken.type != TOKEN_RBRACE && currentToken.type != TOKEN_EOF) { + auto stmt = statement(); + if (stmt) { + statements.emplace_back(std::move(stmt)); + } + } + return statements; + } + + std::unique_ptr statement() { + switch (currentToken.type) { + case TOKEN_INT: + return declaration(); + case TOKEN_ID: + return assignment(); + case TOKEN_IF: + return if_statement(); + case TOKEN_RETURN: + return return_statement(); + case TOKEN_LBRACE: + return compound_statement(); + case TOKEN_SEMICOLON: + eat(TOKEN_SEMICOLON); + return nullptr; + default: + { + std::stringstream ss; + ss << "Syntax error: Unexpected token '" << currentToken.value + << "' at line " << currentToken.lineno; + LOG_ERROR("%s", ss.str().c_str()); + exit(EXIT_FAILURE); + } + } + return nullptr; + } + + std::unique_ptr declaration() { + eat(TOKEN_INT); + if (currentToken.type != TOKEN_ID) { + std::stringstream ss; + ss << "Syntax error: Expected identifier after 'int' at line " << currentToken.lineno; + LOG_ERROR("%s", ss.str().c_str()); + exit(EXIT_FAILURE); + } + std::string var_name = currentToken.value; + eat(TOKEN_ID); + std::unique_ptr value = nullptr; + if (currentToken.type == TOKEN_ASSIGN) { + eat(TOKEN_ASSIGN); + value = expression(); + } + eat(TOKEN_SEMICOLON); + return std::make_unique(var_name, std::move(value)); + } + + std::unique_ptr assignment() { + std::string var_name = currentToken.value; + eat(TOKEN_ID); + eat(TOKEN_ASSIGN); + auto value = expression(); + eat(TOKEN_SEMICOLON); + return std::make_unique(var_name, std::move(value)); + } + + std::unique_ptr if_statement() { + eat(TOKEN_IF); + eat(TOKEN_LPAREN); + auto condition = expression(); + eat(TOKEN_RPAREN); + auto then_branch = statement(); + std::unique_ptr else_branch = nullptr; + if (currentToken.type == TOKEN_ELSE) { + eat(TOKEN_ELSE); + else_branch = statement(); + } + return std::make_unique(std::move(condition), std::move(then_branch), std::move(else_branch)); + } + + std::unique_ptr return_statement() { + eat(TOKEN_RETURN); + std::unique_ptr expr = nullptr; + if (currentToken.type != TOKEN_SEMICOLON) { + expr = expression(); + } + eat(TOKEN_SEMICOLON); + return std::make_unique(std::move(expr)); + } + + std::unique_ptr expression() { + return equality_expression(); + } + + // Handling operator precedence + std::unique_ptr equality_expression() { + auto node = relational_expression(); + while (currentToken.type == TOKEN_EQ || currentToken.type == TOKEN_NEQ) { + std::string op = currentToken.value; + if (currentToken.type == TOKEN_EQ) { + eat(TOKEN_EQ); + } else { + eat(TOKEN_NEQ); + } + auto right = relational_expression(); + node = std::make_unique(op, std::move(node), std::move(right)); + } + return node; + } + + std::unique_ptr relational_expression() { + auto node = additive_expression(); + while (currentToken.type == TOKEN_LT || currentToken.type == TOKEN_GT || + currentToken.type == TOKEN_LE || currentToken.type == TOKEN_GE) { + std::string op = currentToken.value; + switch (currentToken.type) { + case TOKEN_LT: + eat(TOKEN_LT); + break; + case TOKEN_GT: + eat(TOKEN_GT); + break; + case TOKEN_LE: + eat(TOKEN_LE); + break; + case TOKEN_GE: + eat(TOKEN_GE); + break; + default: + break; + } + auto right = additive_expression(); + node = std::make_unique(op, std::move(node), std::move(right)); + } + return node; + } + + std::unique_ptr additive_expression() { + auto node = term(); + while (currentToken.type == TOKEN_PLUS || currentToken.type == TOKEN_MINUS) { + std::string op = currentToken.value; + if (currentToken.type == TOKEN_PLUS) { + eat(TOKEN_PLUS); + } else { + eat(TOKEN_MINUS); + } + auto right = term(); + node = std::make_unique(op, std::move(node), std::move(right)); + } + return node; + } + + std::unique_ptr term() { + auto node = factor(); + while (currentToken.type == TOKEN_TIMES || currentToken.type == TOKEN_DIVIDE) { + std::string op = currentToken.value; + if (currentToken.type == TOKEN_TIMES) { + eat(TOKEN_TIMES); + } else { + eat(TOKEN_DIVIDE); + } + auto right = factor(); + node = std::make_unique(op, std::move(node), std::move(right)); + } + return node; + } + + std::unique_ptr factor() { + Token token = currentToken; + if (token.type == TOKEN_PLUS) { + eat(TOKEN_PLUS); + return factor(); + } else if (token.type == TOKEN_MINUS) { + eat(TOKEN_MINUS); + auto node = factor(); + // Implement unary minus as multiplication by -1 + auto minus_one = std::make_unique(-1); + return std::make_unique("*", std::move(minus_one), std::move(node)); + } else if (token.type == TOKEN_NUMBER) { + eat(TOKEN_NUMBER); + return std::make_unique(std::stoi(token.value)); + } else if (token.type == TOKEN_ID) { + eat(TOKEN_ID); + return std::make_unique(token.value); + } else if (token.type == TOKEN_LPAREN) { + eat(TOKEN_LPAREN); + auto node = expression(); + eat(TOKEN_RPAREN); + return node; + } else { + std::stringstream ss; + ss << "Syntax error: Unexpected token '" << token.value + << "' at line " << token.lineno; + LOG_ERROR("%s", ss.str().c_str()); + exit(EXIT_FAILURE); + } + return nullptr; + } + +public: + Parser(const std::string& input) + : lexer(input), currentToken(lexer.getNextToken()) {} + + std::unique_ptr parse() { + return program(); + } +}; + +// ------------------- +// Interpreter Definitions +// ------------------- + +// Environment to store variables +class Environment { +private: + std::unordered_map vars; + +public: + void declare(const std::string& var_name, int value = 0) { + if (vars.find(var_name) != vars.end()) { + std::stringstream ss; + ss << "Runtime error: Variable '" << var_name << "' already declared."; + LOG_ERROR("%s", ss.str().c_str()); + exit(EXIT_FAILURE); + } + vars[var_name] = value; + } + + void assign(const std::string& var_name, int value) { + if (vars.find(var_name) == vars.end()) { + std::stringstream ss; + ss << "Runtime error: Variable '" << var_name << "' not declared."; + LOG_ERROR("%s", ss.str().c_str()); + exit(EXIT_FAILURE); + } + vars[var_name] = value; + } + + int get(const std::string& var_name) const { + auto it = vars.find(var_name); + if (it == vars.end()) { + std::stringstream ss; + ss << "Runtime error: Variable '" << var_name << "' not declared."; + LOG_ERROR("%s", ss.str().c_str()); + exit(EXIT_FAILURE); + } + return it->second; + } + + void printFinalState() const { + std::cout << "\nFinal Variable States:\n"; + for (const auto& pair : vars) { + std::cout << pair.first << " = " << pair.second << "\n"; + } + } +}; + +// Interpreter class +class Interpreter { +private: + std::unique_ptr tree; + Environment env; + bool has_returned; + +public: + Interpreter(std::unique_ptr tree) + : tree(std::move(tree)), has_returned(false) {} + + void interpret() { + visit(tree.get()); + } + + // Public method to print final state + void printFinalState() const { + env.printFinalState(); + } + +private: + void visit(ASTNode* node) { + if (has_returned) return; // Stop execution after return + + if (auto program = dynamic_cast(node)) { + visit(program); + } + else if (auto func = dynamic_cast(node)) { + visit(func); + } + else if (auto compound = dynamic_cast(node)) { + visit(compound); + } + else if (auto decl = dynamic_cast(node)) { + visit(decl); + } + else if (auto assign = dynamic_cast(node)) { + visit(assign); + } + else if (auto if_stmt = dynamic_cast(node)) { + visit(if_stmt); + } + else if (auto ret_stmt = dynamic_cast(node)) { + visit(ret_stmt); + } + else { + std::stringstream ss; + ss << "Runtime error: Unknown AST node type."; + LOG_ERROR("%s", ss.str().c_str()); + exit(EXIT_FAILURE); + } + } + + void visit(Program* node) { + for (auto& func : node->functions) { + if (func->name == "main") { + visit(func.get()); + return; // Execute only the main function + } + } + LOG_ERROR("No 'main' function found."); + exit(EXIT_FAILURE); + } + + void visit(Function* node) { + // Execute the main function's body + visit(node->body.get()); + } + + void visit(Compound* node) { + for (const auto& stmt : node->statements) { + if (stmt && !has_returned) { + visit(stmt.get()); + } + if (has_returned) break; + } + } + + void visit(Declaration* node) { + int value = 0; + if (node->value) { + value = evaluate(node->value.get()); + } + env.declare(node->var_name, value); + } + + void visit(Assignment* node) { + int value = evaluate(node->value.get()); + env.assign(node->var_name, value); + } + + void visit(IfStatement* node) { + int condition = evaluate(node->condition.get()); + if (condition) { + visit(node->then_branch.get()); + } else if (node->else_branch) { + visit(node->else_branch.get()); + } + } + + void visit(ReturnStatement* node) { + if (node->expression) { + int ret_val = evaluate(node->expression.get()); + LOG_INFO("Return statement with value: %d", ret_val); + } else { + LOG_INFO("Return statement with no value."); + } + has_returned = true; + } + + int evaluate(ASTNode* node) { + if (auto binop = dynamic_cast(node)) { + int left = evaluate(binop->left.get()); + int right = evaluate(binop->right.get()); + if (binop->op == "+") { + return left + right; + } else if (binop->op == "-") { + return left - right; + } else if (binop->op == "*") { + return left * right; + } else if (binop->op == "/") { + if (right == 0) { + LOG_ERROR("Runtime error: Division by zero."); + exit(EXIT_FAILURE); + } + return left / right; + } else if (binop->op == "==") { + return left == right; + } else if (binop->op == "!=") { + return left != right; + } else if (binop->op == "<") { + return left < right; + } else if (binop->op == ">") { + return left > right; + } else if (binop->op == "<=") { + return left <= right; + } else if (binop->op == ">=") { + return left >= right; + } else { + std::stringstream ss; + ss << "Runtime error: Unknown operator '" << binop->op << "'."; + LOG_ERROR("%s", ss.str().c_str()); + exit(EXIT_FAILURE); + } + } + else if (auto num = dynamic_cast(node)) { + return num->value; + } + else if (auto var = dynamic_cast(node)) { + return env.get(var->name); + } + else { + std::stringstream ss; + ss << "Runtime error: Invalid expression."; + LOG_ERROR("%s", ss.str().c_str()); + exit(EXIT_FAILURE); + } + return 0; + } +}; + +// ------------------- +// Main Execution +// ------------------- + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::cerr << "Usage: ./cintr.exe \n"; + return 1; + } + + std::string source_file = argv[1]; + std::ifstream infile(source_file); + if (!infile.is_open()) { + std::cerr << "Error: Cannot open file '" << source_file << "'.\n"; + return 1; + } + + std::stringstream buffer; + buffer << infile.rdbuf(); + std::string source_code = buffer.str(); + infile.close(); + + try { + // Parse the input + Parser parser(source_code); + std::unique_ptr ast = parser.parse(); + + // Interpret the AST + Interpreter interpreter(std::move(ast)); + interpreter.interpret(); + + // Print the final state of variables + interpreter.printFinalState(); + } + catch (const std::exception& e) { + std::cerr << "Exception: " << e.what() << "\n"; + return 1; + } + + return 0; +}