From cc5bbd61f2994632c9829b6d7555f3b8e878d532 Mon Sep 17 00:00:00 2001 From: samsonium Date: Thu, 22 Oct 2026 18:24:28 +0300 Subject: [PATCH] feat: implement loops --- include/compiler/fzl_parser.h | 11 ++ include/compiler/fzl_token.h | 3 + include/compiler/fzl_walker.h | 20 ++- src/compiler/fzl_debug.c | 18 ++- src/compiler/fzl_parser.c | 241 ++++++++++++++++++++++++++++++++++ src/compiler/fzl_reader.c | 5 + src/compiler/fzl_walker.c | 102 +++++++++++++- 7 files changed, 396 insertions(+), 4 deletions(-) diff --git a/include/compiler/fzl_parser.h b/include/compiler/fzl_parser.h index f96c307..08d20ea 100644 --- a/include/compiler/fzl_parser.h +++ b/include/compiler/fzl_parser.h @@ -18,6 +18,7 @@ typedef enum FZL_NODETYPE_CLASS, FZL_NODETYPE_FIELD, FZL_NODETYPE_CONSTRUCT, + FZL_NODETYPE_LOOP, FZL_NODETYPE_ADD, FZL_NODETYPE_SUB, @@ -140,6 +141,16 @@ typedef struct fzl_node { struct fzl_node *name; } construct; + + // Loop + struct + { + struct fzl_node *init; + struct fzl_node *cond; + struct fzl_node *step; + + struct fzl_node *body; + } loop; }; } fzl_node_t; diff --git a/include/compiler/fzl_token.h b/include/compiler/fzl_token.h index a051cd0..37967bd 100644 --- a/include/compiler/fzl_token.h +++ b/include/compiler/fzl_token.h @@ -14,6 +14,9 @@ typedef enum FZL_TOKTYPE_FUNCTION, // fn FZL_TOKTYPE_RETURN, // return + FZL_TOKTYPE_FOR, // for + FZL_TOKTYPE_WHILE, // while + FZL_TOKTYPE_IF, // if FZL_TOKTYPE_ELSE, // else diff --git a/include/compiler/fzl_walker.h b/include/compiler/fzl_walker.h index a984e68..8947136 100644 --- a/include/compiler/fzl_walker.h +++ b/include/compiler/fzl_walker.h @@ -16,7 +16,8 @@ typedef enum FZL_WALK_CTX_TYPE_GLOBAL, FZL_WALK_CTX_TYPE_FN, FZL_WALK_CTX_TYPE_CLASS, - FZL_WALK_CTX_TYPE_ASSIGN + FZL_WALK_CTX_TYPE_ASSIGN, + FZL_WALK_CTX_TYPE_LOOP, } fzl_walk_ctx_type_t; // Internal walk function result @@ -36,8 +37,23 @@ typedef struct fzl_walk_ctx fzl_walk_ctx_type_t type; struct fzl_walk_ctx *parent; - fzl_node_t *fn; size_t initial_env_lvl; + union + { + // function info + struct + { + fzl_node_t *fn; + }; + + // loops `break`s writeback addrs + struct + { + size_t *breaks; + size_t breaks_count; + size_t breaks_remainder; + }; + }; } fzl_walk_ctx_t; // Walker descriptor diff --git a/src/compiler/fzl_debug.c b/src/compiler/fzl_debug.c index cbab185..0ba3d7f 100644 --- a/src/compiler/fzl_debug.c +++ b/src/compiler/fzl_debug.c @@ -31,6 +31,22 @@ void print_node(fzl_node_t *node, int level) switch (node->type) { + case FZL_NODETYPE_LOOP: + printf("%s Loop:\n", levelbuf); + printf("-%s Init:\n", levelbuf); + if (node->loop.init) + print_node(node->loop.init, level + 2); + printf("-%s Condition:\n", levelbuf); + if (node->loop.cond) + print_node(node->loop.cond, level + 2); + printf("-%s Step:\n", levelbuf); + if (node->loop.step) + print_node(node->loop.step, level + 2); + printf("-%s Body:\n", levelbuf); + if (node->loop.body) + print_node(node->loop.body, level + 2); + break; + case FZL_NODETYPE_CONSTRUCT: printf("%s Construct class '%s':\n", levelbuf, (char *)node->construct.name->token->value); @@ -169,7 +185,7 @@ void fzl_debug_print_ast(fzl_node_t *root) fzl_node_t *curr = root; while (curr != NULL) { - print_node(curr, 2); + print_node(curr, 3); curr = curr->next; } diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index 4274af0..afcedac 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -159,6 +159,7 @@ fzl_node_t *parse_primary(fzl_parser_t *parser) { snprintf(plast_err, sizeof(plast_err), "'%s': expected ')' at %ld:%ld", peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); + fzl_freeast(node); return NULL; } consume(parser); @@ -256,6 +257,7 @@ fzl_node_t *parse_postfix(fzl_parser_t *parser) if (!rparen || rparen->type != FZL_TOKTYPE_RPAREN) { free(args); + fzl_freeast(left); snprintf(plast_err, sizeof(plast_err), "'%s': expected ')' after argument list at %ld:%ld", peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); @@ -273,6 +275,7 @@ fzl_node_t *parse_postfix(fzl_parser_t *parser) { if (!is(parser, FZL_TOKTYPE_NAME)) { + fzl_freeast(left); snprintf(plast_err, sizeof(plast_err), "'%s': expected name after '.' at %ld:%ld", last_known_tok->filename, last_known_tok->ln, @@ -339,6 +342,7 @@ fzl_node_t *parse_power(fzl_parser_t *parser) right = parse_unary(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': failed to parse right side of power at %ld:%ld", @@ -382,6 +386,7 @@ fzl_node_t *parse_factor(fzl_parser_t *parser) right = parse_factor(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': unfinished factor (mul/div) expression at %ld:%ld", @@ -423,6 +428,7 @@ fzl_node_t *parse_term(fzl_parser_t *parser) right = parse_factor(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': unfinished term (add/sub) expression at %ld:%ld", @@ -459,6 +465,7 @@ fzl_node_t *parse_cond(fzl_parser_t *parser) right = parse_term(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': unfinished expression at %ld:%ld", op->filename, op->ln, @@ -517,6 +524,7 @@ fzl_node_t *parse_and(fzl_parser_t *parser) right = parse_cond(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': unfinished expression at %ld:%ld", or->filename, or->ln, @@ -551,6 +559,7 @@ fzl_node_t *parse_or(fzl_parser_t *parser) right = parse_and(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': unfinished expression at %ld:%ld", or->filename, or->ln, @@ -578,6 +587,7 @@ fzl_node_t *parse_new(fzl_parser_t *parser) if (objname->type != FZL_NODETYPE_IDENTIFIER && objname->type != FZL_NODETYPE_FIELD) { + fzl_freeast(objname); snprintf(plast_err, sizeof(plast_err), "'%s': expect class name after 'new' keyword at %ld:%ld", last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); @@ -604,6 +614,7 @@ fzl_node_t *parse_assignment(fzl_parser_t *parser) left->type != FZL_NODETYPE_DECLARATION && left->type != FZL_NODETYPE_FIELD) { + fzl_freeast(left); snprintf(plast_err, sizeof(plast_err), "'%s': cannot assign to '%s' at %ld:%ld", left->token->filename, (char *)left->token->value, asn->ln, asn->cn); @@ -612,7 +623,10 @@ fzl_node_t *parse_assignment(fzl_parser_t *parser) fzl_node_t *right = parse_assignment(parser); if (!right) + { + fzl_freeast(left); return NULL; + } fzl_node_t *node = fzl_mknode(FZL_NODETYPE_ASSIGN, asn); node->binary_op.left = left; @@ -692,6 +706,7 @@ fzl_node_t *parse_class(fzl_parser_t *parser) fields = realloc(fields, fremainder * sizeof(fzl_node_t *)); if (!fields) { + fzl_freeast(node); snprintf(plast_err, sizeof(plast_err), "failed to resize fields array"); free(methods); @@ -709,6 +724,7 @@ fzl_node_t *parse_class(fzl_parser_t *parser) methods = realloc(methods, mremainder * sizeof(fzl_node_t *)); if (!methods) { + fzl_freeast(node); snprintf(plast_err, sizeof(plast_err), "failed to resize methods array"); free(fields); @@ -723,6 +739,7 @@ fzl_node_t *parse_class(fzl_parser_t *parser) snprintf(plast_err, sizeof(plast_err), "failed to reallocate method args list"); free(fields); + fzl_freeast(node); return NULL; } @@ -741,6 +758,7 @@ fzl_node_t *parse_class(fzl_parser_t *parser) } else { + fzl_freeast(node); snprintf(plast_err, sizeof(plast_err), "'%s': unknown type of class member at %ld:%ld", node->token->filename, node->token->ln, node->token->cn); @@ -827,6 +845,7 @@ fzl_node_t *parse_function(fzl_parser_t *parser) fzl_token_t *rpar = consume(parser); if (!rpar || rpar->type != FZL_TOKTYPE_RPAREN) { + free(args); snprintf(plast_err, sizeof(plast_err), "'%s': expected ')' after argument list at %ld:%ld", last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); @@ -842,6 +861,7 @@ fzl_node_t *parse_function(fzl_parser_t *parser) if (!is(parser, FZL_TOKTYPE_LBRACE)) { + free(args); snprintf(plast_err, sizeof(plast_err), "'%s': expected '{' after function declaration at %ld:%ld", last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); @@ -850,7 +870,10 @@ fzl_node_t *parse_function(fzl_parser_t *parser) fzl_node_t *body = parse_statement(parser); if (!body) + { + free(args); return NULL; + } fzl_node_t *node = fzl_mknode(FZL_NODETYPE_FUNCTION, name); node->fn.args = args; @@ -875,7 +898,10 @@ fzl_node_t *parse_return(fzl_parser_t *parser) { fzl_node_t *retval = parse_expression(parser); if (!retval) + { + fzl_freeast(node); return NULL; + } node->ret.value = retval; } @@ -905,6 +931,7 @@ fzl_node_t *parse_if(fzl_parser_t *parser) fzl_token_t *rparen = consume(parser); if (rparen->type != FZL_TOKTYPE_RPAREN) { + fzl_freeast(condition); snprintf(plast_err, sizeof(plast_err), "'%s': expected ')' after condition at %ld:%ld", rparen->filename, rparen->ln, rparen->cn); @@ -914,14 +941,21 @@ fzl_node_t *parse_if(fzl_parser_t *parser) fzl_node_t *else_block = NULL; fzl_node_t *true_block = parse_statement(parser); if (!true_block) + { + fzl_freeast(condition); return NULL; + } if (is(parser, FZL_TOKTYPE_ELSE)) { consume(parser); // else else_block = parse_statement(parser); if (!else_block) + { + fzl_freeast(condition); + fzl_freeast(true_block); return NULL; + } } fzl_node_t *stmt_node = fzl_mknode(FZL_NODETYPE_CONDITIONAL, if_tok); @@ -948,7 +982,10 @@ fzl_node_t *parse_block(fzl_parser_t *parser) { fzl_node_t *node = parse_statement(parser); if (!node) + { + free(stmts); return NULL; + } if (remainder <= count) { @@ -961,6 +998,10 @@ fzl_node_t *parse_block(fzl_parser_t *parser) fzl_token_t *rbrace = consume(parser); if (!rbrace || rbrace->type != FZL_TOKTYPE_RBRACE) { + for (size_t i = 0; i < count; i++) + fzl_freeast(stmts[i]); + free(stmts); + snprintf(plast_err, sizeof(plast_err), "'%s': missing '}' after block at %ld:%ld", rbrace->filename, rbrace->ln, rbrace->cn); @@ -973,6 +1014,190 @@ fzl_node_t *parse_block(fzl_parser_t *parser) return node; } +// For-loop parsing +fzl_node_t *parse_for(fzl_parser_t *parser) +{ + fzl_token_t *tok = consume(parser); + fzl_token_t *lpar = consume(parser); + if (lpar->type != FZL_TOKTYPE_LPAREN) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected '(' after for keyword at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *start = NULL; + if (!is(parser, FZL_TOKTYPE_SEMICOLON)) + { + start = parse_expression(parser); + if (!start) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected loop init or ';' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; + } + + fzl_token_t *sc = consume(parser); + if (!sc || sc->type != FZL_TOKTYPE_SEMICOLON) + { + fzl_freeast(start); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing ';' after init section at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; + } + } + else + consume(parser); + + fzl_node_t *condition = NULL; + if (!is(parser, FZL_TOKTYPE_SEMICOLON)) + { + condition = parse_expression(parser); + if (!condition) + { + fzl_freeast(start); + snprintf(plast_err, sizeof(plast_err), + "'%s': expected loop condition or ';' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; + } + + fzl_token_t *sc = consume(parser); + if (!sc || sc->type != FZL_TOKTYPE_SEMICOLON) + { + fzl_freeast(start); + fzl_freeast(condition); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing ';' after condition at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; + } + } + else + consume(parser); + + fzl_node_t *step = NULL; + if (!is(parser, FZL_TOKTYPE_RPAREN)) + { + step = parse_expression(parser); + if (!step) + { + fzl_freeast(start); + fzl_freeast(condition); + snprintf(plast_err, sizeof(plast_err), + "'%s': expected loop step end or ')' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; + } + } + + fzl_token_t *rpar = consume(parser); + if (!rpar || rpar->type != FZL_TOKTYPE_RPAREN) + { + fzl_freeast(start); + fzl_freeast(condition); + fzl_freeast(step); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing ')' after loop setup at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *body = parse_statement(parser); + if (!body) + { + fzl_freeast(start); + fzl_freeast(condition); + fzl_freeast(step); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing body of for loop at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_LOOP, tok); + node->loop.init = start; + node->loop.cond = condition; + node->loop.step = step; + node->loop.body = body; + + return node; +} + +// While-loop parsing +fzl_node_t *parse_while(fzl_parser_t *parser) +{ + fzl_token_t *tok = consume(parser); + fzl_token_t *lpar = consume(parser); + if (!lpar || lpar->type != FZL_TOKTYPE_LPAREN) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': missing '(' after while keyword at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *condition = parse_expression(parser); + if (!condition) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': missing condition for 'while' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_token_t *rpar = consume(parser); + if (!rpar || rpar->type != FZL_TOKTYPE_RPAREN) + { + fzl_freeast(condition); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing ')' after while loop condition at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *body = parse_statement(parser); + if (!body) + { + fzl_freeast(condition); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing while loop body at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_LOOP, tok); + node->loop.cond = condition; + node->loop.body = body; + + return node; +} + +// `while` and `for` loops +fzl_node_t *parse_loop(fzl_parser_t *parser) +{ + if (!peek(parser)) + return NULL; + + if (is(parser, FZL_TOKTYPE_FOR)) + return parse_for(parser); + else if (is(parser, FZL_TOKTYPE_WHILE)) + return parse_while(parser); + + snprintf(plast_err, sizeof(plast_err), + "'%s': unknown type of loop at %ld:%ld", last_known_tok->filename, + last_known_tok->ln, last_known_tok->cn); + return NULL; +} + // Statement parsing fzl_node_t *parse_statement(fzl_parser_t *parser) { @@ -991,6 +1216,8 @@ fzl_node_t *parse_statement(fzl_parser_t *parser) return parse_if(parser); else if (is(parser, FZL_TOKTYPE_LBRACE)) return parse_block(parser); + else if (is(parser, FZL_TOKTYPE_FOR) || is(parser, FZL_TOKTYPE_WHILE)) + return parse_loop(parser); if (!res) res = parse_expression(parser); @@ -1116,6 +1343,20 @@ void fzl_freeast(fzl_node_t *root) fzl_freeast(root->cond.else_block); break; + case FZL_NODETYPE_LOOP: + if (root->loop.init) + fzl_freeast(root->loop.init); + + if (root->loop.cond) + fzl_freeast(root->loop.cond); + + if (root->loop.step) + fzl_freeast(root->loop.step); + + if (root->loop.body) + fzl_freeast(root->loop.body); + break; + case FZL_NODETYPE_ADD: case FZL_NODETYPE_SUB: case FZL_NODETYPE_MUL: diff --git a/src/compiler/fzl_reader.c b/src/compiler/fzl_reader.c index 540d29c..dc37a24 100644 --- a/src/compiler/fzl_reader.c +++ b/src/compiler/fzl_reader.c @@ -73,6 +73,11 @@ fzl_token_t *parse_name(fzl_reader_t *reader) if (strcmp(value, "class") == 0) tok = fzl_mktoken(FZL_TOKTYPE_CLASS, l.line, l.col, value, size, l.filename); + else if (strcmp(value, "for") == 0) + tok = fzl_mktoken(FZL_TOKTYPE_FOR, l.line, l.col, value, size, l.filename); + else if (strcmp(value, "while") == 0) + tok = + fzl_mktoken(FZL_TOKTYPE_WHILE, l.line, l.col, value, size, l.filename); else if (strcmp(value, "new") == 0) tok = fzl_mktoken(FZL_TOKTYPE_NEW, l.line, l.col, value, size, l.filename); else if (strcmp(value, "fn") == 0) diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index 9c68106..bc269b5 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -200,6 +200,96 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) { switch (node->type) { + // NOTE: add new levels for infinite loop check: + // 1. Dead condition (f.e. `while (i < 5)` w/o `i` increment) + // 2. Step direction (f.e. `for (; i > 5; i = i - 1)`) + case FZL_NODETYPE_LOOP: + { + fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_LOOP); + if (!newctx) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to nest context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + newctx->initial_env_lvl = walker->sym_top - 1; + newctx->breaks_count = 0; + newctx->breaks_remainder = 2; + newctx->breaks = malloc(newctx->breaks_remainder * sizeof(size_t)); + + push_op(walker, FZL_OPCODE_MK_ENVTABLE); + walker->syms[walker->sym_top++] = fzl_mksymt(); + + if (node->loop.init) + { + fzl_walk_res_t res = walk(walker, node->loop.init); + if (res.error) + { + free(newctx->breaks); + return res; + } + } + + size_t start_ip = walker->op_count; + if (node->loop.cond) + { + fzl_walk_res_t res = walk(walker, node->loop.cond); + if (res.error) + { + free(newctx->breaks); + return res; + } + } + else + { + push_op(walker, FZL_OPCODE_PUSHNUM); + push_word(walker, 1); + } + + push_op(walker, FZL_OPCODE_JZ); + newctx->breaks[newctx->breaks_count++] = walker->op_count; + push_word(walker, 0); + + fzl_walk_res_t loop_body_res = walk(walker, node->loop.body); + if (loop_body_res.error) + { + free(newctx->breaks); + return loop_body_res; + } + + if (node->loop.step) + { + fzl_walk_res_t res = walk(walker, node->loop.step); + if (res.error) + { + free(newctx->breaks); + return res; + } + } + + push_op(walker, FZL_OPCODE_JMP); + push_word(walker, start_ip); + push_op(walker, FZL_OPCODE_RM_ENVTABLE); + + for (size_t i = walker->sym_top - 1; i > newctx->initial_env_lvl; i--) + fzl_freesymt(walker->syms[--walker->sym_top]); + + // backpatch `break`s + for (size_t i = 0; i < newctx->breaks_count; i++) + walker->ops[newctx->breaks[i]] = walker->op_count - 1; + + free(newctx->breaks); + if (!fin_ctx(walker)) + { + snprintf(wlast_err, sizeof(wlast_err), + "failed to finish loop context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; + break; + } + case FZL_NODETYPE_CLASS: { fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_CLASS); @@ -461,7 +551,16 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) case FZL_NODETYPE_RETURN: { - if (!walker->ctx || walker->ctx->type != FZL_WALK_CTX_TYPE_FN) + fzl_walk_ctx_t *ctx = walker->ctx; + while (ctx) + { + if (ctx && ctx->type == FZL_WALK_CTX_TYPE_FN) + break; + + ctx = ctx->parent; + } + + if (!ctx || ctx->type != FZL_WALK_CTX_TYPE_FN) { snprintf( wlast_err, sizeof(wlast_err), @@ -731,6 +830,7 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) } fzl_freesymt(walker->syms[--walker->sym_top]); push_op(walker, FZL_OPCODE_RM_ENVTABLE); + current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; break; case FZL_NODETYPE_CALL: