diff options
Diffstat (limited to 'js/scripting-lang/c/src/main.c')
-rw-r--r-- | js/scripting-lang/c/src/main.c | 514 |
1 files changed, 514 insertions, 0 deletions
diff --git a/js/scripting-lang/c/src/main.c b/js/scripting-lang/c/src/main.c new file mode 100644 index 0000000..30c9cbd --- /dev/null +++ b/js/scripting-lang/c/src/main.c @@ -0,0 +1,514 @@ +/** + * @file main.c + * @brief Main entry point for Baba Yaga interpreter + * @author eli_oat + * @version 0.0.1 + * @date 2025 + * + * This file contains the main entry point and command-line interface + * for the Baba Yaga scripting language implementation. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> +#include <dirent.h> + +#include "baba_yaga.h" + +/* ============================================================================ + * Constants + * ============================================================================ */ + +#define VERSION "0.0.1" +#define MAX_LINE_LENGTH 4096 +#define MAX_FILE_SIZE (1024 * 1024) /* 1MB */ + +/* ============================================================================ + * Function Declarations + * ============================================================================ */ + +static void print_usage(const char* program_name); +static void print_version(void); +static void print_error(const char* message); +static char* read_file(const char* filename); +static void run_repl(Interpreter* interp); +static void run_file(Interpreter* interp, const char* filename); +static void run_tests(Interpreter* interp, const char* test_dir); + +/* ============================================================================ + * Main Function + * ============================================================================ */ + +/** + * @brief Main entry point + * + * @param argc Argument count + * @param argv Argument vector + * @return Exit status + */ +int main(int argc, char* argv[]) { + Interpreter* interp = NULL; + int opt; + bool run_repl_mode = false; + bool run_test_mode = false; + char* filename = NULL; + char* test_dir = NULL; + ExecResult result; + Value value; + + /* Parse command line options */ + while ((opt = getopt(argc, argv, "hvrt:f:")) != -1) { + switch (opt) { + case 'h': + print_usage(argv[0]); + return EXIT_SUCCESS; + case 'v': + print_version(); + return EXIT_SUCCESS; + case 'r': + run_repl_mode = true; + break; + case 't': + run_test_mode = true; + test_dir = optarg; + break; + case 'f': + filename = optarg; + break; + default: + print_usage(argv[0]); + return EXIT_FAILURE; + } + } + + /* Create interpreter */ + interp = baba_yaga_create(); + if (interp == NULL) { + print_error("Failed to create interpreter"); + return EXIT_FAILURE; + } + + /* Set debug level from environment */ + const char* debug_env = getenv("DEBUG"); + if (debug_env != NULL) { + int debug_level = atoi(debug_env); + if (debug_level >= 0 && debug_level <= 5) { + baba_yaga_set_debug_level((DebugLevel)debug_level); + } + } + + /* Execute based on mode */ + if (run_test_mode) { + run_tests(interp, test_dir); + } else if (run_repl_mode) { + run_repl(interp); + } else if (filename != NULL) { + run_file(interp, filename); + } else if (optind < argc) { + /* Check if the argument looks like a file (not starting with -) */ + char* arg = argv[optind]; + if (arg[0] != '-' && access(arg, F_OK) == 0) { + /* Treat as file */ + run_file(interp, arg); + } else { + /* Execute source code from command line */ + char* source = arg; + value = baba_yaga_execute(interp, source, strlen(source), &result); + if (result == EXEC_SUCCESS) { + /* Print result using value_to_string for consistent formatting */ + /* Don't print special IO return value */ + if (value.type != VAL_NUMBER || value.data.number != -999999) { + char* str = baba_yaga_value_to_string(&value); + printf("%s\n", str); + free(str); + } + } else { + BabaYagaError* error = baba_yaga_get_error(interp); + if (error != NULL) { + fprintf(stderr, "Error: %s\n", error->message); + baba_yaga_error_destroy(error); + } else { + fprintf(stderr, "Error: Execution failed\n"); + } + } + baba_yaga_value_destroy(&value); + } + } else { + /* No arguments - read from stdin (pipe-friendly) */ + char buffer[MAX_FILE_SIZE]; + size_t total_read = 0; + size_t bytes_read; + + /* Read all input from stdin */ + while ((bytes_read = fread(buffer + total_read, 1, + MAX_FILE_SIZE - total_read - 1, stdin)) > 0) { + total_read += bytes_read; + if (total_read >= MAX_FILE_SIZE - 1) { + fprintf(stderr, "Error: Input too large (max %d bytes)\n", MAX_FILE_SIZE); + baba_yaga_destroy(interp); + return EXIT_FAILURE; + } + } + + if (total_read > 0) { + buffer[total_read] = '\0'; + + /* Execute the input */ + value = baba_yaga_execute(interp, buffer, total_read, &result); + if (result == EXEC_SUCCESS) { + /* Don't print special IO return value */ + if (value.type != VAL_NUMBER || value.data.number != -999999) { + char* str = baba_yaga_value_to_string(&value); + printf("%s\n", str); + free(str); + } + } else { + BabaYagaError* error = baba_yaga_get_error(interp); + if (error != NULL) { + fprintf(stderr, "Error: %s\n", error->message); + baba_yaga_error_destroy(error); + } else { + fprintf(stderr, "Error: Execution failed\n"); + } + } + baba_yaga_value_destroy(&value); + } else { + /* No input from stdin and no arguments - show usage */ + print_usage(argv[0]); + } + } + + /* Cleanup */ + baba_yaga_destroy(interp); + return EXIT_SUCCESS; +} + +/* ============================================================================ + * Helper Functions + * ============================================================================ */ + +/** + * @brief Print usage information + * + * @param program_name Name of the program + */ +static void print_usage(const char* program_name) { + printf("Baba Yaga C Implementation v%s\n", VERSION); + printf("Usage: %s [OPTIONS] [SOURCE_CODE]\n", program_name); + printf("\nOptions:\n"); + printf(" -h Show this help message\n"); + printf(" -v Show version information\n"); + printf(" -r Start interactive REPL mode\n"); + printf(" -f FILE Execute source code from file\n"); + printf(" -t DIR Run tests from directory\n"); + printf("\nExamples:\n"); + printf(" %s # Execute from stdin (pipe-friendly)\n", program_name); + printf(" %s -r # Start interactive REPL\n", program_name); + printf(" %s -f script.txt # Execute file\n", program_name); + printf(" %s 'x : 42; ..out x' # Execute code\n", program_name); + printf(" %s -t tests/ # Run tests\n", program_name); +} + +/** + * @brief Print version information + */ +static void print_version(void) { + printf("Baba Yaga C Implementation v%s\n", VERSION); + printf("Copyright (c) 2025 eli_oat\n"); + printf("License: Custom - see LICENSE file\n"); +} + +/** + * @brief Print error message + * + * @param message Error message + */ +static void print_error(const char* message) { + fprintf(stderr, "Error: %s\n", message); +} + +/** + * @brief Read entire file into memory + * + * @param filename Name of file to read + * @return File contents (must be freed by caller) + */ +static char* read_file(const char* filename) { + FILE* file; + char* buffer; + long file_size; + size_t bytes_read; + + /* Open file */ + file = fopen(filename, "rb"); + if (file == NULL) { + print_error("Failed to open file"); + return NULL; + } + + /* Get file size */ + if (fseek(file, 0, SEEK_END) != 0) { + fclose(file); + print_error("Failed to seek to end of file"); + return NULL; + } + + file_size = ftell(file); + if (file_size < 0) { + fclose(file); + print_error("Failed to get file size"); + return NULL; + } + + if (file_size > MAX_FILE_SIZE) { + fclose(file); + print_error("File too large"); + return NULL; + } + + /* Allocate buffer */ + buffer = malloc(file_size + 1); + if (buffer == NULL) { + fclose(file); + print_error("Failed to allocate memory"); + return NULL; + } + + /* Read file */ + rewind(file); + bytes_read = fread(buffer, 1, file_size, file); + fclose(file); + + if (bytes_read != (size_t)file_size) { + free(buffer); + print_error("Failed to read file"); + return NULL; + } + + buffer[file_size] = '\0'; + + /* DEBUG: Print buffer info */ + DEBUG_DEBUG("File buffer length: %ld", file_size); + DEBUG_DEBUG("File buffer content (first 200 chars): %.*s", + (int)(file_size > 200 ? 200 : file_size), buffer); + + return buffer; +} + +/** + * @brief Run enhanced REPL (Read-Eval-Print Loop) + * + * @param interp Interpreter instance + */ +static void run_repl(Interpreter* interp) { + char line[MAX_LINE_LENGTH]; + ExecResult result; + Value value; + + printf("๐งโโ๏ธ Baba Yaga Interactive REPL v%s\n", VERSION); + printf("Type 'help' for commands, 'exit' to quit\n"); + printf("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n"); + + while (1) { + printf("baba-yaga> "); + fflush(stdout); + + if (fgets(line, sizeof(line), stdin) == NULL) { + printf("\n"); + break; + } + + /* Remove newline */ + line[strcspn(line, "\n")] = '\0'; + + /* Check for special commands */ + if (strcmp(line, "exit") == 0 || strcmp(line, "quit") == 0) { + printf(" \n"); + break; + } + + if (strcmp(line, "help") == 0) { + printf("Available commands:\n"); + printf(" help - Show this help message\n"); + printf(" exit - Exit the REPL\n"); + printf(" clear - Clear the screen\n"); + printf("\nLanguage features:\n"); + printf(" Variables: x : 42\n"); + printf(" Functions: f : x -> x + 1\n"); + printf(" Output: ..out \"Hello World\"\n"); + printf(" Patterns: when x is 0 then \"zero\" _ then \"other\"\n"); + printf(" Tables: {a: 1, b: 2}\n"); + continue; + } + + if (strcmp(line, "clear") == 0) { + printf("\033[2J\033[H"); /* ANSI clear screen */ + continue; + } + + /* Skip empty lines */ + if (strlen(line) == 0) { + continue; + } + + /* Execute line */ + value = baba_yaga_execute(interp, line, strlen(line), &result); + if (result == EXEC_SUCCESS) { + /* Don't print special IO return value */ + if (value.type != VAL_NUMBER || value.data.number != -999999) { + char* str = baba_yaga_value_to_string(&value); + printf("=> %s\n", str); + free(str); + } + } else { + BabaYagaError* error = baba_yaga_get_error(interp); + if (error != NULL) { + fprintf(stderr, "โ Error: %s\n", error->message); + baba_yaga_error_destroy(error); + } else { + fprintf(stderr, "โ Error: Execution failed\n"); + } + } + baba_yaga_value_destroy(&value); + } +} + +/** + * @brief Execute source code from file + * + * @param interp Interpreter instance + * @param filename Name of file to execute + */ +static void run_file(Interpreter* interp, const char* filename) { + char* source; + ExecResult result; + Value value; + + /* Read file */ + source = read_file(filename); + if (source == NULL) { + return; + } + + DEBUG_DEBUG("About to execute source of length %zu", strlen(source)); + + /* Execute source */ + value = baba_yaga_execute(interp, source, strlen(source), &result); + DEBUG_DEBUG("Execution completed with result %d", result); + free(source); + + if (result == EXEC_SUCCESS) { + DEBUG_DEBUG("Execution successful, processing result"); + /* Print result using value_to_string for consistent formatting */ + /* Don't print special IO return value */ + if (value.type != VAL_NUMBER || value.data.number != -999999) { + char* str = baba_yaga_value_to_string(&value); + printf("%s\n", str); + free(str); + } + } else { + DEBUG_DEBUG("Execution failed, getting error"); + BabaYagaError* error = baba_yaga_get_error(interp); + if (error != NULL) { + fprintf(stderr, "Error: %s\n", error->message); + baba_yaga_error_destroy(error); + } else { + fprintf(stderr, "Error: Execution failed\n"); + } + exit(EXIT_FAILURE); + } + + DEBUG_DEBUG("About to destroy value"); + baba_yaga_value_destroy(&value); + DEBUG_DEBUG("run_file completed successfully"); +} + +/** + * @brief Run tests from directory + * + * @param interp Interpreter instance + * @param test_dir Test directory + */ +static void run_tests(Interpreter* interp, const char* test_dir) { + printf("๐งช Baba Yaga Test Runner\n"); + printf("========================\n\n"); + + /* Check if directory exists */ + DIR* dir = opendir(test_dir); + if (dir == NULL) { + fprintf(stderr, "โ Error: Cannot open test directory '%s'\n", test_dir); + return; + } + + int total_tests = 0; + int passed_tests = 0; + int failed_tests = 0; + + struct dirent* entry; + char filepath[1024]; + + /* Read all .txt files in the directory */ + while ((entry = readdir(dir)) != NULL) { + /* Skip non-.txt files */ + if (strstr(entry->d_name, ".txt") == NULL) { + continue; + } + + /* Skip hidden files */ + if (entry->d_name[0] == '.') { + continue; + } + + total_tests++; + snprintf(filepath, sizeof(filepath), "%s/%s", test_dir, entry->d_name); + + printf("Running %s... ", entry->d_name); + fflush(stdout); + + /* Read test file */ + char* test_code = read_file(filepath); + if (test_code == NULL) { + printf("โ FAIL (cannot read file)\n"); + failed_tests++; + continue; + } + + /* Execute test code */ + ExecResult result; + Value value = baba_yaga_execute(interp, test_code, strlen(test_code), &result); + + if (result == EXEC_SUCCESS) { + printf("โ PASS\n"); + passed_tests++; + } else { + printf("โ FAIL\n"); + BabaYagaError* error = baba_yaga_get_error(interp); + if (error != NULL) { + printf(" Error: %s\n", error->message); + baba_yaga_error_destroy(error); + } + failed_tests++; + } + + baba_yaga_value_destroy(&value); + free(test_code); + } + + closedir(dir); + + /* Print summary */ + printf("\n๐ Test Summary\n"); + printf("===============\n"); + printf("Total tests: %d\n", total_tests); + printf("Passed: %d\n", passed_tests); + printf("Failed: %d\n", failed_tests); + + if (failed_tests == 0) { + printf("๐ All tests passed!\n"); + } else { + printf("โ %d test(s) failed\n", failed_tests); + } +} |