diff --git a/Makefile b/Makefile index f42a566..8b196e0 100644 --- a/Makefile +++ b/Makefile @@ -25,11 +25,16 @@ TESTS = $(TEST_DIR)/feature_test.rc \ $(TEST_DIR)/math_test.rc \ $(TEST_DIR)/string_test.rc \ $(TEST_DIR)/string_manip_test.rc \ - $(TEST_DIR)/increment_decrement_test.rc + $(TEST_DIR)/increment_decrement_test.rc \ + $(TEST_DIR)/file_io_test.rc \ + $(TEST_DIR)/double_test.rc \ + $(TEST_DIR)/break_continue_test.rc \ + $(TEST_DIR)/async_io_test.rc EXAMPLES = $(EXAMPLE_DIR)/http_simple.rc \ $(EXAMPLE_DIR)/http_persistent.rc \ - $(EXAMPLE_DIR)/http_multi.rc + $(EXAMPLE_DIR)/http_multi.rc \ + $(EXAMPLE_DIR)/async_demo.rc .PHONY: all clean test run-tests run-examples help @@ -88,6 +93,18 @@ run-string-manip: $(TARGET) run-increment-decrement-test: $(TARGET) $(TARGET) $(TEST_DIR)/increment_decrement_test.rc +run-file-io-test: $(TARGET) + $(TARGET) $(TEST_DIR)/file_io_test.rc + +run-double-test: $(TARGET) + $(TARGET) $(TEST_DIR)/double_test.rc + +run-break-continue-test: $(TARGET) + $(TARGET) $(TEST_DIR)/break_continue_test.rc + +run-async-io-test: $(TARGET) + $(TARGET) $(TEST_DIR)/async_io_test.rc + run-http-simple: $(TARGET) $(TARGET) $(EXAMPLE_DIR)/http_simple.rc @@ -97,6 +114,9 @@ run-http-persistent: $(TARGET) run-http-multi: $(TARGET) $(TARGET) $(EXAMPLE_DIR)/http_multi.rc +run-async-demo: $(TARGET) + $(TARGET) $(EXAMPLE_DIR)/async_demo.rc + help: @echo "RC - Retoor's C Interpreter" @echo "" @@ -106,16 +126,21 @@ help: @echo " make clean - Remove all build artifacts" @echo "" @echo "Individual Tests:" - @echo " make run-feature-test - Run feature_test.rc (negative numbers, ==, !=)" - @echo " make run-endless-loop - Run endless_loop_test.rc (while(1) test)" - @echo " make run-math-test - Run math_test.rc (math functions)" - @echo " make run-string-test - Run string_test.rc (string concatenation)" - @echo " make run-string-manip - Run string_manip_test.rc (string manipulation & slicing)" + @echo " make run-feature-test - Run feature_test.rc (negative numbers, ==, !=)" + @echo " make run-endless-loop - Run endless_loop_test.rc (while(1) test)" + @echo " make run-math-test - Run math_test.rc (math functions)" + @echo " make run-string-test - Run string_test.rc (string concatenation)" + @echo " make run-string-manip - Run string_manip_test.rc (string manipulation & slicing)" + @echo " make run-file-io-test - Run file_io_test.rc (file I/O operations)" + @echo " make run-double-test - Run double_test.rc (double data type)" + @echo " make run-break-continue-test - Run break_continue_test.rc (break/continue)" + @echo " make run-async-io-test - Run async_io_test.rc (async I/O)" @echo "" @echo "Examples:" - @echo " make run-http-simple - Run http_simple.rc (single connection HTTP server)" - @echo " make run-http-persistent - Run http_persistent.rc (persistent HTTP server)" - @echo " make run-http-multi - Run http_multi.rc (HTTP server, 100 connections)" + @echo " make run-http-simple - Run http_simple.rc (single connection HTTP server)" + @echo " make run-http-persistent - Run http_persistent.rc (persistent HTTP server)" + @echo " make run-http-multi - Run http_multi.rc (HTTP server, 100 connections)" + @echo " make run-async-demo - Run async_demo.rc (comprehensive async I/O demo)" @echo "" @echo "Directory Structure:" @echo " src/ - Source code files" diff --git a/README.md b/README.md index 4edfdfc..c822acc 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ RC is a lightweight, recursive-descent C interpreter written in C. It executes a **Data Types** - Integers (long) +- Doubles (floating-point numbers) - Character pointers (char*) - Pointer operations (address-of &, dereference *) - Array declarations and indexing @@ -15,6 +16,7 @@ RC is a lightweight, recursive-descent C interpreter written in C. It executes a **Control Flow** - if/else statements - while loops (including infinite loops) +- break and continue statements - Comparison operators: ==, !=, <, >, <=, >= - Logical operators: &&, || @@ -39,6 +41,13 @@ RC is a lightweight, recursive-descent C interpreter written in C. It executes a - sin(x), cos(x), tan(x) - Trigonometric functions - floor(x), ceil(x) - Rounding functions +**Double Type Functions** +- int_to_double(i) - Convert int to double +- double_to_int(d) - Convert double to int +- double_add(a, b), double_sub(a, b) - Double arithmetic +- double_mul(a, b), double_div(a, b) - Double arithmetic +- printf with %f format for doubles + **String Functions** - strpos(haystack, needle) - Find substring position - substr(str, start, length) - Extract substring @@ -49,6 +58,27 @@ RC is a lightweight, recursive-descent C interpreter written in C. It executes a - endswith(str, suffix) - Suffix check - strlen(str) - String length +**File I/O** +- fopen(filename, mode) - Open a file +- fclose(file) - Close a file +- fread(file, buffer, size) - Read from file +- fwrite(file, buffer, size) - Write to file +- fgets(file, max_size) - Read a line +- fputs(file, string) - Write a string +- feof(file) - Check end of file +- ftell(file), fseek(file, offset, whence) - File positioning +- fremove(filename), frename(old, new) - File operations +- SEEK_SET, SEEK_CUR, SEEK_END constants + +**Async I/O (Multi-threaded)** +- async_fread(file, buffer, size) - Async file read +- async_fwrite(file, buffer, size) - Async file write +- async_recv(socket, buffer, len, flags) - Async socket receive +- async_send(socket, buffer, len, flags) - Async socket send +- async_wait(handle) - Wait for async operation to complete +- async_poll(handle) - Check if async operation is complete +- async_result(handle) - Get result of completed async operation + **Socket Programming** - socket(), bind(), listen(), accept() - send(), recv(), close() @@ -169,10 +199,12 @@ Extensible system for binding C functions. Current bindings include math operati - Memory model uses long cells (not byte-accurate) - No support for structs, unions, or enums as user types -- Limited type system (int and char* only) +- Limited type system (int, double, and char*) +- Double arithmetic requires explicit helper functions - No preprocessor directives - Error messages show token index rather than line/column - Pointer arithmetic works on virtual memory addresses +- Maximum 100 concurrent async operations ## Testing diff --git a/TUTORIAL.md b/TUTORIAL.md index 780a9c1..b16d13c 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -986,3 +986,174 @@ int main() { --- For more examples, see the `tests/` and `examples/` directories in the RC repository. + +## File I/O + +### Writing to a File + +```c +int main() { + int f = fopen("output.txt", "w"); + fputs(f, "Hello, File!\n"); + fputs(f, "Second line\n"); + fclose(f); + return 0; +} +``` + +### Reading from a File + +```c +int main() { + int f = fopen("output.txt", "r"); + char *line = fgets(f, 256); + while (strlen(line) > 0 && feof(f) == 0) { + printf("%s", line); + line = fgets(f, 256); + } + fclose(f); + return 0; +} +``` + +## Double Data Type + +### Using Doubles + +```c +int main() { + double pi = 3.14159; + double radius = 5.0; + + printf("Pi: %f\n", pi); + printf("Radius: %f\n", radius); + + double area = double_mul(pi, double_mul(radius, radius)); + printf("Circle area: %f\n", area); + + return 0; +} +``` + +### Type Conversions + +```c +int main() { + int x = 42; + double dx = int_to_double(x); + printf("Int %d as double: %f\n", x, dx); + + double y = 99.9; + int iy = double_to_int(y); + printf("Double %f as int: %d\n", y, iy); + + return 0; +} +``` + +## Break and Continue + +### Break Statement + +```c +int main() { + int i = 0; + while (i < 10) { + i = i + 1; + if (i == 5) { + printf("Breaking at %d\n", i); + break; + } + printf("%d ", i); + } + printf("\nDone\n"); + return 0; +} +``` + +### Continue Statement + +```c +int main() { + int i = 0; + while (i < 10) { + i = i + 1; + if (i == 3 || i == 7) { + continue; + } + printf("%d ", i); + } + printf("\n"); + return 0; +} +``` + +## Async I/O + +### Async File Operations + +```c +int main() { + int f = fopen("async_file.txt", "w"); + + int write_handle = async_fwrite(f, "Async write!", 12); + printf("Write started, handle: %d\n", write_handle); + + int result = async_wait(write_handle); + printf("Write completed, bytes: %d\n", result); + + fclose(f); + return 0; +} +``` + +### Polling for Completion + +```c +int main() { + int f = fopen("data.txt", "r"); + int buffer[1024]; + + int read_handle = async_fread(f, &buffer, 1024); + + printf("Reading asynchronously...\n"); + while (async_poll(read_handle) == 0) { + printf("."); + } + printf("\nRead complete!\n"); + + int bytes = async_result(read_handle); + printf("Bytes read: %d\n", bytes); + + fclose(f); + return 0; +} +``` + +### Async Socket I/O + +```c +int main() { + int server = socket(AF_INET(), SOCK_STREAM(), 0); + bind(server, 8080); + listen(server, 5); + + int client = accept(server); + + int buffer[1024]; + int recv_handle = async_recv(client, &buffer, 1024, 0); + + printf("Async receive started...\n"); + int bytes = async_wait(recv_handle); + printf("Received %d bytes\n", bytes); + + char *response = "HTTP/1.1 200 OK\r\n\r\nOK"; + int send_handle = async_send(client, response, strlen(response), 0); + async_wait(send_handle); + + close(client); + close(server); + return 0; +} +``` + diff --git a/examples/async_demo.rc b/examples/async_demo.rc new file mode 100644 index 0000000..9d8b248 --- /dev/null +++ b/examples/async_demo.rc @@ -0,0 +1,86 @@ +int main() { + printf("=== Async I/O Demo: File I/O + Async I/O + Socket I/O ===\n\n"); + + printf("Step 1: Synchronous File I/O\n"); + int log_file = fopen("demo_log.txt", "w"); + fputs(log_file, "Server Log Started\n"); + fclose(log_file); + printf("Created log file\n\n"); + + printf("Step 2: Async File Operations\n"); + log_file = fopen("demo_log.txt", "a"); + int write_op1 = async_fwrite(log_file, "Initializing server...\n", 24); + int write_op2 = async_fwrite(log_file, "Binding to port 8080...\n", 25); + + printf("Multiple async writes queued\n"); + async_wait(write_op1); + async_wait(write_op2); + printf("All async writes completed\n\n"); + + printf("Step 3: Socket Server Setup\n"); + int server_socket = socket(AF_INET(), SOCK_STREAM(), 0); + if (server_socket < 0) { + printf("ERROR: Could not create socket\n"); + return 1; + } + printf("Socket created: %d\n", server_socket); + + int bind_result = bind(server_socket, 8080); + if (bind_result < 0) { + printf("ERROR: Could not bind to port\n"); + return 1; + } + printf("Bound to port 8080\n"); + + listen(server_socket, 5); + printf("Listening for connections...\n\n"); + + printf("Step 4: Log async write while waiting for connection\n"); + int log_op = async_fwrite(log_file, "Waiting for client...\n", 23); + + printf("Accepting client (this will wait for a connection)\n"); + printf("In another terminal, run: curl http://localhost:8080/\n\n"); + + int client = accept(server_socket); + async_wait(log_op); + printf("Client connected: %d\n\n", client); + + printf("Step 5: Handle client with async socket I/O\n"); + int buffer[1024]; + int recv_op = async_recv(client, &buffer, 1024, 0); + + printf("Async receive started...\n"); + while (async_poll(recv_op) == 0) { + printf("."); + } + printf("\n"); + + int bytes = async_result(recv_op); + printf("Received %d bytes\n", bytes); + + char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nAsync I/O Demo Success!\n"; + int send_op = async_send(client, response, strlen(response), 0); + async_wait(send_op); + printf("Response sent asynchronously\n\n"); + + printf("Step 6: Final log entry\n"); + int final_log = async_fwrite(log_file, "Client served successfully\n", 28); + async_wait(final_log); + + close(client); + close(server_socket); + fclose(log_file); + + printf("\nStep 7: Read log file\n"); + log_file = fopen("demo_log.txt", "r"); + char *line = fgets(log_file, 256); + while (strlen(line) > 0 && feof(log_file) == 0) { + printf("LOG: %s", line); + line = fgets(log_file, 256); + } + fclose(log_file); + + printf("\n=== Demo Complete ===\n"); + printf("Demonstrated: Sync File I/O, Async File I/O, Socket I/O, and Async Socket I/O\n"); + return 0; +} diff --git a/src/native_functions.c b/src/native_functions.c index cacc22b..c3c2dc2 100644 --- a/src/native_functions.c +++ b/src/native_functions.c @@ -7,10 +7,193 @@ #include #include #include +#include #include "types.h" #include "native_functions.h" #include "interpreter.h" +#define MAX_ASYNC_OPS 100 + +typedef struct { + int active; + int complete; + long result; + pthread_t thread; + void *data; +} AsyncOp; + +static AsyncOp async_ops[MAX_ASYNC_OPS]; +static pthread_mutex_t async_mutex = PTHREAD_MUTEX_INITIALIZER; +static int async_initialized = 0; + +typedef struct { + FILE *f; + int addr; + int size; +} AsyncFileReadData; + +typedef struct { + FILE *f; + long buf_arg; + int size; +} AsyncFileWriteData; + +typedef struct { + int sockfd; + int addr; + int len; + int flags; +} AsyncRecvData; + +typedef struct { + int sockfd; + long buf_arg; + int len; + int flags; +} AsyncSendData; + +void init_async() { + if (!async_initialized) { + for (int i = 0; i < MAX_ASYNC_OPS; i++) { + async_ops[i].active = 0; + async_ops[i].complete = 0; + async_ops[i].result = 0; + async_ops[i].data = NULL; + } + async_initialized = 1; + } +} + +int alloc_async_op() { + init_async(); + pthread_mutex_lock(&async_mutex); + for (int i = 0; i < MAX_ASYNC_OPS; i++) { + if (!async_ops[i].active) { + async_ops[i].active = 1; + async_ops[i].complete = 0; + async_ops[i].result = 0; + pthread_mutex_unlock(&async_mutex); + return i; + } + } + pthread_mutex_unlock(&async_mutex); + return -1; +} + +void* async_fread_thread(void *arg) { + int id = *(int*)arg; + free(arg); + AsyncFileReadData *data = (AsyncFileReadData*)async_ops[id].data; + + char temp_buf[8192]; + int size = data->size; + if (size > 8192) size = 8192; + + int result = fread(temp_buf, 1, size, data->f); + if (result > 0) { + for (int i = 0; i < result && data->addr + i < MEM_SIZE; i++) { + memory[data->addr + i] = temp_buf[i]; + } + } + + pthread_mutex_lock(&async_mutex); + async_ops[id].result = result; + async_ops[id].complete = 1; + pthread_mutex_unlock(&async_mutex); + + free(data); + async_ops[id].data = NULL; + return NULL; +} + +void* async_fwrite_thread(void *arg) { + int id = *(int*)arg; + free(arg); + AsyncFileWriteData *data = (AsyncFileWriteData*)async_ops[id].data; + + long result = -1; + if (data->buf_arg > MEM_SIZE * 8 || data->buf_arg < 0) { + result = fwrite((char*)data->buf_arg, 1, data->size, data->f); + } else if (data->buf_arg < MEM_SIZE) { + char temp_buf[8192]; + int size = data->size; + if (size > 8192) size = 8192; + if (data->buf_arg + size > MEM_SIZE) { + size = MEM_SIZE - data->buf_arg; + } + for (int i = 0; i < size; i++) { + temp_buf[i] = (char)memory[data->buf_arg + i]; + } + result = fwrite(temp_buf, 1, size, data->f); + } + + pthread_mutex_lock(&async_mutex); + async_ops[id].result = result; + async_ops[id].complete = 1; + pthread_mutex_unlock(&async_mutex); + + free(data); + async_ops[id].data = NULL; + return NULL; +} + +void* async_recv_thread(void *arg) { + int id = *(int*)arg; + free(arg); + AsyncRecvData *data = (AsyncRecvData*)async_ops[id].data; + + char temp_buf[8192]; + int len = data->len; + if (len > 8192) len = 8192; + + int result = recv(data->sockfd, temp_buf, len, data->flags); + if (result > 0) { + for (int i = 0; i < result && data->addr + i < MEM_SIZE; i++) { + memory[data->addr + i] = temp_buf[i]; + } + } + + pthread_mutex_lock(&async_mutex); + async_ops[id].result = result; + async_ops[id].complete = 1; + pthread_mutex_unlock(&async_mutex); + + free(data); + async_ops[id].data = NULL; + return NULL; +} + +void* async_send_thread(void *arg) { + int id = *(int*)arg; + free(arg); + AsyncSendData *data = (AsyncSendData*)async_ops[id].data; + + long result = -1; + if (data->buf_arg > MEM_SIZE * 8 || data->buf_arg < 0) { + result = send(data->sockfd, (char*)data->buf_arg, data->len, data->flags); + } else if (data->buf_arg < MEM_SIZE) { + char temp_buf[8192]; + int len = data->len; + if (len > 8192) len = 8192; + if (data->buf_arg + len > MEM_SIZE) { + len = MEM_SIZE - data->buf_arg; + } + for (int i = 0; i < len; i++) { + temp_buf[i] = (char)memory[data->buf_arg + i]; + } + result = send(data->sockfd, temp_buf, len, data->flags); + } + + pthread_mutex_lock(&async_mutex); + async_ops[id].result = result; + async_ops[id].complete = 1; + pthread_mutex_unlock(&async_mutex); + + free(data); + async_ops[id].data = NULL; + return NULL; +} + void register_native_func(char *name, NativeFunc func) { if (!name || !func || native_func_cnt >= 100) { return; @@ -28,7 +211,12 @@ long native_socket(long *args, int argc) { int domain = (int)args[0]; int type = (int)args[1]; int protocol = (int)args[2]; - return socket(domain, type, protocol); + int sockfd = socket(domain, type, protocol); + if (sockfd >= 0) { + int opt = 1; + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + } + return sockfd; } long native_bind(long *args, int argc) { @@ -716,6 +904,137 @@ long native_double_div(long *args, int argc) { return result.l; } +long native_async_fread(long *args, int argc) { + if (!args || argc < 3) return -1; + FILE *f = (FILE*)args[0]; + int addr = (int)args[1]; + int size = (int)args[2]; + if (!f || addr < 0 || addr >= MEM_SIZE || size <= 0) return -1; + + int id = alloc_async_op(); + if (id < 0) return -1; + + AsyncFileReadData *data = malloc(sizeof(AsyncFileReadData)); + data->f = f; + data->addr = addr; + data->size = size; + async_ops[id].data = data; + + int *id_ptr = malloc(sizeof(int)); + *id_ptr = id; + pthread_create(&async_ops[id].thread, NULL, async_fread_thread, id_ptr); + return id; +} + +long native_async_fwrite(long *args, int argc) { + if (!args || argc < 3) return -1; + FILE *f = (FILE*)args[0]; + long buf_arg = args[1]; + int size = (int)args[2]; + if (!f || size <= 0) return -1; + + int id = alloc_async_op(); + if (id < 0) return -1; + + AsyncFileWriteData *data = malloc(sizeof(AsyncFileWriteData)); + data->f = f; + data->buf_arg = buf_arg; + data->size = size; + async_ops[id].data = data; + + int *id_ptr = malloc(sizeof(int)); + *id_ptr = id; + pthread_create(&async_ops[id].thread, NULL, async_fwrite_thread, id_ptr); + return id; +} + +long native_async_recv(long *args, int argc) { + if (!args || argc < 4) return -1; + int sockfd = (int)args[0]; + int addr = (int)args[1]; + int len = (int)args[2]; + int flags = (int)args[3]; + if (addr < 0 || addr >= MEM_SIZE) return -1; + + int id = alloc_async_op(); + if (id < 0) return -1; + + AsyncRecvData *data = malloc(sizeof(AsyncRecvData)); + data->sockfd = sockfd; + data->addr = addr; + data->len = len; + data->flags = flags; + async_ops[id].data = data; + + int *id_ptr = malloc(sizeof(int)); + *id_ptr = id; + pthread_create(&async_ops[id].thread, NULL, async_recv_thread, id_ptr); + return id; +} + +long native_async_send(long *args, int argc) { + if (!args || argc < 4) return -1; + int sockfd = (int)args[0]; + long buf_arg = args[1]; + int len = (int)args[2]; + int flags = (int)args[3]; + + int id = alloc_async_op(); + if (id < 0) return -1; + + AsyncSendData *data = malloc(sizeof(AsyncSendData)); + data->sockfd = sockfd; + data->buf_arg = buf_arg; + data->len = len; + data->flags = flags; + async_ops[id].data = data; + + int *id_ptr = malloc(sizeof(int)); + *id_ptr = id; + pthread_create(&async_ops[id].thread, NULL, async_send_thread, id_ptr); + return id; +} + +long native_async_wait(long *args, int argc) { + if (!args || argc < 1) return -1; + int id = (int)args[0]; + if (id < 0 || id >= MAX_ASYNC_OPS || !async_ops[id].active) return -1; + + pthread_join(async_ops[id].thread, NULL); + + pthread_mutex_lock(&async_mutex); + long result = async_ops[id].result; + async_ops[id].active = 0; + pthread_mutex_unlock(&async_mutex); + + return result; +} + +long native_async_poll(long *args, int argc) { + if (!args || argc < 1) return 0; + int id = (int)args[0]; + if (id < 0 || id >= MAX_ASYNC_OPS || !async_ops[id].active) return 0; + + pthread_mutex_lock(&async_mutex); + int complete = async_ops[id].complete; + pthread_mutex_unlock(&async_mutex); + + return complete; +} + +long native_async_result(long *args, int argc) { + if (!args || argc < 1) return -1; + int id = (int)args[0]; + if (id < 0 || id >= MAX_ASYNC_OPS || !async_ops[id].active) return -1; + + pthread_mutex_lock(&async_mutex); + long result = async_ops[id].result; + async_ops[id].active = 0; + pthread_mutex_unlock(&async_mutex); + + return result; +} + void register_native_functions() { register_native_func("socket", native_socket); register_native_func("bind", native_bind); @@ -763,4 +1082,11 @@ void register_native_functions() { register_native_func("double_sub", native_double_sub); register_native_func("double_mul", native_double_mul); register_native_func("double_div", native_double_div); + register_native_func("async_fread", native_async_fread); + register_native_func("async_fwrite", native_async_fwrite); + register_native_func("async_recv", native_async_recv); + register_native_func("async_send", native_async_send); + register_native_func("async_wait", native_async_wait); + register_native_func("async_poll", native_async_poll); + register_native_func("async_result", native_async_result); } diff --git a/tests/async_io_test.rc b/tests/async_io_test.rc new file mode 100644 index 0000000..07766b4 --- /dev/null +++ b/tests/async_io_test.rc @@ -0,0 +1,44 @@ +int main() { + printf("=== Async I/O Tests ===\n"); + + printf("Test 1: Async file write\n"); + int f = fopen("async_test.txt", "w"); + if (f == 0) { + printf("ERROR: Could not open file\n"); + return 1; + } + int write_handle = async_fwrite(f, "Hello from async I/O!", 21); + printf("Write started, handle: %d\n", write_handle); + int result = async_wait(write_handle); + printf("Write completed, bytes written: %d\n", result); + fclose(f); + printf("PASS: Async write works\n"); + + printf("Test 2: Async file read\n"); + f = fopen("async_test.txt", "r"); + if (f == 0) { + printf("ERROR: Could not open file\n"); + return 1; + } + int buffer[256]; + int read_handle = async_fread(f, &buffer, 256); + printf("Read started, handle: %d\n", read_handle); + + printf("Polling for completion...\n"); + while (async_poll(read_handle) == 0) { + printf("."); + } + printf("\nRead complete!\n"); + + result = async_result(read_handle); + printf("Bytes read: %d\n", result); + fclose(f); + printf("PASS: Async read works\n"); + + printf("Test 3: Cleanup\n"); + fremove("async_test.txt"); + printf("PASS: File removed\n"); + + printf("\n=== All Async I/O Tests Completed ===\n"); + return 0; +}