/* * Statement parsing routines for the bootstrap compiler. * * Copyright © 2025 Samuel Lidén Borell * * SPDX-License-Identifier: EUPL-1.2+ OR LGPL-2.1-or-later */ #include #include #include "compiler.h" #include "token.h" static int current_stmt_id = 0; static struct Stmt *current_loop = NULL; static struct Var **nextptr_vardecl = NULL; static enum Token parse_stmt_block(struct Stmt **stmt_out); static void parser_enter_scope(void); static void parser_leave_scope(void); static struct Var *parse_local_var(enum VarType vartype); struct Scope { struct Scope *outer; struct TreeNode *vars; }; static struct Scope *current_scope = NULL; void parse_func_body(void) { struct Stmt **stmt_ptr = ¤t_func->code; struct Section **section_ptr = ¤t_func->section_first; current_stmt_id = 0; assert(current_func->vardecls == NULL); parser_enter_scope(); /* params scope */ current_scope->vars = current_funcparams; nextptr_vardecl = ¤t_func->vardecls; for (;;) { enum Token t = parse_stmt_block(stmt_ptr); if (t == T_KW_end) { assert(current_loop == NULL); parser_leave_scope(); assert(current_scope == NULL); return; } else if (t == T_KW_section) { struct LexemeInfo li; struct Section *section; expect(&li, T_LowerIdent, "Expected lowercase section name"); section = (struct Section *)tree_insert_str( ¤t_func->section_by_name, li.string, li.len, NULL, sizeof(struct Section)); if (section->ident.node.is_defined) { error("Duplicate section name"); } srcloc_init(§ion->ident.srcloc); section->ident.node.is_defined = 1; *section_ptr = section; section_ptr = §ion->next; section->next = NULL; section->code = NULL; stmt_ptr = §ion->code; expect_next_line(); } else { error(tokenizer_line_is_indented() ? "Too many `end`s" : "Expected `end` or `section xxx` after code block"); } } } static struct Stmt *new_stmt(void) { struct Stmt *s = malloc(sizeof(struct Stmt)); NO_NULL(s); s->next = NULL; s->line = (unsigned short)current_line; s->has_break = 0; s->id = current_stmt_id++; return s; } static enum Token parse_stmt_block(struct Stmt **stmt_out) { *stmt_out = NULL; parser_enter_scope(); for (;;) { struct Stmt *s; struct LexemeInfo li; enum Token t, endt; t = tokenize(&li); switch ((int)t) { case T_EOL: expect_next_line(); continue; case T_KW_case: case T_KW_default: case T_KW_else: case T_KW_elif: case T_KW_end: case T_KW_loopempty: case T_KW_loopend: case T_KW_section: /* End of statement block */ parser_leave_scope(); return t; } s = new_stmt(); *stmt_out = s; switch ((int)t) { case T_KW_assert: s->kind = S_ASSERT; s->u.expr = parse_expr(); break; case T_KW_break: s->kind = S_BREAK; if (!current_loop) { error("`break` must be inside a loop"); } s->u.break_.loop = current_loop; current_loop->has_break = 1; break; case T_KW_continue: s->kind = S_CONTINUE; if (!current_loop) { error("`continue` must be inside a loop"); } s->u.break_.loop = current_loop; break; case T_KW_for: { struct Var *var; s->kind = S_FOR; var = parse_local_var(VAR_DECL_ONLY); s->u.for_.var = var; expect(&li, T_KW_in, "Expected `in` keyword in `for` statement"); s->u.for_.loop.cond = parse_expr(); goto loopbody; } case T_KW_if: { struct StmtElif **elif_next; s->kind = S_IF; /* true-block */ s->u.if_.cond = parse_expr(); expect_next_line(); endt = parse_stmt_block(&s->u.if_.true_); /* elif-blocks */ s->u.if_.elifs = NULL; elif_next = &s->u.if_.elifs; while (endt == T_KW_elif) { struct StmtElif *elif = malloc(sizeof(struct StmtElif)); NO_NULL(elif); elif->cond = parse_expr(); expect_next_line(); endt = parse_stmt_block(&elif->body); elif->next = NULL; *elif_next = elif; elif_next = &elif->next; } /* false-block */ if (endt == T_KW_else) { expect_next_line(); endt = parse_stmt_block(&s->u.if_.false_); } else { s->u.if_.false_ = NULL; } if (endt != T_KW_end) { error("Expected `end` after `if` block"); } break; } case T_KW_return: t = tokenize(&li); unread_token(); if (t == T_EOL) { s->kind = S_RETURN_NOVALUE; if (current_func->returns != NULL) { error("`return` without value in function with returns"); } } else { s->kind = S_RETURN_VALUE; if (current_func->returns == NULL) { error("`return` with value in function without returns"); } assert(current_func->num_returns >= 1); s->u.expr = parse_expr(); } break; case T_KW_switch: { struct Case **nextptr; s->kind = S_SWITCH; s->u.switch_.cond = parse_expr(); s->u.switch_.default_ = NULL; s->u.switch_.cases = NULL; nextptr = &s->u.switch_.cases; expect_next_line(); t = tokenize(&li); for (;;) { if (t == T_KW_case) { struct Case *case_ = malloc(sizeof(struct Case)); NO_NULL(case_); case_->value = parse_expr(); expect_next_line(); t = parse_stmt_block(&case_->block); case_->next = NULL; *nextptr = case_; nextptr = &case_->next; } else if (t == T_KW_default) { expect_next_line(); t = parse_stmt_block(&s->u.switch_.default_); if (t != T_KW_end) { error("Expected `end` after `default` block"); } } else if (t == T_KW_end) { break; } else if (t == T_EOL) { /* Empty / comment-only lines are allowed */ expect_next_line(); t = tokenize(&li); } else { error("Expected `case`, `default` or `end` " "in `switch` block"); } } break; } case T_KW_while: { struct Stmt *outer_loop; s->kind = S_WHILE; s->u.loop.cond = parse_expr(); loopbody: s->u.loop.loopempty = NULL; s->u.loop.loopend = NULL; expect_next_line(); outer_loop = current_loop; current_loop = s; endt = parse_stmt_block(&s->u.loop.body); current_loop = outer_loop; /* loopempty block: Reached if loop is never executed */ if (endt == T_KW_loopempty) { expect_next_line(); endt = parse_stmt_block(&s->u.loop.loopempty); } /* TODO consider renaming this: - finish? - complete? */ /* loopend block: Reached if loop is not exited (or if empty) */ if (endt == T_KW_loopend) { expect_next_line(); endt = parse_stmt_block(&s->u.loop.loopend); } if (endt != T_KW_end) { const char *msg; if (endt == T_KW_loopempty) { msg="`loopempty` must come before `loopend`"; } else if (s->u.loop.loopend) { msg="Expected `end` after loop"; } else if (s->u.loop.loopempty) { msg="Expected `end` or `finish` after loop"; } else { msg="Expected `end`, `loopempty` or `loopend` after loop"; } error(msg); } break; } TOKEN_CASES_QUALIFIERS case T_KW_bool: case T_KW_byte: case T_KW_int: case T_KW_long: case T_UpperIdent: { /* Variable definition */ s->kind = S_VARDECL; unread_token(); s->u.var = parse_local_var(VAR_ALLOW_INITVAL); break; } case T_LowerIdent: { struct Expr *expr; /* Function call or assignment expression */ unread_token(); expr = parse_expr(); t = tokenize(&li); if (t == T_SYM_SingleEqual) { struct AssignDestination *dest; s->kind = S_ASSIGN; dest = &s->u.assign.first_dest; for (;;) { struct AssignDestination *nextdest; dest->expr = expr; expr = parse_expr(); t = tokenize(&li); if (t != T_SYM_SingleEqual) break; nextdest = malloc(sizeof(struct AssignDestination)); NO_NULL(nextdest); dest->next = nextdest; dest = nextdest; } dest->next = NULL; s->u.assign.sourceexpr = expr; } else { unread_token(); s->kind = S_EXPR; s->u.expr = expr; } break; } default: error("Unexpected token at start of statement"); } expect_next_line(); stmt_out = &s->next; } } static void parser_enter_scope(void) { struct Scope *scope = malloc(sizeof(struct Scope)); NO_NULL(scope); scope->outer = current_scope; scope->vars = NULL; current_scope = scope; } static void parser_leave_scope(void) { struct Scope *prev; assert(current_scope != NULL); prev = current_scope; /* TODO "unassign" vars when they go out of scope */ current_scope = current_scope->outer; free(prev); } static bool exists_in_outer_scope(const struct Var *var) { const struct Scope *scope; for (scope = current_scope->outer; scope != NULL; scope = scope->outer) { if (tree_search_node(scope->vars, &var->ident.node)) { return true; } } return false; } static struct Var *parse_local_var(enum VarType vartype) { struct Var *var; /* TODO disallow defining the same name with different types in different scopes? */ var = parse_var(¤t_scope->vars, vartype, nextptr_vardecl); if (exists_in_outer_scope(var)) { error("Name shadows an existing variable name in an outer scope"); } var->is_funcparam = 0; nextptr_vardecl = &var->next; return var; } struct Var *lookup_local_var(const char *name, size_t len) { struct Scope *scope; HashCode h; assert(name != NULL); assert(len != 0); h = hash_str(name, len); /* Should have a funcparam scope + at least one statement scope */ assert(current_scope != NULL && current_scope->outer != NULL); for (scope = current_scope; scope != NULL; scope = scope->outer) { struct TreeNode *node = tree_search(scope->vars, h, len, name); if (node) { return (struct Var *)node; } } return NULL; }