diff --git a/Makefile b/Makefile index 911dab2..d1138e1 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,8 @@ SOURCES = $(SRC_DIR)/main.c \ $(SRC_DIR)/parser.c \ $(SRC_DIR)/interpreter.c \ $(SRC_DIR)/native_functions.c \ - $(SRC_DIR)/string_utils.c + $(SRC_DIR)/string_utils.c \ + $(SRC_DIR)/preprocessor.c OBJECTS = $(SOURCES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o) @@ -36,7 +37,9 @@ TESTS = $(TEST_DIR)/feature_test.rc \ $(TEST_DIR)/comparison_test.rc \ $(TEST_DIR)/functions_test.rc \ $(TEST_DIR)/trig_test.rc \ - $(TEST_DIR)/file_seek_test.rc + $(TEST_DIR)/file_seek_test.rc \ + $(TEST_DIR)/include_test.rc \ + $(TEST_DIR)/nested_include_test.rc EXAMPLES = $(EXAMPLE_DIR)/http_simple.rc \ $(EXAMPLE_DIR)/http_persistent.rc \ @@ -113,6 +116,12 @@ test: $(TARGET) @echo "Running file seek tests..." @$(TARGET) $(TEST_DIR)/file_seek_test.rc 2>&1 | grep -v "Error at token" || true @echo "" + @echo "Running include tests..." + @$(TARGET) $(TEST_DIR)/include_test.rc 2>&1 | grep -v "Error at token" || true + @echo "" + @echo "Running nested include tests..." + @$(TARGET) $(TEST_DIR)/nested_include_test.rc 2>&1 | grep -v "Error at token" || true + @echo "" @echo "=== All Tests Completed ===" run-feature-test: $(TARGET) @@ -166,6 +175,12 @@ run-trig-test: $(TARGET) run-file-seek-test: $(TARGET) $(TARGET) $(TEST_DIR)/file_seek_test.rc +run-include-test: $(TARGET) + $(TARGET) $(TEST_DIR)/include_test.rc + +run-nested-include-test: $(TARGET) + $(TARGET) $(TEST_DIR)/nested_include_test.rc + run-http-simple: $(TARGET) $(TARGET) $(EXAMPLE_DIR)/http_simple.rc @@ -204,6 +219,8 @@ help: @echo " make run-functions-test - Run functions_test.rc (function calls & recursion)" @echo " make run-trig-test - Run trig_test.rc (sin, cos, tan)" @echo " make run-file-seek-test - Run file_seek_test.rc (fseek, ftell)" + @echo " make run-include-test - Run include_test.rc (#include directive)" + @echo " make run-nested-include-test - Run nested_include_test.rc (nested includes)" @echo "" @echo "Examples:" @echo " make run-http-simple - Run http_simple.rc (single connection HTTP server)" diff --git a/src/main.c b/src/main.c index 393a9c9..7ee6b94 100644 --- a/src/main.c +++ b/src/main.c @@ -1,9 +1,12 @@ #include #include +#include +#include #include "types.h" #include "tokenizer.h" #include "interpreter.h" #include "native_functions.h" +#include "preprocessor.h" int main(int argc, char **argv) { if (argc < 2) { @@ -11,24 +14,21 @@ int main(int argc, char **argv) { return 1; } - FILE *f = fopen(argv[1], "rb"); - if (!f) { - printf("Could not open file.\n"); - return 1; - } + char file_path[512]; + strncpy(file_path, argv[1], 511); + file_path[511] = 0; - src_code = malloc(MAX_SRC + 1); + char dir_path[512]; + strncpy(dir_path, argv[1], 511); + dir_path[511] = 0; + char *dir = dirname(dir_path); + + src_code = preprocess(file_path, dir); if (!src_code) { - printf("Memory allocation failed.\n"); - fclose(f); + printf("Preprocessing failed.\n"); return 1; } - size_t n = fread(src_code, 1, MAX_SRC, f); - if (n >= MAX_SRC) n = MAX_SRC - 1; - src_code[n] = 0; - fclose(f); - register_native_functions(); tokenize(src_code); diff --git a/src/preprocessor.c b/src/preprocessor.c new file mode 100644 index 0000000..05ef441 --- /dev/null +++ b/src/preprocessor.c @@ -0,0 +1,247 @@ +#include +#include +#include +#include +#include +#include +#include +#include "preprocessor.h" + +static IncludedFile included_files[MAX_INCLUDED_FILES]; +static int included_count = 0; +static int include_depth = 0; + +static void normalize_path(const char *path, char *normalized) { + strncpy(normalized, path, 255); + normalized[255] = 0; +} + +static int is_already_included(const char *path) { + char normalized[256]; + normalize_path(path, normalized); + + for (int i = 0; i < included_count; i++) { + if (strcmp(included_files[i].path, normalized) == 0) { + return 1; + } + } + return 0; +} + +static void mark_as_included(const char *path) { + if (included_count >= MAX_INCLUDED_FILES) { + fprintf(stderr, "Error: Too many included files (max %d)\n", MAX_INCLUDED_FILES); + return; + } + + char normalized[256]; + normalize_path(path, normalized); + + strncpy(included_files[included_count].path, normalized, 255); + included_files[included_count].path[255] = 0; + included_files[included_count].included = 1; + included_count++; +} + +static char* read_file(const char *filename) { + FILE *f = fopen(filename, "rb"); + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + fseek(f, 0, SEEK_SET); + + if (size <= 0 || size > MAX_PREPROCESSED_SIZE) { + fclose(f); + return NULL; + } + + char *content = malloc(size + 1); + if (!content) { + fclose(f); + return NULL; + } + + size_t read_size = fread(content, 1, size, f); + content[read_size] = 0; + fclose(f); + + return content; +} + +static void resolve_include_path(const char *current_dir, const char *include_file, char *resolved_path) { + if (include_file[0] == '/') { + strncpy(resolved_path, include_file, 511); + resolved_path[511] = 0; + return; + } + + snprintf(resolved_path, 512, "%s/%s", current_dir, include_file); +} + +static char* process_includes(const char *filename, const char *content, const char *current_dir); + +static char* extract_include_filename(const char *line) { + const char *p = line; + while (*p && isspace(*p)) p++; + + if (*p != '#') return NULL; + p++; + + while (*p && isspace(*p)) p++; + + if (strncmp(p, "include", 7) != 0) return NULL; + p += 7; + + while (*p && isspace(*p)) p++; + + if (*p != '"' && *p != '<') return NULL; + + char quote = (*p == '"') ? '"' : '>'; + p++; + + static char filename[256]; + int i = 0; + while (*p && *p != quote && i < 255) { + filename[i++] = *p++; + } + filename[i] = 0; + + if (*p != quote) return NULL; + + return filename; +} + +static char* process_includes(const char *filename, const char *content, const char *current_dir) { + if (include_depth >= MAX_INCLUDE_DEPTH) { + fprintf(stderr, "Error: Include depth exceeded (max %d) in file: %s\n", + MAX_INCLUDE_DEPTH, filename); + exit(1); + } + + include_depth++; + + size_t result_size = MAX_PREPROCESSED_SIZE; + char *result = malloc(result_size); + if (!result) { + fprintf(stderr, "Error: Memory allocation failed\n"); + exit(1); + } + + result[0] = 0; + size_t result_len = 0; + + const char *p = content; + char line[1024]; + + while (*p) { + int line_len = 0; + while (*p && *p != '\n' && line_len < 1023) { + line[line_len++] = *p++; + } + line[line_len] = 0; + if (*p == '\n') { + p++; + } + + char *include_file = extract_include_filename(line); + + if (include_file) { + char resolved_path[512]; + resolve_include_path(current_dir, include_file, resolved_path); + + if (is_already_included(resolved_path)) { + continue; + } + + mark_as_included(resolved_path); + + char *included_content = read_file(resolved_path); + if (!included_content) { + fprintf(stderr, "Error: Cannot open included file: %s\n", resolved_path); + fprintf(stderr, " Included from: %s\n", filename); + free(result); + exit(1); + } + + char include_dir[512]; + char temp_path[512]; + strncpy(temp_path, resolved_path, 511); + temp_path[511] = 0; + char *dir = dirname(temp_path); + strncpy(include_dir, dir, 511); + include_dir[511] = 0; + + char *processed = process_includes(resolved_path, included_content, include_dir); + free(included_content); + + size_t processed_len = strlen(processed); + if (result_len + processed_len + 2 >= result_size) { + fprintf(stderr, "Error: Preprocessed output too large (max %d bytes)\n", + MAX_PREPROCESSED_SIZE); + free(processed); + free(result); + exit(1); + } + + memcpy(result + result_len, processed, processed_len); + result_len += processed_len; + result[result_len++] = '\n'; + result[result_len] = 0; + + free(processed); + } else { + size_t line_total = line_len + 1; + if (result_len + line_total >= result_size) { + fprintf(stderr, "Error: Preprocessed output too large (max %d bytes)\n", + MAX_PREPROCESSED_SIZE); + free(result); + exit(1); + } + + if (line_len > 0) { + memcpy(result + result_len, line, line_len); + result_len += line_len; + } + result[result_len++] = '\n'; + result[result_len] = 0; + } + } + + include_depth--; + return result; +} + +char* preprocess(const char *filename, const char *source_dir) { + included_count = 0; + include_depth = 0; + memset(included_files, 0, sizeof(included_files)); + + mark_as_included(filename); + + char *content = read_file(filename); + if (!content) { + fprintf(stderr, "Error: Cannot read file: %s\n", filename); + return NULL; + } + + char dir_path[512]; + if (source_dir) { + strncpy(dir_path, source_dir, 511); + dir_path[511] = 0; + } else { + char temp_path[512]; + strncpy(temp_path, filename, 511); + temp_path[511] = 0; + char *dir = dirname(temp_path); + strncpy(dir_path, dir, 511); + dir_path[511] = 0; + } + + char *result = process_includes(filename, content, dir_path); + free(content); + + return result; +} diff --git a/src/preprocessor.h b/src/preprocessor.h new file mode 100644 index 0000000..e12d2dc --- /dev/null +++ b/src/preprocessor.h @@ -0,0 +1,15 @@ +#ifndef PREPROCESSOR_H +#define PREPROCESSOR_H + +#define MAX_INCLUDE_DEPTH 32 +#define MAX_INCLUDED_FILES 256 +#define MAX_PREPROCESSED_SIZE 500000 + +typedef struct { + char path[256]; + int included; +} IncludedFile; + +char* preprocess(const char *filename, const char *source_dir); + +#endif diff --git a/tests/include_test.rc b/tests/include_test.rc new file mode 100644 index 0000000..626404d --- /dev/null +++ b/tests/include_test.rc @@ -0,0 +1,27 @@ +#include "includes/math_lib.rc" + +int main() { + printf("=== Include Directive Tests ===\n"); + + printf("Test 1: Basic include\n"); + int sum = add(10, 5); + printf("add(10, 5) = %d\n", sum); + printf("PASS: Included function works\n"); + + printf("Test 2: Multiple functions from include\n"); + int diff = subtract(20, 7); + printf("subtract(20, 7) = %d\n", diff); + int prod = multiply(6, 4); + printf("multiply(6, 4) = %d\n", prod); + int quot = divide(15, 3); + printf("divide(15, 3) = %d\n", quot); + printf("PASS: All included functions work\n"); + + printf("Test 3: Using included functions in expressions\n"); + int result = add(multiply(3, 4), subtract(10, 5)); + printf("add(multiply(3, 4), subtract(10, 5)) = %d\n", result); + printf("PASS: Included functions in expressions work\n"); + + printf("\n=== All Include Tests Completed ===\n"); + return 0; +} diff --git a/tests/includes/math_lib.rc b/tests/includes/math_lib.rc new file mode 100644 index 0000000..294a363 --- /dev/null +++ b/tests/includes/math_lib.rc @@ -0,0 +1,18 @@ +int add(int a, int b) { + return a + b; +} + +int subtract(int a, int b) { + return a - b; +} + +int multiply(int a, int b) { + return a * b; +} + +int divide(int a, int b) { + if (b == 0) { + return 0; + } + return a / b; +} diff --git a/tests/includes/string_lib.rc b/tests/includes/string_lib.rc new file mode 100644 index 0000000..499fec9 --- /dev/null +++ b/tests/includes/string_lib.rc @@ -0,0 +1,19 @@ +int string_length(char *s) { + return strlen(s); +} + +char* string_upper(char *s) { + return upper(s); +} + +char* string_lower(char *s) { + return lower(s); +} + +int string_contains(char *haystack, char *needle) { + int pos = strpos(haystack, needle); + if (pos >= 0) { + return 1; + } + return 0; +} diff --git a/tests/includes/utils.rc b/tests/includes/utils.rc new file mode 100644 index 0000000..4d43019 --- /dev/null +++ b/tests/includes/utils.rc @@ -0,0 +1,14 @@ +#include "math_lib.rc" +#include "string_lib.rc" + +int is_even(int n) { + int half = divide(n, 2); + return multiply(half, 2) == n; +} + +int is_odd(int n) { + if (is_even(n)) { + return 0; + } + return 1; +} diff --git a/tests/nested_include_test.rc b/tests/nested_include_test.rc new file mode 100644 index 0000000..9ba48df --- /dev/null +++ b/tests/nested_include_test.rc @@ -0,0 +1,22 @@ +#include "includes/utils.rc" + +int main() { + printf("=== Nested Include Tests ===\n"); + + printf("Test 1: Functions from nested includes\n"); + int sum = add(5, 3); + printf("add(5, 3) = %d\n", sum); + printf("PASS: Functions from first-level include work\n"); + + printf("Test 2: Utility functions using nested includes\n"); + if (is_even(10)) { + printf("is_even(10) = true\n"); + } + if (is_odd(7)) { + printf("is_odd(7) = true\n"); + } + printf("PASS: Utility functions using nested includes work\n"); + + printf("\n=== All Nested Include Tests Completed ===\n"); + return 0; +}