diff --git a/README.md b/README.md index 450906b..18b0fc7 100644 --- a/README.md +++ b/README.md @@ -204,9 +204,9 @@ npx vsce package #### 3. Install in VS Code - Open VS Code. -- Press Ctrl+Shift+P (or ++P on macOS) and select _`Extensions: Install from VSIX…`_. +- Press Ctrl+Shift+P (or ++P on macOS) and select Extensions: Install from VSIX…. - Select the generated `.vsix` file within the `vscode-extension` folder. -- Restart your extensions via the popup notificaiton. +- Restart your extensions via the popup notification. --- diff --git a/docs/syntax_examples.md b/docs/syntax_examples.md index 4b1ca1f..6521026 100644 --- a/docs/syntax_examples.md +++ b/docs/syntax_examples.md @@ -23,6 +23,8 @@ These are examples showcasing the unique (& fun) syntax of FlavorLang. They give 15. [Get Current Year with `get_time()`](#15) 16. [Ternary Operator](#16) 17. [Nested Function Application](#17) +18. [Basic Arrays & Operations](#18) +19. [2D Arrays](#19) --- @@ -338,6 +340,156 @@ let a = test(10, times_2, times_3); # 180 serve(a); ``` +### 18. Basic Arrays & Operations + +```py +# 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!"); +``` + +### 19. 2D Arrays + +```py +# Test for array length +let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +serve("Initial array:", array); +serve("Length of array:", length(array)); + +# Test compound slicing +serve("Original array:", array); +serve("Double slice (first 5, then reverse):", (array[0:5])[::-1]); +serve("Triple slice (first 5, skip every second, then reverse):", ((array[0:5])[::2])[::-1]); + +# Test declaring and using 2D arrays +let array2D = [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9] +]; +serve("2D array:", array2D); + +# Access individual elements +serve("Element at (2, 2):", array2D[1][1]); +serve("Element at (3, 3):", array2D[2][2]); + +# Modify elements in a 2D array +array2D[0][0] = 10; +serve("2D array after modifying (1, 1) to 10:", array2D); + +# Test slicing on rows +serve("First row:", array2D[0]); +serve("Last row reversed:", array2D[2][::-1]); + +# Test slicing on columns (manual transpose-like slicing) +let first_column = [array2D[0][0], array2D[1][0], array2D[2][0]]; +serve("First column:", first_column); + +let second_column_reversed = [array2D[2][1], array2D[1][1], array2D[0][1]]; +serve("Second column reversed:", second_column_reversed); + +# Test length of 2D array and its rows +serve("Number of rows:", length(array2D)); +serve("Number of elements in first row:", length(array2D[0])); + +# Immutable 2D arrays +const const_2D_array = [ + [10, 20, 30], + [40, 50, 60], + [70, 80, 90] +]; +serve("Immutable 2D array:", const_2D_array); + +# Attempt mutation on immutable 2D array +try { + const_2D_array[0][0] = 100; # Trying to modify element + serve("This should not be shown; const 2D array was mutated!"); +} rescue { + serve("Caught error: Cannot mutate a const 2D array."); +} + +# Confirm immutability +serve("Const 2D array remains unchanged:", const_2D_array); + +# Final confirmation +serve("Immutable 2D array tests complete!"); + +# Test length of a mixed array +let mixed_array = [1, "two", [3, 4], [5, 6]]; +serve("Mixed array:", mixed_array); +serve("Length of mixed array:", length(mixed_array)); + +# Compound operations on mixed array +serve("First element of nested array (3, 4):", mixed_array[2][0]); +serve("Length of nested array:", length(mixed_array[2])); + +# Test completed +serve("All array operation tests complete!"); +``` + --- ## License diff --git a/src/README.md b/src/README.md deleted file mode 100644 index d882d9c..0000000 --- a/src/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# `/FlavorLang` - -## Structure - -| File | Purpose | -| ---------- | -------------------------------------- | -| `common.h` | Shared constancts, macros, and structs | -| `lexer.h` | Function declarations for lexer | -| `lexer.c` | Implementation of lexer logic | -| `main.c` | Entry point & lexer testing | -| `test.flv` | Sample FlavorLang code | - -```bash -FlavorLang/ -├── common.h # Shared constants and structs -├── lexer.h # Function declarations -├── lexer.c # lexer implementation -├── main.c # Entry point for testing -└── test.flv # Sample FlavorLang code -``` - -## Lexer Workflow - -1. **Input**: `test.flv` as a string. -2. **Scan**: Read said string char by char. -3. **Recognize tokens**: - -- Skip whitespace & comments. -- Recognize keywords, strings, numbers, operators, and delimiters. -- Record the line number for error reporting. - -4. **Output**: An array of tokens. diff --git a/src/interpreter/builtins.c b/src/interpreter/builtins.c index 1aa4d3b..5fd8056 100644 --- a/src/interpreter/builtins.c +++ b/src/interpreter/builtins.c @@ -5,7 +5,25 @@ #include #include -// Function to interpret a mix of argument types +// Helper function to check if a LiteralType matches an ArgType +bool literal_type_matches_arg_type(LiteralType lit_type, ArgType arg_type) { + switch (arg_type) { + case ARG_TYPE_INTEGER: + return lit_type == TYPE_INTEGER; + case ARG_TYPE_FLOAT: + return lit_type == TYPE_FLOAT; + case ARG_TYPE_STRING: + return lit_type == TYPE_STRING; + case ARG_TYPE_BOOLEAN: + return lit_type == TYPE_BOOLEAN; + case ARG_TYPE_ARRAY: + return lit_type == TYPE_ARRAY; + default: + return false; // Unknown ArgType + } +} + +// Function to interpret arguments with single expected type per ArgumentSpec InterpretResult interpret_arguments(ASTNode *node, Environment *env, size_t num_args, ArgumentSpec *specs) { ASTNode *arg_node = node; @@ -24,60 +42,57 @@ InterpretResult interpret_arguments(ASTNode *node, Environment *env, LiteralValue lv = arg_res.value; // Reference to the current argument specification - ArgType expected_type = specs[i].type; - void *output_ptr = specs[i].out_ptr; - - // Verify and assign the argument based on its expected type - switch (expected_type) { - case ARG_TYPE_INTEGER: - if (lv.type == TYPE_INTEGER) { - *((INT_SIZE *)output_ptr) = lv.data.integer; - } else if (lv.type == TYPE_FLOAT) { - *((INT_SIZE *)output_ptr) = (INT_SIZE)lv.data.floating_point; - } else if (lv.type == TYPE_BOOLEAN) { - *((INT_SIZE *)output_ptr) = lv.data.boolean ? 1 : 0; - } else { - return raise_error("Expected integer for argument %zu.\n", - i + 1); - } - break; - - case ARG_TYPE_FLOAT: - if (lv.type == TYPE_FLOAT) { - *((FLOAT_SIZE *)output_ptr) = lv.data.floating_point; - } else if (lv.type == TYPE_INTEGER) { - *((FLOAT_SIZE *)output_ptr) = (FLOAT_SIZE)lv.data.integer; - } else if (lv.type == TYPE_BOOLEAN) { - *((FLOAT_SIZE *)output_ptr) = lv.data.boolean ? 1.0 : 0.0; - } else { - return raise_error("Expected float for argument %zu.\n", i + 1); - } - break; - - case ARG_TYPE_STRING: - if (lv.type == TYPE_STRING) { - *((char **)output_ptr) = lv.data.string; - } else { - return raise_error("Expected string for argument %zu.\n", + ArgumentSpec current_spec = specs[i]; + bool type_matched = + literal_type_matches_arg_type(lv.type, current_spec.type); + + if (type_matched) { + // Assign the value based on the expected type + switch (current_spec.type) { + case ARG_TYPE_INTEGER: + *((INT_SIZE *)current_spec.out_ptr) = lv.data.integer; + break; + case ARG_TYPE_FLOAT: + *((FLOAT_SIZE *)current_spec.out_ptr) = lv.data.floating_point; + break; + case ARG_TYPE_STRING: + *((char **)current_spec.out_ptr) = lv.data.string; + break; + case ARG_TYPE_BOOLEAN: + *((bool *)current_spec.out_ptr) = lv.data.boolean; + break; + case ARG_TYPE_ARRAY: + *((ArrayValue **)current_spec.out_ptr) = &lv.data.array; + break; + default: + return raise_error("Unknown argument type for argument %zu.\n", i + 1); } - break; - - case ARG_TYPE_BOOLEAN: - if (lv.type == TYPE_BOOLEAN) { - *((bool *)output_ptr) = lv.data.boolean; - } else if (lv.type == TYPE_INTEGER) { - *((bool *)output_ptr) = (lv.data.integer != 0); - } else if (lv.type == TYPE_FLOAT) { - *((bool *)output_ptr) = (lv.data.floating_point != 0.0); - } else { - return raise_error("Expected boolean for argument %zu.\n", - i + 1); + } else { + // Construct a string of expected type for the error message + char expected_type[32] = ""; + switch (current_spec.type) { + case ARG_TYPE_INTEGER: + strcpy(expected_type, "integer"); + break; + case ARG_TYPE_FLOAT: + strcpy(expected_type, "float"); + break; + case ARG_TYPE_STRING: + strcpy(expected_type, "string"); + break; + case ARG_TYPE_BOOLEAN: + strcpy(expected_type, "boolean"); + break; + case ARG_TYPE_ARRAY: + strcpy(expected_type, "array"); + break; + default: + strcpy(expected_type, "unknown"); + break; } - break; - default: - return raise_error("Unknown argument type for argument %zu.\n", + return raise_error("Expected %s for argument %zu.\n", expected_type, i + 1); } @@ -715,3 +730,55 @@ InterpretResult builtin_file_write(ASTNode *node, Environment *env) { InterpretResult builtin_file_append(ASTNode *node, Environment *env) { return helper_file_writer(node, env, true); } + +/** + * @brief Built-in function to calculate the length of an array or string. + * + * @param node The AST node representing the function call. + * @param env The current environment. + * @return InterpretResult containing the length as an integer or an error. + */ +InterpretResult builtin_length(ASTNode *node, Environment *env) { + // Ensure the function call has exactly one argument + size_t num_args = 0; + ASTNode *arg_node = node->function_call.arguments; + ASTNode *temp = arg_node; + while (temp) { + num_args++; + temp = temp->next; + } + + if (num_args != 1) { + return raise_error( + "`length()` expects exactly one argument, but %zu were given.\n", + num_args); + } + + // Interpret the single argument + InterpretResult arg_res = interpret_node(arg_node, env); + if (arg_res.is_error) { + // Propagate the error + return arg_res; + } + + LiteralValue lv = arg_res.value; + + // Initialize the result + LiteralValue result; + result.type = TYPE_INTEGER; + + // Determine the type of the argument and calculate length accordingly + if (lv.type == TYPE_STRING) { + result.data.integer = (INT_SIZE)strlen(lv.data.string); + } else if (lv.type == TYPE_ARRAY) { + result.data.integer = (INT_SIZE)lv.data.array.count; + } else { + // Unsupported type + return raise_error("`length()` expects an array or a string as an " + "argument, but received type `%d`.\n", + lv.type); + } + + // Return the length as a LiteralValue + return make_result(result, false, false); +} diff --git a/src/interpreter/builtins.h b/src/interpreter/builtins.h index 2468bef..1da77b0 100644 --- a/src/interpreter/builtins.h +++ b/src/interpreter/builtins.h @@ -15,10 +15,12 @@ typedef enum { ARG_TYPE_FLOAT, ARG_TYPE_STRING, ARG_TYPE_BOOLEAN, + ARG_TYPE_ARRAY, } ArgType; typedef struct { - ArgType type; // expected argument type - void *out_ptr; // pointer to store interpreted value + size_t num_types; // number of acceptable types + ArgType type; // expected argument type + void *out_ptr; // pointer to store interpreted value } ArgumentSpec; // Built-in functions for the standard library @@ -31,8 +33,10 @@ InterpretResult builtin_time(void); InterpretResult builtin_file_read(ASTNode *node, Environment *env); InterpretResult builtin_file_write(ASTNode *node, Environment *env); InterpretResult builtin_file_append(ASTNode *node, Environment *env); +InterpretResult builtin_length(ASTNode *node, Environment *env); // Helpers +bool literal_type_matches_arg_type(LiteralType lit_type, ArgType arg_type); InterpretResult interpret_arguments(ASTNode *node, Environment *env, size_t num_args, ArgumentSpec *specs); void print_formatted_string(const char *str); diff --git a/src/interpreter/interpreter.c b/src/interpreter/interpreter.c index a78bad5..09a38be 100644 --- a/src/interpreter/interpreter.c +++ b/src/interpreter/interpreter.c @@ -1493,6 +1493,8 @@ InterpretResult interpret_function_call(ASTNode *node, Environment *env) { return builtin_file_write(node, env); } else if (strcmp(func->name, "garnish_file") == 0) { return builtin_file_append(node, env); + } else if (strcmp(func->name, "length") == 0) { + return builtin_length(node, env); } else { return raise_error("Unknown built-in function `%s`\n", func->name); @@ -1905,72 +1907,167 @@ InterpretResult interpret_array_index_access(ASTNode *node, Environment *env) { return make_result(element, false, false); } +/** + * @brief Collects the indices from a nested AST_ARRAY_INDEX_ACCESS node. + * + * @param node The AST node representing the LHS of the assignment. + * @param env The current execution environment. + * @param indices Pointer to store the array of indices. + * @param count Pointer to store the number of indices collected. + * @return InterpretResult indicating success or error. + */ +InterpretResult collect_indices(ASTNode *node, Environment *env, + INT_SIZE **indices, size_t *count) { + size_t capacity = 4; + *indices = malloc(capacity * sizeof(INT_SIZE)); + if (!(*indices)) { + return raise_error("Memory allocation failed.\n"); + } + *count = 0; + + ASTNode *current_node = node; + + while (current_node->type == AST_ARRAY_INDEX_ACCESS) { + ASTNode *index_node = current_node->array_index_access.index; + + // Interpret the index + InterpretResult index_res = interpret_node(index_node, env); + if (index_res.is_error) { + free(*indices); + return index_res; + } + + if (index_res.value.type != TYPE_INTEGER) { + free(*indices); + return raise_error("Array index must be an integer.\n"); + } + + INT_SIZE index = index_res.value.data.integer; + + // Add to the indices array + if (*count == capacity) { + capacity *= 2; + INT_SIZE *temp = realloc(*indices, capacity * sizeof(INT_SIZE)); + if (!temp) { + free(*indices); + return raise_error("Memory allocation failed.\n"); + } + *indices = temp; + } + + (*indices)[(*count)++] = index; + + // Move to the next array node + current_node = current_node->array_index_access.array; + } + + // After traversal, current_node should be AST_VARIABLE_REFERENCE + if (current_node->type != AST_VARIABLE_REFERENCE) { + free(*indices); + return raise_error("Index assignment requires a variable reference.\n"); + } + + return make_result( + (LiteralValue){.type = TYPE_BOOLEAN, .data.boolean = true}, false, + false); +} + +/** + * @brief Handles assignments to array indices, supporting nested assignments. + * + * @param node The AST node representing the LHS of the assignment. + * @param env The current execution environment. + * @param new_value The new value to assign. + * @return InterpretResult indicating success or error. + */ 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; + INT_SIZE *indices = NULL; + size_t count = 0; - // Interpret the array - InterpretResult array_res = interpret_node(array_node, env); - if (array_res.is_error) { - return array_res; + // Collect the indices from the AST + InterpretResult res = collect_indices(node, env, &indices, &count); + if (res.is_error) { + return res; } - if (array_res.value.type != TYPE_ARRAY) { - return raise_error("Index access requires an array operand.\n"); + if (count == 0) { + free(indices); + return raise_error("No indices provided for array assignment.\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"); + // Get the base variable + ASTNode *current_node = node; + while (current_node->type == AST_ARRAY_INDEX_ACCESS) { + current_node = current_node->array_index_access.array; } - const char *var_name = array_node->variable_name; + + const char *var_name = current_node->variable_name; Variable *var = get_variable(env, var_name); if (!var) { + free(indices); 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); + free(indices); 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; + if (var->value.type != TYPE_ARRAY) { + free(indices); + return raise_error("Assignment requires an array variable.\n"); } - // Index must be integer - if (index_res.value.type != TYPE_INTEGER) { - return raise_error("Array index must be an integer.\n"); + ArrayValue *current_array = &var->value.data.array; + + // Traverse the array using indices up to the penultimate index + for (size_t i = 0; i < count - 1; i++) { + INT_SIZE index = indices[i]; + + // Handle negative indices + if (index < 0) { + index = (INT_SIZE)current_array->count + index; + } + + if (index < 0 || (size_t)index >= current_array->count) { + free(indices); + return raise_error("Array index `%lld` out of bounds.\n", index); + } + + LiteralValue *elem = ¤t_array->elements[index]; + if (elem->type != TYPE_ARRAY) { + free(indices); + return raise_error( + "Cannot assign to a non-array element in nested assignment.\n"); + } + + current_array = &elem->data.array; } - INT_SIZE index = index_res.value.data.integer; + // Handle last index for assignment + INT_SIZE final_index = indices[count - 1]; - // Handle negative indices (e.g., -1 refers to the last element) - if (index < 0) { - index = (INT_SIZE)array->count + index; + // Handle negative indices + if (final_index < 0) { + final_index = (INT_SIZE)current_array->count + final_index; } - if (index < 0 || (size_t)index >= array->count) { - return raise_error("Array index `%lld` out of bounds.\n", index); + if (final_index < 0 || (size_t)final_index >= current_array->count) { + free(indices); + return raise_error("Array index `%lld` out of bounds.\n", final_index); } - // Assign the new value to the array element - array->elements[index] = new_value; + // Assign new value + current_array->elements[final_index] = new_value; + + free(indices); + return make_result(new_value, false, false); } -// Helper function to get the index of a variable in the environment +// Helper function to get 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) { @@ -1999,7 +2096,7 @@ InterpretResult interpret_array_slice_access(ASTNode *node, Environment *env) { ASTNode *end_node = node->array_slice_access.end; ASTNode *step_node = node->array_slice_access.step; - // Interpret the array + // Interpret array InterpretResult array_res = interpret_node(array_node, env); if (array_res.is_error) { return array_res; @@ -2009,7 +2106,7 @@ InterpretResult interpret_array_slice_access(ASTNode *node, Environment *env) { return raise_error("Slice access requires an array operand.\n"); } - // Access the ArrayValue by reference + // Access ArrayValue by reference ArrayValue *array = &array_res.value.data.array; size_t array_count = array->count; diff --git a/src/interpreter/interpreter.h b/src/interpreter/interpreter.h index 6849eb3..0c4d8f9 100644 --- a/src/interpreter/interpreter.h +++ b/src/interpreter/interpreter.h @@ -34,8 +34,16 @@ InterpretResult call_user_defined_function(Function *func_ref, ASTNode *call_node, Environment *env); InterpretResult interpret_try(ASTNode *node, Environment *env); + +// Arrays +typedef struct { + ArrayValue *array; // Pointer to target array where assignment occurs + INT_SIZE index; // Index in target array to assign new value +} AssignmentTargetInfo; InterpretResult interpret_array_literal(ASTNode *node, Environment *env); InterpretResult interpret_array_operation(ASTNode *node, Environment *env); +InterpretResult collect_indices(ASTNode *node, Environment *env, + INT_SIZE **indices, size_t *count); InterpretResult interpret_array_index_access(ASTNode *node, Environment *env); InterpretResult interpret_array_index_assignment(ASTNode *node, Environment *env, diff --git a/src/interpreter/utils.c b/src/interpreter/utils.c index b740105..eb02bce 100644 --- a/src/interpreter/utils.c +++ b/src/interpreter/utils.c @@ -54,8 +54,9 @@ void initialize_builtin_function(Environment *env, const char *name) { void initialize_all_builtin_functions(Environment *env) { const char *builtin_functions[] = { - "string", "float", "int", "sample", "serve", "burn", - "random", "get_time", "taste_file", "plate_file", "garnish_file"}; + "string", "float", "int", "sample", + "serve", "burn", "random", "get_time", + "taste_file", "plate_file", "garnish_file", "length"}; for (size_t i = 0; i < sizeof(builtin_functions) / sizeof(builtin_functions[0]); i++) { diff --git a/src/lexer/scanner.c b/src/lexer/scanner.c index 2109d3f..30f936e 100644 --- a/src/lexer/scanner.c +++ b/src/lexer/scanner.c @@ -119,6 +119,19 @@ void scan_array(ScannerState *state, Token **tokens, size_t *token_count, continue; } + // Handle nested array + if (inner_c == '[') { + // Recursively scan the nested array + scan_array(state, tokens, token_count, capacity); + continue; + } + + // Handle string literals + if (inner_c == '"') { + scan_string(state, tokens, token_count, capacity); + continue; + } + // Handle special array operations first if (inner_c == '^' || inner_c == '+' || inner_c == '-') { // Peek the next character @@ -136,7 +149,7 @@ void scan_array(ScannerState *state, Token **tokens, size_t *token_count, state->pos += 2; // Move past the two-character operator continue; } - // **Handle negative numbers (e.g., -1) within slicing** + // 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 @@ -152,6 +165,13 @@ void scan_array(ScannerState *state, Token **tokens, size_t *token_count, } } + // Handle identifiers + if (is_valid_identifier_start(inner_c)) { + scan_identifier_or_keyword(state, tokens, token_count, + capacity); + continue; + } + // Handle numbers (including negative numbers) after array operators if (isdigit(inner_c) || (inner_c == '-' && state->pos + 1 < state->length && diff --git a/src/parser/parser.c b/src/parser/parser.c index b39e761..231ed10 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -173,6 +173,7 @@ ASTNode *parse_variable_assignment(ParserState *state) { // Parse the left-hand side (LHS) expression ASTNode *lhs = parse_expression(state); + debug_print_par("Parsed LHS expression\n"); // Expect `=` operator Token *op_token = get_current_token(state); @@ -181,11 +182,12 @@ ASTNode *parse_variable_assignment(ParserState *state) { parser_error("Expected `=` operator after variable name or slice", op_token); } + debug_print_par("Found '=' operator\n"); advance_token(state); // consume `=` // Parse the expression on the right-hand side (RHS) ASTNode *rhs = parse_expression(state); - debug_print_par("Parsed expression for assignment\n"); + debug_print_par("Parsed RHS expression\n"); // Expect `;` delimiter Token *delimiter = get_current_token(state); @@ -968,33 +970,32 @@ bool is_assignment(ParserState *state) { if (state->current->type != TOKEN_IDENTIFIER) return false; - // Peek the next token - Token *next = peek_next_token(state); - if (!next) - return false; + // Start checking after the identifier + size_t temp_token = state->current_token + 1; + Token *tokens = state->tokens; - // Case 1: identifier '=' - if (next->type == TOKEN_OPERATOR && strcmp(next->lexeme, "=") == 0) - return true; + // Traverse any number of `[ expression ]` sequences + while (tokens[temp_token].type == TOKEN_SQ_BRACKET_OPEN) { + temp_token++; // consume `[` - // 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; - } + // Traverse tokens until `]` is found + while (tokens[temp_token].type != TOKEN_SQ_BRACKET_CLOSE && + tokens[temp_token].type != TOKEN_EOF) { + temp_token++; + } + + if (tokens[temp_token].type == TOKEN_SQ_BRACKET_CLOSE) { + temp_token++; // consume `]` + } else { + // Unmatched `[` found + return false; } } - // Not an assignment + // After traversing array indices, check if the next token is `=` + if (tokens[temp_token].type == TOKEN_OPERATOR && + strcmp(tokens[temp_token].lexeme, "=") == 0) + return true; + return false; } diff --git a/src/run_tests.sh b/src/run_tests.sh new file mode 100755 index 0000000..fdcb06b --- /dev/null +++ b/src/run_tests.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Directory containing test files +TEST_DIR="tests" + +# Loop through all .flv files in the tests directory +for test_file in "$TEST_DIR"/*.flv; do + # Skip the file named `all.flv` + if [[ "$(basename "$test_file")" == "all.flv" ]]; then + echo "Skipping file: $test_file" + continue + fi + + echo "Running test: $test_file" + ./flavor "$test_file" + sleep 0.1 # Add a short delay to allow cleanup for macOS + + # Optionally check the exit code of each test + if [ $? -ne 0 ]; then + echo "Test failed: $test_file" + exit 1 # Exit the script on failure + fi +done + +echo "All tests completed successfully!" diff --git a/src/tests/19_arrays_2.flv b/src/tests/19_arrays_2.flv new file mode 100644 index 0000000..a8815e0 --- /dev/null +++ b/src/tests/19_arrays_2.flv @@ -0,0 +1,104 @@ +# Test for array length +let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +serve("Initial array:", array); +serve("Length of array:", length(array)); + +# Test compound slicing +serve("Original array:", array); +serve("Double slice (first 5, then reverse):", (array[0:5])[::-1]); +serve("Triple slice (first 5, skip every second, then reverse):", ((array[0:5])[::2])[::-1]); + +# Test declaring and using 2D arrays +let array2D = [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9] +]; +serve("2D array:", array2D); + +# Access individual elements +serve("Element at (2, 2):", array2D[1][1]); +serve("Element at (3, 3):", array2D[2][2]); + +# Modify elements in a 2D array +array2D[0][0] = 10; +serve("2D array after modifying (1, 1) to 10:", array2D); + +# Test slicing on rows +serve("First row:", array2D[0]); +serve("Last row reversed:", array2D[2][::-1]); + +# Test slicing on columns (manual transpose-like slicing) +let first_column = [array2D[0][0], array2D[1][0], array2D[2][0]]; +serve("First column:", first_column); + +let second_column_reversed = [array2D[2][1], array2D[1][1], array2D[0][1]]; +serve("Second column reversed:", second_column_reversed); + +# Test length of 2D array and its rows +serve("Number of rows:", length(array2D)); +serve("Number of elements in first row:", length(array2D[0])); + +# Immutable 2D arrays +const const_2D_array = [ + [10, 20, 30], + [40, 50, 60], + [70, 80, 90] +]; +serve("Immutable 2D array:", const_2D_array); + +# Attempt mutation on immutable 2D array +try { + const_2D_array[0][0] = 100; # Trying to modify element + serve("This should not be shown; const 2D array was mutated!"); +} rescue { + serve("Caught error: Cannot mutate a const 2D array."); +} + +# Confirm immutability +serve("Const 2D array remains unchanged:", const_2D_array); + +# Final confirmation +serve("Immutable 2D array tests complete!"); + +# Test length of a mixed array +let mixed_array = [1, "two", [3, 4], [5, 6]]; +serve("Mixed array:", mixed_array); +serve("Length of mixed array:", length(mixed_array)); + +# Compound operations on mixed array +serve("First element of nested array (3, 4):", mixed_array[2][0]); +serve("Length of nested array:", length(mixed_array[2])); + +# Test completed +serve("All array operation tests complete!"); + + +# EXPECTED OUTPUT +# +# Initial array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +# Length of array: 10 +# Original array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +# Double slice (first 5, then reverse): [5, 4, 3, 2, 1] +# Triple slice (first 5, skip every second, then reverse): [5, 3, 1] +# 2D array: [[1, 2, 3], [4, 5, 6], [7, 8, 9]] +# Element at (2, 2): 5 +# Element at (3, 3): 9 +# 2D array after modifying (1, 1) to 10: [[10, 2, 3], [4, 5, 6], [7, 8, 9]] +# First row: [10, 2, 3] +# Last row reversed: [9, 8, 7] +# First column: [10, 4, 7] +# Second column reversed: [8, 5, 2] +# Number of rows: 3 +# Number of elements in first row: 3 +# Immutable 2D array: [[10, 20, 30], [40, 50, 60], [70, 80, 90]] +# Error: Cannot mutate a constant array `const_2D_array`. +# +# Caught error: Cannot mutate a const 2D array. +# Const 2D array remains unchanged: [[10, 20, 30], [40, 50, 60], [70, 80, 90]] +# Immutable 2D array tests complete! +# Mixed array: [1, "two", [3, 4], [5, 6]] +# Length of mixed array: 4 +# First element of nested array (3, 4): 3 +# Length of nested array: 2 +# All array operation tests complete!