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!