From b2f953a6afb8eba21f2f5604b194fb27ceb494d4 Mon Sep 17 00:00:00 2001 From: Kenny <70860732+KennyOliver@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:05:51 +0000 Subject: [PATCH 1/6] Add: `debug_print_tokens` & `debug_print_basic` calls back to `main.c` #234 --- src/main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.c b/src/main.c index ead80d3..3aafcbe 100644 --- a/src/main.c +++ b/src/main.c @@ -410,17 +410,22 @@ int main(int argc, char **argv) { // Execute script Token *tokens = tokenize(source); + debug_print_tokens(tokens); + debug_print_basic("Tokenization complete!\n\n"); ASTNode *ast = parse_program(tokens); + debug_print_basic("Parsing complete!\n\n"); Environment env; init_environment(&env); env.script_dir = strdup(script_dir); interpret_program(ast, &env); + debug_print_basic("Execution complete!\n\n"); // Clean up memory free(tokens); free(source); free_environment(&env); free_ast(ast); + debug_print_basic("Memory cleared!\n\n"); return EXIT_SUCCESS; } From 1859e47b4c844a0c740290bb61514a65e7d93763 Mon Sep 17 00:00:00 2001 From: Kenny <70860732+KennyOliver@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:11:52 +0000 Subject: [PATCH 2/6] Remove: Redundant `recipe` keyword #234 --- src/lexer/keywords.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lexer/keywords.c b/src/lexer/keywords.c index bb36473..142d565 100644 --- a/src/lexer/keywords.c +++ b/src/lexer/keywords.c @@ -22,7 +22,6 @@ const char *KEYWORDS[] = { "plate", // write file "garnish", // append file "taste", // read file - "recipe", // import "True", // Boolean True "False", // Boolean False "import", // Import `.flv` script From 881385d92316030d93c5f7bd210eaadde2b5da6a Mon Sep 17 00:00:00 2001 From: Kenny <70860732+KennyOliver@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:12:18 +0000 Subject: [PATCH 3/6] Add: Parser support for iterables in for loops #234 --- src/parser/parser.c | 172 ++++++++++++++++++++++++----------------- src/shared/ast_types.h | 7 ++ 2 files changed, 109 insertions(+), 70 deletions(-) diff --git a/src/parser/parser.c b/src/parser/parser.c index 5127503..d553a4f 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -469,20 +469,21 @@ ASTNode *parse_while_loop(ParserState *state) { ASTNode *parse_for_loop(ParserState *state) { debug_print_par("Parsing a `for` loop...\n"); - ASTNode *node = calloc(1, sizeof(ASTNode)); if (!node) { parser_error("Memory allocation failed", get_current_token(state)); } node->type = AST_FOR_LOOP; - // Expect `for` keyword + // Expect 'for' keyword expect_token(state, TOKEN_KEYWORD, "Expected `for` keyword"); debug_print_par("Found `for` keyword\n"); - // Parse loop variable + // Parse loop variable: instead of checking token type strictly, + // ensure the lexeme is not a reserved word like "for" or "in" Token *var_token = get_current_token(state); - if (var_token->type != TOKEN_IDENTIFIER) { + if (strcmp(var_token->lexeme, "for") == 0 || + strcmp(var_token->lexeme, "in") == 0) { parser_error("Expected loop variable identifier", var_token); } char *loop_var = strdup(var_token->lexeme); @@ -492,83 +493,114 @@ ASTNode *parse_for_loop(ParserState *state) { debug_print_par("Loop variable: %s\n", loop_var); advance_token(state); // Consume loop variable - // Expect `in` keyword + // Expect 'in' keyword expect_token(state, TOKEN_KEYWORD, "Expected `in` keyword"); debug_print_par("Found `in` keyword\n"); - // Parse start expression - ASTNode *start_expr = parse_expression(state); - if (!start_expr) { - parser_error("Expected start expression in for loop", - get_current_token(state)); - } - debug_print_par("Parsed start expression\n"); - - // Parse `..` or `..=` operator - Token *range_op = get_current_token(state); - bool inclusive = false; - if (range_op->type == TOKEN_OPERATOR && - strcmp(range_op->lexeme, "..") == 0) { - inclusive = false; - } else if (range_op->type == TOKEN_OPERATOR && - strcmp(range_op->lexeme, "..=") == 0) { - inclusive = true; - } else { - parser_error("Expected `..` or `..=` operator in for loop", range_op); - } - debug_print_par("Found range operator: %s\n", range_op->lexeme); - advance_token(state); // Consume range operator + // Peek to decide if it is a range-based or iterable loop + Token *next_token = get_current_token(state); + if (next_token->type == TOKEN_OPERATOR && + (strcmp(next_token->lexeme, "..") == 0 || + strcmp(next_token->lexeme, "..=") == 0)) { + // Range-based loop branch + node->for_loop.is_iterable_loop = false; + + // Parse start expression + ASTNode *start_expr = parse_expression(state); + if (!start_expr) { + parser_error("Expected start expression in for loop", + get_current_token(state)); + } + debug_print_par("Parsed start expression\n"); + + // Parse range operator + Token *range_op = get_current_token(state); + bool inclusive = false; + if (range_op->type == TOKEN_OPERATOR && + strcmp(range_op->lexeme, "..") == 0) { + inclusive = false; + } else if (range_op->type == TOKEN_OPERATOR && + strcmp(range_op->lexeme, "..=") == 0) { + inclusive = true; + } else { + parser_error("Expected `..` or `..=` operator in for loop", + range_op); + } + debug_print_par("Found range operator: %s\n", range_op->lexeme); + advance_token(state); // Consume operator - // Parse end expression - ASTNode *end_expr = parse_expression(state); - if (!end_expr) { - parser_error("Expected end expression in for loop", - get_current_token(state)); - } - debug_print_par("Parsed end expression\n"); + // Parse end expression + ASTNode *end_expr = parse_expression(state); + if (!end_expr) { + parser_error("Expected end expression in for loop", + get_current_token(state)); + } + debug_print_par("Parsed end expression\n"); - // Initialize step expression to NULL - ASTNode *step_expr = NULL; + // Optional: Parse 'by' keyword and step expression + ASTNode *step_expr = NULL; + Token *current = get_current_token(state); + if (current->type == TOKEN_KEYWORD && + strcmp(current->lexeme, "by") == 0) { + debug_print_par("Found `by` keyword\n"); + advance_token(state); // Consume 'by' + step_expr = parse_expression(state); + if (!step_expr) { + parser_error("Expected step expression after `by`", + get_current_token(state)); + } + debug_print_par("Parsed step expression\n"); + } - // Check for optional `by` keyword - Token *current = get_current_token(state); - if (current->type == TOKEN_KEYWORD && strcmp(current->lexeme, "by") == 0) { - debug_print_par("Found `by` keyword\n"); - advance_token(state); // Consume `by` keyword - - // Parse step expression as a full expression - step_expr = parse_expression(state); - if (!step_expr) { - parser_error("Expected step expression after `by`", + // Parse loop body + expect_token(state, TOKEN_BRACE_OPEN, + "Expected `{` delimiter to start loop body"); + debug_print_par("Found `{` to start loop body\n"); + ASTNode *body = parse_block(state); + if (!body) { + parser_error("Expected loop body", get_current_token(state)); + } + debug_print_par("Parsed loop body\n"); + expect_token(state, TOKEN_BRACE_CLOSE, + "Expected `}` delimiter to end loop body"); + debug_print_par("Found `}` to end loop body\n"); + + // Populate range-based loop fields + node->for_loop.loop_variable = loop_var; + node->for_loop.start_expr = start_expr; + node->for_loop.end_expr = end_expr; + node->for_loop.inclusive = inclusive; + node->for_loop.step_expr = step_expr; + node->for_loop.body = body; + } else { + // Iterable loop branch: e.g., "for recipe in recipes { ... }" + node->for_loop.is_iterable_loop = true; + ASTNode *collection_expr = parse_expression(state); + if (!collection_expr) { + parser_error("Expected collection expression after `in`", get_current_token(state)); } - debug_print_par("Parsed step expression\n"); - } - - expect_token(state, TOKEN_BRACE_OPEN, - "Expected `{` delimiter to start loop body"); - debug_print_par("Found `{` to start loop body\n"); + debug_print_par("Parsed collection expression for iterable loop\n"); + + // Parse loop body + expect_token(state, TOKEN_BRACE_OPEN, + "Expected `{` delimiter to start loop body"); + debug_print_par("Found `{` to start loop body\n"); + ASTNode *body = parse_block(state); + if (!body) { + parser_error("Expected loop body", get_current_token(state)); + } + debug_print_par("Parsed loop body\n"); + expect_token(state, TOKEN_BRACE_CLOSE, + "Expected `}` delimiter to end loop body"); + debug_print_par("Found `}` to end loop body\n"); - // Parse loop body - ASTNode *body = parse_block(state); - if (!body) { - parser_error("Expected loop body", get_current_token(state)); + node->for_loop.loop_variable = loop_var; + node->for_loop.collection_expr = collection_expr; + node->for_loop.body = body; } - debug_print_par("Parsed loop body\n"); - - expect_token(state, TOKEN_BRACE_CLOSE, - "Expected `}` delimiter to end loop body"); - debug_print_par("Found `}` to end loop body\n"); - - // Assign parsed components to ASTForLoop - node->for_loop.loop_variable = loop_var; - node->for_loop.start_expr = start_expr; - node->for_loop.end_expr = end_expr; - node->for_loop.inclusive = inclusive; - node->for_loop.step_expr = step_expr; - node->for_loop.body = body; - node->next = NULL; + node->next = NULL; debug_print_par("Successfully parsed a `for` loop\n"); return node; } diff --git a/src/shared/ast_types.h b/src/shared/ast_types.h index 8bacc21..4e572dc 100644 --- a/src/shared/ast_types.h +++ b/src/shared/ast_types.h @@ -93,10 +93,17 @@ typedef struct { // AST For Loop Node typedef struct { char *loop_variable; + + // Range-based struct ASTNode *start_expr; struct ASTNode *end_expr; bool inclusive; struct ASTNode *step_expr; + + // Iterables + bool is_iterable_loop; // true if `for X in Y` + struct ASTNode *collection_expr; // e.g. `recipes` or any expression + struct ASTNode *body; } ASTForLoop; From 89f7a18f383c6ae9ba0ee90d848bfefee59bdc90 Mon Sep 17 00:00:00 2001 From: Kenny <70860732+KennyOliver@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:12:25 +0000 Subject: [PATCH 4/6] Add: Interpreter support for iterables in for loops #234 --- src/interpreter/interpreter.c | 108 +++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/src/interpreter/interpreter.c b/src/interpreter/interpreter.c index 29ce10e..fabdeb2 100644 --- a/src/interpreter/interpreter.c +++ b/src/interpreter/interpreter.c @@ -1153,10 +1153,56 @@ InterpretResult interpret_while_loop(ASTNode *node, Environment *env) { InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { if (node->type != AST_FOR_LOOP) { return raise_error( - "`interpret_for_loop` called with non-`for`-loop `ASTNode`\n"); + "`interpret_for_loop` called with non-`for`-loop ASTNode\n"); } - // Extract loop components + // If it's an iterable loop: "for item in collection { ... }" + if (node->for_loop.is_iterable_loop) { + InterpretResult coll_res = + interpret_node(node->for_loop.collection_expr, env); + if (coll_res.is_error) { + return coll_res; + } + if (coll_res.value.type != TYPE_ARRAY) { + return raise_error("For loop iterable must be an array.\n"); + } + ArrayValue *array = &coll_res.value.data.array; + + char *loop_var = strdup(node->for_loop.loop_variable); + if (!loop_var) { + return raise_error("Memory allocation failed for loop variable\n"); + } + InterpretResult var_res = allocate_variable(env, loop_var); + if (var_res.is_error) { + free(loop_var); + return var_res; + } + // Iterate over each element in the array + for (size_t i = 0; i < array->count; i++) { + Variable *var = get_variable(env, loop_var); + if (!var) { + free(loop_var); + return raise_error( + "Loop variable `%s` not found in environment\n", loop_var); + } + var->value = array->elements[i]; + // Execute loop body + ASTNode *current_stmt = node->for_loop.body; + while (current_stmt) { + InterpretResult body_res = interpret_node(current_stmt, env); + if (body_res.did_return || body_res.did_break) { + free(loop_var); + return body_res; + } + current_stmt = current_stmt->next; + } + } + free(loop_var); + return make_result(create_default_value(), false, false); + } + + // Otherwise, handle range-based loop: "for i in start_expr ..[=] end_expr + // [by step] { ... }" char *loop_var = strdup(node->for_loop.loop_variable); if (!loop_var) { return raise_error("Memory allocation failed for loop variable\n"); @@ -1164,23 +1210,20 @@ InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { ASTNode *start_expr = node->for_loop.start_expr; ASTNode *end_expr = node->for_loop.end_expr; bool inclusive = node->for_loop.inclusive; - ASTNode *step_expr = node->for_loop.step_expr; // may be `NULL` + ASTNode *step_expr = node->for_loop.step_expr; // may be NULL ASTNode *body = node->for_loop.body; - // Evaluate start and end expressions InterpretResult start_res = interpret_node(start_expr, env); if (start_res.is_error) { free(loop_var); return start_res; } - InterpretResult end_res = interpret_node(end_expr, env); if (end_res.is_error) { free(loop_var); return end_res; } - // 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; @@ -1190,7 +1233,6 @@ InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { free(loop_var); return raise_error("Start expression in `for` loop must be numeric\n"); } - if (end_res.value.type == TYPE_FLOAT) { end_val = end_res.value.data.floating_point; } else if (end_res.value.type == TYPE_INTEGER) { @@ -1200,15 +1242,13 @@ InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { return raise_error("End expression in `for` loop must be numeric\n"); } - // Evaluate step expression if present - FLOAT_SIZE step = 1.0; // default + FLOAT_SIZE step = 1.0; if (step_expr) { InterpretResult step_res = interpret_node(step_expr, env); if (step_res.is_error) { free(loop_var); return step_res; } - if (step_res.value.type == TYPE_FLOAT) { step = step_res.value.data.floating_point; } else if (step_res.value.type == TYPE_INTEGER) { @@ -1219,35 +1259,24 @@ InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { "Step expression in `for` loop must be numeric\n"); } } else { - // Determine implied step based on range direction - if (start_val < end_val) { - step = 1.0; - } else { - step = -1.0; - } + step = (start_val < end_val) ? 1.0 : -1.0; } - - // Validate step to prevent infinite loops if (step < 1e-9 && step > -1e-9) { free(loop_var); return raise_error("Step value cannot be zero in `for` loop\n"); } - // Assign or update loop variable in the environment InterpretResult var_res = allocate_variable(env, loop_var); if (var_res.is_error) { free(loop_var); - return var_res; // Propagate the error + return var_res; } - - // Initialize the loop variable with the start value Variable *var = get_variable(env, loop_var); if (!var) { free(loop_var); - return raise_error("Failed to allocate and retrieve variable `%s`.\n", + return raise_error("Failed to retrieve loop variable `%s`.\n", loop_var); } - if (start_res.value.type == TYPE_FLOAT) { var->value.type = TYPE_FLOAT; var->value.data.floating_point = start_val; @@ -1255,19 +1284,14 @@ InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { var->value.type = TYPE_INTEGER; var->value.data.integer = (INT_SIZE)start_val; } - - // Determine loop direction bool is_ascending = step > 0.0; - while (1) { - // Re-fetch the variable pointer at the start of each iteration var = get_variable(env, loop_var); if (!var) { free(loop_var); return raise_error("Loop variable `%s` not found in environment\n", loop_var); } - FLOAT_SIZE current_val; if (var->value.type == TYPE_FLOAT) { current_val = var->value.data.floating_point; @@ -1278,8 +1302,6 @@ InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { return raise_error("Loop variable `%s` must be numeric\n", loop_var); } - - // Check if the loop should continue bool condition_true = false; if (is_ascending) { condition_true = @@ -1288,43 +1310,31 @@ InterpretResult interpret_for_loop(ASTNode *node, Environment *env) { condition_true = inclusive ? (current_val >= end_val) : (current_val > end_val); } - if (!condition_true) { break; } - - // Interpret the loop body - ASTNode *current = body; - while (current) { - InterpretResult body_res = interpret_node(current, env); + ASTNode *current_stmt = body; + while (current_stmt) { + InterpretResult body_res = interpret_node(current_stmt, env); if (body_res.did_return || body_res.did_break) { free(loop_var); return body_res; } - current = current->next; + current_stmt = current_stmt->next; } - - // Re-fetch the variable pointer after interpreting the loop body var = get_variable(env, loop_var); if (!var) { free(loop_var); - return raise_error( - "Loop variable `%s` not found in environment after loop body\n", - loop_var); + return raise_error("Loop variable `%s` not found after loop body\n", + loop_var); } - - // Update loop variable if (var->value.type == TYPE_FLOAT) { var->value.data.floating_point += step; } else if (var->value.type == TYPE_INTEGER) { var->value.data.integer += (INT_SIZE)step; } } - - // Clean up free(loop_var); - - // Loops do not return values return make_result(create_default_value(), false, false); } From dfd967a88cb544da2cd2f0a926ba3346b2604a15 Mon Sep 17 00:00:00 2001 From: Kenny <70860732+KennyOliver@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:16:17 +0000 Subject: [PATCH 5/6] Update tutorial.md #234 --- docs/tutorial.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index 6a9ccb6..ce6709d 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -109,10 +109,16 @@ if temperature > 180 { #### For Loops +`for` loops allow you to iterate over a range or a collection. + +##### Iterating Over a Range + +You can use the range operator (exclusive `..` or inclusive `..=`) to specify the start & end of the loop. Use the optional `by` keyword to specify a step: + ```py # Count up for i in 1..5 { - serve("Step", i); + serve(i); # Counts: 1, 2, 3, 4 } # Count down @@ -121,8 +127,22 @@ for i in 10..=1 by -2 { } ``` +##### Iterating Over an Array + +```py +let recipes = ["Cake", "Cookies", "Pasta"]; + +for recipe in recipes { + serve("Preparing:", recipe); +} + +serve("All recipes are ready!"); +``` + #### While Loops +`while` loops allow you to iterate whilst a condition holds `True`. + ```js let ingredients = 3; From dcc76990f17ab819164f803bfa00ebfebc627aea Mon Sep 17 00:00:00 2001 From: Kenny <70860732+KennyOliver@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:17:40 +0000 Subject: [PATCH 6/6] Update 4_for_loop.flv #234 --- src/tests/4_for_loop.flv | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tests/4_for_loop.flv b/src/tests/4_for_loop.flv index 52885f5..a7780e5 100644 --- a/src/tests/4_for_loop.flv +++ b/src/tests/4_for_loop.flv @@ -7,6 +7,17 @@ for j in 10..=1 by -3 { } +let recipes = ["Cake", "Cookies", "Pasta"]; + +serve("We have", length(recipes), "recipes today!"); + +for recipe in recipes { + serve("Preparing:", recipe); +} + +serve("All recipes are ready!"); + + # Invalid loop - interpreter throws an error # for j in 10..=1 by 2 { # serve(j);