diff --git a/docs/language_design.md b/docs/language_design.md index a8363e3..a47f54c 100644 --- a/docs/language_design.md +++ b/docs/language_design.md @@ -10,7 +10,9 @@ This `docs/` page details the core design of FlavorLang's syntax, the various da 2. [Data Types](#data-types) -3. [Extended Backus-Naur Form (EBNF)](#ebnf) +3. [Arrays](#arrays) + +4. [Extended Backus-Naur Form (EBNF)](#ebnf) --- @@ -73,6 +75,32 @@ This `docs/` page details the core design of FlavorLang's syntax, the various da --- +## Arrays + +> [!Note] +> +> All array operations do not mutate original array to reduce confusion. + +| **Operation** | **Syntax** | **Explanation** | +| ------------------------ | --------------- | ----------------------------------------------------------------------------------- | +| **Element Access** | `array[5]` | Accesses element at index `5`. | +| **Append** | `array[^+]` | Appends an element to end. | +| **Prepend** | `array[+^]` | Prepends an element to beginning. | +| **Remove Last Element** | `array[^-]` | Removes last element. | +| **Remove First Element** | `array[-^]` | Removes first element. | +| **Slicing** | `array[0:5]` | Slices from index `0` (incl.) to `5` (excl.). | +| | `array[::-1]` | Reverses using step of `-1`. | +| | `array[3:]` | Slices from index `3` (incl.) to end. | +| | `array[:8]` | Slices from start to index `8` (excl.). | +| | `array[3:8:2]` | Slices from index `3` (incl.) to `8` (excl.), skipping every 2nd element. | +| | `array[8:3:-2]` | Slices from index `8` (incl.) to `3` (excl.), skipping every 2nd element (reverse). | +| | `array[3::-1]` | Slices from index `3` (incl.) to start (reversed). | +| | `array[:8:-1]` | Slices from end towards index `8` (excl., reversed). | +| | `array[::2]` | Slices entire array, skipping every 2nd element. | +| | `array[::-3]` | Reverses entire array, skipping every 3rd element. | + +--- + ## Extended Backus-Naur Form (EBNF) of FlavorLang's Syntax ```ebnf diff --git a/src/interpreter/builtins.c b/src/interpreter/builtins.c index a771a69..1aa4d3b 100644 --- a/src/interpreter/builtins.c +++ b/src/interpreter/builtins.c @@ -241,6 +241,45 @@ InterpretResult builtin_random(ASTNode *node, Environment *env) { return make_result(result, false, false); } +void print_literal_value(LiteralValue lv) { + switch (lv.type) { + case TYPE_FLOAT: + if ((INT_SIZE)lv.data.floating_point == lv.data.floating_point) { + printf("%.1Lf", lv.data.floating_point); + } else { + printf("%Lf", lv.data.floating_point); + } + break; + case TYPE_INTEGER: + printf("%lld", lv.data.integer); + break; + case TYPE_STRING: + printf("\"%s\"", lv.data.string); + break; + case TYPE_BOOLEAN: + printf("%s", lv.data.boolean ? "True" : "False"); + break; + case TYPE_ARRAY: + printf("["); + for (size_t i = 0; i < lv.data.array.count; i++) { + print_literal_value(lv.data.array.elements[i]); + if (i < lv.data.array.count - 1) { + printf(", "); + } + } + printf("]"); + break; + case TYPE_FUNCTION: + printf("", lv.data.function_ptr->name); + break; + case TYPE_ERROR: + printf(""); + break; + default: + printf(""); + } +} + // Built-in `serve()` function for printing InterpretResult builtin_output(ASTNode *node, Environment *env) { debug_print_int("builtin_output() called\n"); @@ -248,6 +287,10 @@ InterpretResult builtin_output(ASTNode *node, Environment *env) { ASTNode *arg_node = node->function_call.arguments; while (arg_node != NULL) { InterpretResult r = interpret_node(arg_node, env); + if (r.is_error) { + return r; // propagate error + } + LiteralValue lv = r.value; switch (lv.type) { @@ -267,6 +310,18 @@ InterpretResult builtin_output(ASTNode *node, Environment *env) { case TYPE_BOOLEAN: printf("%s", lv.data.boolean ? "True" : "False"); break; + case TYPE_ARRAY: + printf("["); + for (size_t i = 0; i < lv.data.array.count; i++) { + LiteralValue elem = lv.data.array.elements[i]; + print_literal_value(elem); + + if (i < lv.data.array.count - 1) { + printf(", "); + } + } + printf("]"); + break; case TYPE_ERROR: fprintf( stderr, diff --git a/src/interpreter/builtins.h b/src/interpreter/builtins.h index ae85290..2468bef 100644 --- a/src/interpreter/builtins.h +++ b/src/interpreter/builtins.h @@ -39,5 +39,6 @@ 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); char *process_escape_sequences(const char *input); +void print_literal_value(LiteralValue lv); #endif diff --git a/src/interpreter/interpreter.c b/src/interpreter/interpreter.c index 0426b93..a78bad5 100644 --- a/src/interpreter/interpreter.c +++ b/src/interpreter/interpreter.c @@ -24,11 +24,27 @@ InterpretResult interpret_node(ASTNode *node, Environment *env) { result = interpret_literal(node); break; + case AST_VAR_DECLARATION: { + debug_print_int("\tMatched: `AST_VAR_DECLARATION`\n"); + result = interpret_var_declaration(node, env); + break; + } + + case AST_CONST_DECLARATION: { + debug_print_int("\tMatched: `AST_CONST_DECLARATION`\n"); + result = interpret_const_declaration(node, env); + break; + } + case AST_ASSIGNMENT: debug_print_int("\tMatched: `AST_ASSIGNMENT`\n"); result = interpret_assignment(node, env); break; + case AST_VARIABLE_REFERENCE: + result = interpret_variable_reference(node, env); + break; + case AST_UNARY_OP: debug_print_int("\tMatched: `AST_UNARY_OP`\n"); result = interpret_unary_op(node, env); @@ -108,19 +124,6 @@ InterpretResult interpret_node(ASTNode *node, Environment *env) { break; } - case AST_VAR_DECLARATION: { - debug_print_int("\tMatched: `AST_VAR_DECLARATION`\n"); - // Replace LiteralValue with InterpretResult - result = interpret_variable(node, env); - break; - } - - case AST_CONST_DECLARATION: { - debug_print_int("\tMatched: `AST_CONST_DECLARATION`\n"); - result = interpret_constant(node, env); - break; - } - case AST_SWITCH: { debug_print_int("\tMatched: `AST_SWITCH`\n"); result = interpret_switch(node, env); @@ -142,6 +145,26 @@ InterpretResult interpret_node(ASTNode *node, Environment *env) { result = interpret_try(node, env); break; + case AST_ARRAY_LITERAL: + debug_print_int("\tMatched: `AST_ARRAY_LITERAL`\n"); + result = interpret_array_literal(node, env); + break; + + case AST_ARRAY_OPERATION: + debug_print_int("\tMatched: `AST_ARRAY_OPERATION`\n"); + result = interpret_array_operation(node, env); + break; + + case AST_ARRAY_INDEX_ACCESS: + debug_print_int("\tMatched: `AST_ARRAY_INDEX_ACCESS`\n"); + result = interpret_array_index_access(node, env); + break; + + case AST_ARRAY_SLICE_ACCESS: + debug_print_int("\tMatched: `AST_ARRAY_SLICE_ACCESS`\n"); + result = interpret_array_slice_access(node, env); + break; + default: return raise_error("Unsupported `ASTNode` type.\n"); } @@ -201,7 +224,11 @@ InterpretResult interpret_literal(ASTNode *node) { return make_result(value, false, false); } -InterpretResult interpret_variable(ASTNode *node, Environment *env) { +InterpretResult interpret_variable_reference(ASTNode *node, Environment *env) { + if (node->type != AST_VARIABLE_REFERENCE) { + return raise_error("Expected AST_VARIABLE_REFERENCE node.\n"); + } + Variable *var = get_variable(env, node->variable_name); if (!var) { return raise_error("Undefined variable `%s`.\n", node->variable_name); @@ -210,90 +237,133 @@ InterpretResult interpret_variable(ASTNode *node, Environment *env) { return make_result(var->value, false, false); } -InterpretResult interpret_constant(ASTNode *node, Environment *env) { - if (node->type != AST_CONST_DECLARATION) { - return raise_error("Invalid node type for constant declaration.\n"); +InterpretResult interpret_var_declaration(ASTNode *node, Environment *env) { + if (node->type != AST_VAR_DECLARATION) { + return raise_error("Invalid node type for variable declaration.\n"); } - // Extract the constant name and value - char *const_name = strdup(node->assignment.variable_name); - InterpretResult value_res = interpret_node(node->assignment.value, env); - LiteralValue const_value = value_res.value; + char *var_name = node->var_declaration.variable_name; + ASTNode *init_expr = node->var_declaration.initializer; - if (value_res.is_error) { - return value_res; + // Evaluate the initializer expression + InterpretResult init_val_res = interpret_node(init_expr, env); + if (init_val_res.is_error) { + return init_val_res; // propagate the error + } + + // If that variable already exists in Environment, possibly handle + // re-declaration For now, just add a brand-new variable + Variable new_var; + new_var.variable_name = strdup(var_name); + new_var.value = init_val_res.value; + new_var.is_constant = false; + + // Add the variable to Environment + InterpretResult add_res = add_variable(env, new_var); + if (add_res.is_error) { + return add_res; + } + + return make_result(init_val_res.value, false, false); +} + +InterpretResult interpret_const_declaration(ASTNode *node, Environment *env) { + if (node->type != AST_CONST_DECLARATION) { + return raise_error("Invalid node type for constant declaration.\n"); } - // Check if the constant already exists - Variable *existing_var = get_variable(env, const_name); - if (existing_var) { - return raise_error("Constant `%s` is already defined.\n", const_name); + char *const_name = node->const_declaration.constant_name; + ASTNode *init_expr = node->const_declaration.initializer; + + // Evaluate initializer + InterpretResult init_val_res = interpret_node(init_expr, env); + if (init_val_res.is_error) { + return init_val_res; } // Add the constant to the environment - Variable new_var = {.variable_name = strdup(const_name), - .value = const_value, - .is_constant = true}; - add_variable(env, new_var); + Variable new_var; + new_var.variable_name = strdup(const_name); + new_var.value = init_val_res.value; + new_var.is_constant = true; + + InterpretResult add_res = add_variable(env, new_var); + if (add_res.is_error) { + return add_res; + } - return make_result(const_value, false, false); + return make_result(init_val_res.value, false, false); } InterpretResult interpret_assignment(ASTNode *node, Environment *env) { - if (!node->assignment.variable_name) { - debug_print_int("Assignment variable name missing for node type: %d\n", - node->type); - return make_result((LiteralValue){.type = TYPE_ERROR}, false, false); + if (node->type != AST_ASSIGNMENT) { + return raise_error("Invalid node type for assignment.\n"); } - // Evaluate the right-hand side - InterpretResult assign_r = interpret_node(node->assignment.value, env); - - // If the RHS triggered an error or break, propagate it - if (assign_r.is_error || assign_r.did_break) { - return assign_r; + // Interpret the RHS expression + InterpretResult rhs_val_res = interpret_node(node->assignment.rhs, env); + if (rhs_val_res.is_error) { + return rhs_val_res; // propagate the error } - LiteralValue new_value = assign_r.value; + // Determine the LHS type + ASTNode *lhs_node = node->assignment.lhs; - // 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; + switch (lhs_node->type) { + case AST_VARIABLE_REFERENCE: { + const char *var_name = lhs_node->variable_name; + + Variable *var = get_variable(env, var_name); + if (!var) { + // Variable not found; optionally handle declaration here + Variable new_var; + new_var.variable_name = strdup(var_name); + if (!new_var.variable_name) { + return raise_error( + "Memory allocation failed for variable name `%s`.\n", + var_name); + } + new_var.value = rhs_val_res.value; + new_var.is_constant = false; + + InterpretResult add_res = add_variable(env, new_var); + if (add_res.is_error) { + free(new_var.variable_name); // clean up on error + return add_res; } + + return add_res; } - if (existing_var) { - break; + + if (var->is_constant) { + return raise_error("Cannot reassign to constant `%s`.\n", var_name); } - target_env = target_env->parent; - } - // Determine the scope to modify - Environment *scope_to_modify = existing_var ? target_env : env; + // If assigning to a string, free the old string to prevent memory leaks + if (var->value.type == TYPE_STRING && var->value.data.string) { + free(var->value.data.string); + } - // Create a new variable instance - Variable new_var = {.variable_name = strdup(node->assignment.variable_name), - .value = new_value, - .is_constant = false}; + var->value = rhs_val_res.value; - // Assign the variable - InterpretResult add_res = add_variable(scope_to_modify, new_var); - if (add_res.is_error) { - return add_res; + return rhs_val_res; } - // Now, if a return was triggered during assignment, propagate it - if (assign_r.did_return) { - return assign_r; + case AST_ARRAY_INDEX_ACCESS: { + return interpret_array_index_assignment(lhs_node, env, + rhs_val_res.value); } - // Otherwise, return the assigned value - return make_result(new_value, false, false); + case AST_ARRAY_SLICE_ACCESS: { + return interpret_array_slice_access(node, env); + } + + case AST_ARRAY_OPERATION: + return interpret_array_operation(node, env); + + default: + return raise_error("Invalid LHS in assignment.\n"); + } } InterpretResult handle_string_concatenation(InterpretResult left, @@ -363,10 +433,12 @@ InterpretResult handle_numeric_operator(const char *op, (left.type == TYPE_FLOAT || right.type == TYPE_FLOAT); // 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; + FLOAT_SIZE left_val = (left.type == TYPE_FLOAT) + ? left.data.floating_point + : (FLOAT_SIZE)left.data.integer; + FLOAT_SIZE right_val = (right.type == TYPE_FLOAT) + ? right.data.floating_point + : (FLOAT_SIZE)right.data.integer; LiteralValue result; memset(&result, 0, sizeof(LiteralValue)); @@ -406,7 +478,7 @@ InterpretResult handle_numeric_operator(const char *op, if (right_val == 0.0) { return raise_error("Floor division by zero.\n"); } - double div_result = left_val / right_val; + FLOAT_SIZE div_result = left_val / right_val; if (result_is_float) { result.type = TYPE_FLOAT; result.data.floating_point = floor(div_result); @@ -724,6 +796,12 @@ Variable *get_variable(Environment *env, const char *variable_name) { current_env->variables[i] .value.data.function_ptr->name); break; + case TYPE_ARRAY: + debug_print_int( + "Variable found: `%s` with array of %zu elements.\n", + variable_name, + current_env->variables[i].value.data.array.count); + break; default: debug_print_int("Variable found: `%s` with unknown type.\n", variable_name); @@ -743,6 +821,8 @@ InterpretResult add_variable(Environment *env, Variable var) { 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) { + debug_print_int("Attempted to reassign to constant `%s`\n", + var.variable_name); return raise_error("Error: Cannot reassign to constant `%s`.\n", var.variable_name); } @@ -754,7 +834,12 @@ InterpretResult add_variable(Environment *env, Variable var) { } env->variables[i].value = var.value; - env->variables[i].is_constant = var.is_constant; + // Do not modify is_constant when updating existing variable + // env->variables[i].is_constant = var.is_constant; + + debug_print_int("Updated variable `%s` with is_constant=%d\n", + var.variable_name, env->variables[i].is_constant); + return make_result(var.value, false, false); } } @@ -803,6 +888,9 @@ InterpretResult add_variable(Environment *env, Variable var) { env->variables[env->variable_count].is_constant = var.is_constant; env->variable_count++; + debug_print_int("Added variable `%s` with is_constant=%d\n", + var.variable_name, var.is_constant); + return make_result(var.value, false, false); } @@ -993,12 +1081,12 @@ InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { return end_res; } - // Determine start & end as doubles for flexibility - double start_val, end_val; + // Determine start & end as FLOAT_SIZEs for flexibility + FLOAT_SIZE 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 = (double)start_res.value.data.integer; + start_val = (FLOAT_SIZE)start_res.value.data.integer; } else { free(loop_var); return raise_error("Start expression in `for` loop must be numeric\n"); @@ -1007,14 +1095,14 @@ InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { 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 = (double)end_res.value.data.integer; + end_val = (FLOAT_SIZE)end_res.value.data.integer; } else { 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 + FLOAT_SIZE step = 1.0; // default if (step_expr) { InterpretResult step_res = interpret_node(step_expr, env); if (step_res.is_error) { @@ -1025,7 +1113,7 @@ InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { if (step_res.value.type == TYPE_FLOAT) { step = step_res.value.data.floating_point; } else if (step_res.value.type == TYPE_INTEGER) { - step = (double)step_res.value.data.integer; + step = (FLOAT_SIZE)step_res.value.data.integer; } else { free(loop_var); return raise_error( @@ -1081,11 +1169,11 @@ InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { loop_var); } - double current_val; + FLOAT_SIZE current_val; if (var->value.type == TYPE_FLOAT) { current_val = var->value.data.floating_point; } else if (var->value.type == TYPE_INTEGER) { - current_val = (double)var->value.data.integer; + current_val = (FLOAT_SIZE)var->value.data.integer; } else { free(loop_var); return raise_error("Loop variable `%s` must be numeric\n", @@ -1562,3 +1650,542 @@ InterpretResult interpret_try(ASTNode *node, Environment *env) { // Normal execution return result; } + +/** + * @brief Interprets array literals like `[1, 2, 3]`. + * + * @param node + * @param env + * @return InterpretResult + */ +InterpretResult interpret_array_literal(ASTNode *node, Environment *env) { + if (node->type != AST_ARRAY_LITERAL) { + return raise_error("Expected AST_ARRAY_LITERAL node.\n"); + } + + ArrayValue array; + array.count = 0; + array.capacity = node->array_literal.count > 4 ? node->array_literal.count + : 4; // initial capacity + array.elements = malloc(array.capacity * sizeof(LiteralValue)); + if (!array.elements) { + return raise_error("Memory allocation failed for array elements.\n"); + } + + for (size_t i = 0; i < node->array_literal.count; i++) { + ASTNode *element_node = node->array_literal.elements[i]; + InterpretResult elem_res = interpret_node(element_node, env); + if (elem_res.is_error) { + // Free previously allocated elements if necessary + // For simplicity, assume no deep copies needed here + free(array.elements); + return elem_res; // propagate the error + } + + // Add the element to the array + if (array.count == array.capacity) { + size_t new_capacity = array.capacity * 2; + LiteralValue *new_elements = + realloc(array.elements, new_capacity * sizeof(LiteralValue)); + if (!new_elements) { + free(array.elements); + return raise_error( + "Memory allocation failed while expanding array.\n"); + } + array.elements = new_elements; + array.capacity = new_capacity; + } + + array.elements[array.count++] = elem_res.value; + } + + LiteralValue result; + result.type = TYPE_ARRAY; + result.data.array = array; // by value + + return make_result(result, false, false); +} + +InterpretResult interpret_array_operation(ASTNode *node, Environment *env) { + if (node->type == AST_ASSIGNMENT) { + // Handle assignment-based array operations (append, prepend) + ASTNode *lhs_node = node->assignment.lhs; + ASTNode *rhs_node = node->assignment.rhs; + + if (lhs_node->type != AST_ARRAY_OPERATION) { + return raise_error("Expected AST_ARRAY_OPERATION in assignment.\n"); + } + + const char *operator= lhs_node->array_operation.operator; + ASTNode *array_node = lhs_node->array_operation.array; + + if (array_node->type != AST_VARIABLE_REFERENCE) { + return raise_error("Array operation requires a variable reference " + "as the array.\n"); + } + + const char *var_name = array_node->variable_name; + + // Retrieve variable from environment + Variable *var = get_variable(env, var_name); + if (!var) { + return raise_error("Undefined variable `%s`.\n", var_name); + } + + if (var->value.type != TYPE_ARRAY) { + return raise_error("Array operation requires an array variable.\n"); + } + + // Check for `const`-ness + if (var->is_constant) { + return raise_error("Cannot mutate a constant array `%s`.\n", + var_name); + } + + // Access the ArrayValue by reference + ArrayValue *array = &var->value.data.array; + + // Interpret the operand (RHS of assignment) + InterpretResult operand_res = interpret_node(rhs_node, env); + if (operand_res.is_error) { + return operand_res; + } + + // Perform operation based on operator + if (strcmp(operator, "^+") == 0) { // Append + if (array->count == array->capacity) { + size_t new_capacity = array->capacity * 2; + LiteralValue *new_elements = realloc( + array->elements, new_capacity * sizeof(LiteralValue)); + if (!new_elements) { + return raise_error( + "Memory allocation failed while expanding array.\n"); + } + array->elements = new_elements; + array->capacity = new_capacity; + } + array->elements[array->count++] = operand_res.value; + // Return the modified array + return make_result(var->value, false, false); + } else if (strcmp(operator, "+^") == 0) { // Prepend + if (array->count == array->capacity) { + size_t new_capacity = array->capacity * 2; + LiteralValue *new_elements = realloc( + array->elements, new_capacity * sizeof(LiteralValue)); + if (!new_elements) { + return raise_error( + "Memory allocation failed while expanding array.\n"); + } + array->elements = new_elements; + array->capacity = new_capacity; + } + + // Shift elements to the right + memmove(&array->elements[1], &array->elements[0], + array->count * sizeof(LiteralValue)); + array->elements[0] = operand_res.value; + array->count++; + // Return the modified array + return make_result(var->value, false, false); + } else { + return raise_error( + "Unsupported array operation operator `%s` in assignment.\n", + operator); + } + } else if (node->type == AST_ARRAY_OPERATION) { + // Handle standalone array operations (remove last, remove first) + const char *operator= node->array_operation.operator; + ASTNode *array_node = node->array_operation.array; + + if (array_node->type != AST_VARIABLE_REFERENCE) { + return raise_error("Array operation requires a variable reference " + "as the array.\n"); + } + + const char *var_name = array_node->variable_name; + + // Retrieve variable from environment + Variable *var = get_variable(env, var_name); + if (!var) { + return raise_error("Undefined variable `%s`.\n", var_name); + } + + if (var->value.type != TYPE_ARRAY) { + return raise_error("Array operation requires an array variable.\n"); + } + + // Check for `const`-ness + if (var->is_constant) { + return raise_error("Cannot mutate a constant array `%s`.\n", + var_name); + } + + // Access the ArrayValue by reference + ArrayValue *array = &var->value.data.array; + + // Perform operation based on operator + if (strcmp(operator, "^-") == 0) { // Remove Last Element + if (array->count == 0) { + return raise_error("Cannot remove from an empty array.\n"); + } + LiteralValue removed = array->elements[array->count - 1]; + array->count--; + return make_result(removed, false, false); + } else if (strcmp(operator, "-^") == 0) { // Remove First Element + if (array->count == 0) { + return raise_error("Cannot remove from an empty array.\n"); + } + LiteralValue removed = array->elements[0]; + // Shift elements to the left + memmove(&array->elements[0], &array->elements[1], + (array->count - 1) * sizeof(LiteralValue)); + array->count--; + return make_result(removed, false, false); + } else { + return raise_error( + "Unsupported array operation operator `%s`.\n", operator); + } + } else { + return raise_error("Unsupported node type for array operation.\n"); + } +} + +/** + * @brief Handles accessing array elements like `array[2]`. + * + * @param node + * @param env + * @return InterpretResult + */ +InterpretResult interpret_array_index_access(ASTNode *node, Environment *env) { + if (node->type != AST_ARRAY_INDEX_ACCESS) { + return raise_error("Expected AST_ARRAY_INDEX_ACCESS node.\n"); + } + + ASTNode *array_node = node->array_index_access.array; + ASTNode *index_node = node->array_index_access.index; + + // Interpret the array + InterpretResult array_res = interpret_node(array_node, env); + if (array_res.is_error) { + return array_res; + } + + if (array_res.value.type != TYPE_ARRAY) { + return raise_error("Index access requires an array operand.\n"); + } + + // Access the ArrayValue by reference + ArrayValue *array = &array_res.value.data.array; + + // Interpret the index + InterpretResult index_res = interpret_node(index_node, env); + if (index_res.is_error) { + return index_res; + } + + // Index must be integer + if (index_res.value.type != TYPE_INTEGER) { + return raise_error("Array index must be an integer.\n"); + } + + INT_SIZE index = index_res.value.data.integer; + + // Handle negative indices (e.g., -1 refers to the last element) + if (index < 0) { + index = (INT_SIZE)array->count + index; + } + + if (index < 0 || (size_t)index >= array->count) { + return raise_error("Array index `%lld` out of bounds.\n", index); + } + + // Access the element + LiteralValue element = array->elements[index]; + return make_result(element, false, false); +} + +InterpretResult interpret_array_index_assignment(ASTNode *node, + Environment *env, + LiteralValue new_value) { + if (node->type != AST_ARRAY_INDEX_ACCESS) { + return raise_error("Expected AST_ARRAY_INDEX_ACCESS node.\n"); + } + + ASTNode *array_node = node->array_index_access.array; + ASTNode *index_node = node->array_index_access.index; + + // Interpret the array + InterpretResult array_res = interpret_node(array_node, env); + if (array_res.is_error) { + return array_res; + } + + if (array_res.value.type != TYPE_ARRAY) { + return raise_error("Index access requires an array operand.\n"); + } + + // Retrieve the variable to check for `const`-ness + if (array_node->type != AST_VARIABLE_REFERENCE) { + return raise_error("Index assignment requires a variable reference.\n"); + } + const char *var_name = array_node->variable_name; + Variable *var = get_variable(env, var_name); + if (!var) { + return raise_error("Undefined variable `%s`.\n", var_name); + } + + if (var->is_constant) { + debug_print_int("Attempted to assign to const array `%s`\n", var_name); + return raise_error("Cannot mutate a constant array `%s`.\n", var_name); + } + + // Access the ArrayValue by reference + ArrayValue *array = &var->value.data.array; + + // Interpret the index + InterpretResult index_res = interpret_node(index_node, env); + if (index_res.is_error) { + return index_res; + } + + // Index must be integer + if (index_res.value.type != TYPE_INTEGER) { + return raise_error("Array index must be an integer.\n"); + } + + INT_SIZE index = index_res.value.data.integer; + + // Handle negative indices (e.g., -1 refers to the last element) + if (index < 0) { + index = (INT_SIZE)array->count + index; + } + + if (index < 0 || (size_t)index >= array->count) { + return raise_error("Array index `%lld` out of bounds.\n", index); + } + + // Assign the new value to the array element + array->elements[index] = new_value; + return make_result(new_value, false, false); +} + +// Helper function to get the index of a variable in the environment +int get_variable_index(Environment *env, const char *variable_name) { + for (size_t i = 0; i < env->variable_count; i++) { + if (strcmp(env->variables[i].variable_name, variable_name) == 0) { + return (int)i; + } + } + + // Not found + return -1; +} + +/** + * @brief Handles slicing arrays like `array[1:4]`. + * + * @param node + * @param env + * @return InterpretResult + */ +InterpretResult interpret_array_slice_access(ASTNode *node, Environment *env) { + if (node->type != AST_ARRAY_SLICE_ACCESS) { + return raise_error("Expected AST_ARRAY_SLICE_ACCESS node.\n"); + } + + ASTNode *array_node = node->array_slice_access.array; + ASTNode *start_node = node->array_slice_access.start; + ASTNode *end_node = node->array_slice_access.end; + ASTNode *step_node = node->array_slice_access.step; + + // Interpret the array + InterpretResult array_res = interpret_node(array_node, env); + if (array_res.is_error) { + return array_res; + } + + if (array_res.value.type != TYPE_ARRAY) { + return raise_error("Slice access requires an array operand.\n"); + } + + // Access the ArrayValue by reference + ArrayValue *array = &array_res.value.data.array; + size_t array_count = array->count; + + debug_print_int("Array `%s` has %zu elements.\n", array_node->variable_name, + array_count); + + // Interpret the step first to determine default start and end + FLOAT_SIZE step_val = 1.0; // default step + if (step_node) { + InterpretResult step_res = interpret_node(step_node, env); + if (step_res.is_error) { + return step_res; + } + if (step_res.value.type != TYPE_INTEGER && + step_res.value.type != TYPE_FLOAT) { + return raise_error("Slice step must be integer or float.\n"); + } + step_val = (step_res.value.type == TYPE_INTEGER) + ? (FLOAT_SIZE)step_res.value.data.integer + : step_res.value.data.floating_point; + } + + if (step_val == 0.0) { + return raise_error("Slice step cannot be zero.\n"); + } + + debug_print_int( + "Slice parameters: start_node=%p, end_node=%p, step_val=%Lf\n", + start_node, end_node, step_val); + + // Set default start and end based on step direction + FLOAT_SIZE start_val; + FLOAT_SIZE end_val; + if (step_val > 0) { + start_val = 0.0; // Default start for positive step + end_val = (FLOAT_SIZE)array_count; // Default end for positive step + } else { // step_val < 0 + start_val = + (FLOAT_SIZE)(array_count - 1); // Default start for negative step + end_val = -1.0; // Default end for negative step + } + + // Interpret start if provided + if (start_node) { + InterpretResult start_res = interpret_node(start_node, env); + if (start_res.is_error) { + return start_res; + } + if (start_res.value.type != TYPE_INTEGER && + start_res.value.type != TYPE_FLOAT) { + return raise_error("Slice start must be integer or float.\n"); + } + start_val = (start_res.value.type == TYPE_INTEGER) + ? (FLOAT_SIZE)start_res.value.data.integer + : (step_val > 0) + ? floor(start_res.value.data.floating_point) + : ceil(start_res.value.data.floating_point); + } + + // Interpret end if provided + if (end_node) { + InterpretResult end_res = interpret_node(end_node, env); + if (end_res.is_error) { + return end_res; + } + if (end_res.value.type != TYPE_INTEGER && + end_res.value.type != TYPE_FLOAT) { + return raise_error("Slice end must be integer or float.\n"); + } + end_val = (end_res.value.type == TYPE_INTEGER) + ? (FLOAT_SIZE)end_res.value.data.integer + : (step_val > 0) ? ceil(end_res.value.data.floating_point) + : floor(end_res.value.data.floating_point); + } + + debug_print_int("Interpreted slice values: start_val=%Lf, end_val=%Lf\n", + start_val, end_val); + + // Convert to integer indices + INT_SIZE start_index = (INT_SIZE)floor(start_val); + INT_SIZE end_index = (INT_SIZE)ceil(end_val); + INT_SIZE step = (INT_SIZE)step_val; + + // Handle negative indices based on step direction + if (step > 0) { + if (start_index < 0) + start_index += (INT_SIZE)array_count; + if (end_index < 0) + end_index += (INT_SIZE)array_count; + } else { // step < 0 + if (start_index < 0) + start_index += (INT_SIZE)array_count; + // Do NOT adjust end_index for negative steps + } + + debug_print_int( + "Converted indices: start_index=%lld, end_index=%lld, step=%lld\n", + start_index, end_index, step); + + // Clamp indices based on step direction + if (step > 0) { + if (start_index < 0) + start_index = 0; + if ((size_t)start_index > array_count) + start_index = array_count; + if (end_index < 0) + end_index = 0; + if ((size_t)end_index > array_count) + end_index = array_count; + } else { // step < 0 + if (start_index >= (INT_SIZE)array_count) + start_index = (INT_SIZE)array_count - 1; + if (start_index < 0) + start_index = (INT_SIZE)array_count - 1; + if (end_index < -1) + end_index = -1; + if (end_index >= (INT_SIZE)array_count) + end_index = -1; + } + + // Determine the number of elements in the slice + size_t slice_count = 0; + if (step > 0) { + if (start_index >= end_index) { + slice_count = 0; + } else { + slice_count = (size_t)((end_index - start_index + step - 1) / step); + } + } else { // step < 0 + if (start_index <= end_index) { + slice_count = 0; + } else { + slice_count = + (size_t)((start_index - end_index - step - 1) / (-step)); + } + } + + debug_print_int("Calculated slice_count=%zu\n", slice_count); + + // Initialize the slice array + ArrayValue slice; + slice.count = 0; + slice.capacity = slice_count > 4 ? slice_count : 4; + slice.elements = malloc(slice.capacity * sizeof(LiteralValue)); + if (!slice.elements) { + return raise_error("Memory allocation failed for array slice.\n"); + } + + // Populate the slice + for (INT_SIZE i = start_index; + (step > 0 && i < end_index) || (step < 0 && i > end_index); + i += step) { + debug_print_int("Loop iteration: i=%lld\n", i); + + if (i < 0 || (size_t)i >= array_count) { + debug_print_int("Index out of bounds: i=%lld, array_count=%zu\n", i, + array_count); + break; + } + slice.elements[slice.count++] = array->elements[i]; + if (slice.elements[slice.count - 1].type == TYPE_INTEGER) { + debug_print_int("Added element: %lld (count=%zu)\n", + slice.elements[slice.count - 1].data.integer, + slice.count); + } else { + debug_print_int("Added element of non-integer type (count=%zu)\n", + slice.count); + } + } + + debug_print_int("Final slice count=%zu\n", slice.count); + + // Create the slice LiteralValue + LiteralValue result; + result.type = TYPE_ARRAY; + result.data.array = slice; // by value + + return make_result(result, false, false); +} diff --git a/src/interpreter/interpreter.h b/src/interpreter/interpreter.h index f11e16d..6849eb3 100644 --- a/src/interpreter/interpreter.h +++ b/src/interpreter/interpreter.h @@ -15,8 +15,9 @@ InterpretResult interpret_node(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_variable_reference(ASTNode *node, Environment *env); +InterpretResult interpret_var_declaration(ASTNode *node, Environment *env); +InterpretResult interpret_const_declaration(ASTNode *node, Environment *env); InterpretResult interpret_assignment(ASTNode *node, Environment *env); InterpretResult interpret_binary_op(ASTNode *node, Environment *env); InterpretResult interpret_conditional(ASTNode *node, Environment *env); @@ -33,6 +34,13 @@ InterpretResult call_user_defined_function(Function *func_ref, ASTNode *call_node, Environment *env); InterpretResult interpret_try(ASTNode *node, Environment *env); +InterpretResult interpret_array_literal(ASTNode *node, Environment *env); +InterpretResult interpret_array_operation(ASTNode *node, Environment *env); +InterpretResult interpret_array_index_access(ASTNode *node, Environment *env); +InterpretResult interpret_array_index_assignment(ASTNode *node, + Environment *env, + LiteralValue new_value); +InterpretResult interpret_array_slice_access(ASTNode *node, Environment *env); // Interpret program void interpret_program(ASTNode *program, Environment *env); diff --git a/src/interpreter/interpreter_types.h b/src/interpreter/interpreter_types.h index d0ffa97..3a9c590 100644 --- a/src/interpreter/interpreter_types.h +++ b/src/interpreter/interpreter_types.h @@ -1,68 +1,91 @@ #ifndef INTERPRETER_TYPES_H #define INTERPRETER_TYPES_H +#include +#include +#include +#include +#include +#include + // Forward declarations struct ASTFunctionParameter; struct ASTNode; struct Function; - -// Forward declaration and type alias for Environment typedef struct Environment Environment; +struct LiteralValue; +// Enum for Literal Types typedef enum { TYPE_BOOLEAN, TYPE_FLOAT, TYPE_INTEGER, TYPE_STRING, + TYPE_ARRAY, TYPE_FUNCTION, TYPE_ERROR } LiteralType; +// Enum for Return Types typedef enum { RETURN_NORMAL, RETURN_ERROR } ReturnType; -typedef struct { +// Structure for Array Values +typedef struct ArrayValue { + struct LiteralValue *elements; // Dynamic array of `LiteralValue`s + size_t count; // Number of elements + size_t capacity; // Allocated capacity +} ArrayValue; + +// Structure for Literal Values +typedef struct LiteralValue { LiteralType type; union { char *string; - FLOAT_SIZE floating_point; - INT_SIZE integer; + long double floating_point; // Assuming FLOAT_SIZE is long double + long long integer; // Assuming INT_SIZE is long long bool boolean; struct Function *function_ptr; + struct ArrayValue array; // **By value** } data; } LiteralValue; +// Structure for Function Results typedef struct { LiteralValue value; bool should_return; // Flag to indicate if function should return early bool is_error; // Flag to indicate if this is an error return (burn) } FunctionResult; +// Structure for Variables typedef struct { char *variable_name; LiteralValue value; bool is_constant; } Variable; +// Structure for Functions typedef struct Function { char *name; - ASTFunctionParameter *parameters; // Linked list of parameters - ASTNode *body; // Function body + struct ASTFunctionParameter *parameters; // Linked list of parameters + struct ASTNode *body; // Function body FunctionResult return_value; bool is_builtin; } Function; +// Structure for Environment struct Environment { Variable *variables; size_t variable_count; - size_t capacity; // to handle dynamic resizing + size_t capacity; // To handle dynamic resizing Function *functions; // Array of functions size_t function_count; size_t function_capacity; - Environment *parent; // Parent environment + Environment *parent; // Parent environment for nested scopes }; +// Structure for Interpret Results typedef struct { LiteralValue value; // Result of interpreting a node bool did_return; // True if this node caused a function return to bubble up diff --git a/src/interpreter/utils.c b/src/interpreter/utils.c index f31f0d3..b740105 100644 --- a/src/interpreter/utils.c +++ b/src/interpreter/utils.c @@ -188,52 +188,70 @@ ASTFunctionParameter *copy_function_parameters(ASTFunctionParameter *params) { return new_params; } -ASTNode *copy_ast_node(ASTNode *node) { - if (!node) { +// Helper function for safely duplicating strings +char *safe_strdup(const char *str) { + if (!str) + return NULL; + char *copy = strdup(str); + if (!copy) { + fatal_error("Memory allocation failed for string duplication.\n"); + } + return copy; +} + +// Helper function for copying ASTCaseNode linked lists +ASTCaseNode *copy_ast_case_node(ASTCaseNode *case_node) { + if (!case_node) return NULL; + + ASTCaseNode *new_head = NULL, *new_tail = NULL; + + while (case_node) { + ASTCaseNode *new_case = malloc(sizeof(ASTCaseNode)); + if (!new_case) { + fatal_error("Memory allocation failed for ASTCaseNode.\n"); + } + + new_case->condition = copy_ast_node(case_node->condition); + new_case->body = copy_ast_node(case_node->body); + new_case->next = NULL; + + if (!new_head) { + new_head = new_tail = new_case; + } else { + new_tail->next = new_case; + new_tail = new_case; + } + + case_node = case_node->next; } + return new_head; +} + +ASTNode *copy_ast_node(ASTNode *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)); + ASTNode *new_node = calloc(1, sizeof(ASTNode)); if (!new_node) { fatal_error("Memory allocation failed in `copy_ast_node`\n"); } - // 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); + new_node->assignment.lhs = copy_ast_node(node->assignment.lhs); + new_node->assignment.rhs = copy_ast_node(node->assignment.rhs); 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.name = + safe_strdup(node->function_declaration.name); new_node->function_declaration.parameters = copy_function_parameters(node->function_declaration.parameters); new_node->function_declaration.body = @@ -241,15 +259,7 @@ ASTNode *copy_ast_node(ASTNode *node) { 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.name = safe_strdup(node->function_call.name); new_node->function_call.arguments = copy_ast_node(node->function_call.arguments); break; @@ -261,31 +271,12 @@ ASTNode *copy_ast_node(ASTNode *node) { 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"); + if (node->literal.type == LITERAL_STRING) { + new_node->literal.value.string = + safe_strdup(node->literal.value.string); + } else { + new_node->literal.value = + node->literal.value; // Copy union directly } break; @@ -298,26 +289,12 @@ ASTNode *copy_ast_node(ASTNode *node) { 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.operator= safe_strdup(node->unary_op.operator); 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.operator= safe_strdup(node->binary_op.operator); 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; @@ -331,16 +308,8 @@ ASTNode *copy_ast_node(ASTNode *node) { 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.loop_variable = + safe_strdup(node->for_loop.loop_variable); 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); @@ -352,43 +321,22 @@ ASTNode *copy_ast_node(ASTNode *node) { 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; + new_node->switch_case.cases = + copy_ast_case_node(node->switch_case.cases); break; case AST_VAR_DECLARATION: - case AST_CONST_DECLARATION: - 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; - } + new_node->var_declaration.variable_name = + safe_strdup(node->var_declaration.variable_name); + new_node->var_declaration.initializer = + copy_ast_node(node->var_declaration.initializer); break; - case AST_BREAK: - // No fields to copy + case AST_CONST_DECLARATION: + new_node->const_declaration.constant_name = + safe_strdup(node->const_declaration.constant_name); + new_node->const_declaration.initializer = + copy_ast_node(node->const_declaration.initializer); break; case AST_TERNARY: @@ -406,6 +354,57 @@ ASTNode *copy_ast_node(ASTNode *node) { copy_ast_node(node->try_block.finally_block); break; + case AST_BREAK: + case AST_CATCH: + case AST_FINALLY: + // Nothing to copy + break; + + case AST_ARRAY_LITERAL: + new_node->array_literal.count = node->array_literal.count; + new_node->array_literal.elements = + malloc(sizeof(ASTNode *) * node->array_literal.count); + if (!new_node->array_literal.elements) { + fatal_error( + "Memory allocation failed for array literal elements.\n"); + } + for (size_t i = 0; i < node->array_literal.count; i++) { + new_node->array_literal.elements[i] = + copy_ast_node(node->array_literal.elements[i]); + } + break; + + case AST_ARRAY_OPERATION: + new_node->array_operation.operator= + safe_strdup(node->array_operation.operator); + new_node->array_operation.array = + copy_ast_node(node->array_operation.array); + new_node->array_operation.operand = + copy_ast_node(node->array_operation.operand); + break; + + case AST_ARRAY_INDEX_ACCESS: + new_node->array_index_access.array = + copy_ast_node(node->array_index_access.array); + new_node->array_index_access.index = + copy_ast_node(node->array_index_access.index); + break; + + case AST_ARRAY_SLICE_ACCESS: + new_node->array_slice_access.array = + copy_ast_node(node->array_slice_access.array); + new_node->array_slice_access.start = + copy_ast_node(node->array_slice_access.start); + new_node->array_slice_access.end = + copy_ast_node(node->array_slice_access.end); + new_node->array_slice_access.step = + copy_ast_node(node->array_slice_access.step); + break; + + case AST_VARIABLE_REFERENCE: + new_node->variable_name = safe_strdup(node->variable_name); + break; + default: fatal_error("Unknown ASTNodeType encountered in `copy_ast_node`.\n"); } diff --git a/src/interpreter/utils.h b/src/interpreter/utils.h index 29e4653..a5e81c5 100644 --- a/src/interpreter/utils.h +++ b/src/interpreter/utils.h @@ -32,6 +32,8 @@ 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); +ASTCaseNode *copy_ast_case_node(ASTCaseNode *case_node); +char *safe_strdup(const char *str); // Type Helpers bool is_numeric_type(LiteralType type); diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index e1edfb8..958ff03 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -72,6 +72,12 @@ Token *tokenize(const char *source) { continue; } + // Array + if (strchr("[]", c)) { + scan_array(&state, &tokens, &token_count, &capacity); + continue; + } + // Identifier if (is_valid_identifier_start(c)) { scan_identifier_or_keyword(&state, &tokens, &token_count, diff --git a/src/lexer/scanner.c b/src/lexer/scanner.c index 78218c2..2109d3f 100644 --- a/src/lexer/scanner.c +++ b/src/lexer/scanner.c @@ -20,49 +20,182 @@ void scan_number(ScannerState *state, Token **tokens, size_t *token_count, TokenType prev_type = (*tokens)[*token_count - 1].type; if (prev_type == TOKEN_INTEGER || prev_type == TOKEN_FLOAT || prev_type == TOKEN_IDENTIFIER || prev_type == TOKEN_STRING || - prev_type == TOKEN_PAREN_CLOSE) { + prev_type == TOKEN_PAREN_CLOSE || + prev_type == TOKEN_SQ_BRACKET_CLOSE) { is_start_of_expression = false; } } if (is_start_of_expression) { is_negative = true; - state->pos++; // move past the negative sign + state->pos++; // Move past the negative sign } else { + // The '-' is not part of a negative number; treat it as an operator scan_operator(state, tokens, token_count, capacity); return; } } + // After handling '-', the next character must be a digit if (state->pos >= state->length || !isdigit(state->source[state->pos])) { if (is_negative) { - state->pos = start; // reset position + // The '-' was not followed by a digit; treat it as an operator + state->pos = start; // Reset position to include '-' scan_operator(state, tokens, token_count, capacity); return; } token_error("Invalid number format", state->line); } + // Continue scanning digits and possible decimal point while (state->pos < state->length && (isdigit(state->source[state->pos]) || (state->source[state->pos] == '.' && !has_decimal_point))) { if (state->source[state->pos] == '.') { if (state->pos + 1 >= state->length || !isdigit(state->source[state->pos + 1])) { - break; // if there's no digit after the decimal point, treat the - // decimal as a separate token + break; // If there's no digit after '.', treat '.' as a separate + // token } has_decimal_point = true; } state->pos++; } - char *lexeme = strndup(&state->source[start], state->pos - start); + // Calculate the end position + size_t end = state->pos; + + // Allocate and construct the lexeme + char *lexeme; + if (is_negative) { + // Allocate space for '-' and the number + size_t lexeme_length = end - start; + lexeme = malloc(lexeme_length + 1); // +1 for null terminator + if (!lexeme) { + token_error("Memory allocation failed for lexeme", state->line); + } + lexeme[0] = '-'; + // Copy the number part after '-' + strncpy(lexeme + 1, &state->source[start + 1], lexeme_length - 1); + lexeme[lexeme_length] = '\0'; + } else { + // Positive number; no '-' sign + lexeme = strndup(&state->source[start], end - start); + if (!lexeme) { + token_error("Memory allocation failed for lexeme", state->line); + } + } + + // Determine the token type based on the presence of a decimal point TokenType type = has_decimal_point ? TOKEN_FLOAT : TOKEN_INTEGER; + + // Append the token to the tokens array append_token(tokens, token_count, capacity, type, lexeme, state->line); + + // Free the lexeme if append_token duplicates it free(lexeme); } +void scan_array(ScannerState *state, Token **tokens, size_t *token_count, + size_t *capacity) { + char c = state->source[state->pos]; + + if (c == '[') { + // Add opening bracket token + append_token(tokens, token_count, capacity, TOKEN_SQ_BRACKET_OPEN, "[", + state->line); + state->pos++; // Move past `[` + + // Scan inside the brackets + while (state->pos < state->length && state->source[state->pos] != ']') { + char inner_c = state->source[state->pos]; + + // Handle whitespace + if (is_whitespace(inner_c)) { + if (inner_c == '\n') { + state->line++; + } + state->pos++; + continue; + } + + // Handle special array operations first + if (inner_c == '^' || inner_c == '+' || inner_c == '-') { + // Peek the next character + char next_c = (state->pos + 1 < state->length) + ? state->source[state->pos + 1] + : '\0'; + + // Handle two-character array operators (e.g., ^+, +^, ^-, -^) + if (next_c == '^' || next_c == '+' || next_c == '-') { + // Two-character array operator + char *op = strndup(&state->source[state->pos], 2); + append_token(tokens, token_count, capacity, TOKEN_ARRAY_OP, + op, state->line); + free(op); + state->pos += 2; // Move past the two-character operator + continue; + } + // **Handle negative numbers (e.g., -1) within slicing** + else if (inner_c == '-' && (state->pos + 1 < state->length) && + isdigit(state->source[state->pos + 1])) { + // Treat '-' as part of a negative number + scan_number(state, tokens, token_count, capacity); + continue; + } else { + // Single-character array operator (if valid) + char op[2] = {inner_c, '\0'}; + append_token(tokens, token_count, capacity, TOKEN_ARRAY_OP, + op, state->line); + state->pos++; + continue; + } + } + + // Handle numbers (including negative numbers) after array operators + if (isdigit(inner_c) || + (inner_c == '-' && state->pos + 1 < state->length && + isdigit(state->source[state->pos + 1]))) { + scan_number(state, tokens, token_count, capacity); + continue; + } + + // Handle separators `,` + if (inner_c == ',') { + append_token(tokens, token_count, capacity, TOKEN_DELIMITER, + ",", state->line); + state->pos++; + continue; + } + + // Handle slicing syntax `:` + if (inner_c == ':') { + append_token(tokens, token_count, capacity, TOKEN_COLON, ":", + state->line); + state->pos++; + continue; + } + + // Unexpected character + fprintf(stderr, + "Unexpected character `%c` at position %zu (line %d)\n", + inner_c, state->pos, state->line); + token_error("Unexpected character in array", state->line); + } + + // Check for closing bracket + if (state->pos < state->length && state->source[state->pos] == ']') { + append_token(tokens, token_count, capacity, TOKEN_SQ_BRACKET_CLOSE, + "]", state->line); + state->pos++; // Move past `]`. + } else { + token_error("Unmatched opening bracket `[`", state->line); + } + } else if (c == ']') { + token_error("Unmatched closing bracket `]`", state->line); + } +} + void scan_string(ScannerState *state, Token **tokens, size_t *token_count, size_t *capacity) { size_t start = ++(state->pos); // skip opening quote diff --git a/src/lexer/scanner.h b/src/lexer/scanner.h index bde640a..0b382e6 100644 --- a/src/lexer/scanner.h +++ b/src/lexer/scanner.h @@ -60,6 +60,25 @@ void scan_number(ScannerState *state, Token **tokens, size_t *token_count, void scan_string(ScannerState *state, Token **tokens, size_t *token_count, size_t *capacity); +/** + * Processes an array literal or access in the source code. + * + * This function is triggered when encountering an opening or closing square + * bracket (`[` or `]`). It determines whether the brackets represent an array + * literal, an index access, or slicing operations. It ensures proper matching + * of brackets and validates the syntax. + * + * If mismatched or unexpected brackets are encountered, it triggers an error. + * + * @param state The current scanning state, which includes the source code, + * the current position, and the line number. + * @param tokens A pointer to the token array being built. + * @param token_count The current number of tokens. + * @param capacity The allocated capacity for the token array. + */ +void scan_array(ScannerState *state, Token **tokens, size_t *token_count, + size_t *capacity); + /** * Processes a boolean literal in the source code. * diff --git a/src/parser/array_parser.c b/src/parser/array_parser.c new file mode 100644 index 0000000..c81a5de --- /dev/null +++ b/src/parser/array_parser.c @@ -0,0 +1,178 @@ +#include "array_parser.h" + +ASTNode *parse_array_literal(ParserState *state) { + ASTNode *node = malloc(sizeof(ASTNode)); + if (!node) { + parser_error("Memory allocation failed for array literal", + get_current_token(state)); + } + node->type = AST_ARRAY_LITERAL; + node->array_literal.elements = NULL; + node->array_literal.count = 0; + node->next = NULL; + + expect_token(state, TOKEN_SQ_BRACKET_OPEN, + "Expected `[` to start array literal"); + + // Initialize dynamic array for elements + size_t capacity = 4; // initial capacity + node->array_literal.elements = malloc(sizeof(ASTNode *) * capacity); + if (!node->array_literal.elements) { + parser_error("Memory allocation failed for array elements", + get_current_token(state)); + } + + // Handle empty array + if (get_current_token(state)->type == TOKEN_SQ_BRACKET_CLOSE) { + expect_token(state, TOKEN_SQ_BRACKET_CLOSE, + "Expected `]` to close array literal"); + return node; + } + + while (get_current_token(state)->type != TOKEN_SQ_BRACKET_CLOSE) { + // Parse the next expression + ASTNode *element = parse_expression(state); + if (!element) { + parser_error("Failed to parse array element", + get_current_token(state)); + } + + // Add the element to the array + if (node->array_literal.count >= capacity) { + capacity *= 2; + ASTNode **new_elements = realloc(node->array_literal.elements, + sizeof(ASTNode *) * capacity); + if (!new_elements) { + parser_error( + "Memory allocation failed while resizing array elements", + get_current_token(state)); + } + node->array_literal.elements = new_elements; + } + node->array_literal.elements[node->array_literal.count++] = element; + + // Check for comma `,` separator + if (get_current_token(state)->type == TOKEN_DELIMITER && + strcmp(get_current_token(state)->lexeme, ",") == 0) { + advance_token(state); // consume comma `,` + // Allow trailing comma before closing bracket + if (get_current_token(state)->type == TOKEN_SQ_BRACKET_CLOSE) { + break; + } + } else { + // No more elements + break; + } + } + + expect_token(state, TOKEN_SQ_BRACKET_CLOSE, + "Expected `]` to close array literal"); + + return node; +} + +ASTNode *parse_index_access(ASTNode *array, ParserState *state) { + ASTNode *node = malloc(sizeof(ASTNode)); + if (!node) { + parser_error("Memory allocation failed for index access", + get_current_token(state)); + } + + // Ensure we start with `[` + expect_token(state, TOKEN_SQ_BRACKET_OPEN, + "Expected `[` for array indexing or slicing"); + + Token *current = get_current_token(state); + + // Check if it's a slice (contains `:`) + bool is_slice = false; + size_t temp_token = state->current_token; + + // Iterate through tokens until `]` to detect a `:` for slicing + while (state->tokens[temp_token].type != TOKEN_SQ_BRACKET_CLOSE && + state->tokens[temp_token].type != TOKEN_EOF) { + if (state->tokens[temp_token].type == TOKEN_COLON) { + is_slice = true; + break; + } + temp_token++; + } + + if (is_slice) { + // It's a slice + node->type = AST_ARRAY_SLICE_ACCESS; + node->array_slice_access.array = array; + node->array_slice_access.start = NULL; + node->array_slice_access.end = NULL; + node->array_slice_access.step = NULL; + + // Parse start expression (optional) + if (get_current_token(state)->type != TOKEN_COLON) { + node->array_slice_access.start = parse_expression(state); + } + + // Expect and consume first `:` + expect_token(state, TOKEN_COLON, "Expected `:` in slice expression"); + + // Parse end expression (optional) + if (get_current_token(state)->type != TOKEN_COLON && + get_current_token(state)->type != TOKEN_SQ_BRACKET_CLOSE) { + node->array_slice_access.end = parse_expression(state); + } + + // Check for optional step + if (get_current_token(state)->type == TOKEN_COLON) { + advance_token(state); // consume second `:` + if (get_current_token(state)->type != TOKEN_SQ_BRACKET_CLOSE) { + node->array_slice_access.step = parse_expression(state); + } else { + // Handle cases like `[::]` + node->array_slice_access.step = NULL; + } + } + + // Expect and consume `]` + expect_token(state, TOKEN_SQ_BRACKET_CLOSE, + "Expected `]` to close slice expression"); + node->next = NULL; + } else if (is_array_operator(current)) { + // It's an array operator like `^+`, `+^`, `^-`, `-^` + node->type = AST_ARRAY_OPERATION; + node->array_operation.operator= strdup(current->lexeme); + if (!node->array_operation.operator) { + parser_error("Memory allocation failed for array operator", + current); + } + + node->array_operation.array = array; + node->next = NULL; + advance_token(state); // consume the operator + + // Expect and consume `]` + expect_token(state, TOKEN_SQ_BRACKET_CLOSE, + "Expected `]` after array operator"); + } else { + // It's a simple index + node->type = AST_ARRAY_INDEX_ACCESS; + node->array_index_access.array = array; + node->array_index_access.index = parse_expression(state); + + // Expect and consume `]` + expect_token(state, TOKEN_SQ_BRACKET_CLOSE, + "Expected `]` to close index expression"); + node->next = NULL; + } + + return node; +} + +// Helper function to check if a token is an array operator +bool is_array_operator(Token *token) { + if (token->type != TOKEN_ARRAY_OP) { + return false; + } + + return strcmp(token->lexeme, "^+") == 0 || + strcmp(token->lexeme, "+^") == 0 || + strcmp(token->lexeme, "^-") == 0 || strcmp(token->lexeme, "-^") == 0; +} diff --git a/src/parser/array_parser.h b/src/parser/array_parser.h new file mode 100644 index 0000000..5e4b280 --- /dev/null +++ b/src/parser/array_parser.h @@ -0,0 +1,17 @@ +#ifndef ARRAY_PARSER_H +#define ARRAY_PARSER_H + +#include "../shared/ast_types.h" +#include "../shared/token_types.h" +#include "parser.h" +#include "parser_state.h" +#include "utils.h" +#include + +ASTNode *parse_array_literal(ParserState *state); +ASTNode *parse_index_access(ASTNode *array, ParserState *state); + +// Helpers +bool is_array_operator(Token *token); + +#endif diff --git a/src/parser/operator_parser.c b/src/parser/operator_parser.c index d4ba84d..68c7290 100644 --- a/src/parser/operator_parser.c +++ b/src/parser/operator_parser.c @@ -5,6 +5,7 @@ #include #include "../shared/ast_types.h" +#include "array_parser.h" #include "parser_state.h" // Implementation of the main expression parser @@ -166,18 +167,17 @@ ASTNode *parse_unary(ParserState *state) { return parse_primary(state); } -// Primary Expressions: literals, variables, function calls, parentheses +// Primary Expressions: literals, variables, function calls, parentheses, arrays ASTNode *parse_primary(ParserState *state) { + // Identify the "base" expression + ASTNode *node = NULL; Token *current = get_current_token(state); if (current->type == TOKEN_INTEGER || current->type == TOKEN_FLOAT || current->type == TOKEN_STRING || current->type == TOKEN_BOOLEAN) { - ASTNode *node = create_literal_node(current); + node = create_literal_node(current); advance_token(state); - return node; - } - - if (current->type == TOKEN_FUNCTION_NAME) { + } else if (current->type == TOKEN_FUNCTION_NAME) { // Parse function call char *func_name = strdup(current->lexeme); if (!func_name) { @@ -193,11 +193,8 @@ ASTNode *parse_primary(ParserState *state) { expect_token(state, TOKEN_PAREN_CLOSE, "Expected ')' after function arguments"); - ASTNode *node = create_function_call_node(func_name, args); - return node; - } - - if (current->type == TOKEN_IDENTIFIER) { + node = create_function_call_node(func_name, args); + } else if (current->type == TOKEN_IDENTIFIER) { // Check if identifier is followed by '(' indicating a function call Token *next = peek_next_token(state); if (next && next->type == TOKEN_PAREN_OPEN) { @@ -220,22 +217,27 @@ ASTNode *parse_primary(ParserState *state) { ASTNode *node = create_function_call_node(func_name, args); return node; } else { - // It's a variable - ASTNode *node = create_variable_node(current->lexeme); + // It's a variable reference + node = create_variable_reference_node(current->lexeme); advance_token(state); - return node; } - } - - if (current->type == TOKEN_PAREN_OPEN) { + } else if (current->type == TOKEN_PAREN_OPEN) { advance_token(state); // consume `(` - ASTNode *node = parse_operator_expression(state); + node = parse_operator_expression(state); expect_token(state, TOKEN_PAREN_CLOSE, "Expected `)` after expression"); - return node; + } else if (current->type == TOKEN_SQ_BRACKET_OPEN) { + node = parse_array_literal(state); + node->type = AST_ARRAY_LITERAL; + } else { + parser_error("Expected expression", current); } - parser_error("Expected expression", current); - return NULL; // unreachable + // Handle chained indexing/slicing + while (get_current_token(state)->type == TOKEN_SQ_BRACKET_OPEN) { + node = parse_index_access(node, state); + } + + return node; } ASTNode *parse_argument_list(ParserState *state) { @@ -315,9 +317,10 @@ ASTNode *create_literal_node(Token *token) { if (!node) { parser_error("Memory allocation failed for literal node", token); } + node->type = AST_LITERAL; + node->literal.type = LITERAL_INTEGER; - // Assign the correct literal type and value based on the token type switch (token->type) { case TOKEN_INTEGER: node->literal.type = LITERAL_INTEGER; @@ -325,20 +328,24 @@ ASTNode *create_literal_node(Token *token) { break; case TOKEN_FLOAT: node->literal.type = LITERAL_FLOAT; - node->literal.value.floating_point = strtold(token->lexeme, NULL); + node->literal.value.floating_point = atof(token->lexeme); break; case TOKEN_STRING: node->literal.type = LITERAL_STRING; node->literal.value.string = strdup(token->lexeme); + if (!node->literal.value.string) { + free(node); + parser_error("Memory allocation failed for string literal", token); + } break; case TOKEN_BOOLEAN: node->literal.type = LITERAL_BOOLEAN; - node->literal.value.boolean = strcmp(token->lexeme, "True") == 0; + node->literal.value.boolean = (strcmp(token->lexeme, "True") == 0); break; default: - parser_error("Unsupported literal type", token); + free(node); + parser_error("Unknown literal type", token); } - node->next = NULL; return node; } diff --git a/src/parser/parser.c b/src/parser/parser.c index fff9309..b39e761 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -32,6 +32,7 @@ ASTNode *parse_statement(ParserState *state) { debug_print_par("Current Token: Type=`%d`, Lexeme=`%s`\n", token->type, token->lexeme); + if (match_token(state, "let")) return parse_variable_declaration(state); if (match_token(state, "const")) @@ -61,119 +62,130 @@ ASTNode *parse_statement(ParserState *state) { return node; } - // Handle variable assignments - if (token->type == TOKEN_IDENTIFIER) { - Token *next_token = peek_next_token(state); - if (next_token) { - debug_print_par("Peek Next Token: Type=`%d`, Lexeme=`%s`\n", - next_token->type, next_token->lexeme); - if (next_token->type == TOKEN_OPERATOR && - strcmp(next_token->lexeme, "=") == 0) { - return parse_variable_assignment(state); - } - } - return parse_expression_statement(state); + // Handle variable assignments using the helper function + if (token->type == TOKEN_IDENTIFIER && is_assignment(state)) { + return parse_variable_assignment(state); } - // If none of the above, raise an error - parser_error("Unexpected token at start of statement", token); - return NULL; + // Handle expression statements (e.g., array operations without assignment) + ASTNode *expr_stmt = parse_expression_statement(state); + expect_token(state, TOKEN_DELIMITER, + "Expected `;` after expression statement"); + return expr_stmt; } ASTNode *parse_expression_statement(ParserState *state) { // Parse the expression (which can handle binary ops, variables, etc.) - ASTNode *expr_node = parse_expression(state); - expect_token(state, TOKEN_DELIMITER, - "Expected `;` after expression statement"); - return expr_node; + // ASTNode *expr_node = parse_expression(state); + // expect_token(state, TOKEN_DELIMITER, + // "Expected `;` after expression statement"); + // return expr_node; + return parse_expression(state); } ASTNode *parse_declaration(ParserState *state, ASTNodeType type) { + // Parse keyword (`let` or `const`) if (type == AST_CONST_DECLARATION) { debug_print_par("Starting constant declaration parse\n"); expect_token(state, TOKEN_KEYWORD, "Expected `const` keyword"); - } else { + } else if (type == AST_VAR_DECLARATION) { debug_print_par("Starting variable declaration parse\n"); expect_token(state, TOKEN_KEYWORD, "Expected `let` keyword"); + } else { + parser_error("Unknown declaration type", get_current_token(state)); } // Parse variable/constant name Token *name = get_current_token(state); - if (type == AST_CONST_DECLARATION) { - expect_token(state, TOKEN_IDENTIFIER, "Expected constant name"); - expect_token(state, TOKEN_OPERATOR, "Expected `=` after constant name"); - } else { - expect_token(state, TOKEN_IDENTIFIER, "Expected variable name"); - expect_token(state, TOKEN_OPERATOR, "Expected `=` after variable name"); + if (name->type != TOKEN_IDENTIFIER && name->type != TOKEN_FUNCTION_NAME) { + parser_error("Expected name in declaration", name); } - - // Create AST node - ASTNode *node = malloc(sizeof(ASTNode)); - if (!node) { - parser_error("Memory allocation failed", get_current_token(state)); + char *decl_name = strdup(name->lexeme); + if (!decl_name) { + parser_error("Memory allocation failed for declaration name", name); } - node->type = type; + advance_token(state); // Consume name - // Create a deep copy of the variable/constant name - if (name && name->lexeme) { - node->assignment.variable_name = strdup(name->lexeme); - if (!node->assignment.variable_name) { - parser_error(AST_CONST_DECLARATION - ? "Memory allocation failed for constant name" - : "Memory allocation failed for variable name", - name); - } - } else { - parser_error(AST_CONST_DECLARATION ? "Invalid constant name token" - : "Invalid variable name token", - name); - } + // Expect `=` operator + expect_token(state, TOKEN_OPERATOR, "Expected `=` after name"); - node->assignment.value = parse_expression(state); - node->next = NULL; + // Parse initializer expression + ASTNode *initializer = parse_expression(state); + if (!initializer) { + parser_error("Expected initializer expression", + get_current_token(state)); + } + // Expect `;` delimiter expect_token(state, TOKEN_DELIMITER, - AST_CONST_DECLARATION + type == AST_CONST_DECLARATION ? "Expected `;` after constant declaration" : "Expected `;` after variable declaration"); + // Create AST node based on type + ASTNode *node = malloc(sizeof(ASTNode)); + if (!node) { + parser_error("Memory allocation failed for declaration node", + get_current_token(state)); + } + node->type = type; + node->next = NULL; + + if (type == AST_VAR_DECLARATION) { + node->var_declaration.variable_name = decl_name; + node->var_declaration.initializer = initializer; + } else if (type == AST_CONST_DECLARATION) { + node->const_declaration.constant_name = decl_name; + node->const_declaration.initializer = initializer; + } + return node; } ASTNode *parse_variable_declaration(ParserState *state) { - return parse_declaration(state, AST_ASSIGNMENT); + return parse_declaration(state, AST_VAR_DECLARATION); } ASTNode *parse_constant_declaration(ParserState *state) { return parse_declaration(state, AST_CONST_DECLARATION); } +ASTNode *create_variable_reference_node(char *name) { + ASTNode *node = calloc( + 1, sizeof(ASTNode)); // use `calloc` instead of `malloc` to ensure + // all fields are initialized to zero (`NULL`) + if (!node) { + parser_error("Memory allocation failed for variable reference node", + NULL); + } + node->type = AST_VARIABLE_REFERENCE; + node->variable_name = strdup(name); + if (!node->variable_name) { + free(node); + parser_error("Memory allocation failed for variable name", NULL); + } + node->next = NULL; + return node; +} + ASTNode *parse_variable_assignment(ParserState *state) { debug_print_par("Starting variable assignment parse\n"); - // Parse variable name - Token *name = get_current_token(state); - debug_print_par("Variable assignment name: `%s`\n", name->lexeme); - advance_token(state); // consume variable name + // Parse the left-hand side (LHS) expression + ASTNode *lhs = parse_expression(state); // Expect `=` operator Token *op_token = get_current_token(state); - debug_print_par("Expected operator `=`, found: `%s`\n", op_token->lexeme); - expect_token(state, TOKEN_OPERATOR, "Expected `=` after variable name"); - - // Parse the expression on the right-hand side - ASTNode *value_node = parse_expression(state); - debug_print_par("Parsed expression for assignment\n"); - - ASTNode *node = malloc(sizeof(ASTNode)); - if (!node) { - parser_error("Memory allocation failed", get_current_token(state)); + if (op_token->type != TOKEN_OPERATOR || + strcmp(op_token->lexeme, "=") != 0) { + parser_error("Expected `=` operator after variable name or slice", + op_token); } + advance_token(state); // consume `=` - node->type = AST_ASSIGNMENT; - node->assignment.variable_name = strdup(name->lexeme); - node->assignment.value = value_node; - node->next = NULL; + // Parse the expression on the right-hand side (RHS) + ASTNode *rhs = parse_expression(state); + debug_print_par("Parsed expression for assignment\n"); // Expect `;` delimiter Token *delimiter = get_current_token(state); @@ -186,6 +198,18 @@ ASTNode *parse_variable_assignment(ParserState *state) { "Expected `;` after variable assignment"); debug_print_par("Consumed `;` after variable assignment\n"); + // Create AST Assignment Node + ASTNode *node = malloc(sizeof(ASTNode)); + if (!node) { + parser_error("Memory allocation failed for assignment node", + get_current_token(state)); + } + + node->type = AST_ASSIGNMENT; + node->assignment.lhs = lhs; + node->assignment.rhs = rhs; + node->next = NULL; + return node; } @@ -225,7 +249,7 @@ ASTNode *parse_literal_or_identifier(ParserState *state) { } } - // Handle literals and identifiers + // Handle literals if (current->type == TOKEN_FLOAT || current->type == TOKEN_INTEGER || current->type == TOKEN_STRING || current->type == TOKEN_BOOLEAN) { ASTNode *node = malloc(sizeof(ASTNode)); @@ -244,12 +268,12 @@ ASTNode *parse_literal_or_identifier(ParserState *state) { } else if (current->type == TOKEN_STRING) { node->literal.type = LITERAL_STRING; node->literal.value.string = strdup(current->lexeme); - } else if (current->type == TOKEN_BOOLEAN) { // Handle boolean + } else if (current->type == TOKEN_BOOLEAN) { node->literal.type = LITERAL_BOOLEAN; if (strcmp(current->lexeme, "True") == 0) { - node->literal.value.boolean = 1; + node->literal.value.boolean = true; } else { - node->literal.value.boolean = 0; + node->literal.value.boolean = false; } } @@ -258,21 +282,19 @@ ASTNode *parse_literal_or_identifier(ParserState *state) { return node; } else if (current->type == TOKEN_FUNCTION_NAME || current->type == TOKEN_IDENTIFIER) { - // Check if `(` indicates a call - Token *next = peek_next_token(state); - if (next && next->type == TOKEN_PAREN_OPEN) { - // It's a call - ASTNode *node = parse_function_call(state); - return node; - } else { - // It's just a variable (or a function reference used as a variable) - ASTNode *node = create_variable_node(current->lexeme); - advance_token(state); - return node; + // Handle variable or function call + ASTNode *node = create_variable_node(current->lexeme); + advance_token(state); + + // Handle array indexing or slicing + while (get_current_token(state)->type == TOKEN_SQ_BRACKET_OPEN) { + node = parse_index_access(node, state); } + + return node; } else if (current->type == TOKEN_PAREN_OPEN) { advance_token(state); // consume `(` - ASTNode *node = parse_operator_expression(state); + ASTNode *node = parse_expression(state); expect_token(state, TOKEN_PAREN_CLOSE, "Expected `)` after expression"); return node; } @@ -566,7 +588,8 @@ ASTNode *parse_case_body(ParserState *state) { while (1) { Token *current = get_current_token(state); - // Stop parsing the body if any of `is`, `else`, or `}` get encountered + // Stop parsing the body if any of `is`, `else`, or `}` get + // encountered if ((current->type == TOKEN_KEYWORD && (strcmp(current->lexeme, "is") == 0 || strcmp(current->lexeme, "else") == 0)) || @@ -914,3 +937,64 @@ ASTNode *parse_finally_block(ParserState *state) { expect_token(state, TOKEN_BRACE_CLOSE, "Expected `}` to end finish block"); return finally_body; } + +/** + * Peeks ahead 'n' tokens from the current position. + * + * @param state The current parser state. + * @param n The number of tokens to peek ahead. + * @return A pointer to the token 'n' positions ahead, or the last token if out + * of bounds. + */ +Token *peek_ahead(ParserState *state, size_t n) { + size_t target = state->current_token + n; + // Assuming tokens are terminated with TOKEN_EOF + // Return the last token if target exceeds the array + return &state->tokens[target]; +} + +/** + * Determines if the current statement is an assignment. + * + * An assignment can be: + * - identifier = expression + * - identifier [ array_operator ] = expression + * + * @param state The current parser state. + * @return `true` if it's an assignment, `false` otherwise. + */ +bool is_assignment(ParserState *state) { + // Ensure the current token is an identifier + if (state->current->type != TOKEN_IDENTIFIER) + return false; + + // Peek the next token + Token *next = peek_next_token(state); + if (!next) + return false; + + // Case 1: identifier '=' + if (next->type == TOKEN_OPERATOR && strcmp(next->lexeme, "=") == 0) + return true; + + // Case 2: identifier '[' array_operator ']' '=' + if (next->type == TOKEN_SQ_BRACKET_OPEN) { + // Peek two tokens ahead (array operator) + Token *array_op = peek_ahead(state, 2); + if (array_op && is_array_operator(array_op)) { + // Peek three tokens ahead (']') + Token *after_bracket = peek_ahead(state, 3); + if (after_bracket && + after_bracket->type == TOKEN_SQ_BRACKET_CLOSE) { + // Peek four tokens ahead ('=') + Token *equals = peek_ahead(state, 4); + if (equals && equals->type == TOKEN_OPERATOR && + strcmp(equals->lexeme, "=") == 0) + return true; + } + } + } + + // Not an assignment + return false; +} diff --git a/src/parser/parser.h b/src/parser/parser.h index 6d0a5d5..b08d3cb 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -2,6 +2,7 @@ #define PARSER_H #include "../shared/ast_types.h" +#include "array_parser.h" #include "operator_parser.h" #include "parser_state.h" @@ -34,13 +35,17 @@ ASTNode *parse_expression(ParserState *state); ASTNode *parse_literal_or_identifier(ParserState *state); ASTNode *parse_block(ParserState *state); +ASTNode *create_variable_reference_node(char *name); + // Helper functions ASTNode *parse_declaration(ParserState *state, ASTNodeType type); bool match_token(ParserState *state, const char *lexeme); Token *peek_next_token(ParserState *state); +Token *peek_ahead(ParserState *state, size_t n); ASTNode *parse_expression_statement(ParserState *state); ASTNode *parse_block(ParserState *state); ASTNode *parse_case_body(ParserState *state); ASTNode *parse_function_body(ParserState *state); +bool is_assignment(ParserState *state); #endif diff --git a/src/parser/utils.c b/src/parser/utils.c index 9a8b203..1aef54e 100644 --- a/src/parser/utils.c +++ b/src/parser/utils.c @@ -5,10 +5,10 @@ void free_ast(ASTNode *node) { ASTNode *next = node->next; switch (node->type) { - case AST_ASSIGNMENT: - free(node->assignment.variable_name); - free_ast(node->assignment.value); + // free(node->assignment.variable_name); + free_ast(node->assignment.lhs); + free_ast(node->assignment.rhs); break; case AST_LITERAL: @@ -47,25 +47,27 @@ void free_ast(ASTNode *node) { free_ast(node->for_loop.body); break; - case AST_CONST_DECLARATION: case AST_VAR_DECLARATION: - free(node->variable_name); + free(node->var_declaration.variable_name); + break; + + case AST_CONST_DECLARATION: + free(node->const_declaration.constant_name); 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; + free_ast(node->function_declaration.body); + { + 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: @@ -77,87 +79,80 @@ void free_ast(ASTNode *node) { free_ast(node->function_return.return_data); break; - case AST_BREAK: - // No cleanup needed! - break; - case AST_TERNARY: free_ast(node->ternary.condition); free_ast(node->ternary.true_expr); free_ast(node->ternary.false_expr); break; - case AST_SWITCH: { - if (node->switch_case.expression) { - free_ast(node->switch_case.expression); - } - - if (node->switch_case.cases) { + case AST_SWITCH: + free_ast(node->switch_case.expression); + { ASTCaseNode *case_node = node->switch_case.cases; - while (case_node) { ASTCaseNode *next = case_node->next; - - // Free the condition ASTNode if it exists - if (case_node->condition) { - free_ast(case_node->condition); - } - - // Free the body - if (case_node->body) { - free_ast(case_node->body); - } - + free_ast(case_node->condition); + free_ast(case_node->body); free(case_node); case_node = next; } } - 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) { + free_ast(node->try_block.try_block); + { 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_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; + case AST_ARRAY_LITERAL: + for (size_t i = 0; i < node->array_literal.count; i++) { + free_ast(node->array_literal.elements[i]); + } + free(node->array_literal.elements); + break; + + case AST_ARRAY_OPERATION: + free(node->array_operation.operator); + free_ast(node->array_operation.array); + break; + + case AST_ARRAY_INDEX_ACCESS: + free_ast(node->array_index_access.array); + free_ast(node->array_index_access.index); + break; + + case AST_ARRAY_SLICE_ACCESS: + free_ast(node->array_slice_access.array); + free_ast(node->array_slice_access.start); + free_ast(node->array_slice_access.end); + free_ast(node->array_slice_access.step); + break; + // Already handled by `AST_TRY` case AST_CATCH: case AST_FINALLY: + case AST_VARIABLE_REFERENCE: + case AST_BREAK: + // No dynamic memory to free break; default: - fprintf(stderr, - "Error: Unknown `ASTNode` type `%d` in `free_ast`.\n", + fprintf(stderr, "Unknown ASTNode type `%d` in free_ast.\n", node->type); exit(1); } @@ -168,7 +163,7 @@ void free_ast(ASTNode *node) { } // Print indentation based on depth -static void print_indentation(int depth) { +void print_indent(int depth) { for (int i = 0; i < depth; i++) { printf(" "); } @@ -176,213 +171,300 @@ static void print_indentation(int depth) { // Print AST nodes void print_ast(ASTNode *node, int depth) { - if (!debug_flag) { - return; - } + if (debug_flag) { + while (node != NULL) { + print_indent(depth); + switch (node->type) { + case AST_VAR_DECLARATION: + printf("Variable Declaration: %s\n", + node->var_declaration.variable_name); + if (node->var_declaration.initializer) { + print_indent(depth + 1); + printf("Initializer:\n"); + print_ast(node->var_declaration.initializer, depth + 2); + } + break; - while (node != NULL) { - print_indentation(depth); + case AST_CONST_DECLARATION: + printf("Constant Declaration: %s\n", + node->const_declaration.constant_name); + if (node->const_declaration.initializer) { + print_indent(depth + 1); + printf("Initializer:\n"); + print_ast(node->const_declaration.initializer, depth + 2); + } + break; - switch (node->type) { - case AST_ASSIGNMENT: - printf("Assignment:\n"); - print_indentation(depth + 1); - printf("Variable: `%s`\n", node->assignment.variable_name); - print_indentation(depth + 1); - printf("Value:\n"); - print_ast(node->assignment.value, depth + 2); - break; + case AST_ASSIGNMENT: + printf("Assignment:\n"); + print_indent(depth + 1); + printf("LHS:\n"); + print_ast(node->assignment.lhs, depth + 2); + print_indent(depth + 1); + printf("RHS:\n"); + print_ast(node->assignment.rhs, depth + 2); + break; - case AST_FUNCTION_DECLARATION: - printf("Function Declaration: `%s`\n", - node->function_declaration.name); - // Print Parameters - if (node->function_declaration.parameters != NULL) { - print_indentation(depth + 1); + case AST_FUNCTION_DECLARATION: + printf("Function Declaration: %s\n", + node->function_declaration.name); + print_indent(depth + 1); printf("Parameters:\n"); - ASTFunctionParameter *param = - node->function_declaration.parameters; - while (param != NULL) { - print_indentation(depth + 2); - printf("- `%s`\n", param->parameter_name); - param = param->next; + { + ASTFunctionParameter *param = + node->function_declaration.parameters; + while (param != NULL) { + print_indent(depth + 2); + printf("- %s\n", param->parameter_name); + param = param->next; + } } - } else { - print_indentation(depth + 1); - printf("Parameters: None\n"); - } - // Print Body - if (node->function_declaration.body != NULL) { - print_indentation(depth + 1); + print_indent(depth + 1); printf("Body:\n"); print_ast(node->function_declaration.body, depth + 2); - } else { - print_indentation(depth + 1); - printf("Body: None\n"); - } - break; + break; - case AST_FUNCTION_CALL: - printf("Function Call: `%s`\n", node->function_call.name); - // Print Arguments - if (node->function_call.arguments != NULL) { - print_indentation(depth + 1); + case AST_FUNCTION_CALL: + printf("Function Call: %s\n", node->function_call.name); + print_indent(depth + 1); printf("Arguments:\n"); print_ast(node->function_call.arguments, depth + 2); - } else { - print_indentation(depth + 1); - printf("Arguments: None\n"); - } - break; + break; - case AST_FUNCTION_RETURN: - printf("Function Return:\n"); - if (node->function_return.return_data != NULL) { + case AST_FUNCTION_RETURN: + printf("Function Return:\n"); print_ast(node->function_return.return_data, depth + 1); - } else { - print_indentation(depth + 1); - printf("Return Data: None\n"); - } - break; - - case AST_LITERAL: - printf("Literal: "); - switch (node->literal.type) { - case LITERAL_INTEGER: - printf("`%lld`\n", node->literal.value.integer); break; - case LITERAL_FLOAT: - printf("`%Lf`\n", node->literal.value.floating_point); + + case AST_LITERAL: + printf("Literal: "); + switch (node->literal.type) { + case LITERAL_STRING: + printf("String (%s)\n", node->literal.value.string); + break; + case LITERAL_FLOAT: + printf("Float (%Lf)\n", node->literal.value.floating_point); + break; + case LITERAL_INTEGER: + printf("Integer (%lld)\n", + (long long)node->literal.value.integer); + break; + case LITERAL_BOOLEAN: + printf("Boolean (%s)\n", + node->literal.value.boolean ? "true" : "false"); + break; + default: + printf("Unknown Literal Type\n"); + } break; - case LITERAL_STRING: - printf("`\"%s\"`\n", node->literal.value.string); + + case AST_CONDITIONAL: + printf("Conditional:\n"); + print_indent(depth + 1); + printf("Condition:\n"); + print_ast(node->conditional.condition, depth + 2); + print_indent(depth + 1); + printf("Body:\n"); + print_ast(node->conditional.body, depth + 2); + if (node->conditional.else_branch) { + print_indent(depth + 1); + printf("Else Branch:\n"); + print_ast(node->conditional.else_branch, depth + 2); + } break; - case LITERAL_BOOLEAN: - printf(node->literal.value.boolean ? "True\n" : "False\n"); + + case AST_UNARY_OP: + printf("Unary Operation: %s\n", node->unary_op.operator); + print_indent(depth + 1); + printf("Operand:\n"); + print_ast(node->unary_op.operand, depth + 2); break; - default: - printf("Unknown Literal Type\n"); - } - break; - case AST_CONDITIONAL: - printf("Conditional Statement:\n"); - print_indentation(depth + 1); - printf("Condition:\n"); - print_ast(node->conditional.condition, depth + 2); - print_indentation(depth + 1); - printf("Body:\n"); - print_ast(node->conditional.body, depth + 2); - if (node->conditional.else_branch != NULL) { - print_indentation(depth + 1); - printf("Else Branch:\n"); - print_ast(node->conditional.else_branch, depth + 2); - } - break; + case AST_BINARY_OP: + printf("Binary Operation: %s\n", node->binary_op.operator); + print_indent(depth + 1); + printf("Left:\n"); + print_ast(node->binary_op.left, depth + 2); + print_indent(depth + 1); + printf("Right:\n"); + print_ast(node->binary_op.right, depth + 2); + break; - case AST_UNARY_OP: - printf("Unary Operation: `%s`\n", node->unary_op.operator); - print_ast(node->unary_op.operand, depth + 1); - break; + case AST_WHILE_LOOP: + printf("While Loop:\n"); + print_indent(depth + 1); + printf("Condition:\n"); + print_ast(node->while_loop.condition, depth + 2); + print_indent(depth + 1); + printf("Body:\n"); + print_ast(node->while_loop.body, depth + 2); + printf("Re-evaluate Condition: %s\n", + node->while_loop.re_evaluate_condition ? "true" + : "false"); + break; - case AST_BINARY_OP: - printf("Binary Operation: `%s`\n", node->binary_op.operator); - print_indentation(depth + 1); - printf("Left Operand:\n"); - print_ast(node->binary_op.left, depth + 2); - print_indentation(depth + 1); - printf("Right Operand:\n"); - print_ast(node->binary_op.right, depth + 2); - break; + case AST_FOR_LOOP: + printf("For Loop:\n"); + print_indent(depth + 1); + printf("Loop Variable: %s\n", node->for_loop.loop_variable); + print_indent(depth + 1); + printf("Start Expression:\n"); + print_ast(node->for_loop.start_expr, depth + 2); + print_indent(depth + 1); + printf("End Expression:\n"); + print_ast(node->for_loop.end_expr, depth + 2); + print_indent(depth + 1); + printf("Inclusive: %s\n", + node->for_loop.inclusive ? "true" : "false"); + if (node->for_loop.step_expr) { + print_indent(depth + 1); + printf("Step Expression:\n"); + print_ast(node->for_loop.step_expr, depth + 2); + } + print_indent(depth + 1); + printf("Body:\n"); + print_ast(node->for_loop.body, depth + 2); + break; - case AST_WHILE_LOOP: - printf("While Loop:\n"); - print_indentation(depth + 1); - printf("Condition:\n"); - print_ast(node->while_loop.condition, depth + 2); - print_indentation(depth + 1); - printf("Body:\n"); - print_ast(node->while_loop.body, depth + 2); - break; + case AST_SWITCH: + printf("Switch Statement:\n"); + print_indent(depth + 1); + printf("Expression:\n"); + print_ast(node->switch_case.expression, depth + 2); + print_indent(depth + 1); + printf("Cases:\n"); + { + ASTCaseNode *case_node = node->switch_case.cases; + while (case_node != NULL) { + print_indent(depth + 2); + printf("Case:\n"); + if (case_node->condition) { + print_indent(depth + 3); + printf("Condition:\n"); + print_ast(case_node->condition, depth + 4); + } else { + print_indent(depth + 3); + printf("Condition: None (Default)\n"); + } + print_indent(depth + 3); + printf("Body:\n"); + print_ast(case_node->body, depth + 4); + case_node = case_node->next; + } + } + break; - case AST_FOR_LOOP: - printf("For Loop:\n"); - print_indentation(depth + 1); - printf("Loop Variable: `%s`\n", node->for_loop.loop_variable); - print_indentation(depth + 1); - printf("Start Expression:\n"); - print_ast(node->for_loop.start_expr, depth + 2); - print_indentation(depth + 1); - printf("End Expression:\n"); - print_ast(node->for_loop.end_expr, depth + 2); - print_indentation(depth + 1); - printf("Inclusive: `%s`\n", - node->for_loop.inclusive ? "True" : "False"); - if (node->for_loop.step_expr != NULL) { - print_indentation(depth + 1); - printf("Step Expression:\n"); - print_ast(node->for_loop.step_expr, depth + 2); - } else { - print_indentation(depth + 1); - printf("Step Expression: None\n"); - } - print_indentation(depth + 1); - printf("Body:\n"); - print_ast(node->for_loop.body, depth + 2); - break; + case AST_BREAK: + printf("Break Statement\n"); + break; - case AST_SWITCH: - printf("Switch Statement:\n"); - print_indentation(depth + 1); - printf("Expression:\n"); - print_ast(node->switch_case.expression, depth + 2); - print_indentation(depth + 1); - printf("Cases:\n"); - ASTCaseNode *case_node = node->switch_case.cases; - size_t case_num = 1; - while (case_node != NULL) { - print_indentation(depth + 2); - printf("Case %zu:\n", case_num++); - print_indentation(depth + 3); + case AST_TERNARY: + printf("Ternary Operation:\n"); + print_indent(depth + 1); printf("Condition:\n"); - print_ast(case_node->condition, depth + 4); - print_indentation(depth + 3); - printf("Body:\n"); - print_ast(case_node->body, depth + 4); - case_node = case_node->next; - } - break; + print_ast(node->ternary.condition, depth + 2); + print_indent(depth + 1); + printf("True Expression:\n"); + print_ast(node->ternary.true_expr, depth + 2); + print_indent(depth + 1); + printf("False Expression:\n"); + print_ast(node->ternary.false_expr, depth + 2); + break; - case AST_BREAK: - printf("Break Statement\n"); - break; + case AST_TRY: + printf("Try Block:\n"); + print_indent(depth + 1); + printf("Try Block Content:\n"); + print_ast(node->try_block.try_block, depth + 2); + // Handle catch blocks + if (node->try_block.catch_blocks) { + ASTCatchNode *catch_node = node->try_block.catch_blocks; + while (catch_node != NULL) { + print_indent(depth + 1); + printf("Catch Block (Error Variable: %s):\n", + catch_node->error_variable + ? catch_node->error_variable + : "None"); + print_ast(catch_node->body, depth + 2); + catch_node = catch_node->next; + } + } + // Handle finally block + if (node->try_block.finally_block) { + print_indent(depth + 1); + printf("Finally Block:\n"); + print_ast(node->try_block.finally_block, depth + 2); + } + break; - case AST_TERNARY: - printf("Ternary Operation:\n"); - print_indentation(depth + 1); - printf("Condition:\n"); - print_ast(node->ternary.condition, depth + 2); - print_indentation(depth + 1); - printf("True Expression:\n"); - print_ast(node->ternary.true_expr, depth + 2); - print_indentation(depth + 1); - printf("False Expression:\n"); - print_ast(node->ternary.false_expr, depth + 2); - break; + case AST_ARRAY_LITERAL: + printf("Array Literal: [\n"); + for (size_t i = 0; i < node->array_literal.count; i++) { + print_ast(node->array_literal.elements[i], depth + 1); + } + print_indent(depth); + printf("]\n"); + break; - case AST_VAR_DECLARATION: - printf("Variable: `%s`\n", node->variable_name); - break; + case AST_ARRAY_OPERATION: + printf("Array Operation: %s\n", node->array_operation.operator); + print_indent(depth + 1); + printf("Array:\n"); + print_ast(node->array_operation.array, depth + 2); + break; - case AST_CONST_DECLARATION: - printf("Constant: `%s`\n", node->variable_name); - break; + case AST_ARRAY_INDEX_ACCESS: + printf("Array Index Access:\n"); + print_indent(depth + 1); + printf("Array:\n"); + print_ast(node->array_index_access.array, depth + 2); + print_indent(depth + 1); + printf("Index:\n"); + print_ast(node->array_index_access.index, depth + 2); + break; - default: - printf("Unknown Node Type: `%d`\n", node->type); - } + case AST_ARRAY_SLICE_ACCESS: + printf("Array Slice Access:\n"); + print_indent(depth + 1); + printf("Array:\n"); + print_ast(node->array_slice_access.array, depth + 2); + if (node->array_slice_access.start) { + print_indent(depth + 1); + printf("Start:\n"); + print_ast(node->array_slice_access.start, depth + 2); + } else { + print_indent(depth + 1); + printf("Start: NULL\n"); + } + if (node->array_slice_access.end) { + print_indent(depth + 1); + printf("End:\n"); + print_ast(node->array_slice_access.end, depth + 2); + } else { + print_indent(depth + 1); + printf("End: NULL\n"); + } + if (node->array_slice_access.step) { + print_indent(depth + 1); + printf("Step:\n"); + print_ast(node->array_slice_access.step, depth + 2); + } else { + print_indent(depth + 1); + printf("Step: NULL\n"); + } + break; + + case AST_VARIABLE_REFERENCE: + printf("Variable Reference: %s\n", node->variable_name); + break; + + default: + printf("Unknown AST Node Type: %d\n", node->type); + break; + } - // Move to the next node in the linked list - node = node->next; + // Move to the next node in the linked list, if any + node = node->next; + } } } diff --git a/src/parser/utils.h b/src/parser/utils.h index 0551714..d9ab7eb 100644 --- a/src/parser/utils.h +++ b/src/parser/utils.h @@ -8,7 +8,7 @@ void free_ast(ASTNode *node); // Print indentation based on depth -static void print_indentation(int depth); +void print_indent(int depth); // Print AST nodes void print_ast(ASTNode *node, int depth); diff --git a/src/shared/ast_types.h b/src/shared/ast_types.h index eba21e0..61502f0 100644 --- a/src/shared/ast_types.h +++ b/src/shared/ast_types.h @@ -5,15 +5,15 @@ #include #include -#define MAX_ARGUMENTS 1024 - // AST Node Types typedef enum { + AST_VAR_DECLARATION, + AST_CONST_DECLARATION, AST_ASSIGNMENT, + AST_LITERAL, AST_FUNCTION_DECLARATION, AST_FUNCTION_CALL, AST_FUNCTION_RETURN, - AST_LITERAL, AST_CONDITIONAL, AST_UNARY_OP, AST_BINARY_OP, @@ -22,11 +22,14 @@ typedef enum { AST_SWITCH, AST_BREAK, AST_TERNARY, - AST_VAR_DECLARATION, - AST_CONST_DECLARATION, AST_TRY, AST_CATCH, - AST_FINALLY + AST_FINALLY, + AST_ARRAY_LITERAL, + AST_ARRAY_OPERATION, + AST_ARRAY_INDEX_ACCESS, + AST_ARRAY_SLICE_ACCESS, + AST_VARIABLE_REFERENCE } ASTNodeType; // Literal Node @@ -97,9 +100,7 @@ typedef struct { // AST Function Parameter typedef struct ASTFunctionParameter { - char *parameter_name; // Parameter name - // LiteralNode *type; // Optional: parameter type (e.g., - // int, string, etc.) + char *parameter_name; // Parameter name struct ASTFunctionParameter *next; // Linked list for multiple parameters } ASTFunctionParameter; @@ -128,48 +129,95 @@ typedef struct { struct ASTNode *false_expr; } ASTTernary; -// AST Rescue Node (optional error object) +// AST Catch Node 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) + struct ASTCatchNode *next; // For multiple `rescue` clauses } 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 + struct ASTNode *finally_block; // Optional `finish` block } ASTTry; +// AST Array Literal Node +typedef struct { + struct ASTNode **elements; // Array of element expressions + size_t count; // Number of elements +} ASTArrayLiteral; + +// AST Index Access Node +typedef struct { + struct ASTNode *array; // The array expression + struct ASTNode *index; // The index expression +} ASTArrayIndexAccess; + +// AST Slice Access Node +typedef struct { + struct ASTNode *array; // The array expression + struct ASTNode *start; // Start index (can be NULL) + struct ASTNode *end; // End index (can be NULL) + struct ASTNode *step; // Step value (can be NULL) +} ASTArraySliceAccess; + +// AST Array Operation Node +typedef struct { + char *operator; // Array operation operator (e.g., `^+`, `+^`, `^-`, `-^`) + struct ASTNode *array; // Array on which the operation is performed + struct ASTNode *operand; // Element to operate with (e.g., to append) +} ASTArrayOperation; + // AST Node Structure typedef struct ASTNode { ASTNodeType type; union { - // Assignment + // Variable Declaration struct { char *variable_name; - struct ASTNode *value; + struct ASTNode *initializer; + } var_declaration; + + // Constant Declaration + struct { + char *constant_name; + struct ASTNode *initializer; + } const_declaration; + + // Assignment + struct { + struct ASTNode *lhs; + struct ASTNode *rhs; } assignment; - LiteralNode literal; - ASTUnaryOp unary_op; - ASTBinaryOp binary_op; + // Function Nodes + ASTFunctionDeclaration function_declaration; + ASTFunctionCall function_call; + ASTFunctionReturn function_return; + + // Control Flow Nodes ASTConditional conditional; ASTSwitch switch_case; ASTWhileLoop while_loop; ASTForLoop for_loop; ASTTernary ternary; - ASTFunctionDeclaration function_declaration; - ASTFunctionCall function_call; - ASTFunctionReturn function_return; + ASTTry try_block; - ASTTry try_block; // Try Block - // ASTCatchNode *catch_block; // Rescue Block - // struct ASTNode *finally_block; // Finish Block + // Operations + ASTUnaryOp unary_op; + ASTBinaryOp binary_op; - // Variable - char *variable_name; + // Array Nodes + ASTArrayLiteral array_literal; + ASTArrayOperation array_operation; + ASTArrayIndexAccess array_index_access; + ASTArraySliceAccess array_slice_access; + + // Literal and Reference + LiteralNode literal; + char *variable_name; // For AST_VARIABLE_REFERENCE }; struct ASTNode *next; diff --git a/src/shared/token_types.h b/src/shared/token_types.h index 6ccf368..9f87488 100644 --- a/src/shared/token_types.h +++ b/src/shared/token_types.h @@ -23,6 +23,9 @@ typedef enum { TOKEN_BRACE_OPEN, TOKEN_BRACE_CLOSE, TOKEN_COLON, + TOKEN_ARRAY_OP, + TOKEN_SQ_BRACKET_OPEN, + TOKEN_SQ_BRACKET_CLOSE, TOKEN_EOF } TokenType; diff --git a/src/tests/18_arrays.flv b/src/tests/18_arrays.flv new file mode 100644 index 0000000..1b8d40d --- /dev/null +++ b/src/tests/18_arrays.flv @@ -0,0 +1,93 @@ +# Initialize test array +let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +serve("Initial array:", array); + +# Element Access +serve("Accessing 6th element (index 5):", array[5]); + +# Append +array[^+] = 11; +serve("Array after appending 11:", array); + +# Prepend +array[+^] = 0; +serve("Array after prepending 0:", array); + +# Remove Last Element +serve("Removing last element:", array[^-]); +serve("Array after removing last element:", array); + +# Remove First Element +serve("Removing first element:", array[-^]); +serve("Array after removing first element:", array); + +# Slicing +serve("Slice from index 0 to 5:", array[0:5]); +serve("Reversed array:", array[::-1]); +serve("Slice from index 3 to end:", array[3:]); +serve("Slice from start to index 8:", array[:8]); +serve("Slice from index 3 to 8, skipping every 2nd element:", array[3:8:2]); +serve("Slice from index 8 to 3, skipping every 2nd element (reverse):", array[8:3:-2]); +serve("Slice from index 3 to start (reversed):", array[3::-1]); +serve("Slice from end towards index 8 (exclusive, reversed):", array[:8:-1]); +serve("Slice entire array, skipping every 2nd element:", array[::2]); +serve("Reverse array, skipping every 3rd element:", array[::-3]); + +# Test completed +serve("Array operation tests complete!"); + +# Initialize const (immutable) array +const const_array = [10, 20, 30, 40, 50]; +serve("Initial const array:", const_array); + +# Attempt mutation +try { + const_array[^+] = 60; # Trying to append to const array + serve("This should not be shown; const array was mutated!"); +} rescue { + serve("Caught error: Cannot mutate a const array."); +} + +# Confirm immutability +serve("Const array remains unchanged:", const_array); + +# Try to remove element +try { + const_array[^-]; # Attempting to remove the last element + serve("This should not be shown; const array was mutated!"); +} rescue { + serve("Caught error: Cannot mutate a const array."); +} + +# Final confirmation +serve("Const array is still:", const_array); + +# Test completed +serve("Immutable array tests complete!"); + + +# EXPECTED OUTPUT +# +# Initial array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +# Accessing 6th element (index 5): 6 +# Array after appending 11: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] +# Array after prepending 0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] +# Array after removing last element: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +# Array after removing first element: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +# Slice from index 0 to 5: [1, 2, 3, 4, 5] +# Reversed array: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] +# Slice from index 3 to end: [4, 5, 6, 7, 8, 9, 10] +# Slice from start to index 8: [1, 2, 3, 4, 5, 6, 7, 8] +# Slice from index 3 to 8, skipping every 2nd element: [4, 6, 8] +# Slice from index 8 to 3, skipping every 2nd element (reverse): [9, 7, 5] +# Slice from index 3 to start (reversed): [4, 3, 2, 1] +# Slice from end towards index 8 (exclusive, reversed): [10, 9] +# Slice entire array, skipping every 2nd element: [1, 3, 5, 7, 9] +# Reverse array, skipping every 3rd element: [10, 7, 4, 1] +# Array operation tests complete! +# Initial const array: [10, 20, 30, 40, 50] +# Caught error: Cannot mutate a const array. +# Const array remains unchanged: [10, 20, 30, 40, 50] +# Caught error: Cannot mutate a const array. +# Const array is still: [10, 20, 30, 40, 50] +# Immutable array tests complete! diff --git a/src/tests/all.flv b/src/tests/all.flv index 6e20ffb..a03199a 100644 --- a/src/tests/all.flv +++ b/src/tests/all.flv @@ -276,3 +276,73 @@ create test(num, func_a, func_b) { let a = test(10, times_2, times_3); # 180 serve(a); + +# ================================================== +# 18 +# ================================================== +# Initialize test array +let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +serve("Initial array:", array); + +# Element Access +serve("Accessing 6th element (index 5):", array[5]); + +# Append +array[^+] = 11; +serve("Array after appending 11:", array); + +# Prepend +array[+^] = 0; +serve("Array after prepending 0:", array); + +# Remove Last Element +serve("Removing last element:", array[^-]); +serve("Array after removing last element:", array); + +# Remove First Element +serve("Removing first element:", array[-^]); +serve("Array after removing first element:", array); + +# Slicing +serve("Slice from index 0 to 5:", array[0:5]); +serve("Reversed array:", array[::-1]); +serve("Slice from index 3 to end:", array[3:]); +serve("Slice from start to index 8:", array[:8]); +serve("Slice from index 3 to 8, skipping every 2nd element:", array[3:8:2]); +serve("Slice from index 8 to 3, skipping every 2nd element (reverse):", array[8:3:-2]); +serve("Slice from index 3 to start (reversed):", array[3::-1]); +serve("Slice from end towards index 8 (exclusive, reversed):", array[:8:-1]); +serve("Slice entire array, skipping every 2nd element:", array[::2]); +serve("Reverse array, skipping every 3rd element:", array[::-3]); + +# Test completed +serve("Array operation tests complete!"); + +# Initialize const (immutable) array +const const_array = [10, 20, 30, 40, 50]; +serve("Initial const array:", const_array); + +# Attempt mutation +try { + const_array[^+] = 60; # Trying to append to const array + serve("This should not be shown; const array was mutated!"); +} rescue { + serve("Caught error: Cannot mutate a const array."); +} + +# Confirm immutability +serve("Const array remains unchanged:", const_array); + +# Try to remove element +try { + const_array[^-]; # Attempting to remove the last element + serve("This should not be shown; const array was mutated!"); +} rescue { + serve("Caught error: Cannot mutate a const array."); +} + +# Final confirmation +serve("Const array is still:", const_array); + +# Test completed +serve("Immutable array tests complete!");