V 0.1.0
This commit is contained in:
parent
323bf8a7c7
commit
9682b44a7a
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.exe
|
||||||
|
*.vscode
|
15
README.md
15
README.md
@ -1,3 +1,18 @@
|
|||||||
# C_Interpreter
|
# C_Interpreter
|
||||||
|
|
||||||
A Simple Interpreter for the C Language
|
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)
|
13
main.c
Normal file
13
main.c
Normal file
@ -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;
|
||||||
|
}
|
455
src/gcml.h
Normal file
455
src/gcml.h
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
#ifndef GCML_H
|
||||||
|
#define GCML_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// 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
|
||||||
|
|
||||||
|
|
||||||
|
|
848
src/main.cpp
Normal file
848
src/main.cpp
Normal file
@ -0,0 +1,848 @@
|
|||||||
|
// src/main.cpp
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
// 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<std::string, TokenType> 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<std::unique_ptr<Function>> functions;
|
||||||
|
|
||||||
|
Program(std::vector<std::unique_ptr<Function>> functions)
|
||||||
|
: functions(std::move(functions)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Function : public ASTNode {
|
||||||
|
std::string return_type;
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::pair<std::string, std::string>> parameters; // Not used in this simple interpreter
|
||||||
|
std::unique_ptr<Compound> body;
|
||||||
|
|
||||||
|
Function(const std::string& return_type, const std::string& name,
|
||||||
|
std::unique_ptr<Compound> body)
|
||||||
|
: return_type(return_type), name(name), body(std::move(body)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Compound : public ASTNode {
|
||||||
|
std::vector<std::unique_ptr<ASTNode>> statements;
|
||||||
|
|
||||||
|
Compound(std::vector<std::unique_ptr<ASTNode>> statements)
|
||||||
|
: statements(std::move(statements)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Declaration : public ASTNode {
|
||||||
|
std::string var_name;
|
||||||
|
std::unique_ptr<ASTNode> value; // Can be nullptr
|
||||||
|
|
||||||
|
Declaration(const std::string& var_name, std::unique_ptr<ASTNode> value = nullptr)
|
||||||
|
: var_name(var_name), value(std::move(value)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Assignment : public ASTNode {
|
||||||
|
std::string var_name;
|
||||||
|
std::unique_ptr<ASTNode> value;
|
||||||
|
|
||||||
|
Assignment(const std::string& var_name, std::unique_ptr<ASTNode> value)
|
||||||
|
: var_name(var_name), value(std::move(value)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IfStatement : public ASTNode {
|
||||||
|
std::unique_ptr<ASTNode> condition;
|
||||||
|
std::unique_ptr<ASTNode> then_branch;
|
||||||
|
std::unique_ptr<ASTNode> else_branch; // Can be nullptr
|
||||||
|
|
||||||
|
IfStatement(std::unique_ptr<ASTNode> condition,
|
||||||
|
std::unique_ptr<ASTNode> then_branch,
|
||||||
|
std::unique_ptr<ASTNode> 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<ASTNode> expression; // Can be nullptr
|
||||||
|
|
||||||
|
ReturnStatement(std::unique_ptr<ASTNode> expression = nullptr)
|
||||||
|
: expression(std::move(expression)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BinaryOp : public ASTNode {
|
||||||
|
std::string op;
|
||||||
|
std::unique_ptr<ASTNode> left;
|
||||||
|
std::unique_ptr<ASTNode> right;
|
||||||
|
|
||||||
|
BinaryOp(const std::string& op, std::unique_ptr<ASTNode> left, std::unique_ptr<ASTNode> 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<ASTNode> program() {
|
||||||
|
std::vector<std::unique_ptr<Function>> functions;
|
||||||
|
while (currentToken.type != TOKEN_EOF) {
|
||||||
|
functions.emplace_back(function_definition());
|
||||||
|
}
|
||||||
|
return std::make_unique<Program>(std::move(functions));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Function> 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<Function>(return_type, func_name, std::unique_ptr<Compound>(dynamic_cast<Compound*>(body.release())));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ASTNode> compound_statement() {
|
||||||
|
eat(TOKEN_LBRACE);
|
||||||
|
std::vector<std::unique_ptr<ASTNode>> statements = statement_list();
|
||||||
|
eat(TOKEN_RBRACE);
|
||||||
|
return std::make_unique<Compound>(std::move(statements));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<ASTNode>> statement_list() {
|
||||||
|
std::vector<std::unique_ptr<ASTNode>> 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<ASTNode> 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<ASTNode> 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<ASTNode> value = nullptr;
|
||||||
|
if (currentToken.type == TOKEN_ASSIGN) {
|
||||||
|
eat(TOKEN_ASSIGN);
|
||||||
|
value = expression();
|
||||||
|
}
|
||||||
|
eat(TOKEN_SEMICOLON);
|
||||||
|
return std::make_unique<Declaration>(var_name, std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ASTNode> assignment() {
|
||||||
|
std::string var_name = currentToken.value;
|
||||||
|
eat(TOKEN_ID);
|
||||||
|
eat(TOKEN_ASSIGN);
|
||||||
|
auto value = expression();
|
||||||
|
eat(TOKEN_SEMICOLON);
|
||||||
|
return std::make_unique<Assignment>(var_name, std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ASTNode> if_statement() {
|
||||||
|
eat(TOKEN_IF);
|
||||||
|
eat(TOKEN_LPAREN);
|
||||||
|
auto condition = expression();
|
||||||
|
eat(TOKEN_RPAREN);
|
||||||
|
auto then_branch = statement();
|
||||||
|
std::unique_ptr<ASTNode> else_branch = nullptr;
|
||||||
|
if (currentToken.type == TOKEN_ELSE) {
|
||||||
|
eat(TOKEN_ELSE);
|
||||||
|
else_branch = statement();
|
||||||
|
}
|
||||||
|
return std::make_unique<IfStatement>(std::move(condition), std::move(then_branch), std::move(else_branch));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ASTNode> return_statement() {
|
||||||
|
eat(TOKEN_RETURN);
|
||||||
|
std::unique_ptr<ASTNode> expr = nullptr;
|
||||||
|
if (currentToken.type != TOKEN_SEMICOLON) {
|
||||||
|
expr = expression();
|
||||||
|
}
|
||||||
|
eat(TOKEN_SEMICOLON);
|
||||||
|
return std::make_unique<ReturnStatement>(std::move(expr));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ASTNode> expression() {
|
||||||
|
return equality_expression();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handling operator precedence
|
||||||
|
std::unique_ptr<ASTNode> 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<BinaryOp>(op, std::move(node), std::move(right));
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ASTNode> 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<BinaryOp>(op, std::move(node), std::move(right));
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ASTNode> 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<BinaryOp>(op, std::move(node), std::move(right));
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ASTNode> 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<BinaryOp>(op, std::move(node), std::move(right));
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ASTNode> 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<Number>(-1);
|
||||||
|
return std::make_unique<BinaryOp>("*", std::move(minus_one), std::move(node));
|
||||||
|
} else if (token.type == TOKEN_NUMBER) {
|
||||||
|
eat(TOKEN_NUMBER);
|
||||||
|
return std::make_unique<Number>(std::stoi(token.value));
|
||||||
|
} else if (token.type == TOKEN_ID) {
|
||||||
|
eat(TOKEN_ID);
|
||||||
|
return std::make_unique<Variable>(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<ASTNode> parse() {
|
||||||
|
return program();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------
|
||||||
|
// Interpreter Definitions
|
||||||
|
// -------------------
|
||||||
|
|
||||||
|
// Environment to store variables
|
||||||
|
class Environment {
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, int> 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<ASTNode> tree;
|
||||||
|
Environment env;
|
||||||
|
bool has_returned;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Interpreter(std::unique_ptr<ASTNode> 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<Program*>(node)) {
|
||||||
|
visit(program);
|
||||||
|
}
|
||||||
|
else if (auto func = dynamic_cast<Function*>(node)) {
|
||||||
|
visit(func);
|
||||||
|
}
|
||||||
|
else if (auto compound = dynamic_cast<Compound*>(node)) {
|
||||||
|
visit(compound);
|
||||||
|
}
|
||||||
|
else if (auto decl = dynamic_cast<Declaration*>(node)) {
|
||||||
|
visit(decl);
|
||||||
|
}
|
||||||
|
else if (auto assign = dynamic_cast<Assignment*>(node)) {
|
||||||
|
visit(assign);
|
||||||
|
}
|
||||||
|
else if (auto if_stmt = dynamic_cast<IfStatement*>(node)) {
|
||||||
|
visit(if_stmt);
|
||||||
|
}
|
||||||
|
else if (auto ret_stmt = dynamic_cast<ReturnStatement*>(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<BinaryOp*>(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<Number*>(node)) {
|
||||||
|
return num->value;
|
||||||
|
}
|
||||||
|
else if (auto var = dynamic_cast<Variable*>(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 <source_file.c>\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<ASTNode> 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user