diff --git a/README.md b/README.md index a128a1a..450906b 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ $ cd src $ make ``` -> [!Note] +> [!Warning] > > Unless you move `flavor` to `/usr/local/bin/`, > you'll have to use `./flavor` for commands with relative file paths. @@ -160,7 +160,9 @@ $ flavor recipe.flv --debug # Debug mode $ flavor --about # About FlavorLang ``` -The `--debug` flag is really useful for understanding how FlavorLang is executing (tokenizing, parsing, and interpreting) your file. +> [!Note] +> +> The `--debug` flag is really useful for understanding how FlavorLang is executing (tokenizing, parsing, and interpreting) your file. --- diff --git a/docs/language_design.md b/docs/language_design.md index 9b8c10e..a8363e3 100644 --- a/docs/language_design.md +++ b/docs/language_design.md @@ -16,31 +16,31 @@ This `docs/` page details the core design of FlavorLang's syntax, the various da ## Syntax Keywords -| Keyword | Usage | Description | Implemented? | -| --------- | ---------------------------- | ------------------------------------------------------------------------------------------- | ------------ | -| `let` | Define variables | Declares and initializes variables. | ✅ | -| `const` | Define constants | Declares and initializes constants. | ✅ | -| `if` | Conditional logic | Executes code only if a condition is true. | ✅ | -| `elif` | Conditional logic fallback | Executes only if a prior `if` condition is false. | ✅ | -| `else` | Conditional fallback | Executes code if any prior `if`/`is` conditions are false. | ✅ | -| `for` | For-loop | Iterates over a range or sequence, executing a block of code for each step. | ✅ | -| `in` | Range declaration | Specifies the range or sequence to iterate over. | ✅ | -| `by` | Optional step specifier | Defines the step interval for iteration; defaults to `1`/`-1` (range dependent) if omitted. | ✅ | -| `while` | While-loop | Repeatedly runs code while a condition is true. | ✅ | -| `check` | Switch-case equivalent | Matches a value to multiple cases. | ✅ | -| `is` | Case clause | Defines a case inside `check`. | ✅ | -| `break` | Exit control flow | Stops execution of further cases in `check` and exits the current flow. | ✅ | -| `create` | Define a function | Creates a reusable block of logic. | ✅ | -| `deliver` | Return statement | Returns a value and stops function execution. | ✅ | -| `try` | Try block | Executes code that might fail. | ❌ | -| `crumbs` | Catch block | Handles errors during execution. | ❌ | -| `burn` | Force exit or raise an error | Stops execution immediately with a message. | ✅ | -| `serve` | Print or output | Outputs a value or message immediately. | ✅ | -| `sample` | Input from console | Reads user input. | ✅ | -| `plate` | Write to file | Writes data to a file. | ✅ | -| `garnish` | Append to file | Appends data to a file. | ✅ | -| `taste` | Read from file | Reads data from a file. | ✅ | -| `recipe` | Import `.flv` file | Imports logic from another `.flv` file. | ❌ | +| Keyword | Usage | Description | +| --------- | ---------------------------- | ------------------------------------------------------------------------------------------- | +| `let` | Define variables | Declares and initializes variables. | +| `const` | Define constants | Declares and initializes constants. | +| `if` | Conditional logic | Executes code only if a condition is true. | +| `elif` | Conditional logic fallback | Executes only if a prior `if` condition is false. | +| `else` | Conditional fallback | Executes code if any prior `if`/`is` conditions are false. | +| `for` | For-loop | Iterates over a range or sequence, executing a block of code for each step. | +| `in` | Range declaration | Specifies the range or sequence to iterate over. | +| `by` | Optional step specifier | Defines the step interval for iteration; defaults to `1`/`-1` (range dependent) if omitted. | +| `while` | While-loop | Repeatedly runs code while a condition is true. | +| `check` | Switch-case equivalent | Matches a value to multiple cases. | +| `is` | Case clause | Defines a case inside `check`. | +| `break` | Exit control flow | Stops execution of further cases in `check` and exits the current flow. | +| `create` | Define a function | Creates a reusable block of logic. | +| `deliver` | Return statement | Returns a value and stops function execution. | +| `try` | Try block | Executes code that might fail. | +| `rescue` | Catch block | Handles errors during execution. | +| `finish` | Finally block | Optional cleanup & always runs. | +| `burn` | Force exit or raise an error | Stops execution immediately with a message. | +| `serve` | Print or output | Outputs a value or message immediately. | +| `sample` | Input from console | Reads user input. | +| `plate` | Write to file | Writes data to a file. | +| `garnish` | Append to file | Appends data to a file. | +| `taste` | Read from file | Reads data from a file. | --- diff --git a/docs/syntax_examples.md b/docs/syntax_examples.md index 3c5653c..4b1ca1f 100644 --- a/docs/syntax_examples.md +++ b/docs/syntax_examples.md @@ -117,11 +117,21 @@ Use `try` and `rescue` to handle errors. ```py try { + serve("This will run!"); burn("This recipe failed!"); serve("This won't run!"); } rescue { serve("Caught an error: Recipe needs improvement."); } + +try { + int("abc"); + serve("This won't run!"); +} rescue { + serve("Runtime error: Can't cast string `abc` to int."); +} finish { + serve("This always executes!"); +} ``` ### 8. 🔎 Switch-Case Logic diff --git a/src/Makefile b/src/Makefile index 4954a88..161a50c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,7 @@ # Compiler and Flags -CC = gcc -CFLAGS = -Wall -Wextra -g -LDFLAGS = -lm +CC = clang +CFLAGS = -fsanitize=address -fsanitize=undefined -g -Wall -Wextra -pedantic +LDFLAGS = -fsanitize=address -fsanitize=undefined # Directories SRC_DIRS = . shared lexer parser interpreter debug diff --git a/src/debug/debug.c b/src/debug/debug.c index aee93cb..6cc37c3 100644 --- a/src/debug/debug.c +++ b/src/debug/debug.c @@ -104,4 +104,4 @@ void debug_print_int(const char *format, ...) { debug_print(INTERPRETER, "%s", new_format); } -} \ No newline at end of file +} diff --git a/src/interpreter/builtins.c b/src/interpreter/builtins.c index fa26198..a771a69 100644 --- a/src/interpreter/builtins.c +++ b/src/interpreter/builtins.c @@ -5,27 +5,22 @@ #include #include -// Helper function to handle debug printing for floats -#define DEBUG_PRINT_FLOAT(format, ...) \ - do { \ - if (debug_flag) \ - printf(format, __VA_ARGS__); \ - } while (0) - // Function to interpret a mix of argument types -bool interpret_arguments(ASTNode *node, Environment *env, size_t num_args, - ArgumentSpec *specs) { - // size_t arg_count = 0; +InterpretResult interpret_arguments(ASTNode *node, Environment *env, + size_t num_args, ArgumentSpec *specs) { ASTNode *arg_node = node; for (size_t i = 0; i < num_args; i++) { if (arg_node == NULL) { - error_interpreter("Too few arguments provided.\n"); - return false; + return raise_error("Too few arguments provided.\n"); } // Interpret the current argument InterpretResult arg_res = interpret_node(arg_node, env); + if (arg_res.is_error) { + // Propagate the error + return arg_res; + } LiteralValue lv = arg_res.value; // Reference to the current argument specification @@ -42,9 +37,8 @@ bool interpret_arguments(ASTNode *node, Environment *env, size_t num_args, } else if (lv.type == TYPE_BOOLEAN) { *((INT_SIZE *)output_ptr) = lv.data.boolean ? 1 : 0; } else { - error_interpreter("Expected integer for argument %zu.\n", - i + 1); - return false; + return raise_error("Expected integer for argument %zu.\n", + i + 1); } break; @@ -56,8 +50,7 @@ bool interpret_arguments(ASTNode *node, Environment *env, size_t num_args, } else if (lv.type == TYPE_BOOLEAN) { *((FLOAT_SIZE *)output_ptr) = lv.data.boolean ? 1.0 : 0.0; } else { - error_interpreter("Expected float for argument %zu.\n", i + 1); - return false; + return raise_error("Expected float for argument %zu.\n", i + 1); } break; @@ -65,8 +58,8 @@ bool interpret_arguments(ASTNode *node, Environment *env, size_t num_args, if (lv.type == TYPE_STRING) { *((char **)output_ptr) = lv.data.string; } else { - error_interpreter("Expected string for argument %zu.\n", i + 1); - return false; + return raise_error("Expected string for argument %zu.\n", + i + 1); } break; @@ -78,30 +71,26 @@ bool interpret_arguments(ASTNode *node, Environment *env, size_t num_args, } else if (lv.type == TYPE_FLOAT) { *((bool *)output_ptr) = (lv.data.floating_point != 0.0); } else { - error_interpreter("Expected boolean for argument %zu.\n", - i + 1); - return false; + return raise_error("Expected boolean for argument %zu.\n", + i + 1); } break; - // Handle additional types as needed - default: - error_interpreter("Unknown argument type for argument %zu.\n", - i + 1); - return false; + return raise_error("Unknown argument type for argument %zu.\n", + i + 1); } - // arg_count++; arg_node = arg_node->next; } if (arg_node != NULL) { - error_interpreter("Too many arguments provided.\n"); - return false; + return raise_error("Too many arguments provided.\n"); } - return true; + // Indicate success + LiteralValue success_val = {.type = TYPE_BOOLEAN, .data.boolean = true}; + return make_result(success_val, false, false); } void print_formatted_string(const char *str) { @@ -133,7 +122,7 @@ void print_formatted_string(const char *str) { } // Built-in `input()` function -LiteralValue builtin_input(ASTNode *node, Environment *env) { +InterpretResult builtin_input(ASTNode *node, Environment *env) { (void)node; // Unused parameter, suppress compiler warning (void)env; // Unused parameter @@ -142,7 +131,8 @@ LiteralValue builtin_input(ASTNode *node, Environment *env) { char *input_buffer = malloc(buffer_size); if (!input_buffer) { fprintf(stderr, "Error: Failed to allocate memory for input buffer.\n"); - return (LiteralValue){.type = TYPE_ERROR}; + LiteralValue lv = (LiteralValue){.type = TYPE_ERROR}; + return make_result(lv, false, false); } int c; @@ -155,7 +145,8 @@ LiteralValue builtin_input(ASTNode *node, Environment *env) { stderr, "Error: Failed to reallocate memory for input buffer.\n"); free(input_buffer); - return (LiteralValue){.type = TYPE_ERROR}; + LiteralValue lv = (LiteralValue){.type = TYPE_ERROR}; + return make_result(lv, false, false); } input_buffer = new_buffer; } @@ -170,28 +161,56 @@ LiteralValue builtin_input(ASTNode *node, Environment *env) { if (!result.data.string) { fprintf(stderr, "Error: Failed to duplicate input string.\n"); - return (LiteralValue){.type = TYPE_ERROR}; + LiteralValue lv = (LiteralValue){.type = TYPE_ERROR}; + return make_result(lv, false, false); } - DEBUG_PRINT_FLOAT("Input received: `%s`\n", result.data.string); + debug_print_int("Input received: `%s`\n", result.data.string); - return result; + return make_result(result, false, false); } // Built-in `random()` function with 0, 1, or 2 arguments -LiteralValue builtin_random(ASTNode *node, Environment *env) { - FLOAT_SIZE min = 0.0L; - FLOAT_SIZE max = 1.0L; +InterpretResult builtin_random(ASTNode *node, Environment *env) { + FLOAT_SIZE min = 0.0L; // default min + FLOAT_SIZE max = 1.0L; // default max - ArgumentSpec specs[2]; - specs[0].type = ARG_TYPE_FLOAT; - specs[0].out_ptr = &min; - specs[1].type = ARG_TYPE_FLOAT; - specs[1].out_ptr = &max; - - if (!interpret_arguments(node->function_call.arguments, env, 2, specs)) { - // Return an error type on failure - return (LiteralValue){.type = TYPE_ERROR}; + ASTNode *arg_node = node->function_call.arguments; + + size_t num_args = 0; + ASTNode *temp = arg_node; + while (temp) { + num_args++; + temp = temp->next; + } + if (num_args > 2) { + return raise_error( + "`random()` takes at most 2 arguments, but %zu provided.\n", + num_args); + } + + if (num_args == 1) { + // One argument provided: set max, min remains 0.0 + ArgumentSpec specs[1]; + specs[0].type = ARG_TYPE_FLOAT; + specs[0].out_ptr = &max; + + InterpretResult args_res = interpret_arguments(arg_node, env, 1, specs); + if (args_res.is_error) { + return args_res; + } + } else if (num_args == 2) { + // Two arguments provided: set min and max + ArgumentSpec specs[2]; + specs[0].type = ARG_TYPE_FLOAT; + specs[0].out_ptr = &min; + specs[1].type = ARG_TYPE_FLOAT; + specs[1].out_ptr = &max; + + InterpretResult args_res = interpret_arguments(arg_node, env, 2, specs); + if (args_res.is_error) { + return args_res; + } } // Seed the random number generator once @@ -201,28 +220,31 @@ LiteralValue builtin_random(ASTNode *node, Environment *env) { seeded = true; } - // Swap min and max if min > max + // Swap min & max if min > max to ensure correct range if (min > max) { - FLOAT_SIZE temp = min; + FLOAT_SIZE temp_val = min; min = max; - max = temp; + max = temp_val; } + // Generate random number FLOAT_SIZE random_number = min + ((FLOAT_SIZE)rand() / (FLOAT_SIZE)RAND_MAX) * (max - min); - DEBUG_PRINT_FLOAT("Random number generated (min: %Lf, max: %Lf): `%Lf`\n", - min, max, random_number); + debug_print_int("Random number generated (min: %Lf, max: %Lf): `%Lf`\n", + min, max, random_number); LiteralValue result; result.type = TYPE_FLOAT; result.data.floating_point = random_number; - return result; + return make_result(result, false, false); } // Built-in `serve()` function for printing -LiteralValue builtin_output(ASTNode *node, Environment *env) { +InterpretResult builtin_output(ASTNode *node, Environment *env) { + debug_print_int("builtin_output() called\n"); + ASTNode *arg_node = node->function_call.arguments; while (arg_node != NULL) { InterpretResult r = interpret_node(arg_node, env); @@ -246,10 +268,12 @@ LiteralValue builtin_output(ASTNode *node, Environment *env) { printf("%s", lv.data.boolean ? "True" : "False"); break; case TYPE_ERROR: - fprintf(stderr, "Error: Invalid literal type in `serve()`.\n"); + fprintf( + stderr, + "Error: Invalid literal type in `serve()` (`TYPE_ERROR`).\n"); break; default: - fprintf(stderr, "Error: Unknown literal type in s`erve()`.\n"); + fprintf(stderr, "Error: Unknown literal type in `serve()`.\n"); break; } printf(" "); // Space padding @@ -257,12 +281,13 @@ LiteralValue builtin_output(ASTNode *node, Environment *env) { } printf("\n"); - return (LiteralValue){.type = TYPE_INTEGER, - .data.integer = 0}; // Return 0 as default + LiteralValue lv = {.type = TYPE_INTEGER, + .data.integer = 0}; // return 0 as default + return make_result(lv, false, false); } // Built-in `burn()` function to raise errors -LiteralValue builtin_error(ASTNode *node, Environment *env) { +InterpretResult builtin_error(ASTNode *node, Environment *env) { ASTNode *arg_node = node->function_call.arguments; char error_message[512] = "Error raised by burn(): "; @@ -298,7 +323,7 @@ LiteralValue builtin_error(ASTNode *node, Environment *env) { sizeof(error_message) - strlen(error_message) - 1); break; default: - strncat(error_message, "Unknown literal type in b`urn()`.", + strncat(error_message, "Unknown literal type in `burn()`.", sizeof(error_message) - strlen(error_message) - 1); break; } @@ -311,190 +336,157 @@ LiteralValue builtin_error(ASTNode *node, Environment *env) { arg_node = arg_node->next; } - error_interpreter(error_message); - - return (LiteralValue){.type = TYPE_ERROR}; // keep compiler happy -} - -bool is_valid_int(const char *str, INT_SIZE *out_value) { - char *endptr; - errno = 0; // reset errno before conversion - long long temp = strtoll(str, &endptr, 10); - - // Check for conversion errors - if (errno != 0 || endptr == str || *endptr != '\0') { - return false; - } - - // Optionally, check for overflow - if (temp < LLONG_MIN || temp > LLONG_MAX) { - return false; - } - - if (out_value) { - *out_value = (INT_SIZE)temp; - } - - return true; -} - -bool is_valid_float(const char *str, FLOAT_SIZE *out_value) { - char *endptr; - errno = 0; // reset errno before conversion - long double temp = strtold(str, &endptr); - - // Check for conversion errors - if (errno != 0 || endptr == str || *endptr != '\0') { - return false; - } - - if (out_value) { - *out_value = (FLOAT_SIZE)temp; - } - - return true; + // Propagate the exception + return raise_error("%s", error_message); } -LiteralValue builtin_cast(ASTNode *node, Environment *env) { +InterpretResult builtin_cast(ASTNode *node, Environment *env) { if (node->type != AST_FUNCTION_CALL) { - error_interpreter( + return raise_error( "`builtin_cast()` expects an `AST_FUNCTION_CALL` node.\n"); } - char *cast_type = node->function_call.name; + char *cast_type = strdup(node->function_call.name); if (!cast_type) { - error_interpreter("No cast type provided to `builtin_cast()`.\n"); + return raise_error("No cast type provided to `builtin_cast()`.\n"); } ASTNode *arg_node = node->function_call.arguments; if (!arg_node) { - error_interpreter( + return raise_error( "No expression provided for cast in `builtin_cast()`.\n"); } // Ensure there's only one argument if (arg_node->next != NULL) { - error_interpreter("`%s` cast function takes exactly one argument.\n", - cast_type); + return raise_error("`%s` cast function takes exactly one argument.\n", + cast_type); } ASTNode *expr = arg_node; // Interpret the expression to be casted InterpretResult expr_result = interpret_node(expr, env); - LiteralValue original = expr_result.value; - - // If interpreting the expression resulted in an error, propagate it - if (original.type == TYPE_ERROR) { - return original; + if (expr_result.is_error) { + return expr_result; // Propagate the error } - LiteralValue result; - memset(&result, 0, sizeof(LiteralValue)); // initialize result + LiteralValue original = expr_result.value; + // Initialize the result + InterpretResult result_res = {0}; + result_res.did_return = false; + result_res.did_break = false; + result_res.is_error = false; + + // Perform the cast based on `cast_type` if (strcmp(cast_type, "string") == 0) { - result.type = TYPE_STRING; + LiteralValue cast_val; + cast_val.type = TYPE_STRING; char buffer[256] = {0}; switch (original.type) { case TYPE_INTEGER: snprintf(buffer, sizeof(buffer), INT_FORMAT_SPECIFIER, original.data.integer); - result.data.string = strdup(buffer); + cast_val.data.string = strdup(buffer); break; case TYPE_FLOAT: snprintf(buffer, sizeof(buffer), FLOAT_FORMAT_SPECIFIER, original.data.floating_point); - result.data.string = strdup(buffer); + cast_val.data.string = strdup(buffer); break; case TYPE_BOOLEAN: - result.data.string = + cast_val.data.string = strdup(original.data.boolean ? "True" : "False"); break; case TYPE_STRING: - result.data.string = strdup(original.data.string); + cast_val.data.string = strdup(original.data.string); break; default: - error_interpreter("Unsupported type for string cast.\n"); + return raise_error("Unsupported type for string cast.\n"); } - if (!result.data.string) { - error_interpreter("Memory allocation failed during string cast.\n"); + if (!cast_val.data.string) { + return raise_error( + "Memory allocation failed during string cast.\n"); } - debug_print_int("Casted value to string: `%s`\n", result.data.string); + result_res.value = cast_val; } else if (strcmp(cast_type, "int") == 0) { - result.type = TYPE_INTEGER; + LiteralValue cast_val; + cast_val.type = TYPE_INTEGER; switch (original.type) { case TYPE_STRING: { INT_SIZE temp; if (!is_valid_int(original.data.string, &temp)) { - error_interpreter("Cannot cast string \"%s\" to int.\n", - original.data.string); + return raise_error("Cannot cast string \"%s\" to int.\n", + original.data.string); } - result.data.integer = temp; + cast_val.data.integer = temp; break; } case TYPE_FLOAT: - result.data.integer = (INT_SIZE)original.data.floating_point; + cast_val.data.integer = (INT_SIZE)original.data.floating_point; break; case TYPE_BOOLEAN: - result.data.integer = original.data.boolean ? 1 : 0; + cast_val.data.integer = original.data.boolean ? 1 : 0; break; case TYPE_INTEGER: - result.data.integer = original.data.integer; + cast_val.data.integer = original.data.integer; break; default: - error_interpreter("Unsupported type for int cast.\n"); + return raise_error("Unsupported type for int cast.\n"); } - debug_print_int("Casted value to int: `%lld`\n", result.data.integer); + result_res.value = cast_val; } else if (strcmp(cast_type, "float") == 0) { - result.type = TYPE_FLOAT; + LiteralValue cast_val; + cast_val.type = TYPE_FLOAT; switch (original.type) { case TYPE_STRING: { FLOAT_SIZE temp; if (!is_valid_float(original.data.string, &temp)) { - error_interpreter("Cannot cast string \"%s\" to float.\n", - original.data.string); + return raise_error("Cannot cast string \"%s\" to float.\n", + original.data.string); } - result.data.floating_point = temp; + cast_val.data.floating_point = temp; break; } case TYPE_INTEGER: - result.data.floating_point = (FLOAT_SIZE)original.data.integer; + cast_val.data.floating_point = (FLOAT_SIZE)original.data.integer; break; case TYPE_BOOLEAN: - result.data.floating_point = original.data.boolean ? 1.0 : 0.0; + cast_val.data.floating_point = original.data.boolean ? 1.0 : 0.0; break; case TYPE_FLOAT: - result.data.floating_point = original.data.floating_point; + cast_val.data.floating_point = original.data.floating_point; break; default: - error_interpreter("Unsupported type for float cast.\n"); + return raise_error("Unsupported type for float cast.\n"); } - debug_print_int("Casted value to float: `%Lf`\n", - result.data.floating_point); + result_res.value = cast_val; } else { - error_interpreter("Unsupported cast type: `%s`\n", cast_type); + return raise_error("Unsupported cast type: `%s`\n", cast_type); } - return result; + return result_res; } -LiteralValue builtin_time() { +InterpretResult builtin_time(void) { time_t current_time = time(NULL); if (current_time == -1) { - error_interpreter("Failed to get the current time\n"); - exit(1); + return raise_error("Failed to get the current time\n"); } - return (LiteralValue){.type = TYPE_INTEGER, - .data.integer = (INT_SIZE)current_time}; + LiteralValue time_val = {.type = TYPE_INTEGER, + .data.integer = (INT_SIZE)current_time}; + + return make_result(time_val, false, false); } char *process_escape_sequences(const char *input) { @@ -544,21 +536,25 @@ char *process_escape_sequences(const char *input) { return processed; } -LiteralValue builtin_file_read(ASTNode *node, Environment *env) { +InterpretResult builtin_file_read(ASTNode *node, Environment *env) { char *filepath; ArgumentSpec specs[1]; specs[0].type = ARG_TYPE_STRING; specs[0].out_ptr = &filepath; - if (!interpret_arguments(node->function_call.arguments, env, 1, specs)) { - return (LiteralValue){.type = TYPE_ERROR}; + InterpretResult args_res = + interpret_arguments(node->function_call.arguments, env, 1, specs); + if (args_res.is_error) { + LiteralValue lv = (LiteralValue){.type = TYPE_ERROR}; + return make_result(lv, false, false); } FILE *file = fopen(filepath, "r"); if (file == NULL) { perror("Failed to open file"); - return (LiteralValue){.type = TYPE_ERROR}; + LiteralValue lv = (LiteralValue){.type = TYPE_ERROR}; + return make_result(lv, false, false); } size_t buffer_size = 1024; @@ -569,7 +565,8 @@ LiteralValue builtin_file_read(ASTNode *node, Environment *env) { if (file_contents == NULL) { perror("Failed to allocate memory for file contents"); fclose(file); - return (LiteralValue){.type = TYPE_ERROR}; + LiteralValue lv = (LiteralValue){.type = TYPE_ERROR}; + return make_result(lv, false, false); } // Initialize buffer for reading each line @@ -587,7 +584,8 @@ LiteralValue builtin_file_read(ASTNode *node, Environment *env) { free(buffer); free(file_contents); fclose(file); - return (LiteralValue){.type = TYPE_ERROR}; + LiteralValue lv = (LiteralValue){.type = TYPE_ERROR}; + return make_result(lv, false, false); } file_contents = temp; @@ -602,10 +600,12 @@ LiteralValue builtin_file_read(ASTNode *node, Environment *env) { free(buffer); fclose(file); - return (LiteralValue){.type = TYPE_STRING, .data.string = file_contents}; + LiteralValue lv = {.type = TYPE_STRING, .data.string = file_contents}; + return make_result(lv, false, false); } -LiteralValue helper_file_writer(ASTNode *node, Environment *env, bool append) { +InterpretResult helper_file_writer(ASTNode *node, Environment *env, + bool append) { char *filepath; char *content; @@ -616,40 +616,47 @@ LiteralValue helper_file_writer(ASTNode *node, Environment *env, bool append) { specs[1].type = ARG_TYPE_STRING; specs[1].out_ptr = &content; - if (!interpret_arguments(node->function_call.arguments, env, 2, specs)) { - return (LiteralValue){.type = TYPE_ERROR}; + InterpretResult args_res = + interpret_arguments(node->function_call.arguments, env, 2, specs); + if (args_res.is_error) { + LiteralValue lv = (LiteralValue){.type = TYPE_ERROR}; + return make_result(lv, false, false); } // Process the content to handle escape sequences char *processed_content = process_escape_sequences(content); if (processed_content == NULL) { - return (LiteralValue){.type = TYPE_ERROR}; + LiteralValue lv = (LiteralValue){.type = TYPE_ERROR}; + return make_result(lv, false, false); } FILE *file = fopen(filepath, append ? "a" : "w"); if (file == NULL) { perror("Failed to open file for writing"); free(processed_content); - return (LiteralValue){.type = TYPE_ERROR}; + LiteralValue lv = (LiteralValue){.type = TYPE_ERROR}; + return make_result(lv, false, false); } if (fputs(processed_content, file) == EOF) { perror("Failed to write to file"); free(processed_content); fclose(file); - return (LiteralValue){.type = TYPE_ERROR}; + LiteralValue lv = (LiteralValue){.type = TYPE_ERROR}; + return make_result(lv, false, false); } free(processed_content); fclose(file); - return (LiteralValue){.type = TYPE_BOOLEAN, .data.boolean = true}; + LiteralValue lv = {.type = TYPE_BOOLEAN, .data.boolean = true}; + return make_result(lv, false, false); } -LiteralValue builtin_file_write(ASTNode *node, Environment *env) { +InterpretResult builtin_file_write(ASTNode *node, Environment *env) { return helper_file_writer(node, env, false); } -LiteralValue builtin_file_append(ASTNode *node, Environment *env) { +InterpretResult builtin_file_append(ASTNode *node, Environment *env) { return helper_file_writer(node, env, true); } diff --git a/src/interpreter/builtins.h b/src/interpreter/builtins.h index 84bbd06..ae85290 100644 --- a/src/interpreter/builtins.h +++ b/src/interpreter/builtins.h @@ -4,6 +4,7 @@ #include "../debug/debug.h" #include "../shared/ast_types.h" #include "interpreter_types.h" +#include "utils.h" #include #include #include @@ -21,19 +22,19 @@ typedef struct { } ArgumentSpec; // Built-in functions for the standard library -LiteralValue builtin_input(ASTNode *node, Environment *env); -LiteralValue builtin_random(ASTNode *node, Environment *env); -LiteralValue builtin_output(ASTNode *node, Environment *env); -LiteralValue builtin_error(ASTNode *node, Environment *env); -LiteralValue builtin_cast(ASTNode *node, Environment *env); -LiteralValue builtin_time(); -LiteralValue builtin_file_read(ASTNode *node, Environment *env); -LiteralValue builtin_file_write(ASTNode *node, Environment *env); -LiteralValue builtin_file_append(ASTNode *node, Environment *env); +InterpretResult builtin_input(ASTNode *node, Environment *env); +InterpretResult builtin_random(ASTNode *node, Environment *env); +InterpretResult builtin_output(ASTNode *node, Environment *env); +InterpretResult builtin_error(ASTNode *node, Environment *env); +InterpretResult builtin_cast(ASTNode *node, Environment *env); +InterpretResult builtin_time(void); +InterpretResult builtin_file_read(ASTNode *node, Environment *env); +InterpretResult builtin_file_write(ASTNode *node, Environment *env); +InterpretResult builtin_file_append(ASTNode *node, Environment *env); // Helpers -bool interpret_arguments(ASTNode *node, Environment *env, size_t num_args, - ArgumentSpec *specs); +InterpretResult interpret_arguments(ASTNode *node, Environment *env, + size_t num_args, ArgumentSpec *specs); void print_formatted_string(const char *str); bool is_valid_int(const char *str, INT_SIZE *out_value); bool is_valid_float(const char *str, FLOAT_SIZE *out_value); diff --git a/src/interpreter/interpreter.c b/src/interpreter/interpreter.c index 3580d25..444ea3e 100644 --- a/src/interpreter/interpreter.c +++ b/src/interpreter/interpreter.c @@ -1,149 +1,155 @@ #include "interpreter.h" // Helper function to create a default LiteralValue (zero number) -LiteralValue create_default_value() { - LiteralValue value = { - .type = TYPE_INTEGER, - .data = { - // .floating_point = 0.0, - .integer = 0, // main value used for comparisons, etc - // .string = "", - }}; +LiteralValue create_default_value(void) { + LiteralValue value; + memset(&value, 0, sizeof(value)); + value.type = TYPE_INTEGER; + value.data.integer = 0; return value; } -// A helper to wrap `LiteralValue` in `InterpretResult` -static InterpretResult make_result(LiteralValue val, bool did_return, - bool did_break) { - InterpretResult r; - r.value = val; - r.did_return = did_return; - r.did_break = did_break; - return r; -} - InterpretResult interpret_node(ASTNode *node, Environment *env) { if (!node) { - fprintf(stderr, "Error: Attempt to interpret NULL node\n"); - return make_result((LiteralValue){.type = TYPE_ERROR}, false, false); + return make_result(create_default_value(), false, false); } debug_print_int("`interpret_node()` called\n"); + InterpretResult result = {0}; + switch (node->type) { case AST_LITERAL: debug_print_int("\tMatched: `AST_LITERAL`\n"); - return make_result(interpret_literal(node), false, false); + result = interpret_literal(node); + break; case AST_ASSIGNMENT: debug_print_int("\tMatched: `AST_ASSIGNMENT`\n"); - return interpret_assignment(node, env); + result = interpret_assignment(node, env); + break; case AST_UNARY_OP: debug_print_int("\tMatched: `AST_UNARY_OP`\n"); - { - LiteralValue unary_result = interpret_unary_op(node, env); - return make_result(unary_result, false, false); - } + result = interpret_unary_op(node, env); + break; case AST_BINARY_OP: debug_print_int("\tMatched: `AST_BINARY_OP`\n"); - return make_result(interpret_binary_op(node, env), false, false); + result = interpret_binary_op(node, env); + break; case AST_CONDITIONAL: { debug_print_int("\tMatched: `AST_CONDITIONAL`\n"); - InterpretResult cond_res = interpret_conditional(node, env); - if (cond_res.did_return || cond_res.did_break) { - return cond_res; - } - return make_result(cond_res.value, false, false); + result = interpret_conditional(node, env); + break; } case AST_FUNCTION_CALL: { debug_print_int("\tMatched: `AST_FUNCTION_CALL`\n"); - // interpret_function_call(...) returns a LiteralValue - LiteralValue fc_val = interpret_function_call(node, env); - return make_result(fc_val, false, false); + // interpret_function_call(...) returns an InterpretResult + result = interpret_function_call(node, env); + break; } case AST_FUNCTION_DECLARATION: { debug_print_int("\tMatched: `AST_FUNCTION_DECLARATION`\n"); interpret_function_declaration(node, env); // No direct return from a function declaration - return make_result(create_default_value(), false, false); + result = make_result(create_default_value(), false, false); + break; } case AST_FUNCTION_RETURN: { - debug_print_int("\tMatched: `AST_FUNCTION_RETURN`\n"); - LiteralValue return_value = - interpret_node(node->assignment.value, env).value; - debug_print_int("Return value before returning: type=%d, value=%lld\n", - return_value.type, - (return_value.type == TYPE_INTEGER) - ? return_value.data.integer - : 0); + // After assignment + debug_print_int("===Value stored in 'a': %lld\n", + get_variable(env, "a")); - // Return this value, but also set did_return = true - return make_result(return_value, true, false); + InterpretResult return_res = + interpret_node(node->function_return.return_data, env); + + // Before serve call + debug_print_int("===About to execute serve(a)\n"); + if (return_res.is_error) { + return return_res; + } + return_res.did_return = true; + return return_res; } case AST_WHILE_LOOP: { debug_print_int("\tMatched: `AST_WHILE_LOOP`\n"); - InterpretResult loop_res = interpret_while_loop(node, env); - return loop_res; + result = interpret_while_loop(node, env); + break; } case AST_FOR_LOOP: { debug_print_int("\tMatched: `AST_FOR_LOOP`\n"); - LiteralValue for_loop = interpret_for_loop(node, env); - return make_result(for_loop, false, false); + InterpretResult loop_res = interpret_for_loop(node, env); + result = loop_res; + break; } case AST_VARIABLE: { debug_print_int("\tMatched: `AST_VARIABLE`\n"); - LiteralValue var_val = interpret_variable(node, env); - return make_result(var_val, false, false); + // Replace LiteralValue with InterpretResult + InterpretResult var_res = interpret_variable(node, env); + result = var_res; + break; } case AST_CONSTANT: { debug_print_int("\tMatched: `AST_CONSTANT`\n"); - return interpret_constant(node, env); + result = interpret_constant(node, env); + break; } case AST_SWITCH: { debug_print_int("\tMatched: `AST_SWITCH`\n"); - InterpretResult switch_res = interpret_switch(node, env); - if (switch_res.did_return || switch_res.did_break) { - return switch_res; - } - return make_result(switch_res.value, false, false); + result = interpret_switch(node, env); + break; } case AST_BREAK: debug_print_int("\tMatched: `AST_BREAK`\n"); - return make_result(create_default_value(), false, true); + result = make_result(create_default_value(), false, true); + break; case AST_TERNARY: debug_print_int("\tMatched: `AST_TERNARY`\n"); - return interpret_ternary(node, env); // Delegate to helper + result = interpret_ternary(node, env); + break; + + case AST_TRY: + debug_print_int("\tMatched: `AST_TRY`\n"); + result = interpret_try(node, env); + break; default: - error_interpreter("Unsupported `ASTNode` type.\n"); - return make_result(create_default_value(), false, - false); // keep compiler happy + return raise_error("Unsupported `ASTNode` type.\n"); } + + return result; } void interpret_program(ASTNode *program, Environment *env) { ASTNode *current = program; while (current) { debug_print_int("Executing top-level statement\n"); - interpret_node(current, env); + InterpretResult res = interpret_node(current, env); + if (res.is_error) { + fprintf(stderr, "Unhandled error: %s\n", res.value.data.string); + break; // (or handle as needed in future) + } + if (res.did_return) { + // Handle unexpected return at top-level, if applicable + break; + } current = current->next; } } -LiteralValue interpret_literal(ASTNode *node) { +InterpretResult interpret_literal(ASTNode *node) { LiteralValue value; debug_print_int("Interpreting literal value...\n"); debug_print_int("Literal type: %d\n", node->literal.type); @@ -173,45 +179,48 @@ LiteralValue interpret_literal(ASTNode *node) { value.data.boolean ? "True" : "False"); break; default: - error_interpreter("Unsupported literal type.\n"); + // Let `interpret_node` handle unsupported literals + value.type = TYPE_ERROR; + value.data.string = strdup("Unsupported literal type.\n"); + break; } - return value; + + return make_result(value, false, false); } -LiteralValue interpret_variable(ASTNode *node, Environment *env) { +InterpretResult interpret_variable(ASTNode *node, Environment *env) { Variable *var = get_variable(env, node->variable_name); if (!var) { - error_interpreter("Undefined variable `%s`.\n", node->variable_name); + return raise_error("Undefined variable `%s`.\n", node->variable_name); } - return var->value; + return make_result(var->value, false, false); } InterpretResult interpret_constant(ASTNode *node, Environment *env) { if (node->type != AST_CONSTANT) { - fprintf(stderr, "Error: Invalid node type for constant declaration.\n"); - exit(1); + return raise_error("Invalid node type for constant declaration.\n"); } // Extract the constant name and value - char *const_name = node->assignment.variable_name; + char *const_name = strdup(node->assignment.variable_name); InterpretResult value_res = interpret_node(node->assignment.value, env); LiteralValue const_value = value_res.value; + if (value_res.is_error) { + return value_res; + } + // Check if the constant already exists Variable *existing_var = get_variable(env, const_name); if (existing_var) { - fprintf(stderr, "Error: Constant `%s` is already defined.\n", - const_name); - return make_result(create_default_value(), false, false); + return raise_error("Constant `%s` is already defined.\n", const_name); } // Add the constant to the environment - Variable new_var = { - .variable_name = strdup(const_name), - .value = const_value, - .is_constant = true // Mark as constant - }; + Variable new_var = {.variable_name = strdup(const_name), + .value = const_value, + .is_constant = true}; add_variable(env, new_var); return make_result(const_value, false, false); @@ -226,7 +235,6 @@ InterpretResult interpret_assignment(ASTNode *node, Environment *env) { // Evaluate the right-hand side InterpretResult assign_r = interpret_node(node->assignment.value, env); - // If the RHS triggered a return or break, propagate it if (assign_r.did_return || assign_r.did_break) { return assign_r; @@ -234,345 +242,483 @@ InterpretResult interpret_assignment(ASTNode *node, Environment *env) { LiteralValue new_value = assign_r.value; - // Add or update variable - Variable new_var = { - .variable_name = strdup(node->assignment.variable_name), - .value = new_value, - .is_constant = false // 'let' declarations are not constants - }; - add_variable(env, new_var); + // Find the environment where the variable exists + Environment *target_env = env; + Variable *existing_var = NULL; + while (target_env) { + for (size_t i = 0; i < target_env->variable_count; i++) { + if (strcmp(target_env->variables[i].variable_name, + node->assignment.variable_name) == 0) { + existing_var = &target_env->variables[i]; + break; + } + } + if (existing_var) { + break; + } + target_env = target_env->parent; + } + + // If variable doesn't exist anywhere, add to current scope + // If it exists, update in the scope where it was found + Environment *scope_to_modify = existing_var ? target_env : env; + + Variable new_var = {.variable_name = strdup(node->assignment.variable_name), + .value = new_value, + .is_constant = false}; - return make_result(new_value, false, false); + return add_variable(scope_to_modify, new_var); } -LiteralValue handle_string_concatenation(LiteralValue left, - LiteralValue right) { - LiteralValue result; - result.type = TYPE_STRING; +InterpretResult handle_string_concatenation(InterpretResult left, + InterpretResult right) { + LiteralValue lv_result; + lv_result.type = TYPE_STRING; + + LiteralValue left_val = left.value; + LiteralValue right_val = right.value; // Convert numbers to strings char num_str1[50] = {0}; char num_str2[50] = {0}; - if (left.type == TYPE_FLOAT) { + if (left_val.type == TYPE_FLOAT) { snprintf(num_str1, sizeof(num_str1), FLOAT_FORMAT_SPECIFIER, - left.data.floating_point); + left_val.data.floating_point); } - if (right.type == TYPE_FLOAT) { + if (right_val.type == TYPE_FLOAT) { snprintf(num_str2, sizeof(num_str2), FLOAT_FORMAT_SPECIFIER, - right.data.floating_point); + right_val.data.floating_point); } // Allocate memory for the concatenated string size_t new_size = strlen(num_str1) + strlen(num_str2) + - strlen(left.type == TYPE_STRING ? left.data.string : "") + - strlen(right.type == TYPE_STRING ? right.data.string : "") + 1; + strlen(left_val.type == TYPE_STRING ? left_val.data.string : "") + + strlen(right_val.type == TYPE_STRING ? right_val.data.string : "") + 1; char *new_string = malloc(new_size); if (!new_string) { - error_interpreter( + return raise_error( "Memory allocation failed for string concatenation.\n"); } - strcpy(new_string, left.type == TYPE_STRING ? left.data.string : num_str1); + strcpy(new_string, + left_val.type == TYPE_STRING ? left_val.data.string : num_str1); strcat(new_string, - right.type == TYPE_STRING ? right.data.string : num_str2); - result.data.string = new_string; + right_val.type == TYPE_STRING ? right_val.data.string : num_str2); + lv_result.data.string = new_string; - return result; + return make_result(lv_result, false, false); } -LiteralValue evaluate_unary_operator(const char *op, LiteralValue operand) { - // Debugging output - debug_print_int("Unary Operator: `%s`\n", op); - - LiteralValue result; - memset(&result, 0, sizeof(LiteralValue)); // initialize result - - // Handle logical NOT - if (strcmp(op, "!") == 0) { - // Ensure operand is boolean - if (operand.type != TYPE_BOOLEAN) { - error_interpreter( - "Unary operator `%s` requires a boolean operand.\n", op); - } - - result.type = TYPE_BOOLEAN; - result.data.boolean = !operand.data.boolean; - return result; - } - - // Handle unary minus - if (strcmp(op, "-") == 0) { - if (operand.type == TYPE_INTEGER) { - result.type = TYPE_INTEGER; - result.data.integer = -operand.data.integer; - } else if (operand.type == TYPE_FLOAT) { - result.type = TYPE_FLOAT; - result.data.floating_point = -operand.data.floating_point; - } else { - error_interpreter( - "Unary operator `%s` requires a numeric operand.\n", op); - } - return result; +// Helper function to handle numeric operations and comparisons +InterpretResult handle_numeric_operator(const char *op, + InterpretResult left_res, + InterpretResult right_res) { + // Check for errors in operands + if (left_res.is_error) { + return left_res; } - - // Handle unary plus (no effect) - if (strcmp(op, "+") == 0) { - // Unary plus doesn't change the operand - result = operand; - return result; + if (right_res.is_error) { + return right_res; } - // Add more unary operators as needed + LiteralValue left = left_res.value; + LiteralValue right = right_res.value; - error_interpreter("Unknown unary operator `%s`\n", op); - return result; // unreachable -} - -LiteralValue interpret_unary_op(ASTNode *node, Environment *env) { - if (!node || node->type != AST_UNARY_OP) { - error_interpreter("Invalid unary operation node.\n"); + // Ensure both operands are numeric + if (!is_numeric_type(left.type) || !is_numeric_type(right.type)) { + return raise_error("Operator `%s` requires numeric operands.\n", op); } - // Extract the operator and operand - const char *op = node->unary_op.operator; - ASTNode *operand_node = node->unary_op.operand; + // Determine if the result should be a float + bool result_is_float = + (left.type == TYPE_FLOAT || right.type == TYPE_FLOAT); - // Interpret the operand - InterpretResult operand_r = interpret_node(operand_node, env); - LiteralValue operand = operand_r.value; + // Fetch numeric values with coercion + double left_val = (left.type == TYPE_FLOAT) ? left.data.floating_point + : (double)left.data.integer; + double right_val = (right.type == TYPE_FLOAT) ? right.data.floating_point + : (double)right.data.integer; + + LiteralValue result; + memset(&result, 0, sizeof(LiteralValue)); - // If interpreting the operand caused a return, propagate it - if (operand_r.did_return) { - return operand; + // Handle operators + if (strcmp(op, "+") == 0) { + if (result_is_float) { + result.type = TYPE_FLOAT; + result.data.floating_point = left_val + right_val; + } else { + result.type = TYPE_INTEGER; + result.data.integer = (INT_SIZE)(left_val + right_val); + } + } else if (strcmp(op, "*") == 0) { + if (result_is_float) { + result.type = TYPE_FLOAT; + result.data.floating_point = left_val * right_val; + } else { + result.type = TYPE_INTEGER; + result.data.integer = (INT_SIZE)(left_val * right_val); + } + } else if (strcmp(op, "-") == 0) { + if (result_is_float) { + result.type = TYPE_FLOAT; + result.data.floating_point = left_val - right_val; + } else { + result.type = TYPE_INTEGER; + result.data.integer = (INT_SIZE)(left_val - right_val); + } + } else if (strcmp(op, "/") == 0) { + if (right_val == 0.0) { + return raise_error("Division by zero.\n"); + } + result.type = TYPE_FLOAT; + result.data.floating_point = left_val / right_val; + } else if (strcmp(op, "//") == 0) { // floor Division + if (right_val == 0.0) { + return raise_error("Floor division by zero.\n"); + } + double div_result = left_val / right_val; + if (result_is_float) { + result.type = TYPE_FLOAT; + result.data.floating_point = floor(div_result); + } else { + result.type = TYPE_INTEGER; + result.data.integer = (INT_SIZE)floor(div_result); + } + } else if (strcmp(op, "%") == 0) { // modulo + if (right_val == 0.0) { + return raise_error("Modulo by zero.\n"); + } + if (result_is_float) { + result.type = TYPE_FLOAT; + result.data.floating_point = fmod(left_val, right_val); + } else { + result.type = TYPE_INTEGER; + result.data.integer = + (INT_SIZE)((INT_SIZE)left_val % (INT_SIZE)right_val); + } + } else if (strcmp(op, "**") == 0) { // exponentiation + if (result_is_float) { + result.type = TYPE_FLOAT; + result.data.floating_point = pow(left_val, right_val); + } else { + result.type = TYPE_INTEGER; + result.data.integer = (INT_SIZE)pow(left_val, right_val); + } + } else if (strcmp(op, "<") == 0) { + result.type = TYPE_BOOLEAN; + result.data.boolean = (left_val < right_val); + } else if (strcmp(op, ">") == 0) { + result.type = TYPE_BOOLEAN; + result.data.boolean = (left_val > right_val); + } else if (strcmp(op, "<=") == 0) { + result.type = TYPE_BOOLEAN; + result.data.boolean = (left_val <= right_val); + } else if (strcmp(op, ">=") == 0) { + result.type = TYPE_BOOLEAN; + result.data.boolean = (left_val >= right_val); + } else { + return raise_error("Unknown operator `%s`.\n", op); } - // Evaluate the unary operator - return evaluate_unary_operator(op, operand); + return make_result(result, false, false); } -LiteralValue evaluate_operator(const char *op, LiteralValue left, - LiteralValue right) { +// Function to evaluate binary operators +InterpretResult evaluate_operator(const char *op, InterpretResult left_res, + InterpretResult right_res) { debug_print_int("Operator: `%s`\n", op); - LiteralValue result; - memset(&result, 0, sizeof(LiteralValue)); // Initialize result - // Handle string concatenation with "+" operator - if (strcmp(op, "+") == 0 && - (left.type == TYPE_STRING || right.type == TYPE_STRING)) { - return handle_string_concatenation(left, right); + if (strcmp(op, "+") == 0 && (left_res.value.type == TYPE_STRING || + right_res.value.type == TYPE_STRING)) { + return handle_string_concatenation(left_res, right_res); } // Handle logical AND and OR if (strcmp(op, "&&") == 0 || strcmp(op, "||") == 0) { // Ensure both operands are boolean - if (left.type != TYPE_BOOLEAN || right.type != TYPE_BOOLEAN) { - error_interpreter( - "Logical operator `%s` requires boolean operands.\n", op); + if (!is_boolean_type(left_res.value.type) || + !is_boolean_type(right_res.value.type)) { + return raise_error( + "Logical operators `&&` and `||` require boolean operands.\n"); } + LiteralValue result; result.type = TYPE_BOOLEAN; if (strcmp(op, "&&") == 0) { - result.data.boolean = left.data.boolean && right.data.boolean; + result.data.boolean = + left_res.value.data.boolean && right_res.value.data.boolean; } else { // op == "||" - result.data.boolean = left.data.boolean || right.data.boolean; + result.data.boolean = + left_res.value.data.boolean || right_res.value.data.boolean; } - return result; - } - - // Get numeric values for arithmetic and comparison - FLOAT_SIZE left_value = 0.0, right_value = 0.0; - if (left.type == TYPE_FLOAT || right.type == TYPE_FLOAT) { - left_value = (left.type == TYPE_FLOAT) ? left.data.floating_point - : (FLOAT_SIZE)left.data.integer; - right_value = (right.type == TYPE_FLOAT) - ? right.data.floating_point - : (FLOAT_SIZE)right.data.integer; - } else { - left_value = (FLOAT_SIZE)left.data.integer; - right_value = (FLOAT_SIZE)right.data.integer; + return make_result(result, false, false); } - // Determine result type based on operands - if (left.type == TYPE_FLOAT || right.type == TYPE_FLOAT) { - result.type = TYPE_FLOAT; - } else { - result.type = TYPE_INTEGER; - } + // Handle Equality Operators `==` and `!=` Across All Types + if (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0) { + bool comparison_result = false; - // Handle operators - if (strcmp(op, "*") == 0) { - if (result.type == TYPE_FLOAT) - result.data.floating_point = left_value * right_value; - else - result.data.integer = (INT_SIZE)(left_value * right_value); - } else if (strcmp(op, "+") == 0) { - if (result.type == TYPE_FLOAT) - result.data.floating_point = left_value + right_value; - else - result.data.integer = (INT_SIZE)(left_value + right_value); - } else if (strcmp(op, "-") == 0) { - if (result.type == TYPE_FLOAT) - result.data.floating_point = left_value - right_value; - else - result.data.integer = (INT_SIZE)(left_value - right_value); - } else if (strcmp(op, "/") == 0) { - if (right_value == 0) { - error_interpreter("Division by zero\n"); - } - result.type = TYPE_FLOAT; - result.data.floating_point = left_value / right_value; - } else if (strcmp(op, "//") == 0) { - if (right_value == 0) { - error_interpreter("Floor division by zero\n"); + // If types are the same, perform direct comparison + if (left_res.value.type == right_res.value.type) { + switch (left_res.value.type) { + case TYPE_INTEGER: + comparison_result = (left_res.value.data.integer == + right_res.value.data.integer); + break; + case TYPE_FLOAT: + comparison_result = (left_res.value.data.floating_point == + right_res.value.data.floating_point); + break; + case TYPE_BOOLEAN: + comparison_result = (left_res.value.data.boolean == + right_res.value.data.boolean); + break; + case TYPE_STRING: + if (left_res.value.data.string == NULL || + right_res.value.data.string == NULL) { + return raise_error("Cannot compare NULL strings.\n"); + } + comparison_result = (strcmp(left_res.value.data.string, + right_res.value.data.string) == 0); + break; + default: + return raise_error("Equality operators `==` and `!=` are not " + "supported for this type.\n"); + } } - if (result.type == TYPE_FLOAT) - result.data.floating_point = floor(left_value / right_value); - else - result.data.integer = (INT_SIZE)(left_value / right_value); - } else if (strcmp(op, "%") == 0) { - if (right_value == 0) { - error_interpreter("Modulo by zero\n"); + // Handle cross-type comparisons + else { + // For simplicity, handle numeric comparisons by coercing to float + if (is_numeric_type(left_res.value.type) && + is_numeric_type(right_res.value.type)) { + return handle_numeric_operator(op, left_res, right_res); + } + // Handle boolean and integer comparisons + else if ((left_res.value.type == TYPE_BOOLEAN && + is_numeric_type(right_res.value.type)) || + (right_res.value.type == TYPE_BOOLEAN && + is_numeric_type(left_res.value.type))) { + // Coerce boolean to integer (false=0, true=1) + LiteralValue coerced_left = left_res.value; + LiteralValue coerced_right = right_res.value; + + if (left_res.value.type == TYPE_BOOLEAN) { + coerced_left.type = TYPE_INTEGER; + coerced_left.data.integer = + left_res.value.data.boolean ? 1 : 0; + } + if (right_res.value.type == TYPE_BOOLEAN) { + coerced_right.type = TYPE_INTEGER; + coerced_right.data.integer = + right_res.value.data.boolean ? 1 : 0; + } + + return handle_numeric_operator( + op, make_result(coerced_left, false, false), + make_result(coerced_right, false, false)); + } + // Handle string and other type comparisons if necessary + else { + return raise_error("Cannot compare different types.\n"); + } } - if (result.type == TYPE_FLOAT) { - result.data.floating_point = fmod(left_value, right_value); - } else { - result.data.integer = - (INT_SIZE)((INT_SIZE)left_value % (INT_SIZE)right_value); + + // Apply `!=` logic if operator is "!=" + if (strcmp(op, "!=") == 0) { + comparison_result = !comparison_result; } - } else if (strcmp(op, "**") == 0) { - if (result.type == TYPE_FLOAT) - result.data.floating_point = pow(left_value, right_value); - else - result.data.integer = (INT_SIZE)pow(left_value, right_value); - } else if (strcmp(op, "<") == 0) { - result.type = TYPE_BOOLEAN; - result.data.boolean = (left_value < right_value); - } else if (strcmp(op, ">") == 0) { - result.type = TYPE_BOOLEAN; - result.data.boolean = (left_value > right_value); - } else if (strcmp(op, "<=") == 0) { - result.type = TYPE_BOOLEAN; - result.data.boolean = (left_value <= right_value); - } else if (strcmp(op, ">=") == 0) { - result.type = TYPE_BOOLEAN; - result.data.boolean = (left_value >= right_value); - } else if (strcmp(op, "==") == 0) { - result.type = TYPE_BOOLEAN; - result.data.boolean = (left_value == right_value); - } else if (strcmp(op, "!=") == 0) { + + // Set the result + LiteralValue result; result.type = TYPE_BOOLEAN; - result.data.boolean = (left_value != right_value); - } else { - error_interpreter("Unknown operator `%s`\n", op); + result.data.boolean = comparison_result; + + return make_result(result, false, false); } - return result; -} + // Handle Arithmetic and Comparison Operators + // List of operators that require numeric operands + const char *numeric_operators[] = { + "+", "*", "-", "/", "//", "%", "**", "<", ">", "<=", ">=", + }; + size_t num_numeric_ops = + sizeof(numeric_operators) / sizeof(numeric_operators[0]); -int get_operator_precedence(const char *op) { - if (strcmp(op, "**") == 0) - return 4; // highest precedence - if (strcmp(op, "*") == 0 || strcmp(op, "/") == 0 || strcmp(op, "//") == 0 || - strcmp(op, "%") == 0) - return 3; - if (strcmp(op, "+") == 0 || strcmp(op, "-") == 0) - return 2; - if (strcmp(op, "<") == 0 || strcmp(op, ">") == 0 || strcmp(op, "<=") == 0 || - strcmp(op, ">=") == 0) - return 1; - if (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0) - return 1; - - // Return `0` for unknown operators - return 0; -} + bool is_numeric_op = false; + for (size_t i = 0; i < num_numeric_ops; ++i) { + if (strcmp(op, numeric_operators[i]) == 0) { + is_numeric_op = true; + break; + } + } + + if (is_numeric_op) { + return handle_numeric_operator(op, left_res, right_res); + } -int is_right_associative(const char *op) { - return (strcmp(op, "**") == 0); // exponentiation is right-associative + if (strcmp(op, "+") == 0) { + return handle_string_concatenation(left_res, right_res); + } + + // If operator is not recognized + return raise_error("Unknown operator `%s`.\n", op); } -LiteralValue interpret_binary_op(ASTNode *node, Environment *env) { - if (!node || node->type != AST_BINARY_OP) { - error_interpreter("Invalid binary operation node."); +InterpretResult interpret_binary_op(ASTNode *node, Environment *env) { + if (node->type != AST_BINARY_OP) { + return raise_error("Invalid node type for binary operation.\n"); } // Interpret left operand - InterpretResult left_r = interpret_node(node->binary_op.left, env); - LiteralValue left = left_r.value; - if (left.type == TYPE_ERROR) { - return left; // propagate errors + InterpretResult left_res = interpret_node(node->binary_op.left, env); + if (left_res.is_error) { + return left_res; } // Interpret right operand - InterpretResult right_r = interpret_node(node->binary_op.right, env); - LiteralValue right = right_r.value; - if (right.type == TYPE_ERROR) { - return right; // propagate errors + InterpretResult right_res = interpret_node(node->binary_op.right, env); + if (right_res.is_error) { + return right_res; } - // Evaluate based on operator + // Evaluate the operator const char *op = node->binary_op.operator; + InterpretResult op_res = evaluate_operator(op, left_res, right_res); + if (op_res.is_error) { + return op_res; + } + + return make_result(op_res.value, false, false); +} + +// Implementation of interpret_unary_op +InterpretResult interpret_unary_op(ASTNode *node, Environment *env) { + if (node->type != AST_UNARY_OP) { + return raise_error("Invalid node type for unary operation.\n"); + } - // Check precedence dynamically if subnodes have binary ops - if (node->binary_op.right->type == AST_BINARY_OP) { - int op_prec = get_operator_precedence(op); - int right_prec = - get_operator_precedence(node->binary_op.right->binary_op.operator); + // Interpret the operand + InterpretResult operand_res = interpret_node(node->unary_op.operand, env); + if (operand_res.is_error) { + return operand_res; + } - // Reevaluate RHS if its operator has higher precedence - if (op_prec < right_prec || - (op_prec == right_prec && is_right_associative(op))) { - right = interpret_binary_op(node->binary_op.right, env); + // Evaluate the unary operator + const char *op = node->unary_op.operator; + InterpretResult op_res = evaluate_unary_operator(op, operand_res); + if (op_res.is_error) { + return op_res; + } + + return make_result(op_res.value, false, false); +} + +// Helper function to handle unary operators +InterpretResult evaluate_unary_operator(const char *op, + InterpretResult operand_res) { + debug_print_int("Unary Operator: `%s`\n", op); + + // Check for errors in operand + if (operand_res.is_error) { + return operand_res; + } + + LiteralValue operand = operand_res.value; + LiteralValue result; + + if (strcmp(op, "-") == 0) { + // Arithmetic negation + if (operand.type == TYPE_INTEGER) { + result.type = TYPE_INTEGER; + result.data.integer = -operand.data.integer; + } else if (operand.type == TYPE_FLOAT) { + result.type = TYPE_FLOAT; + result.data.floating_point = -operand.data.floating_point; + } else { + return raise_error( + "Unary `-` operator requires numeric operand.\n"); + } + } else if (strcmp(op, "!") == 0) { + // Logical NOT + if (operand.type == TYPE_BOOLEAN) { + result.type = TYPE_BOOLEAN; + result.data.boolean = !operand.data.boolean; + } else if (operand.type == TYPE_INTEGER) { + // Treat 0 as false, non-zero as true + result.type = TYPE_BOOLEAN; + result.data.boolean = (operand.data.integer == 0) ? true : false; + } else { + return raise_error( + "Unary `!` operator requires boolean or integer operand.\n"); } + } else { + return raise_error("Unsupported unary operator `%s`.\n", op); } - return evaluate_operator(op, left, right); + return make_result(result, false, false); } Variable *get_variable(Environment *env, const char *variable_name) { debug_print_int("Looking up variable: `%s`\n", variable_name); - for (size_t i = 0; i < env->variable_count; i++) { - if (strcmp(env->variables[i].variable_name, variable_name) == 0) { - if (env->variables[i].value.type == TYPE_FLOAT) { - debug_print_int("Variable found: `%s` with value `%Lf`\n", - variable_name, - env->variables[i].value.data.floating_point); - } else if (env->variables[i].value.type == TYPE_INTEGER) { - debug_print_int("Variable found: `%s` with value `%lld`\n", - variable_name, - env->variables[i].value.data.integer); - } else if (env->variables[i].value.type == TYPE_STRING) { - debug_print_int("Variable found: `%s` with value `%s`\n", - variable_name, - env->variables[i].value.data.string); - } else if (env->variables[i].value.type == TYPE_FUNCTION) { - debug_print_int( - "Variable found: `%s` with function reference `%s`\n", - variable_name, - env->variables[i].value.data.function_ptr->name); + Environment *current_env = env; + while (current_env) { + for (size_t i = 0; i < current_env->variable_count; i++) { + if (strcmp(current_env->variables[i].variable_name, + variable_name) == 0) { + // Debugging information based on type + switch (current_env->variables[i].value.type) { + case TYPE_FLOAT: + debug_print_int( + "Variable found: `%s` with value `%Lf`\n", + variable_name, + current_env->variables[i].value.data.floating_point); + break; + case TYPE_INTEGER: + debug_print_int( + "Variable found: `%s` with value `%lld`\n", + variable_name, + current_env->variables[i].value.data.integer); + break; + case TYPE_STRING: + debug_print_int( + "Variable found: `%s` with value `%s`\n", variable_name, + current_env->variables[i].value.data.string); + break; + case TYPE_FUNCTION: + debug_print_int( + "Variable found: `%s` with function reference `%s`\n", + variable_name, + current_env->variables[i] + .value.data.function_ptr->name); + break; + default: + debug_print_int("Variable found: `%s` with unknown type.\n", + variable_name); + } + return ¤t_env->variables[i]; } - return &env->variables[i]; } + current_env = current_env->parent; } debug_print_int("Variable not found: `%s`\n", variable_name); return NULL; } -void add_variable(Environment *env, Variable var) { +InterpretResult add_variable(Environment *env, Variable var) { // Check if the variable already exists for (size_t i = 0; i < env->variable_count; i++) { if (strcmp(env->variables[i].variable_name, var.variable_name) == 0) { // If existing variable is a constant, prevent re-assignment if (env->variables[i].is_constant) { - fprintf(stderr, "Error: Cannot reassign to constant `%s`.\n", - var.variable_name); - return; + return raise_error("Error: Cannot reassign to constant `%s`.\n", + var.variable_name); } // Update the value of the existing variable @@ -583,29 +729,31 @@ void add_variable(Environment *env, Variable var) { env->variables[i].value = var.value; env->variables[i].is_constant = var.is_constant; - return; + return make_result(var.value, false, false); } } // Add a new variable if (env->variable_count == env->capacity) { // Resize the variables array if necessary - env->capacity = env->capacity ? env->capacity * 2 : 4; - env->variables = - realloc(env->variables, env->capacity * sizeof(Variable)); - if (!env->variables) { - error_interpreter( + size_t new_capacity = env->capacity ? env->capacity * 2 : 4; + Variable *new_variables = + realloc(env->variables, new_capacity * sizeof(Variable)); + if (!new_variables) { + return raise_error( "Memory allocation failed while adding variable `%s`.\n", var.variable_name); } + env->variables = new_variables; + env->capacity = new_capacity; } // Copy variable name and value env->variables[env->variable_count].variable_name = strdup(var.variable_name); if (!env->variables[env->variable_count].variable_name) { - error_interpreter("Memory allocation failed for variable name `%s`.\n", - var.variable_name); + return raise_error("Memory allocation failed for variable name `%s`.\n", + var.variable_name); } // Deep copy based on type @@ -613,6 +761,11 @@ void add_variable(Environment *env, Variable var) { env->variables[env->variable_count].value.type = TYPE_STRING; env->variables[env->variable_count].value.data.string = strdup(var.value.data.string); + if (!env->variables[env->variable_count].value.data.string) { + return raise_error( + "Memory allocation failed for string variable `%s`.\n", + var.variable_name); + } } else if (var.value.type == TYPE_FUNCTION) { env->variables[env->variable_count].value.type = TYPE_FUNCTION; env->variables[env->variable_count].value.data.function_ptr = @@ -623,59 +776,71 @@ void add_variable(Environment *env, Variable var) { env->variables[env->variable_count].is_constant = var.is_constant; env->variable_count++; + + return make_result(var.value, false, false); } -Variable *allocate_variable(Environment *env, const char *name) { +InterpretResult allocate_variable(Environment *env, const char *name) { // Check if the variable already exists for (size_t i = 0; i < env->variable_count; i++) { if (strcmp(env->variables[i].variable_name, name) == 0) { - return &env->variables[i]; // return existing variable + return make_result(env->variables[i].value, false, false); } } // If the variable doesn't exist, allocate it in memory if (env->variable_count == env->capacity) { - env->capacity *= 2; - env->variables = - realloc(env->variables, env->capacity * sizeof(Variable)); - if (!env->variables) { - error_interpreter("Memory allocation failed.\n"); + size_t new_capacity = env->capacity ? env->capacity * 2 : 4; + Variable *new_variables = + realloc(env->variables, new_capacity * sizeof(Variable)); + if (!new_variables) { + return raise_error("Memory allocation failed.\n"); } + env->variables = new_variables; + env->capacity = new_capacity; } env->variables[env->variable_count].variable_name = strdup(name); + if (!env->variables[env->variable_count].variable_name) { + return raise_error("Memory allocation failed for variable name `%s`.\n", + name); + } + + // Initialize the variable with default value + env->variables[env->variable_count].value = create_default_value(); + env->variables[env->variable_count].is_constant = false; env->variable_count++; - return &env->variables[env->variable_count - 1]; + Variable *var = &env->variables[env->variable_count - 1]; + return make_result(var->value, false, false); } InterpretResult interpret_conditional(ASTNode *node, Environment *env) { debug_print_int("`interpret_conditional()` called\n"); if (!node) { // Error - return make_result((LiteralValue){.type = TYPE_ERROR}, false, false); + return raise_error("Invalid conditional node."); } - ASTNode *current_branch = node; bool condition_met = false; + ASTNode *current_branch = node; + while (current_branch) { if (current_branch->conditional.condition) { InterpretResult cond_res = interpret_node(current_branch->conditional.condition, env); - if (cond_res.did_return || cond_res.did_break) { - // Bubble up immediately + if (cond_res.is_error) { + // Propagate the error return cond_res; } // Check for valid condition types if (cond_res.value.type != TYPE_INTEGER && cond_res.value.type != TYPE_BOOLEAN) { - fprintf(stderr, "Error: Condition expression must be boolean " - "or integer.\n"); - return make_result((LiteralValue){.type = TYPE_ERROR}, false, - false); + return raise_error( + "Condition expression must be boolean or integer.\n"); } bool condition_true = false; @@ -691,7 +856,8 @@ InterpretResult interpret_conditional(ASTNode *node, Environment *env) { while (cs) { InterpretResult body_res = interpret_node(cs, env); - if (body_res.did_return || body_res.did_break) { + if (body_res.is_error || body_res.did_return || + body_res.did_break) { return body_res; } cs = cs->next; @@ -708,7 +874,8 @@ InterpretResult interpret_conditional(ASTNode *node, Environment *env) { while (cs) { InterpretResult body_res = interpret_node(cs, env); - if (body_res.did_return || body_res.did_break) { + if (body_res.is_error || body_res.did_return || + body_res.did_break) { return body_res; } cs = cs->next; @@ -770,14 +937,17 @@ InterpretResult interpret_while_loop(ASTNode *node, Environment *env) { return make_result(create_default_value(), false, false); } -LiteralValue interpret_for_loop(ASTNode *node, Environment *env) { +InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { if (node->type != AST_FOR_LOOP) { - error_interpreter( + return raise_error( "`interpret_for_loop` called with non-`for`-loop `ASTNode`\n"); } // Extract loop components - char *loop_var = node->for_loop.loop_variable; + char *loop_var = strdup(node->for_loop.loop_variable); + if (!loop_var) { + return raise_error("Memory allocation failed for loop variable\n"); + } ASTNode *start_expr = node->for_loop.start_expr; ASTNode *end_expr = node->for_loop.end_expr; bool inclusive = node->for_loop.inclusive; @@ -786,47 +956,53 @@ LiteralValue interpret_for_loop(ASTNode *node, Environment *env) { // Evaluate start and end expressions InterpretResult start_res = interpret_node(start_expr, env); - if (start_res.value.type == TYPE_ERROR) { - return start_res.value; + if (start_res.is_error) { + free(loop_var); + return start_res; } InterpretResult end_res = interpret_node(end_expr, env); - if (end_res.value.type == TYPE_ERROR) { - return end_res.value; + if (end_res.is_error) { + free(loop_var); + return end_res; } - // Determine start & end as floats for flexibility + // Determine start & end as doubles for flexibility double start_val, end_val; if (start_res.value.type == TYPE_FLOAT) { start_val = start_res.value.data.floating_point; } else if (start_res.value.type == TYPE_INTEGER) { - start_val = (FLOAT_SIZE)start_res.value.data.integer; + start_val = (double)start_res.value.data.integer; } else { - error_interpreter("Start expression in `for` loop must be numeric\n"); + free(loop_var); + return raise_error("Start expression in `for` loop must be numeric\n"); } if (end_res.value.type == TYPE_FLOAT) { end_val = end_res.value.data.floating_point; } else if (end_res.value.type == TYPE_INTEGER) { - end_val = (FLOAT_SIZE)end_res.value.data.integer; + end_val = (double)end_res.value.data.integer; } else { - error_interpreter("End expression in `for` loop must be numeric\n"); + free(loop_var); + return raise_error("End expression in `for` loop must be numeric\n"); } // Evaluate step expression if present double step = 1.0; // default if (step_expr) { InterpretResult step_res = interpret_node(step_expr, env); - if (step_res.value.type == TYPE_ERROR) { - return step_res.value; + if (step_res.is_error) { + free(loop_var); + return step_res; } if (step_res.value.type == TYPE_FLOAT) { step = step_res.value.data.floating_point; } else if (step_res.value.type == TYPE_INTEGER) { - step = (FLOAT_SIZE)step_res.value.data.integer; + step = (double)step_res.value.data.integer; } else { - error_interpreter( + free(loop_var); + return raise_error( "Step expression in `for` loop must be numeric\n"); } } else { @@ -840,76 +1016,88 @@ LiteralValue interpret_for_loop(ASTNode *node, Environment *env) { // Validate step to prevent infinite loops if (step < 1e-9 && step > -1e-9) { - error_interpreter("Step value cannot be zero in `for` loop\n"); + free(loop_var); + return raise_error("Step value cannot be zero in `for` loop\n"); } - // Validate step to check if step is in correct direction - if ((start_val < end_val && step < 0) || - (start_val > end_val && step > 0)) { - error_interpreter("Step value is in the wrong direction for the " - "specified range of the `for` loop\n"); + // Assign or update loop variable in the environment + InterpretResult var_res = allocate_variable(env, loop_var); + if (var_res.is_error) { + free(loop_var); + return var_res; // Propagate the error } - // Assign or update loop variable in the environment + // Initialize the loop variable with the start value Variable *var = get_variable(env, loop_var); if (!var) { - // Variable does not exist; create it - var = allocate_variable(env, loop_var); - if (start_res.value.type == TYPE_FLOAT) { - var->value.type = TYPE_FLOAT; - var->value.data.floating_point = start_val; - } else { - var->value.type = TYPE_INTEGER; - var->value.data.integer = (INT_SIZE)start_val; - } + free(loop_var); + return raise_error("Failed to allocate and retrieve variable `%s`.\n", + loop_var); + } + + if (start_res.value.type == TYPE_FLOAT) { + var->value.type = TYPE_FLOAT; + var->value.data.floating_point = start_val; } else { - // Update existing variable - if (start_res.value.type == TYPE_FLOAT || - var->value.type == TYPE_FLOAT) { - var->value.type = TYPE_FLOAT; - var->value.data.floating_point = start_val; - } else { - var->value.type = TYPE_INTEGER; - var->value.data.integer = (INT_SIZE)start_val; - } + var->value.type = TYPE_INTEGER; + var->value.data.integer = (INT_SIZE)start_val; } // Determine loop direction bool is_ascending = step > 0.0; while (1) { - // Fetch current value + // Re-fetch the variable pointer at the start of each iteration + var = get_variable(env, loop_var); + if (!var) { + free(loop_var); + return raise_error("Loop variable `%s` not found in environment\n", + loop_var); + } + double current_val; if (var->value.type == TYPE_FLOAT) { current_val = var->value.data.floating_point; } else if (var->value.type == TYPE_INTEGER) { - current_val = (FLOAT_SIZE)var->value.data.integer; + current_val = (double)var->value.data.integer; } else { - error_interpreter("Loop variable `%s` must be numeric\n", loop_var); + free(loop_var); + return raise_error("Loop variable `%s` must be numeric\n", + loop_var); } - // Check if condition is still valid - bool condition = is_ascending ? (inclusive ? (current_val <= end_val) - : (current_val < end_val)) - : (inclusive ? (current_val >= end_val) - : (current_val > end_val)); - if (!condition) { + // Check if the loop should continue + bool condition_true = false; + if (is_ascending) { + condition_true = + inclusive ? (current_val <= end_val) : (current_val < end_val); + } else { + condition_true = + inclusive ? (current_val >= end_val) : (current_val > end_val); + } + + if (!condition_true) { break; } - // Execute loop body - ASTNode *current_stmt = body; - while (current_stmt) { - InterpretResult res = interpret_node(current_stmt, env); - if (res.did_return) { - return res.value; - } - if (res.did_break) { - // break out of the for loop - // just exit interpret_for_loop entirely - return create_default_value(); + // Interpret the loop body + ASTNode *current = body; + while (current) { + InterpretResult body_res = interpret_node(current, env); + if (body_res.did_return || body_res.did_break) { + free(loop_var); + return body_res; } - current_stmt = current_stmt->next; + current = current->next; + } + + // Re-fetch the variable pointer after interpreting the loop body + var = get_variable(env, loop_var); + if (!var) { + free(loop_var); + return raise_error( + "Loop variable `%s` not found in environment after loop body\n", + loop_var); } // Update loop variable @@ -920,8 +1108,11 @@ LiteralValue interpret_for_loop(ASTNode *node, Environment *env) { } } + // Clean up + free(loop_var); + // Loops do not return values - return create_default_value(); + return make_result(create_default_value(), false, false); } InterpretResult interpret_switch(ASTNode *node, Environment *env) { @@ -1015,90 +1206,88 @@ InterpretResult interpret_switch(ASTNode *node, Environment *env) { return make_result(create_default_value(), false, false); } -LiteralValue call_user_defined_function(Function *func_ref, ASTNode *call_node, - Environment *env) { +/** + * Function to call a user-defined function + */ +InterpretResult call_user_defined_function(Function *func_ref, + ASTNode *call_node, + Environment *env) { debug_print_int("Calling user-defined function: `%s`\n", func_ref->name); - // 1) Create a new local environment + // Create a new local environment with 'env' as its parent Environment local_env; - init_environment(&local_env); + init_environment_with_parent(&local_env, env); - // 2) Copy global functions into the local environment - // This ensures that nested function calls can access global functions - for (size_t i = 0; i < env->function_count; i++) { - Function *global_func = &env->functions[i]; - - // Deep copy the function - Function func_copy = { - .name = strdup(global_func->name), - .parameters = copy_function_parameters(global_func->parameters), - .body = copy_ast_node(global_func->body), - .is_builtin = global_func->is_builtin}; - - add_function(&local_env, func_copy); - } - - // 3) Bind function parameters with arguments + // Bind function parameters with arguments ASTFunctionParameter *param = func_ref->parameters; ASTNode *arg = call_node->function_call.arguments; while (param && arg) { InterpretResult arg_res = interpret_node(arg, env); - LiteralValue arg_value = arg_res.value; - - if (arg_value.type == TYPE_ERROR) { + if (arg_res.is_error) { free_environment(&local_env); - return arg_value; + return arg_res; // Propagate the error } + LiteralValue arg_value = arg_res.value; + // Bind the argument to the parameter in the local environment Variable param_var = {.variable_name = strdup(param->parameter_name), .value = arg_value, .is_constant = false}; - add_variable(&local_env, param_var); + InterpretResult add_res = add_variable(&local_env, param_var); + if (add_res.is_error) { + free_environment(&local_env); + return add_res; + } param = param->next; arg = arg->next; } - // 4) Check for argument count mismatch + // Check for argument count mismatch if (param || arg) { - fprintf(stderr, - "Error: Argument count mismatch when calling function `%s`\n", - func_ref->name); free_environment(&local_env); - return (LiteralValue){.type = TYPE_ERROR}; + return raise_error( + "Error: Argument count mismatch when calling function `%s`\n", + func_ref->name); } - // 5) Interpret the function body + // Interpret the function body ASTNode *stmt = func_ref->body; - LiteralValue result = create_default_value(); + InterpretResult func_res = + make_result(create_default_value(), false, false); + while (stmt) { InterpretResult r = interpret_node(stmt, &local_env); if (r.did_return) { - result = r.value; + func_res = r; break; } if (r.did_break) { - fprintf(stderr, - "Error: 'break' statement outside of loop or switch.\n"); free_environment(&local_env); - return (LiteralValue){.type = TYPE_ERROR}; + return raise_error( + "Error: 'break' statement outside of loop or switch.\n"); + } + if (r.is_error) { + free_environment(&local_env); + return r; } stmt = stmt->next; } free_environment(&local_env); - // If no explicit return, return default value (e.g., 0) - return result; + // If no explicit return, return default value (e.g., `0`) + return func_res; } -void interpret_function_declaration(ASTNode *node, Environment *env) { +InterpretResult interpret_function_declaration(ASTNode *node, + Environment *env) { debug_print_int("`interpret_function_declaration()` called\n"); - if (!node || !node->function_call.name) { - error_interpreter("Invalid function declaration\n"); + if (!node || !node->function_declaration.name) { + fatal_error("Invalid function declaration\n"); } // Initialize param_list as NULL @@ -1106,18 +1295,18 @@ void interpret_function_declaration(ASTNode *node, Environment *env) { ASTFunctionParameter *param_tail = NULL; // keep track of the last parameter // Copy parameters - ASTFunctionParameter *param = node->function_call.parameters; + ASTFunctionParameter *param = node->function_declaration.parameters; while (param) { ASTFunctionParameter *new_param = malloc(sizeof(ASTFunctionParameter)); if (!new_param) { - error_interpreter( + fatal_error( "Error: Memory allocation failed for function parameter\n"); } new_param->parameter_name = strdup(param->parameter_name); if (!new_param->parameter_name) { free(new_param); - error_interpreter("Memory allocation failed for parameter name\n"); + fatal_error("Memory allocation failed for parameter name\n"); } new_param->next = NULL; @@ -1134,16 +1323,15 @@ void interpret_function_declaration(ASTNode *node, Environment *env) { param = param->next; } - Function func = {.name = strdup(node->function_call.name), + Function func = {.name = strdup(node->function_declaration.name), .parameters = param_list, - .body = node->function_call.body, + .body = node->function_declaration.body, .is_builtin = false}; add_function(env, func); // Also add the function as a variable holding its reference LiteralValue func_ref = {.type = TYPE_FUNCTION, - // Point to the last added function .data.function_ptr = &env->functions[env->function_count - 1]}; @@ -1152,126 +1340,75 @@ void interpret_function_declaration(ASTNode *node, Environment *env) { .is_constant = false}; add_variable(env, var); + + return make_result(create_default_value(), false, false); } -LiteralValue interpret_function_call(ASTNode *node, Environment *env) { +InterpretResult interpret_function_call(ASTNode *node, Environment *env) { debug_print_int("Starting function call interpretation\n"); if (!node || !node->function_call.name) { - fprintf(stderr, "Error: Invalid function call\n"); - return (LiteralValue){.type = TYPE_ERROR}; + return raise_error("Invalid function call"); } const char *func_name = node->function_call.name; - // 1. Check if the function name is a variable holding a function reference - Variable *func_var = get_variable(env, func_name); - if (func_var && func_var->value.type == TYPE_FUNCTION) { - Function *func_ref = func_var->value.data.function_ptr; - // Call the function using the helper - return call_user_defined_function(func_ref, node, env); - } - - // 2. Else, check if it's a built-in or globally defined function + // 1) Try looking up the function in the functions array (built-in or global + // user-defined) Function *func = get_function(env, func_name); - if (!func) { - // Function not found - fprintf(stderr, "Error: Undefined function `%s`\n", func_name); - exit(1); - return (LiteralValue){.type = TYPE_ERROR}; - } - - // If it’s a built-in => call the built-in logic - if (func->is_builtin) { - if (strcmp(func->name, "sample") == 0) { - return builtin_input(node, env); - } else if (strcmp(func->name, "serve") == 0) { - return builtin_output(node, env); - } else if (strcmp(func->name, "burn") == 0) { - return builtin_error(node, env); - } else if (strcmp(func->name, "random") == 0) { - return builtin_random(node, env); - } else if (strcmp(func->name, "string") == 0 || - strcmp(func->name, "int") == 0 || - strcmp(func->name, "float") == 0) { - return builtin_cast(node, env); - } else if (strcmp(func->name, "get_time") == 0) { - return builtin_time(); - } else if (strcmp(func->name, "taste_file") == 0) { - return builtin_file_read(node, env); - } else if (strcmp(func->name, "plate_file") == 0) { - return builtin_file_write(node, env); - } else if (strcmp(func->name, "garnish_file") == 0) { - return builtin_file_append(node, env); - } - - // If no recognized built-in, error - fprintf(stderr, "Error: Unknown built-in `%s`\n", func->name); - return (LiteralValue){.type = TYPE_ERROR}; - } - - // Otherwise, user-defined function. - // Create local environment, bind parameters, interpret AST body, etc. - - Environment local_env; - init_environment(&local_env); - - // Copy parent's functions so calls to other user-defined functions work - for (size_t i = 0; i < env->function_count; i++) { - // NOTE: If the function is built-in or user-defined, - // copy them all anyway. - Function func_copy = {.name = strdup(env->functions[i].name), - .parameters = copy_function_parameters( - env->functions[i].parameters), - .body = copy_ast_node(env->functions[i].body), - .is_builtin = env->functions[i].is_builtin}; - add_function(&local_env, func_copy); - } - - // Now interpret arguments & bind them - ASTFunctionParameter *p = func->parameters; - ASTNode *arg = node->function_call.arguments; - while (p && arg) { - InterpretResult arg_res = interpret_node(arg, env); - LiteralValue arg_value = arg_res.value; - if (arg_value.type == TYPE_ERROR) { - free_environment(&local_env); - return arg_value; + if (func) { + if (func->is_builtin) { + // Handle built-in functions + if (strcmp(func->name, "sample") == 0) { + return builtin_input(node, env); + } else if (strcmp(func->name, "serve") == 0) { + return builtin_output(node, env); + } else if (strcmp(func->name, "burn") == 0) { + return builtin_error(node, env); + } else if (strcmp(func->name, "random") == 0) { + return builtin_random(node, env); + } else if (strcmp(func->name, "string") == 0 || + strcmp(func->name, "int") == 0 || + strcmp(func->name, "float") == 0) { + return builtin_cast(node, env); + } else if (strcmp(func->name, "get_time") == 0) { + return builtin_time(); + } else if (strcmp(func->name, "taste_file") == 0) { + return builtin_file_read(node, env); + } else if (strcmp(func->name, "plate_file") == 0) { + return builtin_file_write(node, env); + } else if (strcmp(func->name, "garnish_file") == 0) { + return builtin_file_append(node, env); + } else { + return raise_error("Unknown built-in function `%s`\n", + func->name); + } + } else { + // Handle user-defined functions in the functions array + return call_user_defined_function(func, node, env); } - - Variable param_var = {.variable_name = strdup(p->parameter_name), - .value = arg_value}; - add_variable(&local_env, param_var); - - p = p->next; - arg = arg->next; } - // interpret function body - LiteralValue result = create_default_value(); - ASTNode *stmt = func->body; - while (stmt) { - InterpretResult r = interpret_node(stmt, &local_env); - if (r.did_return) { - // Short-circuit - free_environment(&local_env); - return r.value; - } - // Else keep going - stmt = stmt->next; + // 2) If not found in functions array, check if it's a variable holding + // a function reference + Variable *func_var = get_variable(env, func_name); + if (func_var && func_var->value.type == TYPE_FUNCTION) { + Function *func_ref = func_var->value.data.function_ptr; + // Delegate to call_user_defined_function + return call_user_defined_function(func_ref, node, env); } - free_environment(&local_env); - return result; // if no explicit return => return `0` (or default) + // 3) If not found in either, raise an error + return raise_error("Undefined function `%s`\n", func_name); } InterpretResult interpret_ternary(ASTNode *node, Environment *env) { if (!node || node->type != AST_TERNARY) { - error_interpreter("Invalid ternary operation node.\n"); + return raise_error("Invalid ternary operation node.\n"); } InterpretResult cond_res = interpret_node(node->ternary.condition, env); - if (cond_res.did_return || cond_res.did_break) { + if (cond_res.is_error || cond_res.did_return || cond_res.did_break) { return cond_res; } @@ -1281,7 +1418,7 @@ InterpretResult interpret_ternary(ASTNode *node, Environment *env) { } else if (cond_res.value.type == TYPE_INTEGER) { is_true = (cond_res.value.data.integer != 0); } else { - error_interpreter("Ternary condition must be boolean or integer.\n"); + return raise_error("Ternary condition must be boolean or integer.\n"); } if (is_true) { @@ -1293,3 +1430,109 @@ InterpretResult interpret_ternary(ASTNode *node, Environment *env) { return false_res; } } + +InterpretResult interpret_try(ASTNode *node, Environment *env) { + if (!node || node->type != AST_TRY) { + return raise_error("Invalid AST node for try block."); + } + + InterpretResult result = make_result(create_default_value(), false, false); + bool exception_occurred = false; + LiteralValue exception_value = create_default_value(); + + // Execute try block + ASTNode *stmt = node->try_block.try_block; + while (stmt) { + InterpretResult res = interpret_node(stmt, env); + if (res.is_error) { + // An exception has been thrown + exception_occurred = true; + exception_value = res.value; + break; + } + if (res.did_return) { + // Propagate return up + return res; + } + stmt = stmt->next; + } + + // If exception occurred, handle rescue blocks + if (exception_occurred) { + ASTCatchNode *catch = node->try_block.catch_blocks; + bool handled = false; + + while (catch && !handled) { + Environment catch_env; + init_environment(&catch_env); + + // Copy global functions to rescue environment + for (size_t i = 0; i < env->function_count; i++) { + Function *global_func = &env->functions[i]; + Function func_copy = {.name = strdup(global_func->name), + .parameters = copy_function_parameters( + global_func->parameters), + .body = copy_ast_node(global_func->body), + .is_builtin = global_func->is_builtin}; + add_function(&catch_env, func_copy); + } + + // If there's an error variable, bind the exception to it + if (catch->error_variable) { + Variable error_var = {.variable_name = + strdup(catch->error_variable), + .value = exception_value, + .is_constant = false}; + add_variable(&catch_env, error_var); + } + + // Execute catch block + ASTNode *catch_stmt = catch->body; + while (catch_stmt) { + InterpretResult res = interpret_node(catch_stmt, &catch_env); + if (res.is_error) { + // Nested exception, propagate + free_environment(&catch_env); + return res; + } + if (res.did_return) { + // Propagate return up + free_environment(&catch_env); + return res; + } + catch_stmt = catch_stmt->next; + } + + handled = true; // Currently handling only one catch block + free_environment(&catch_env); + catch = catch->next; + } + + if (!handled) { + // No rescue block handled the exception, propagate it + result.value = exception_value; + result.is_error = true; + return result; + } + } + + // Execute finish block if it exists + if (node->try_block.finally_block) { + ASTNode *finish_stmt = node->try_block.finally_block; + while (finish_stmt) { + InterpretResult res = interpret_node(finish_stmt, env); + if (res.is_error) { + // If an exception occurs in finish, prioritize it + return res; + } + if (res.did_return) { + // Propagate return up + return res; + } + finish_stmt = finish_stmt->next; + } + } + + // Normal execution + return result; +} diff --git a/src/interpreter/interpreter.h b/src/interpreter/interpreter.h index e9839dc..f11e16d 100644 --- a/src/interpreter/interpreter.h +++ b/src/interpreter/interpreter.h @@ -14,29 +14,33 @@ #include InterpretResult interpret_node(ASTNode *node, Environment *env); -LiteralValue interpret_literal(ASTNode *node); -LiteralValue interpret_variable(ASTNode *node, Environment *env); +InterpretResult interpret_literal(ASTNode *node); +InterpretResult interpret_variable(ASTNode *node, Environment *env); InterpretResult interpret_constant(ASTNode *node, Environment *env); InterpretResult interpret_assignment(ASTNode *node, Environment *env); -LiteralValue interpret_binary_op(ASTNode *node, Environment *env); +InterpretResult interpret_binary_op(ASTNode *node, Environment *env); InterpretResult interpret_conditional(ASTNode *node, Environment *env); InterpretResult interpret_while_loop(ASTNode *node, Environment *env); -LiteralValue interpret_for_loop(ASTNode *node, Environment *env); +InterpretResult interpret_for_loop(ASTNode *node, Environment *env); InterpretResult interpret_switch(ASTNode *node, Environment *env); -void interpret_function_declaration(ASTNode *node, Environment *env); -LiteralValue interpret_function_call(ASTNode *node, Environment *env); -LiteralValue interpret_unary_op(ASTNode *node, Environment *env); -LiteralValue evaluate_unary_operator(const char *op, LiteralValue operand); +InterpretResult interpret_function_declaration(ASTNode *node, Environment *env); +InterpretResult interpret_function_call(ASTNode *node, Environment *env); +InterpretResult interpret_unary_op(ASTNode *node, Environment *env); +InterpretResult evaluate_unary_operator(const char *op, + InterpretResult operand_res); InterpretResult interpret_ternary(ASTNode *node, Environment *env); -LiteralValue call_user_defined_function(Function *func_ref, ASTNode *call_node, - Environment *env); +InterpretResult call_user_defined_function(Function *func_ref, + ASTNode *call_node, + Environment *env); +InterpretResult interpret_try(ASTNode *node, Environment *env); // Interpret program void interpret_program(ASTNode *program, Environment *env); // Helpers +LiteralValue create_default_value(void); Variable *get_variable(Environment *env, const char *variable_name); -void add_variable(Environment *env, Variable var); +InterpretResult add_variable(Environment *env, Variable var); ASTFunctionParameter *copy_function_parameters(ASTFunctionParameter *params); #endif diff --git a/src/interpreter/interpreter_types.h b/src/interpreter/interpreter_types.h index 99cf9ea..d0ffa97 100644 --- a/src/interpreter/interpreter_types.h +++ b/src/interpreter/interpreter_types.h @@ -6,6 +6,9 @@ struct ASTFunctionParameter; struct ASTNode; struct Function; +// Forward declaration and type alias for Environment +typedef struct Environment Environment; + typedef enum { TYPE_BOOLEAN, TYPE_FLOAT, @@ -48,7 +51,7 @@ typedef struct Function { bool is_builtin; } Function; -typedef struct { +struct Environment { Variable *variables; size_t variable_count; size_t capacity; // to handle dynamic resizing @@ -56,12 +59,15 @@ typedef struct { Function *functions; // Array of functions size_t function_count; size_t function_capacity; -} Environment; + + Environment *parent; // Parent environment +}; typedef struct { - LiteralValue value; // The result of interpreting a node + LiteralValue value; // Result of interpreting a node bool did_return; // True if this node caused a function return to bubble up - bool did_break; + bool did_break; // True if this node caused a loop break to bubble up + bool is_error; // True if this node caused an error } InterpretResult; #endif diff --git a/src/interpreter/utils.c b/src/interpreter/utils.c index 915c6a8..883ea4a 100644 --- a/src/interpreter/utils.c +++ b/src/interpreter/utils.c @@ -1,16 +1,44 @@ #include "utils.h" +#include -void error_interpreter(const char *format, ...) { +InterpretResult raise_error(const char *format, ...) { + char error_message[1024]; va_list args; va_start(args, format); - printf("\033[31m"); // red text color - printf("Error: "); - vprintf(format, args); - printf("\033[0m\n"); // reset text color + // Format the error message into the buffer + vsnprintf(error_message, sizeof(error_message), format, args); + va_end(args); + + // Print "Error:" in red, followed by the formatted error message + printf("\033[31mError: %s\033[0m\n", error_message); + + // Create an error LiteralValue + LiteralValue error_value; + error_value.type = TYPE_ERROR; + error_value.data.string = strdup(error_message); + + if (!error_value.data.string) { + fprintf(stderr, + "Error: Failed to allocate memory for error message.\n"); + exit(1); + } + InterpretResult res = {.value = error_value, + .did_return = false, + .did_break = false, + .is_error = true}; + return res; +} + +void fatal_error(const char *format, ...) { + char error_message[1024]; + va_list args; + va_start(args, format); + fprintf(stderr, "\033[31mError: "); + vsnprintf(error_message, sizeof(error_message), format, args); + fprintf(stderr, "%s\033[0m\n", error_message); va_end(args); - fflush(stdout); exit(1); } @@ -35,36 +63,76 @@ void initialize_all_builtin_functions(Environment *env) { } } +// Function to initialize the environment without a parent (global) void init_environment(Environment *env) { - // Existing variable initialization + env->parent = NULL; + + // Initialize variables env->variable_count = 0; env->capacity = 10; env->variables = malloc(env->capacity * sizeof(Variable)); if (!env->variables) { - error_interpreter("Failed to allocate memory for variables.\n"); + fatal_error("Failed to allocate memory for variables.\n"); } - // Add function initialization + // Initialize functions env->function_count = 0; env->function_capacity = 10; env->functions = malloc(env->function_capacity * sizeof(Function)); if (!env->functions) { - error_interpreter("Failed to allocate memory for functions.\n"); + fatal_error("Failed to allocate memory for functions.\n"); } + // Initialize built-in functions ONLY for the GLOBAL environment initialize_all_builtin_functions(env); } +// Function to initialize the environment with a parent (local) +void init_environment_with_parent(Environment *env, Environment *parent) { + env->parent = parent; + + // Initialize variables + env->variable_count = 0; + env->capacity = 10; + env->variables = malloc(env->capacity * sizeof(Variable)); + if (!env->variables) { + fatal_error("Failed to allocate memory for variables.\n"); + } + + // Initialize functions + env->function_count = 0; + env->function_capacity = 10; + env->functions = malloc(env->function_capacity * sizeof(Function)); + if (!env->functions) { + fatal_error("Failed to allocate memory for functions.\n"); + } + + // Do NOT initialize built-in functions in local environments +} + +// Free the environment and its resources void free_environment(Environment *env) { // Free variables for (size_t i = 0; i < env->variable_count; i++) { free(env->variables[i].variable_name); + if (env->variables[i].value.type == TYPE_STRING) { + free(env->variables[i].value.data.string); + } } free(env->variables); // Free functions for (size_t i = 0; i < env->function_count; i++) { + // Free function name free(env->functions[i].name); + + // Free function parameters + free_parameter_list(env->functions[i].parameters); + + // Free function body if user-defined + if (!env->functions[i].is_builtin && env->functions[i].body) { + free(env->functions[i].body); + } } free(env->functions); } @@ -88,26 +156,30 @@ ASTFunctionParameter *copy_function_parameters(ASTFunctionParameter *params) { ASTFunctionParameter *tail = NULL; while (params) { - ASTFunctionParameter *new_param = malloc(sizeof(ASTFunctionParameter)); - if (!new_param) { - error_interpreter( - "Memory allocation failed for function parameter copy.\n"); + ASTFunctionParameter *copied_param = + malloc(sizeof(ASTFunctionParameter)); + if (!copied_param) { + fatal_error("Memory allocation failed for ASTFunctionParameter.\n"); } - new_param->parameter_name = strdup(params->parameter_name); - if (!new_param->parameter_name) { - free(new_param); - error_interpreter( - "Memory allocation failed for parameter name copy.\n"); + // Duplicate the parameter name + if (params->parameter_name) { + copied_param->parameter_name = strdup(params->parameter_name); + if (!copied_param->parameter_name) { + free(copied_param); + fatal_error("Memory allocation failed for parameter name.\n"); + } + } else { + copied_param->parameter_name = NULL; } - new_param->next = NULL; + copied_param->next = NULL; if (!new_params) { - new_params = tail = new_param; + new_params = tail = copied_param; } else { - tail->next = new_param; - tail = new_param; + tail->next = copied_param; + tail = copied_param; } params = params->next; @@ -117,29 +189,261 @@ ASTFunctionParameter *copy_function_parameters(ASTFunctionParameter *params) { } ASTNode *copy_ast_node(ASTNode *node) { - if (!node) + if (!node) { return NULL; + } + + debug_print_int("Copying ASTNode of type %d at %p\n", node->type, + (void *)node); ASTNode *new_node = malloc(sizeof(ASTNode)); if (!new_node) { - error_interpreter("Memory allocation failed in `copy_ast_node`\n"); + fatal_error("Memory allocation failed in `copy_ast_node`\n"); } - memcpy(new_node, node, sizeof(ASTNode)); - - // Deep copy for fields like `function_call`, `body`, or `arguments` - if (node->function_call.arguments) { + // Initialize the new node to zero to prevent dangling pointers + memset(new_node, 0, sizeof(ASTNode)); + + // Copy the node type + new_node->type = node->type; + + // Deep copy based on node type + switch (node->type) { + case AST_ASSIGNMENT: + if (node->assignment.variable_name) { + new_node->assignment.variable_name = + strdup(node->assignment.variable_name); + if (!new_node->assignment.variable_name) { + fatal_error("Memory allocation failed for variable name in " + "assignment.\n"); + } + } else { + new_node->assignment.variable_name = NULL; + } + new_node->assignment.value = copy_ast_node(node->assignment.value); + break; + + case AST_FUNCTION_DECLARATION: + if (node->function_declaration.name) { + new_node->function_declaration.name = + strdup(node->function_declaration.name); + if (!new_node->function_declaration.name) { + fatal_error("Memory allocation failed for function name in " + "declaration.\n"); + } + } else { + new_node->function_declaration.name = NULL; + } + new_node->function_declaration.parameters = + copy_function_parameters(node->function_declaration.parameters); + new_node->function_declaration.body = + copy_ast_node(node->function_declaration.body); + break; + + case AST_FUNCTION_CALL: + if (node->function_call.name) { + new_node->function_call.name = strdup(node->function_call.name); + if (!new_node->function_call.name) { + fatal_error( + "Memory allocation failed for function name in call.\n"); + } + } else { + new_node->function_call.name = NULL; + } new_node->function_call.arguments = copy_ast_node(node->function_call.arguments); + break; + + case AST_FUNCTION_RETURN: + new_node->function_return.return_data = + copy_ast_node(node->function_return.return_data); + break; + + case AST_LITERAL: + new_node->literal.type = node->literal.type; + switch (node->literal.type) { + case LITERAL_STRING: + if (node->literal.value.string) { + new_node->literal.value.string = + strdup(node->literal.value.string); + if (!new_node->literal.value.string) { + fatal_error( + "Memory allocation failed for string literal.\n"); + } + } else { + new_node->literal.value.string = NULL; + } + break; + case LITERAL_FLOAT: + new_node->literal.value.floating_point = + node->literal.value.floating_point; + break; + case LITERAL_INTEGER: + new_node->literal.value.integer = node->literal.value.integer; + break; + case LITERAL_BOOLEAN: + new_node->literal.value.boolean = node->literal.value.boolean; + break; + default: + fatal_error("Unknown literal type during copy.\n"); + } + break; + + case AST_CONDITIONAL: + new_node->conditional.condition = + copy_ast_node(node->conditional.condition); + new_node->conditional.body = copy_ast_node(node->conditional.body); + new_node->conditional.else_branch = + copy_ast_node(node->conditional.else_branch); + break; + + case AST_UNARY_OP: + if (node->unary_op.operator) { + new_node->unary_op.operator= strdup(node->unary_op.operator); + if (!new_node->unary_op.operator) { + fatal_error("Memory allocation failed for unary operator.\n"); + } + } else { + new_node->unary_op.operator= NULL; + } + new_node->unary_op.operand = copy_ast_node(node->unary_op.operand); + break; + + case AST_BINARY_OP: + if (node->binary_op.operator) { + new_node->binary_op.operator= strdup(node->binary_op.operator); + if (!new_node->binary_op.operator) { + fatal_error("Memory allocation failed for binary operator.\n"); + } + } else { + new_node->binary_op.operator= NULL; + } + new_node->binary_op.left = copy_ast_node(node->binary_op.left); + new_node->binary_op.right = copy_ast_node(node->binary_op.right); + break; + + case AST_WHILE_LOOP: + new_node->while_loop.condition = + copy_ast_node(node->while_loop.condition); + new_node->while_loop.body = copy_ast_node(node->while_loop.body); + new_node->while_loop.re_evaluate_condition = + node->while_loop.re_evaluate_condition; + break; + + case AST_FOR_LOOP: + if (node->for_loop.loop_variable) { + new_node->for_loop.loop_variable = + strdup(node->for_loop.loop_variable); + if (!new_node->for_loop.loop_variable) { + fatal_error("Memory allocation failed for loop variable in " + "for-loop.\n"); + } + } else { + new_node->for_loop.loop_variable = NULL; + } + new_node->for_loop.start_expr = + copy_ast_node(node->for_loop.start_expr); + new_node->for_loop.end_expr = copy_ast_node(node->for_loop.end_expr); + new_node->for_loop.inclusive = node->for_loop.inclusive; + new_node->for_loop.step_expr = copy_ast_node(node->for_loop.step_expr); + new_node->for_loop.body = copy_ast_node(node->for_loop.body); + break; + + case AST_SWITCH: + new_node->switch_case.expression = + copy_ast_node(node->switch_case.expression); + ASTCaseNode *current_case = node->switch_case.cases; + ASTCaseNode *new_case_head = NULL; + ASTCaseNode *new_case_tail = NULL; + while (current_case) { + ASTCaseNode *copied_case = malloc(sizeof(ASTCaseNode)); + if (!copied_case) { + fatal_error("Memory allocation failed for ASTCaseNode.\n"); + } + copied_case->condition = copy_ast_node(current_case->condition); + copied_case->body = copy_ast_node(current_case->body); + copied_case->next = NULL; + if (!new_case_head) { + new_case_head = new_case_tail = copied_case; + } else { + new_case_tail->next = copied_case; + new_case_tail = copied_case; + } + current_case = current_case->next; + } + new_node->switch_case.cases = new_case_head; + break; + + case AST_VARIABLE: + case AST_CONSTANT: + if (node->variable_name) { + new_node->variable_name = strdup(node->variable_name); + if (!new_node->variable_name) { + fatal_error( + "Memory allocation failed for variable/constant name.\n"); + } + } else { + new_node->variable_name = NULL; + } + break; + + case AST_BREAK: + // No fields to copy + break; + + case AST_TERNARY: + new_node->ternary.condition = copy_ast_node(node->ternary.condition); + new_node->ternary.true_expr = copy_ast_node(node->ternary.true_expr); + new_node->ternary.false_expr = copy_ast_node(node->ternary.false_expr); + break; + + case AST_TRY: + new_node->try_block.try_block = + copy_ast_node(node->try_block.try_block); + new_node->try_block.catch_blocks = + copy_catch_node(node->try_block.catch_blocks); + new_node->try_block.finally_block = + copy_ast_node(node->try_block.finally_block); + break; + + default: + fatal_error("Unknown ASTNodeType encountered in `copy_ast_node`.\n"); } - if (node->function_call.body) { - new_node->function_call.body = copy_ast_node(node->function_call.body); - } - // Handle other types like linked lists or child nodes as needed + new_node->next = copy_ast_node(node->next); + return new_node; } +ASTCatchNode *copy_catch_node(ASTCatchNode *catch_node) { + if (!catch_node) + return NULL; + + ASTCatchNode *new_catch = malloc(sizeof(ASTCatchNode)); + if (!new_catch) { + fatal_error("Memory allocation failed for ASTCatchNode.\n"); + } + + // Duplicate error variable name if it exists + if (catch_node->error_variable) { + new_catch->error_variable = strdup(catch_node->error_variable); + if (!new_catch->error_variable) { + free(new_catch); + fatal_error("Memory allocation failed for catch error variable.\n"); + } + } else { + new_catch->error_variable = NULL; + } + + // Recursively copy the catch body + new_catch->body = copy_ast_node(catch_node->body); + + // Recursively copy the next catch node + new_catch->next = copy_catch_node(catch_node->next); + + return new_catch; +} + void add_function(Environment *env, Function func) { // Check if this function name already exists in env for (size_t i = 0; i < env->function_count; i++) { @@ -155,7 +459,7 @@ void add_function(Environment *env, Function func) { if (!env->functions && env->function_capacity == 0) { env->functions = calloc(4, sizeof(Function)); if (!env->functions) { - error_interpreter("Initial allocation for functions failed.\n"); + fatal_error("Initial allocation for functions failed.\n"); } env->function_capacity = 4; } @@ -166,14 +470,14 @@ void add_function(Environment *env, Function func) { Function *new_functions = realloc(env->functions, new_capacity * sizeof(Function)); if (!new_functions) { - error_interpreter("Memory allocation failed for functions.\n"); + fatal_error("Memory allocation failed for functions.\n"); } env->functions = new_functions; env->function_capacity = new_capacity; } if (!func.name) { - error_interpreter("Function name is `NULL` or invalid.\n"); + fatal_error("Function name is `NULL` or invalid.\n"); } // Create a deep copy @@ -185,17 +489,83 @@ void add_function(Environment *env, Function func) { stored_func->name = strdup(func.name); if (!stored_func->name) { free(stored_func); - error_interpreter("Memory allocation failed for function name.\n"); + fatal_error("Memory allocation failed for function name.\n"); } debug_print_int("Function `%s` added successfully.\n", stored_func->name); } +/** + * Function to retrieve a function by name, traversing the environment chain. + */ Function *get_function(Environment *env, const char *name) { - for (size_t i = 0; i < env->function_count; i++) { - if (strcmp(env->functions[i].name, name) == 0) { - return &env->functions[i]; + Environment *current_env = env; + while (current_env) { + for (size_t i = 0; i < current_env->function_count; i++) { + if (strcmp(current_env->functions[i].name, name) == 0) { + return ¤t_env->functions[i]; + } } + current_env = current_env->parent; } return NULL; } + +// A helper to wrap `LiteralValue` in `InterpretResult` +InterpretResult make_result(LiteralValue val, bool did_return, bool did_break) { + InterpretResult r; + r.value = val; + r.did_return = did_return; + r.did_break = did_break; + r.is_error = false; + return r; +} + +bool is_valid_int(const char *str, INT_SIZE *out) { + char *endptr; + errno = 0; + long long val = strtoll(str, &endptr, 10); + if (errno != 0 || *endptr != '\0') { + return false; + } + *out = (INT_SIZE)val; + return true; +} + +bool is_valid_float(const char *str, FLOAT_SIZE *out) { + char *endptr; + errno = 0; + long double val = strtold(str, &endptr); + if (errno != 0 || *endptr != '\0') { + return false; + } + *out = (FLOAT_SIZE)val; + return true; +} + +// Type checking helpers +bool is_numeric_type(LiteralType type) { + return type == TYPE_INTEGER || type == TYPE_FLOAT; +} + +bool is_boolean_type(LiteralType type) { return type == TYPE_BOOLEAN; } + +// Convert LiteralType to string for error messages +const char *literal_type_to_string(LiteralType type) { + switch (type) { + case TYPE_INTEGER: + return "integer"; + case TYPE_FLOAT: + return "float"; + case TYPE_BOOLEAN: + return "boolean"; + case TYPE_STRING: + return "string"; + case TYPE_FUNCTION: + return "function"; + case TYPE_ERROR: + return "error"; + default: + return "unknown"; + } +} diff --git a/src/interpreter/utils.h b/src/interpreter/utils.h index 4c3dfe0..29e4653 100644 --- a/src/interpreter/utils.h +++ b/src/interpreter/utils.h @@ -8,14 +8,20 @@ #include #include -// Initialize the environment +// Initialize the environment without a parent (global) void init_environment(Environment *env); +// Initialize the environment with a parent (local) +void init_environment_with_parent(Environment *env, Environment *parent); + // Free the environment void free_environment(Environment *env); -void error_interpreter(const char *format, ...); +// Errors +InterpretResult raise_error(const char *format, ...); +void fatal_error(const char *format, ...); +// Functions void free_parameter_list(ASTFunctionParameter *head); ASTFunctionParameter * copy_function_parameters(ASTFunctionParameter *param_list); @@ -23,4 +29,17 @@ ASTNode *copy_ast_node(ASTNode *node); void add_function(Environment *env, Function func); Function *get_function(Environment *env, const char *name); +// Helpers +InterpretResult make_result(LiteralValue val, bool did_return, bool did_break); +ASTCatchNode *copy_catch_node(ASTCatchNode *catch_node); + +// Type Helpers +bool is_numeric_type(LiteralType type); +bool is_boolean_type(LiteralType type); +const char *literal_type_to_string(LiteralType type); + +// Casting Helpers +bool is_valid_int(const char *str, INT_SIZE *out); +bool is_valid_float(const char *str, FLOAT_SIZE *out); + #endif diff --git a/src/lexer/keywords.c b/src/lexer/keywords.c index 9788b62..d5c4fc7 100644 --- a/src/lexer/keywords.c +++ b/src/lexer/keywords.c @@ -16,8 +16,9 @@ const char *KEYWORDS[] = { "break", // break "create", // function "deliver", // return - "try", // try - "rescue", // catch + "try", // try block + "rescue", // catch block + "finish", // finally block "plate", // write file "garnish", // append file "taste", // read file @@ -76,4 +77,4 @@ int is_valid_identifier_char(char c) { return isalnum(c) || c == '_'; } int is_whitespace(char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; -} \ No newline at end of file +} diff --git a/src/lexer/keywords.h b/src/lexer/keywords.h index a63e1e6..435b3a7 100644 --- a/src/lexer/keywords.h +++ b/src/lexer/keywords.h @@ -103,4 +103,4 @@ int is_valid_identifier_char(char c); */ int is_whitespace(char c); -#endif \ No newline at end of file +#endif diff --git a/src/main.c b/src/main.c index 292ba87..008bb5a 100644 --- a/src/main.c +++ b/src/main.c @@ -30,7 +30,7 @@ void print_logo_from_file(const char *filename) { fclose(file); } -void print_about() { +void print_about(void) { printf("\n"); print_logo_from_file("../logo/logo.txt"); printf("\n"); @@ -101,4 +101,4 @@ int main(int argc, char **argv) { free_ast(ast); return 0; -} \ No newline at end of file +} diff --git a/src/parser/operator_parser.c b/src/parser/operator_parser.c index 9608402..bebc3d2 100644 --- a/src/parser/operator_parser.c +++ b/src/parser/operator_parser.c @@ -24,8 +24,7 @@ ASTNode *parse_ternary(ParserState *state) { // Recursively parse expression for `True` branch (allows for nesting) ASTNode *true_expr = parse_ternary(state); - expect_token(state, TOKEN_OPERATOR, - "Expected `:` in ternary expression"); + expect_token(state, TOKEN_COLON, "Expected `:` in ternary expression"); // Recursively parse expression for `False` branch ASTNode *false_expr = parse_ternary(state); @@ -362,7 +361,7 @@ ASTNode *create_function_call_node(char *name, ASTNode *args) { parser_error("Memory allocation failed for function call node", NULL); } node->type = AST_FUNCTION_CALL; - node->function_call.name = name; + node->function_call.name = strdup(name); node->function_call.arguments = args; node->next = NULL; return node; diff --git a/src/parser/operator_parser.h b/src/parser/operator_parser.h index 8f2d5f9..2243ef9 100644 --- a/src/parser/operator_parser.h +++ b/src/parser/operator_parser.h @@ -30,4 +30,4 @@ ASTNode *create_literal_node(Token *token); ASTNode *create_variable_node(char *name); ASTNode *create_function_call_node(char *name, ASTNode *args); -#endif \ No newline at end of file +#endif diff --git a/src/parser/parser.c b/src/parser/parser.c index 3b876da..9e94950 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -50,6 +50,8 @@ ASTNode *parse_statement(ParserState *state) { return parse_break_statement(state); if (match_token(state, "deliver")) return parse_function_return(state); + if (match_token(state, "try")) + return parse_try_block(state); // Handle function calls if (token->type == TOKEN_FUNCTION_NAME) { @@ -292,8 +294,7 @@ ASTNode *parse_function_return(ParserState *state) { } node->type = AST_FUNCTION_RETURN; - // node->function_call.return_data = parse_expression(state); - node->assignment.value = parse_expression(state); + node->function_return.return_data = parse_expression(state); node->next = NULL; expect_token(state, TOKEN_DELIMITER, @@ -749,10 +750,9 @@ ASTNode *parse_function_declaration(ParserState *state) { } node->type = AST_FUNCTION_DECLARATION; - node->function_call.name = strdup(name->lexeme); - node->function_call.parameters = NULL; - node->function_call.body = NULL; - node->function_call.return_data = NULL; + node->function_declaration.name = strdup(name->lexeme); + node->function_declaration.parameters = NULL; + node->function_declaration.body = NULL; node->next = NULL; advance_token(state); // Move past the function name @@ -763,9 +763,9 @@ ASTNode *parse_function_declaration(ParserState *state) { // Check if the parameter list is empty if (get_current_token(state)->type == TOKEN_PAREN_CLOSE) { - node->function_call.parameters = NULL; // No parameters + node->function_declaration.parameters = NULL; // No parameters } else { - node->function_call.parameters = parse_parameter_list(state); + node->function_declaration.parameters = parse_parameter_list(state); } expect_token(state, TOKEN_PAREN_CLOSE, @@ -779,7 +779,7 @@ ASTNode *parse_function_declaration(ParserState *state) { if (get_current_token(state)->type == TOKEN_BRACE_OPEN) { advance_token(state); // Consume `(` state->in_function_body = true; - node->function_call.body = + node->function_declaration.body = parse_function_body(state); // Parse function body state->in_function_body = false; @@ -820,3 +820,96 @@ ASTNode *parse_function_call(ParserState *state) { node->next = NULL; return node; } + +ASTNode *parse_try_block(ParserState *state) { + expect_token(state, TOKEN_KEYWORD, "Expected `try` keyword"); + expect_token(state, TOKEN_BRACE_OPEN, "Expected `{` to start try block"); + + // Parse `try` block statements + ASTNode *try_body = parse_block(state); + expect_token(state, TOKEN_BRACE_CLOSE, "Expected `}` to end try block"); + + // Initialize try structure + ASTTry try_block = {0}; + try_block.try_block = try_body; + try_block.catch_blocks = NULL; // initialize `rescue` (catch) blocks + try_block.finally_block = NULL; // initialize finish block + + // Parse optional `catch` blocks + while (match_token(state, "rescue")) { + ASTCatchNode *catch = parse_catch_block(state); + if (!try_block.catch_blocks) { + try_block.catch_blocks = catch; + } else { + // For future: Handle multiple `rescue` (catch) blocks + ASTCatchNode *last = try_block.catch_blocks; + while (last->next) { + last = last->next; + } + last->next = catch; + } + } + + // Parse optional `finish` block + if (match_token(state, "finish")) { + ASTNode *finally_body = parse_finally_block(state); + try_block.finally_block = finally_body; + } + + ASTNode *node = malloc(sizeof(ASTNode)); + if (!node) { + parser_error("Memory allocation failed for `try` block", + get_current_token(state)); + } + node->type = AST_TRY; + node->try_block = try_block; + node->next = NULL; + + return node; +} + +ASTCatchNode *parse_catch_block(ParserState *state) { + expect_token(state, TOKEN_KEYWORD, "Expected `rescue` keyword"); + + // Optional: Parse error object variable + char *error_var = NULL; + Token *current = get_current_token(state); + if (current->type == TOKEN_PAREN_OPEN) { + advance_token(state); // consume `(` + Token *var_token = get_current_token(state); + if (var_token->type == TOKEN_IDENTIFIER) { + error_var = strdup(var_token->lexeme); + if (!error_var) { + parser_error("Memory allocation failed for error variable name", + var_token); + } + advance_token(state); // consume variable name + } + expect_token(state, TOKEN_PAREN_CLOSE, + "Expected `)` after error variable"); + } + + expect_token(state, TOKEN_BRACE_OPEN, "Expected `{` to start rescue block"); + + // Parse `rescue` (catch) block statements + ASTNode *catch_body = parse_block(state); + expect_token(state, TOKEN_BRACE_CLOSE, "Expected `}` to end rescue block"); + + ASTCatchNode *catch_node = malloc(sizeof(ASTCatchNode)); + if (!catch_node) { + parser_error("Memory allocation failed for rescue block", current); + } + catch_node->error_variable = error_var; + catch_node->body = catch_body; + catch_node->next = NULL; + + return catch_node; +} + +ASTNode *parse_finally_block(ParserState *state) { + expect_token(state, TOKEN_KEYWORD, "Expected `finish` keyword"); + expect_token(state, TOKEN_BRACE_OPEN, "Expected `{` to start finish block"); + ASTNode *finally_body = parse_block(state); + expect_token(state, TOKEN_BRACE_CLOSE, "Expected `}` to end finish block"); + return finally_body; +} diff --git a/src/parser/parser.h b/src/parser/parser.h index 2c669b8..6d0a5d5 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -25,6 +25,9 @@ ASTNode *parse_switch_block(ParserState *state); ASTNode *parse_function_declaration(ParserState *state); ASTNode *parse_function_call(ParserState *state); ASTNode *parse_function_return(ParserState *state); +ASTNode *parse_try_block(ParserState *state); +ASTCatchNode *parse_catch_block(ParserState *state); +ASTNode *parse_finally_block(ParserState *state); // Expression parsing ASTNode *parse_expression(ParserState *state); diff --git a/src/parser/utils.c b/src/parser/utils.c index 610d639..183e1e5 100644 --- a/src/parser/utils.c +++ b/src/parser/utils.c @@ -53,11 +53,30 @@ void free_ast(ASTNode *node) { break; case AST_FUNCTION_DECLARATION: + free(node->function_declaration.name); + free_ast(node->function_declaration + .body); // free function body recursively + + // Free the function parameters + ASTFunctionParameter *param = node->function_declaration.parameters; + while (param) { + ASTFunctionParameter *next = param->next; + free(param->parameter_name); + free(param); + param = next; + } + + break; + case AST_FUNCTION_CALL: free(node->function_call.name); free_ast(node->function_call.arguments); break; + case AST_FUNCTION_RETURN: + free_ast(node->function_return.return_data); + break; + case AST_BREAK: // No cleanup needed! break; @@ -68,7 +87,7 @@ void free_ast(ASTNode *node) { free_ast(node->ternary.false_expr); break; - case AST_SWITCH: + case AST_SWITCH: { if (node->switch_case.expression) { free_ast(node->switch_case.expression); } @@ -95,6 +114,46 @@ void free_ast(ASTNode *node) { } break; + } + + case AST_TRY: + // Free try block + if (node->try_block.try_block) { + free_ast(node->try_block.try_block); + } + + // Free all catch blocks + if (node->try_block.catch_blocks) { + ASTCatchNode *catch_node = node->try_block.catch_blocks; + + while (catch_node) { + ASTCatchNode *next_catch = catch_node->next; + + // Free the error variable (if allocated) + if (catch_node->error_variable) { + free(catch_node->error_variable); + } + + // Free the body of the catch block + if (catch_node->body) { + free_ast(catch_node->body); + } + + free(catch_node); + catch_node = next_catch; + } + } + + // Free finally block + if (node->try_block.finally_block) { + free_ast(node->try_block.finally_block); + } + break; + + // Already handled by `AST_TRY` + case AST_CATCH: + case AST_FINALLY: + break; default: fprintf(stderr, @@ -135,12 +194,14 @@ void print_ast(ASTNode *node, int depth) { break; case AST_FUNCTION_DECLARATION: - printf("Function Declaration: `%s`\n", node->function_call.name); + printf("Function Declaration: `%s`\n", + node->function_declaration.name); // Print Parameters - if (node->function_call.parameters != NULL) { + if (node->function_declaration.parameters != NULL) { print_indentation(depth + 1); printf("Parameters:\n"); - ASTFunctionParameter *param = node->function_call.parameters; + ASTFunctionParameter *param = + node->function_declaration.parameters; while (param != NULL) { print_indentation(depth + 2); printf("- `%s`\n", param->parameter_name); @@ -151,10 +212,10 @@ void print_ast(ASTNode *node, int depth) { printf("Parameters: None\n"); } // Print Body - if (node->function_call.body != NULL) { + if (node->function_declaration.body != NULL) { print_indentation(depth + 1); printf("Body:\n"); - print_ast(node->function_call.body, depth + 2); + print_ast(node->function_declaration.body, depth + 2); } else { print_indentation(depth + 1); printf("Body: None\n"); @@ -176,8 +237,8 @@ void print_ast(ASTNode *node, int depth) { case AST_FUNCTION_RETURN: printf("Function Return:\n"); - if (node->function_call.return_data != NULL) { - print_ast(node->function_call.return_data, depth + 1); + if (node->function_return.return_data != NULL) { + print_ast(node->function_return.return_data, depth + 1); } else { print_indentation(depth + 1); printf("Return Data: None\n"); diff --git a/src/shared/ast_types.h b/src/shared/ast_types.h index e4593fc..10f616d 100644 --- a/src/shared/ast_types.h +++ b/src/shared/ast_types.h @@ -23,7 +23,10 @@ typedef enum { AST_BREAK, AST_TERNARY, AST_VARIABLE, - AST_CONSTANT + AST_CONSTANT, + AST_TRY, + AST_CATCH, + AST_FINALLY } ASTNodeType; // Literal Node @@ -100,16 +103,24 @@ typedef struct ASTFunctionParameter { struct ASTFunctionParameter *next; // Linked list for multiple parameters } ASTFunctionParameter; -// AST Function Call +// AST Function Declaration Node typedef struct { char *name; - ASTFunctionParameter - *parameters; // For function declarations, parameter names - struct ASTNode *arguments; // For function calls, argument values - struct ASTNode *body; - struct ASTNode *return_data; + ASTFunctionParameter *parameters; // Function parameters + struct ASTNode *body; // Function body +} ASTFunctionDeclaration; + +// AST Function Call Node +typedef struct { + char *name; + struct ASTNode *arguments; // Function call arguments } ASTFunctionCall; +// AST Function Return Node +typedef struct { + struct ASTNode *return_data; // Expression to return +} ASTFunctionReturn; + // AST Ternary typedef struct { struct ASTNode *condition; @@ -117,6 +128,20 @@ typedef struct { struct ASTNode *false_expr; } ASTTernary; +// AST Rescue Node (optional error object) +typedef struct ASTCatchNode { + char *error_variable; // Optional: Variable name to hold the error object + struct ASTNode *body; // Body of the rescue block + struct ASTCatchNode *next; // For multiple rescue clauses (future feature) +} ASTCatchNode; + +// AST Try Node +typedef struct { + struct ASTNode *try_block; // Code within the try block + ASTCatchNode *catch_blocks; // Linked list of rescue blocks + struct ASTNode *finally_block; // Optional finish block +} ASTTry; + // AST Node Structure typedef struct ASTNode { ASTNodeType type; @@ -127,32 +152,21 @@ typedef struct ASTNode { struct ASTNode *value; } assignment; - // Literal LiteralNode literal; - - // Unary operation ASTUnaryOp unary_op; - - // Binary operation ASTBinaryOp binary_op; - - // Conditional ASTConditional conditional; - - // Switch ASTSwitch switch_case; - - // While loop ASTWhileLoop while_loop; - - // For loop ASTForLoop for_loop; - - // Function + ASTTernary ternary; + ASTFunctionDeclaration function_declaration; ASTFunctionCall function_call; + ASTFunctionReturn function_return; - // Ternary - ASTTernary ternary; + ASTTry try_block; // Try Block + // ASTCatchNode *catch_block; // Rescue Block + // struct ASTNode *finally_block; // Finish Block // Variable char *variable_name; diff --git a/src/tests/17_nested_function_application.flv b/src/tests/17_nested_function_application.flv index 171ac95..19fb74a 100644 --- a/src/tests/17_nested_function_application.flv +++ b/src/tests/17_nested_function_application.flv @@ -10,5 +10,5 @@ create test(num, func_a, func_b) { deliver func_b(func_a(func_b(num))); } -let a = test(10, times_2, times_3); # 180 +const a = test(10, times_2, times_3); # 180 serve(a); diff --git a/src/tests/7_try_catch.flv b/src/tests/7_try_catch.flv index 7d88e9f..705cd5b 100644 --- a/src/tests/7_try_catch.flv +++ b/src/tests/7_try_catch.flv @@ -1,6 +1,16 @@ try { + serve("This will run!"); burn("This recipe failed!"); serve("This won't run!"); } rescue { serve("Caught an error: Recipe needs improvement."); } + +try { + int("abc"); + serve("This won't run!"); +} rescue { + serve("(Expect a) Runtime error: Can't cast string `abc` to int."); +} finish { + serve("This always executes!"); +} diff --git a/src/tests/all.flv b/src/tests/all.flv index c02683c..6e20ffb 100644 --- a/src/tests/all.flv +++ b/src/tests/all.flv @@ -75,12 +75,22 @@ serve(result); # ================================================== # 7 # ================================================== -# try { -# burn("This recipe failed!"); -# serve("This won't run!"); -# } rescue { -# serve("Caught an error: Recipe needs improvement."); -# } +try { + serve("This will run!"); + burn("This recipe failed!"); + serve("This won't run!"); +} rescue { + serve("Caught an error: Recipe needs improvement."); +} + +try { + int("abc"); + serve("This won't run!"); +} rescue { + serve("(Expect a) Runtime error: Can't cast string `abc` to int."); +} finish { + serve("This always executes!"); +} # ================================================== @@ -188,8 +198,8 @@ serve(float(False)); # ================================================== # Using `sample()` to get user input serve("Enter your favorite number:"); -const favorite = sample(); -serve("Your favorite is:", favorite); +const favorite2 = sample(); +serve("Your favorite is:", favorite2); # Using `random()` with different argument counts const num1 = random(); # Generates between 0.0 and 1.0 diff --git a/vscode-extension/package.json b/vscode-extension/package.json index 8a7e50a..f768179 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -2,7 +2,7 @@ "name": "flavorlang-vscode", "displayName": "FlavorLang Support", "description": "Syntax highlighting for FlavorLang programming language.", - "version": "1.2.3", + "version": "1.3.0", "publisher": "KennyOliver", "repository": { "type": "git", diff --git a/vscode-extension/syntaxes/flavorlang.tmLanguage.json b/vscode-extension/syntaxes/flavorlang.tmLanguage.json index 5fb112e..9859a4a 100644 --- a/vscode-extension/syntaxes/flavorlang.tmLanguage.json +++ b/vscode-extension/syntaxes/flavorlang.tmLanguage.json @@ -8,7 +8,9 @@ { "include": "#functions" }, { "include": "#numbers" }, { "include": "#operators" }, - { "include": "#variables" } + { "include": "#variables" }, + { "include": "#ranges" }, + { "include": "#error-handling" } ], "repository": { "comments": { @@ -38,7 +40,7 @@ "patterns": [ { "name": "keyword.control.flavorlang", - "match": "\\b(let|const|if|elif|else|for|in|while|create|burn|deliver|check|is|rescue|try|break|continue)\\b" + "match": "\\b(let|const|if|elif|else|for|in|while|create|burn|deliver|check|is|rescue|try|rescue|finish|break|continue)\\b" }, { "name": "keyword.other.flavorlang", @@ -49,8 +51,12 @@ "functions": { "patterns": [ { - "name": "entity.name.function.flavorlang", - "match": "\\b(serve|burn|deliver|sample|plate_file|garnish_file|taste_file|create|check|rescue|string|int|float|get_time)\\b(?=\\()" + "name": "entity.name.function.create.flavorlang", + "match": "\\bcreate\\b\\s+[a-zA-Z_][a-zA-Z0-9_]*\\b" + }, + { + "name": "entity.name.function.call.flavorlang", + "match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b(?=\\()" } ] }, @@ -58,11 +64,11 @@ "patterns": [ { "name": "constant.numeric.integer.flavorlang", - "match": "\\b\\d+\\b" + "match": "\\b-?\\d+\\b" }, { "name": "constant.numeric.float.flavorlang", - "match": "\\b\\d+\\.\\d+\\b" + "match": "\\b-?\\d+\\.\\d+\\b" } ] }, @@ -85,6 +91,22 @@ "match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b" } ] + }, + "ranges": { + "patterns": [ + { + "name": "meta.range.flavorlang", + "match": "\\b\\d+\\.\\.=?\\d+\\b" + } + ] + }, + "error-handling": { + "patterns": [ + { + "name": "keyword.control.error-handling.flavorlang", + "match": "\\b(burn|rescue|try|deliver)\\b" + } + ] } }, "scopeName": "source.flavorlang"