// Copyright (c) 2020-2025 Christoffer Lerno. All rights reserved. // Use of this source code is governed by a LGPLv3.0 // a copy of which can be found in the LICENSE file. #include "compiler_internal.h" #include "parser_internal.h" // --- Internal functions static inline Expr *parse_asm_expr(ParseContext *c); static Ast *parse_decl_stmt_after_type(ParseContext *c, TypeInfo *type) { Ast *ast = ast_calloc(); ast->span = type->span; ast->ast_kind = AST_DECLARE_STMT; ASSIGN_DECL_OR_RET(ast->declare_stmt, parse_local_decl_after_type(c, type), poisoned_ast); Decl *decl = ast->declare_stmt; switch (c->tok) { case TOKEN_LBRACKET: if (!decl->var.init_expr) { SourceSpan span = extend_span_with_token(type->span, c->span); print_error_at(span, "This looks like the beginning of a declaration with the format 'int %s[4]' " "which is a c-style array declaration. In C3, you need to use something like 'int[4] %s' instead.", decl->name, decl->name); return poisoned_ast; } break; case TOKEN_EOS: goto DONE; default: break; } if (decl->attributes || decl->var.init_expr) { if (tok_is(c, TOKEN_COMMA) && peek(c) == TOKEN_IDENT) { if (decl->var.init_expr) { PRINT_ERROR_AT(decl->var.init_expr, "Multiple variable declarations cannot use initialization."); return poisoned_ast; } if (decl->attributes) { ASSERT(VECLAST(decl->attributes)); PRINT_ERROR_AT(VECLAST(decl->attributes), "Multiple variable declarations must have attributes at the end."); return poisoned_ast; } } goto DONE; } Decl **decls = NULL; vec_add(decls, decl); Attr **attributes = NULL; while (try_consume(c, TOKEN_COMMA)) { ASSIGN_DECL_OR_RET(decl, parse_local_decl_after_type(c, copy_type_info_single(type)), poisoned_ast); if (decl->var.init_expr) { PRINT_ERROR_AT(decl->var.init_expr, "Multiple variable declarations cannot use initialization."); return poisoned_ast; } if (decl->attributes) { if (tok_is(c, TOKEN_COMMA)) { ASSERT(VECLAST(decl->attributes)); PRINT_ERROR_AT(VECLAST(decl->attributes), "Multiple variable declarations must have attributes at the end."); return poisoned_ast; } attributes = decl->attributes; } vec_add(decls, decl); } if (attributes) { FOREACH(Decl *, d, decls) { if (d == decl) continue; d->attributes = copy_attributes_single(attributes); } } ast->decls_stmt = decls; ast->ast_kind = AST_DECLS_STMT; DONE: RANGE_EXTEND_PREV(ast); return ast; } /** * declaration_stmt * : declaration ';' */ static inline Ast *parse_declaration_stmt(ParseContext *c) { if (tok_is(c, TOKEN_CONST)) { // Consts don't have multiple declarations. Ast *decl_stmt = new_ast(AST_DECLARE_STMT, c->span); ASSIGN_DECL_OR_RET(decl_stmt->declare_stmt, parse_const_declaration(c, false, false), poisoned_ast); decl_stmt->declare_stmt->visibility = VISIBLE_LOCAL; RANGE_EXTEND_PREV(decl_stmt); CONSUME_EOS_OR_RET(poisoned_ast); return decl_stmt; } bool is_threadlocal = try_consume(c, TOKEN_TLOCAL); bool is_static = !is_threadlocal && try_consume(c, TOKEN_STATIC); is_static = is_threadlocal || is_static; ASSIGN_TYPE_OR_RET(TypeInfo *type, parse_optional_type(c), poisoned_ast); ASSIGN_AST_OR_RET(Ast *result, parse_decl_stmt_after_type(c, type), poisoned_ast); CONSUME_EOS_OR_RET(poisoned_ast); if (result->ast_kind == AST_DECLARE_STMT) { result->declare_stmt->var.is_threadlocal = is_threadlocal; result->declare_stmt->var.is_static = is_static || is_threadlocal; result->declare_stmt->visibility = VISIBLE_LOCAL; return result; } FOREACH(Decl *, var, result->decls_stmt) { var->var.is_threadlocal = is_threadlocal; var->var.is_static = is_static || is_threadlocal; var->visibility = VISIBLE_LOCAL; } return result; } static inline Decl *parse_optional_label(ParseContext *c, Ast *parent) { if (!tok_is(c, TOKEN_CONST_IDENT)) return NULL; Decl *decl = decl_new(DECL_LABEL, symstr(c), c->span); decl->label.parent = astid(parent); advance_and_verify(c, TOKEN_CONST_IDENT); if (!try_consume(c, TOKEN_COLON)) { PRINT_ERROR_AT(decl, "The name must be followed by a ':', did you forget it?"); return poisoned_decl; } return decl; } static inline void parse_optional_label_target(ParseContext *c, Label *label) { if (tok_is(c, TOKEN_CONST_IDENT)) { label->span = c->span; label->name = symstr(c); advance_and_verify(c, TOKEN_CONST_IDENT); } } static inline bool parse_asm_offset(ParseContext *c, ExprAsmArg *asm_arg) { if (!tok_is(c, TOKEN_INTEGER)) { PRINT_ERROR_HERE("Expected an integer value."); return false; } Expr *offset = parse_integer(c, NULL); ASSERT(expr_is_const_int(offset)); Int i = offset->const_expr.ixx; if (i.i.high) { PRINT_ERROR_HERE("The value is too high for an offset."); return false; } asm_arg->offset = i.i.low; return true; } static inline bool parse_asm_scale(ParseContext *c, ExprAsmArg *asm_arg) { if (!tok_is(c, TOKEN_INTEGER)) { PRINT_ERROR_HERE("Expected an integer value."); return false; } Expr *value = parse_integer(c, NULL); ASSERT(expr_is_const_int(value)); Int i = value->const_expr.ixx; if (i.i.high) { PRINT_ERROR_HERE("The value is too high for a scale: %s", int_to_str(i, 10, false)); return false; } switch (i.i.low) { case 1: asm_arg->offset_type = ASM_SCALE_1; break; case 2: asm_arg->offset_type = ASM_SCALE_2; break; case 4: asm_arg->offset_type = ASM_SCALE_4; break; case 8: asm_arg->offset_type = ASM_SCALE_8; break; default: PRINT_ERROR_HERE("Expected 1, 2, 4 or 8."); return false; } return true; } static inline bool parse_asm_addr(ParseContext *c, ExprAsmArg *asm_arg) { asm_arg->kind = ASM_ARG_ADDR; asm_arg->offset_type = ASM_SCALE_1; ASSIGN_EXPR_OR_RET(Expr *base, parse_asm_expr(c), false); // Simple case [foo] if (try_consume(c, TOKEN_RBRACKET)) { if (base->expr_asm_arg.kind == ASM_ARG_ADDROF) { *asm_arg = base->expr_asm_arg; asm_arg->kind = ASM_ARG_MEMVAR; return true; } asm_arg->base = exprid(base); return true; } asm_arg->base = exprid(base); // [foo + ... or [foo - ...] TokenType type = c->tok; switch (type) { case TOKEN_PLUS: case TOKEN_MINUS: advance(c); break; default: PRINT_ERROR_HERE("Expected + or - here."); return false; } if (type == TOKEN_MINUS) asm_arg->neg_offset = true; // If it's an integer, then it's [foo + 123] or [foo - 213] if (tok_is(c, TOKEN_INTEGER)) { if (!parse_asm_offset(c, asm_arg)) return false; CONSUME_OR_RET(TOKEN_RBRACKET, false); return true; } // Otherwise we expect the index. ASSIGN_EXPRID_OR_RET(asm_arg->idx, parse_asm_expr(c), false); // We got [foo + bar] or [foo - bar] if (try_consume(c, TOKEN_RBRACKET)) return true; switch (c->tok) { case TOKEN_STAR: // [foo + bar * ...] advance(c); if (!parse_asm_scale(c, asm_arg)) return false; break; case TOKEN_SHR: advance(c); asm_arg->offset_type = ASM_SCALE_SHR; if (!parse_asm_offset(c, asm_arg)) return false; CONSUME_OR_RET(TOKEN_RBRACKET, false); return true; case TOKEN_SHL: asm_arg->offset_type = ASM_SCALE_SHL; if (!parse_asm_offset(c, asm_arg)) return false; CONSUME_OR_RET(TOKEN_RBRACKET, false); return true; default: break; } // [foo + bar * 4] if (try_consume(c, TOKEN_RBRACKET)) return true; if (asm_arg->neg_offset) { PRINT_ERROR_HERE("Addressing cannot both have a negated index and an offset."); return false; } switch (c->tok) { case TOKEN_MINUS: asm_arg->neg_offset = true; break; case TOKEN_PLUS: break; default: PRINT_ERROR_HERE("Expected + or - here."); return false; } advance(c); if (!parse_asm_offset(c, asm_arg)) return false; CONSUME_OR_RET(TOKEN_RBRACKET, false); return true; } /** * * @param c * @return */ static inline Expr *parse_asm_expr(ParseContext *c) { Expr *expr = EXPR_NEW_TOKEN(EXPR_ASM); switch (c->tok) { case TOKEN_LBRACKET: advance(c); if (!parse_asm_addr(c, &expr->expr_asm_arg)) return poisoned_expr; RANGE_EXTEND_PREV(expr); return expr; case TOKEN_CT_IDENT: case TOKEN_CT_CONST_IDENT: expr->expr_asm_arg.kind = ASM_ARG_REG; expr->expr_asm_arg.reg.name = c->data.string; advance(c); return expr; case TOKEN_HASH_IDENT: PRINT_ERROR_HERE("Compile time variables need to be wrapped in () inside an asm block."); return poisoned_expr; case TOKEN_IDENT: expr->expr_asm_arg.kind = ASM_ARG_REGVAR; expr->expr_asm_arg.ident.name = c->data.string; advance(c); return expr; case TOKEN_AMP: expr->expr_asm_arg.kind = ASM_ARG_ADDROF; advance(c); expr->expr_asm_arg.ident.name = c->data.string; if (!try_consume(c, TOKEN_IDENT)) { PRINT_ERROR_HERE("Expected a variable name after '&', like '&foo'."); return poisoned_expr; } return expr; case TOKEN_MINUS: expr->expr_asm_arg.kind = ASM_ARG_VALUE; ASSIGN_EXPRID_OR_RET(expr->expr_asm_arg.expr_id, parse_expr(c), poisoned_expr); return expr; case TOKEN_INTEGER: case TOKEN_CONST_IDENT: case TOKEN_REAL: expr->expr_asm_arg.kind = ASM_ARG_VALUE; ASSIGN_EXPRID_OR_RET(expr->expr_asm_arg.expr_id, parse_expr(c), poisoned_expr); return expr; case TOKEN_LPAREN: advance(c); expr->expr_asm_arg.kind = ASM_ARG_VALUE; ASSIGN_EXPRID_OR_RET(expr->expr_asm_arg.expr_id, parse_expr(c), poisoned_expr); TRY_CONSUME_OR_RET(TOKEN_RPAREN, "Expected the ')' here.", poisoned_expr); RANGE_EXTEND_PREV(expr); return expr; default: PRINT_ERROR_HERE("This doesn't look like an asm argument."); return poisoned_expr; } } static inline Ast *parse_asm_stmt(ParseContext *c) { Ast *asm_stmt = ast_new_curr(c, AST_ASM_STMT); if (tok_is(c, TOKEN_CONST_IDENT)) { asm_stmt->asm_label = symstr(c); advance_and_verify(c, TOKEN_CONST_IDENT); asm_stmt->ast_kind = AST_ASM_LABEL; TRY_CONSUME_OR_RET(TOKEN_COLON, "Expected a ':' to terminate the label.", poisoned_ast); return asm_stmt; } if (!tok_is(c, TOKEN_IDENT) && !tok_is(c, TOKEN_INT)) { PRINT_ERROR_HERE("Expected an asm instruction here."); return poisoned_ast; } asm_stmt->asm_stmt.instruction = symstr(c); advance(c); if (try_consume(c, TOKEN_DOT)) { if (!tok_is(c, TOKEN_IDENT)) { PRINT_ERROR_HERE("Expected asm instruction variant."); return poisoned_ast; } asm_stmt->asm_stmt.variant = symstr(c); advance_and_verify(c, TOKEN_IDENT); } Expr **list = NULL; while (!try_consume(c, TOKEN_EOS)) { ASSIGN_EXPR_OR_RET(Expr *expr, parse_asm_expr(c), poisoned_ast); vec_add(list, expr); if (try_consume(c, TOKEN_COMMA)) continue; if (!expect(c, TOKEN_EOS)) return poisoned_ast; } asm_stmt->asm_stmt.args = list; return asm_stmt; } /** * asm ::= 'asm' @pure? '{' asm_stmt* '}' | 'asm' '(' string ')' * @param c * @return */ static inline Ast* parse_asm_block_stmt(ParseContext *c) { Ast *ast = new_ast(AST_ASM_BLOCK_STMT, c->span); advance_and_verify(c, TOKEN_ASM); bool is_volatile = true; if (tok_is(c, TOKEN_AT_IDENT)) { if (symstr(c) == kw_at_pure) { is_volatile = false; } else { PRINT_ERROR_HERE("Only the '@pure' attribute is allowed."); return false; } advance_and_verify(c, TOKEN_AT_IDENT); if (!tok_is(c, TOKEN_LBRACE)) { PRINT_ERROR_HERE("Expected '{' after the attribute."); } } if (try_consume(c, TOKEN_LBRACE)) { AsmInlineBlock *block = CALLOCS(AsmInlineBlock); AstId *prev = &block->asm_stmt; while (!try_consume(c, TOKEN_RBRACE)) { ASSIGN_AST_OR_RET(Ast *block_stmt, parse_asm_stmt(c), poisoned_ast); *prev = astid(block_stmt); prev = &block_stmt->next; } ast->asm_block_stmt.block = block; ast->asm_block_stmt.is_volatile = is_volatile; return ast; } ast->asm_block_stmt.is_string = true; CONSUME_OR_RET(TOKEN_LPAREN, poisoned_ast); ASSIGN_EXPRID_OR_RET(ast->asm_block_stmt.asm_string, parse_expr(c), poisoned_ast); CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); if (tok_is(c, TOKEN_AT_IDENT)) { if (symstr(c) == kw_at_pure) { is_volatile = false; } else { PRINT_ERROR_HERE("Only the '@pure' attribute is allowed."); return false; } advance_and_verify(c, TOKEN_AT_IDENT); } ast->asm_block_stmt.is_volatile = is_volatile; RANGE_EXTEND_PREV(ast); CONSUME_EOS_OR_RET(poisoned_ast); return ast; } /** * do_stmt ::= DO optional_label compound_stmt (WHILE '(' expression ')')? ';' * * Note that we transform do to a for loop (with a special flag) * – and in parsing we allow a regular statement, but disambiguate that * during semantic analysis. */ static inline Ast* parse_do_stmt(ParseContext *c) { Ast *do_ast = new_ast(AST_FOR_STMT, c->span); advance_and_verify(c, TOKEN_DO); do_ast->flow.skip_first = true; ASSIGN_DECLID_OR_RET(do_ast->for_stmt.flow.label, parse_optional_label(c, do_ast), poisoned_ast); ASSIGN_ASTID_OR_RET(do_ast->for_stmt.body, parse_stmt(c), poisoned_ast); if (try_consume(c, TOKEN_EOS)) { do_ast->for_stmt.cond = exprid(expr_new_const_bool(c->prev_span, type_bool, false)); } else { CONSUME_OR_RET(TOKEN_WHILE, poisoned_ast); CONSUME_OR_RET(TOKEN_LPAREN, poisoned_ast); ASSIGN_EXPRID_OR_RET(do_ast->for_stmt.cond, parse_expr(c), poisoned_ast); CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); CONSUME_EOS_OR_RET(poisoned_ast); } return do_ast; } static inline bool token_type_ends_case(TokenType type, TokenType case_type, TokenType default_type) { return type == case_type || type == default_type || type == TOKEN_RBRACE || type == TOKEN_CT_ENDSWITCH; } static inline Ast *parse_case_stmts(ParseContext *c, TokenType case_type, TokenType default_type, uint32_t row) { if (token_type_ends_case(c->tok, case_type, default_type)) { if (c->span.row > row + 1 && (c->tok == TOKEN_CASE || c->tok == TOKEN_DEFAULT)) { PRINT_ERROR_LAST("Fallthrough cases with empty rows or comments have unclear meaning, an explicit 'break' or 'nextcase' is needed (or remove the spacing!)."); return poisoned_ast; } return NULL; } Ast *compound = new_ast(AST_COMPOUND_STMT, c->span); AstId *next = &compound->compound_stmt.first_stmt; while (!token_type_ends_case(c->tok, case_type, default_type)) { ASSIGN_AST_OR_RET(Ast *stmt, parse_stmt(c), poisoned_ast); ast_append(&next, stmt); } return compound; } /** * defer_stmt ::= DEFER (TRY | CATCH | '(' CATCH var ')')? statement */ static inline Ast* parse_defer_stmt(ParseContext *c) { advance_and_verify(c, TOKEN_DEFER); Ast *defer_stmt = new_ast(AST_DEFER_STMT, c->span); if (try_consume(c, TOKEN_TRY)) { defer_stmt->defer_stmt.is_try = true; } else if (try_consume(c, TOKEN_CATCH)) { defer_stmt->defer_stmt.is_catch = true; } else if (tok_is(c, TOKEN_LPAREN) && peek(c) == TOKEN_CATCH) { advance_and_verify(c, TOKEN_LPAREN); CONSUME_OR_RET(TOKEN_CATCH, poisoned_ast); if (!expect_ident(c, "identifier")) return poisoned_ast; Ast *compound = ast_new_curr(c, AST_COMPOUND_STMT); Ast *first = ast_new_curr(c, AST_DECLARE_STMT); Decl *decl = decl_new_var(c->data.string, c->span, type_info_new_base(type_fault, c->span), VARDECL_LOCAL); defer_stmt->defer_stmt.is_catch = true; decl->var.init_expr = expr_new(EXPR_LAST_FAULT, decl->span); first->declare_stmt = decl; advance_and_verify(c, TOKEN_IDENT); CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); ASSIGN_ASTID_OR_RET(first->next, parse_stmt(c), poisoned_ast); compound->compound_stmt.first_stmt = astid(first); defer_stmt->defer_stmt.body = astid(compound); return defer_stmt; } ASSIGN_ASTID_OR_RET(defer_stmt->defer_stmt.body, parse_stmt(c), poisoned_ast); return defer_stmt; } /** * while_stmt ::= WHILE optional_label '(' cond ')' statement * * Note that during parsing we rewrite this as a for loop. */ static inline Ast* parse_while_stmt(ParseContext *c) { Ast *while_ast = new_ast(AST_FOR_STMT, c->span); advance_and_verify(c, TOKEN_WHILE); ASSIGN_DECLID_OR_RET(while_ast->for_stmt.flow.label, parse_optional_label(c, while_ast), poisoned_ast); CONSUME_OR_RET(TOKEN_LPAREN, poisoned_ast); ASSIGN_EXPRID_OR_RET(while_ast->for_stmt.cond, parse_cond(c), poisoned_ast); CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); unsigned row = c->prev_span.row; ASSIGN_AST_OR_RET(Ast *body, parse_stmt(c), poisoned_ast); if (body->ast_kind != AST_COMPOUND_STMT && row != body->span.row) { PRINT_ERROR_AT(body, "A single statement after 'while' must be placed on the same line, or be enclosed in {}."); return poisoned_ast; } while_ast->for_stmt.body = astid(body); return while_ast; } /** * if_stmt ::= IF optional_label '(' cond ')' compound_stmt (ELSE compound_stmt)? * | IF optional_label '(' cond ')' statement */ static inline Ast* parse_if_stmt(ParseContext *c) { Ast *if_ast = new_ast(AST_IF_STMT, c->span); advance_and_verify(c, TOKEN_IF); ASSIGN_DECLID_OR_RET(if_ast->if_stmt.flow.label, parse_optional_label(c, if_ast), poisoned_ast); CONSUME_OR_RET(TOKEN_LPAREN, poisoned_ast); ASSIGN_EXPRID_OR_RET(if_ast->if_stmt.cond, parse_cond(c), poisoned_ast); unsigned row = c->span.row; if (!tok_is(c, TOKEN_RPAREN)) { switch (c->tok) { case TOKEN_IDENT: case TOKEN_CONST: PRINT_ERROR_HERE("Finding an identifier here was surprising - did you forget '||' or '&&' or the ending ')' character?"); return poisoned_ast; default: break; } } CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); unsigned next_row = c->span.row; ASSIGN_ASTID_OR_RET(if_ast->if_stmt.then_body, parse_stmt(c), poisoned_ast); if (row != next_row && astptr(if_ast->if_stmt.then_body)->ast_kind != AST_COMPOUND_STMT) { // Poison it and pick it up later. ast_poison(astptr(if_ast->if_stmt.then_body)); } if (try_consume(c, TOKEN_ELSE)) { ASSIGN_ASTID_OR_RET(if_ast->if_stmt.else_body, parse_stmt(c), poisoned_ast); } return if_ast; } /** * * case_stmt * : CASE expression ':' case_stmts * | CASE expression DOTDOT expression ':' case_stmts * | CAST type ':' case_stmts * ; */ static inline Ast *parse_case_stmt(ParseContext *c, TokenType case_type, TokenType default_type) { Ast *ast = new_ast(AST_CASE_STMT, c->span); advance(c); ASSIGN_EXPR_OR_RET(Expr *expr, parse_expr(c), poisoned_ast); ast->case_stmt.expr = exprid(expr); // Change type -> type.typeid if (expr->expr_kind == EXPR_TYPEINFO) { // Fold constant immediately if (expr->type_expr->resolve_status == RESOLVE_DONE) { Type *cond_val = expr->type_expr->type; expr->expr_kind = EXPR_CONST; expr->const_expr.const_kind = CONST_TYPEID; expr->const_expr.typeid = cond_val->canonical; expr->type = type_typeid; } else { expr->expr_kind = EXPR_TYPEID; } } if (try_consume(c, TOKEN_DOTDOT)) { ASSIGN_EXPRID_OR_RET(ast->case_stmt.to_expr, parse_expr(c), poisoned_ast); } uint32_t row = c->span.row; if (!try_consume(c, TOKEN_COLON)) { print_error_at(c->prev_span, "Missing ':' after case"); return poisoned_ast; } RANGE_EXTEND_PREV(ast); ASSIGN_AST_OR_RET(ast->case_stmt.body, parse_case_stmts(c, case_type, default_type, row), poisoned_ast); return ast; } /** * default_stmt * : DEFAULT ':' case_stmts */ static inline Ast *parse_default_stmt(ParseContext *c, TokenType case_type, TokenType default_type) { Ast *ast = new_ast(AST_DEFAULT_STMT, c->span); advance(c); TRY_CONSUME_OR_RET(TOKEN_COLON, "Expected ':' after 'default'.", poisoned_ast); uint32_t row = c->span.row; RANGE_EXTEND_PREV(ast); ASSIGN_AST_OR_RET(ast->case_stmt.body, parse_case_stmts(c, case_type, default_type, row), poisoned_ast); ast->case_stmt.expr = 0; return ast; } /** * switch_body * : case_stmt * | default_stmt * | case_stmt switch_body * | default_stmt switch body * ; */ bool parse_switch_body(ParseContext *c, Ast ***cases, TokenType case_type, TokenType default_type) { CONSUME_OR_RET(TOKEN_LBRACE, false); while (!try_consume(c, TOKEN_RBRACE)) { Ast *result; TokenType tok = c->tok; if (tok == case_type) { ASSIGN_AST_OR_RET(result, parse_case_stmt(c, case_type, default_type), false); } else if (tok == default_type) { ASSIGN_AST_OR_RET(result, parse_default_stmt(c, case_type, default_type), false); } else { PRINT_ERROR_HERE("A 'case' or 'default' would be needed here."); return false; } vec_add((*cases), result); } return true; } /** * switch ::= SWITCH optional_label ('(' cond ')')? switch_body */ static inline Ast* parse_switch_stmt(ParseContext *c) { Ast *switch_ast = new_ast(AST_SWITCH_STMT, c->span); advance_and_verify(c, TOKEN_SWITCH); ASSIGN_DECLID_OR_RET(switch_ast->switch_stmt.flow.label, parse_optional_label(c, switch_ast), poisoned_ast); if (!try_consume(c, TOKEN_LPAREN)) { switch_ast->switch_stmt.cond = 0; } else { ASSIGN_EXPRID_OR_RET(switch_ast->switch_stmt.cond, parse_cond(c), poisoned_ast); CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); } if (tok_is(c, TOKEN_AT_IDENT)) { if (symstr(c) != kw_at_jump) { PRINT_ERROR_HERE("Only '@jump' is allowed after a switch."); return poisoned_ast; } switch_ast->switch_stmt.flow.jump = true; advance(c); } if (!parse_switch_body(c, &switch_ast->switch_stmt.cases, TOKEN_CASE, TOKEN_DEFAULT)) return poisoned_ast; return switch_ast; } /** * for_stmt ::= IF optional_label '(' expression_list? ';' cond? ';' expression_list? ')' statement */ static inline Ast* parse_for_stmt(ParseContext *c) { Ast *ast = new_ast(AST_FOR_STMT, c->span); advance_and_verify(c, TOKEN_FOR); // Label ASSIGN_DECLID_OR_RET(ast->for_stmt.flow.label, parse_optional_label(c, ast), poisoned_ast); CONSUME_OR_RET(TOKEN_LPAREN, poisoned_ast); if (try_consume(c, TOKEN_EOS)) { ast->for_stmt.init = 0; } else { ASSIGN_EXPRID_OR_RET(ast->for_stmt.init, parse_expression_list(c, true), poisoned_ast); CONSUME_EOS_OR_RET(poisoned_ast); } if (try_consume(c, TOKEN_EOS)) { ast->for_stmt.cond = 0; } else { ASSIGN_EXPRID_OR_RET(ast->for_stmt.cond, parse_cond(c), poisoned_ast); CONSUME_EOS_OR_RET(poisoned_ast); } if (try_consume(c, TOKEN_RPAREN)) { ast->for_stmt.incr = 0; } else { ASSIGN_EXPRID_OR_RET(ast->for_stmt.incr, parse_expression_list(c, false), poisoned_ast); CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); } // Ast range does not include the body RANGE_EXTEND_PREV(ast); unsigned row = c->prev_span.row; ASSIGN_AST_OR_RET(Ast *body, parse_stmt(c), poisoned_ast); if (body->ast_kind != AST_COMPOUND_STMT && row != body->span.row) { PRINT_ERROR_AT(body, "A single statement after 'for' must be placed on the same line, or be enclosed in {}."); return poisoned_ast; } ast->for_stmt.body = astid(body); return ast; } /** * foreach_var ::= optional_type? '&'? IDENT */ static inline bool parse_foreach_var(ParseContext *c, Ast *foreach) { TypeInfo *type = NULL; // If we don't get foreach (foo ... or foreach (*foo ... then a type is expected. if (!tok_is(c, TOKEN_IDENT) && !tok_is(c, TOKEN_AMP)) { ASSIGN_TYPE_OR_RET(type, parse_optional_type(c), false); // Add the optional to the type for nicer error reporting. RANGE_EXTEND_PREV(type); } if (try_consume(c, TOKEN_AMP)) { foreach->foreach_stmt.value_by_ref = true; } Decl *var = decl_new_var(symstr(c), c->span, type, VARDECL_LOCAL); if (!try_consume(c, TOKEN_IDENT)) { if (type) RETURN_PRINT_ERROR_HERE("Expected an identifier after the type."); RETURN_PRINT_ERROR_HERE("Expected an identifier or type."); } foreach->foreach_stmt.variable = declid(var); return true; } /** * foreach_stmt ::= (FOREACH | FOREACH_R) optional_label '(' foreach_vars ':' expression ')' statement * foreach_vars ::= foreach_var (',' foreach_var)? */ static inline Ast* parse_foreach_stmt(ParseContext *c) { Ast *ast = new_ast(AST_FOREACH_STMT, c->span); if (!((ast->foreach_stmt.is_reverse = try_consume(c, TOKEN_FOREACH_R)))) { advance_and_verify(c, TOKEN_FOREACH); } ASSIGN_DECLID_OR_RET(ast->foreach_stmt.flow.label, parse_optional_label(c, ast), poisoned_ast); CONSUME_OR_RET(TOKEN_LPAREN, poisoned_ast); // Parse the first variable. if (!parse_foreach_var(c, ast)) return poisoned_ast; // Do we have a second variable? if (try_consume(c, TOKEN_COMMA)) { // Copy the first variable to "index" ast->foreach_stmt.index = ast->foreach_stmt.variable; ast->foreach_stmt.index_by_ref = ast->foreach_stmt.value_by_ref; ast->foreach_stmt.value_by_ref = false; // Parse the second variable if (!parse_foreach_var(c, ast)) return poisoned_ast; } CONSUME_OR_RET(TOKEN_COLON, poisoned_ast); ASSIGN_EXPRID_OR_RET(ast->foreach_stmt.enumeration, parse_expr(c), poisoned_ast); CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); RANGE_EXTEND_PREV(ast); ASSIGN_ASTID_OR_RET(ast->foreach_stmt.body, parse_stmt(c), poisoned_ast); return ast; } /** * continue_stmt ::= CONTINUE optional_label_target ';' */ static inline Ast* parse_continue_stmt(ParseContext *c) { Ast *ast = new_ast(AST_CONTINUE_STMT, c->span); advance_and_verify(c, TOKEN_CONTINUE); parse_optional_label_target(c, &ast->contbreak_stmt.label); if (ast->contbreak_stmt.label.name) ast->contbreak_stmt.is_label = true; RANGE_EXTEND_PREV(ast); CONSUME_EOS_OR_RET(poisoned_ast); return ast; } /** * next ::= NEXTCASE ((CONST_IDENT ':')? expression)? ';' */ static inline Ast* parse_nextcase_stmt(ParseContext *c) { Ast *ast = new_ast(AST_NEXTCASE_STMT, c->span); advance_and_verify(c, TOKEN_NEXTCASE); if (try_consume(c, TOKEN_EOS)) { return ast; } if (tok_is(c, TOKEN_CONST_IDENT) && peek(c) == TOKEN_COLON) { parse_optional_label_target(c, &ast->nextcase_stmt.label); advance_and_verify(c, TOKEN_COLON); } if (try_consume(c, TOKEN_DEFAULT)) { ast->nextcase_stmt.is_default = true; } else { ASSIGN_EXPRID_OR_RET(ast->nextcase_stmt.expr, parse_expr(c), poisoned_ast); } CONSUME_EOS_OR_RET(poisoned_ast); return ast; } /** * break_stmt ::= BREAK optional_label_target ';' */ static inline Ast* parse_break_stmt(ParseContext *c) { Ast *ast = new_ast(AST_BREAK_STMT, c->span); advance_and_verify(c, TOKEN_BREAK); parse_optional_label_target(c, &ast->contbreak_stmt.label); if (ast->contbreak_stmt.label.name) ast->contbreak_stmt.is_label = true; RANGE_EXTEND_PREV(ast); CONSUME_EOS_OR_RET(poisoned_ast); return ast; } /** * expr_stmt * : expression EOS * ; */ static inline Ast *parse_expr_stmt(ParseContext *c) { Ast *stmt = new_ast(AST_EXPR_STMT, c->span); ASSIGN_EXPR_OR_RET(stmt->expr_stmt, parse_expr(c), poisoned_ast); RANGE_EXTEND_PREV(stmt); CONSUME_EOS_OR_RET(poisoned_ast); return stmt; } static inline Ast *parse_decl_or_expr_stmt(ParseContext *c) { ASSIGN_EXPR_OR_RET(Expr *expr, parse_expr(c), poisoned_ast); // We might be parsing "int!" // If so we need to unwrap this. if (expr->expr_kind == EXPR_OPTIONAL && expr->inner_expr->expr_kind == EXPR_TYPEINFO) { UNREACHABLE } if (expr->expr_kind == EXPR_TYPEINFO) { ASSIGN_AST_OR_RET(Ast *ast, parse_decl_stmt_after_type(c, expr->type_expr), poisoned_ast); CONSUME_EOS_OR_RET(poisoned_ast); return ast; } Ast *ast = ast_calloc(); ast->span = expr->span; ast->ast_kind = AST_EXPR_STMT; ast->expr_stmt = expr; if (tok_is(c, TOKEN_IDENT) && expr->expr_kind == EXPR_UNRESOLVED_IDENTIFIER) { RETURN_PRINT_ERROR_AT(poisoned_ast, expr, "Expected a type here."); } CONSUME_EOS_OR_RET(poisoned_ast); return ast; } /** * var_stmt * : var CT_IDENT '=' const_expr EOS * | var CT_TYPE '=' const_expr EOS * ; */ static inline Ast *parse_var_stmt(ParseContext *c) { Ast *ast = new_ast(AST_DECLARE_STMT, c->span); ASSIGN_DECL_OR_RET(ast->var_stmt, parse_var_decl(c), poisoned_ast); RANGE_EXTEND_PREV(ast); CONSUME_EOS_OR_RET(poisoned_ast); return ast; } static inline bool parse_ct_compound_stmt(ParseContext *c, AstId *start) { AstId *next = start; while (1) { TokenType tok = c->tok; if (tok == TOKEN_CT_ELSE || tok == TOKEN_CT_ENDIF) break; ASSIGN_AST_OR_RET(Ast *stmt, parse_stmt(c), false); ast_append(&next, stmt); } return true; } /** * ct_if_stmt * : CT_IF expression ':' ct_compound_stmt (ct_elif_stmt | ct_else_stmt) CT_ENDIF EOS * ; */ static inline Ast *parse_ct_if_stmt(ParseContext *c) { Ast *ast = ast_new_curr(c, AST_CT_IF_STMT); advance_and_verify(c, TOKEN_CT_IF); ASSIGN_EXPR_OR_RET(ast->ct_if_stmt.expr, parse_expr(c), poisoned_ast); CONSUME_OR_RET(TOKEN_COLON, poisoned_ast); if (!parse_ct_compound_stmt(c, &ast->ct_if_stmt.then)) return poisoned_ast; if (tok_is(c, TOKEN_CT_ELSE)) { Ast *else_ast = new_ast(AST_CT_ELSE_STMT, c->span); advance_and_verify(c, TOKEN_CT_ELSE); if (!parse_ct_compound_stmt(c, &else_ast->ct_else_stmt)) return poisoned_ast; ast->ct_if_stmt.elif = astid(else_ast); } CONSUME_OR_RET(TOKEN_CT_ENDIF, poisoned_ast); RANGE_EXTEND_PREV(ast); return ast; } /** * return * : RETURN expression * | RETURN * ; */ static inline Ast *parse_return_stmt(ParseContext *c) { advance_and_verify(c, TOKEN_RETURN); Ast *ast = ast_new_curr(c, AST_RETURN_STMT); if (!tok_is(c, TOKEN_EOS)) { ASSIGN_EXPR_OR_RET(ast->return_stmt.expr, parse_expr(c), poisoned_ast); } CONSUME_EOS_OR_RET(poisoned_ast); return ast; } /** * ct_foreach_stmt ::= CT_FOREACH CT_IDENT (',' CT_IDENT)? ':' expr ':' statement* CT_ENDFOREACH */ static inline Ast* parse_ct_foreach_stmt(ParseContext *c) { Ast *ast = ast_new_curr(c, AST_CT_FOREACH_STMT); advance_and_verify(c, TOKEN_CT_FOREACH); if (peek(c) == TOKEN_COMMA) { Decl *index = decl_new_var(symstr(c), c->span, NULL, VARDECL_LOCAL_CT); ast->ct_foreach_stmt.index = declid(index); TRY_CONSUME_OR_RET(TOKEN_CT_IDENT, "Expected a compile time index variable", poisoned_ast); advance_and_verify(c, TOKEN_COMMA); } ast->ct_foreach_stmt.value = declid(decl_new_var(symstr(c), c->span, NULL, VARDECL_LOCAL_CT)); TRY_CONSUME_OR_RET(TOKEN_CT_IDENT, "Expected a compile time variable", poisoned_ast); TRY_CONSUME_OR_RET(TOKEN_COLON, "Expected ':'.", poisoned_ast); ASSIGN_EXPRID_OR_RET(ast->ct_foreach_stmt.expr, parse_expr(c), poisoned_ast); TRY_CONSUME_OR_RET(TOKEN_COLON, "Expected ':'.", poisoned_ast); Ast *body = new_ast(AST_CT_COMPOUND_STMT, ast->span); ast->ct_foreach_stmt.body = astid(body); AstId *current = &body->ct_compound_stmt; while (!try_consume(c, TOKEN_CT_ENDFOREACH)) { ASSIGN_AST_OR_RET(Ast *stmt, parse_stmt(c), poisoned_ast); *current = astid(stmt); current = &stmt->next; } return ast; } /** * ct_for_stmt * | CT_FOR decl_expr_list? ';' expression_list? ';' expression_list? ':' statement* CT_ENDFOR * ; */ static inline Ast* parse_ct_for_stmt(ParseContext *c) { Ast *ast = ast_new_curr(c, AST_CT_FOR_STMT); advance_and_verify(c, TOKEN_CT_FOR); if (!tok_is(c, TOKEN_EOS)) { ASSIGN_EXPRID_OR_RET(ast->for_stmt.init, parse_ct_expression_list(c, true), poisoned_ast); } CONSUME_EOS_OR_RET(poisoned_ast); // Cond is required. ASSIGN_EXPRID_OR_RET(ast->for_stmt.cond, parse_expr(c), poisoned_ast); CONSUME_EOS_OR_RET(poisoned_ast); if (!tok_is(c, TOKEN_COLON)) { ASSIGN_EXPRID_OR_RET(ast->for_stmt.incr, parse_ct_expression_list(c, false), poisoned_ast); } CONSUME_OR_RET(TOKEN_COLON, poisoned_ast); Ast *body = new_ast(AST_CT_COMPOUND_STMT, ast->span); ast->for_stmt.body = astid(body); AstId *current = &body->ct_compound_stmt; while (!try_consume(c, TOKEN_CT_ENDFOR)) { ASSIGN_AST_OR_RET(Ast *stmt, parse_stmt(c), poisoned_ast); *current = astid(stmt); current = &stmt->next; } return ast; } /** * ct_switch_stmt ::= CT_SWITCH const_expr? ':' ct_case_statement* CT_ENDSWITCH * * ct_case_statement ::= (CT_CASE constant_expr | CT_DEFAULT) ':' opt_stmt_list */ static inline Ast* parse_ct_switch_stmt(ParseContext *c) { Ast *ast = ast_new_curr(c, AST_CT_SWITCH_STMT); advance_and_verify(c, TOKEN_CT_SWITCH); if (!tok_is(c, TOKEN_COLON)) { ASSIGN_EXPRID_OR_RET(ast->ct_switch_stmt.cond, parse_constant_expr(c), poisoned_ast); } CONSUME_OR_RET(TOKEN_COLON, poisoned_ast); Ast **cases = NULL; while (!try_consume(c, TOKEN_CT_ENDSWITCH)) { Ast *result; TokenType tok = c->tok; if (tok == TOKEN_CT_CASE) { ASSIGN_AST_OR_RET(result, parse_case_stmt(c, TOKEN_CT_CASE, TOKEN_CT_DEFAULT), poisoned_ast); } else if (tok == TOKEN_CT_DEFAULT) { ASSIGN_AST_OR_RET(result, parse_default_stmt(c, TOKEN_CT_CASE, TOKEN_CT_DEFAULT), poisoned_ast); } else { PRINT_ERROR_HERE("A '$case' or '$default' would be needed here."); return poisoned_ast; } vec_add(cases, result); } ast->ct_switch_stmt.body = cases; return ast; } static inline Ast *consume_eos(ParseContext *c, Ast *ast) { if (!try_consume(c, TOKEN_EOS)) { print_error_after(c->prev_span, "Expected a ';' here."); advance(c); return poisoned_ast; } return ast; } /** * assert_stmt ::= ASSERT '(' assert_expr (',' expr)? ')' ';' */ static inline Ast *parse_assert_stmt(ParseContext *c) { Ast *ast = ast_new_curr(c, AST_ASSERT_STMT); advance_and_verify(c, TOKEN_ASSERT); TRY_CONSUME_OR_RET(TOKEN_LPAREN, "'assert' needs a '(' here, did you forget it?", poisoned_ast); ASSIGN_EXPRID_OR_RET(ast->assert_stmt.expr, parse_expr(c), poisoned_ast); if (try_consume(c, TOKEN_COMMA)) { Expr **args = NULL; ASSIGN_EXPRID_OR_RET(ast->assert_stmt.message, parse_constant_expr(c), poisoned_ast); while (try_consume(c, TOKEN_COMMA)) { ASSIGN_EXPR_OR_RET(Expr *expr, parse_expr(c), poisoned_ast); vec_add(args, expr); } ast->assert_stmt.args = args; } TRY_CONSUME_OR_RET(TOKEN_RPAREN, "The ending ')' was expected here.", poisoned_ast); return consume_eos(c, ast); } // --- External functions /** * ct_assert_stmt ::= CT_ASSERT constant_expression (':' constant_expression (',' constant_expression)*)? ';' * @param c * @return */ Ast *parse_ct_assert_stmt(ParseContext *c) { Ast *ast = ast_new_curr(c, AST_CT_ASSERT); advance_and_verify(c, TOKEN_CT_ASSERT); ASSIGN_EXPRID_OR_RET(ast->assert_stmt.expr, parse_constant_expr(c), poisoned_ast); if (try_consume(c, TOKEN_COLON)) { ASSIGN_EXPRID_OR_RET(ast->assert_stmt.message, parse_constant_expr(c), poisoned_ast); } while (try_consume(c, TOKEN_COMMA)) { ASSIGN_EXPR_OR_RET(Expr *expr, parse_constant_expr(c), poisoned_ast); vec_add(ast->assert_stmt.args, expr); } return consume_eos(c, ast); } /** * ct_error_stmt ::= CT_ERROR constant_expression (',' constant_expression)* ';' * @param c * @return */ Ast *parse_ct_error_stmt(ParseContext *c) { Ast *ast = ast_new_curr(c, AST_CT_ASSERT); advance_and_verify(c, TOKEN_CT_ERROR); ast->assert_stmt.expr = 0; ASSIGN_EXPRID_OR_RET(ast->assert_stmt.message, parse_constant_expr(c), poisoned_ast); while (try_consume(c, TOKEN_COMMA)) { ASSIGN_EXPR_OR_RET(Expr *expr, parse_constant_expr(c), poisoned_ast); vec_add(ast->assert_stmt.args, expr); } return consume_eos(c, ast); } Ast *parse_ct_echo_stmt(ParseContext *c) { Ast *ast = ast_new_curr(c, AST_CT_ECHO_STMT); advance_and_verify(c, TOKEN_CT_ECHO); ASSIGN_EXPR_OR_RET(ast->expr_stmt, parse_constant_expr(c), poisoned_ast); return consume_eos(c, ast); } Ast *parse_stmt(ParseContext *c) { switch (c->tok) { case TOKEN_LBRACE: return parse_compound_stmt(c); case TYPELIKE_TOKENS: case TOKEN_HASH_IDENT: case TOKEN_IDENT: case TOKEN_CONST_IDENT: return parse_decl_or_expr_stmt(c); case TOKEN_VAR: return parse_var_stmt(c); case TOKEN_TLOCAL: // Global means declaration! case TOKEN_STATIC: // Static means declaration! case TOKEN_CONST: // Const means declaration! return parse_declaration_stmt(c); case TOKEN_RETURN: return parse_return_stmt(c); case TOKEN_IF: return parse_if_stmt(c); case TOKEN_WHILE: return parse_while_stmt(c); case TOKEN_DEFER: return parse_defer_stmt(c); case TOKEN_SWITCH: return parse_switch_stmt(c); case TOKEN_DO: return parse_do_stmt(c); case TOKEN_FOR: return parse_for_stmt(c); case TOKEN_FOREACH: case TOKEN_FOREACH_R: return parse_foreach_stmt(c); case TOKEN_CONTINUE: return parse_continue_stmt(c); case TOKEN_CASE: PRINT_ERROR_HERE("'case' was found outside of 'switch', did you mismatch a '{ }' pair?"); advance(c); return poisoned_ast; case TOKEN_BREAK: return parse_break_stmt(c); case TOKEN_NEXTCASE: return parse_nextcase_stmt(c); case TOKEN_ASM: return parse_asm_block_stmt(c); case TOKEN_DEFAULT: PRINT_ERROR_HERE("'default' was found outside of 'switch', did you mismatch a '{ }' pair?"); advance(c); return poisoned_ast; case TOKEN_CT_ECHO: return parse_ct_echo_stmt(c); case TOKEN_CT_ASSERT: return parse_ct_assert_stmt(c); case TOKEN_CT_ERROR: return parse_ct_error_stmt(c); case TOKEN_CT_IF: return parse_ct_if_stmt(c); case TOKEN_CT_SWITCH: return parse_ct_switch_stmt(c); case TOKEN_CT_FOREACH: return parse_ct_foreach_stmt(c); case TOKEN_CT_FOR: return parse_ct_for_stmt(c); case TOKEN_AMP: case TOKEN_AT: case TOKEN_AT_CONST_IDENT: case TOKEN_AT_IDENT: case TOKEN_AT_TYPE_IDENT: case TOKEN_BANG: case TOKEN_BIT_NOT: case TOKEN_BIT_OR: case TOKEN_BIT_XOR: case TOKEN_BUILTIN: case TOKEN_BYTES: case TOKEN_CHAR_LITERAL: case TOKEN_CT_ALIGNOF: case TOKEN_CT_AND: case TOKEN_CT_ASSIGNABLE: case TOKEN_CT_CONCAT: case TOKEN_CT_CONST_IDENT: case TOKEN_CT_IS_CONST: case TOKEN_CT_DEFINED: case TOKEN_CT_EMBED: case TOKEN_CT_EVAL: case TOKEN_CT_EXTNAMEOF: case TOKEN_CT_FEATURE: case TOKEN_CT_IDENT: case TOKEN_CT_NAMEOF: case TOKEN_CT_OFFSETOF: case TOKEN_CT_OR: case TOKEN_CT_QNAMEOF: case TOKEN_CT_SIZEOF: case TOKEN_CT_STRINGIFY: case TOKEN_CT_VAARG: case TOKEN_CT_VACONST: case TOKEN_CT_VACOUNT: case TOKEN_CT_VAEXPR: case TOKEN_FALSE: case TOKEN_INTEGER: case TOKEN_LPAREN: case TOKEN_MINUS: case TOKEN_MINUSMINUS: case TOKEN_NULL: case TOKEN_OR: case TOKEN_PLUS: case TOKEN_PLUSPLUS: case TOKEN_REAL: case TOKEN_STAR: case TOKEN_STRING: case TOKEN_TRUE: return parse_expr_stmt(c); case TOKEN_ASSERT: return parse_assert_stmt(c); case TOKEN_INVALID_TOKEN: advance(c); return poisoned_ast; case TOKEN_ALIAS: case TOKEN_AND: case TOKEN_ARROW: case TOKEN_ATTRDEF: case TOKEN_BANGBANG: case TOKEN_BITSTRUCT: case TOKEN_BIT_AND_ASSIGN: case TOKEN_BIT_OR_ASSIGN: case TOKEN_BIT_XOR_ASSIGN: case TOKEN_COLON: case TOKEN_COMMA: case TOKEN_CT_CASE: case TOKEN_CT_DEFAULT: case TOKEN_CT_ELSE: case TOKEN_CT_ENDFOR: case TOKEN_CT_ENDFOREACH: case TOKEN_CT_ENDIF: case TOKEN_CT_ENDSWITCH: case TOKEN_CT_EXEC: case TOKEN_CT_INCLUDE: case TOKEN_CT_VASPLAT: case TOKEN_DIV: case TOKEN_DIV_ASSIGN: case TOKEN_DOCS_END: case TOKEN_DOCS_START: case TOKEN_DOC_COMMENT: case TOKEN_DOLLAR: case TOKEN_DOT: case TOKEN_DOTDOT: case TOKEN_ELLIPSIS: case TOKEN_ELSE: case TOKEN_ELVIS: case TOKEN_ENUM: case TOKEN_EQ: case TOKEN_EQEQ: case TOKEN_EXTERN: case TOKEN_FAULTDEF: case TOKEN_FN: case TOKEN_GREATER: case TOKEN_GREATER_EQ: case TOKEN_HASH: case TOKEN_IMPLIES: case TOKEN_IMPORT: case TOKEN_INLINE: case TOKEN_INTERFACE: case TOKEN_LBRACKET: case TOKEN_LESS: case TOKEN_LESS_EQ: case TOKEN_LVEC: case TOKEN_MACRO: case TOKEN_MINUS_ASSIGN: case TOKEN_MOD: case TOKEN_MODULE: case TOKEN_MOD_ASSIGN: case TOKEN_MULT_ASSIGN: case TOKEN_NOT_EQUAL: case TOKEN_PLUS_ASSIGN: case TOKEN_QUESTION: case TOKEN_QUESTQUEST: case TOKEN_RVEC: case TOKEN_SCOPE: case TOKEN_SHL: case TOKEN_SHL_ASSIGN: case TOKEN_SHR: case TOKEN_SHR_ASSIGN: case TOKEN_STRUCT: case TOKEN_TYPEDEF: case TOKEN_UNDERSCORE: case TOKEN_UNION: PRINT_ERROR_HERE("Unexpected '%s' found when expecting a statement.", token_type_to_string(c->tok)); advance(c); return poisoned_ast; case TOKEN_RPAREN: case TOKEN_RBRACE: case TOKEN_RBRACKET: PRINT_ERROR_HERE("Mismatched '%s' found.", token_type_to_string(c->tok)); advance(c); return poisoned_ast; case TOKEN_TRY: case TOKEN_CATCH: PRINT_ERROR_HERE("'%s' can only be used when unwrapping an optional, did you mean '%s?'?", token_type_to_string(c->tok), token_type_to_string(c->tok)); advance(c); return poisoned_ast; case TOKEN_EOS: { Ast *nop = ast_new_curr(c, AST_NOP_STMT); advance(c); return nop; } case TOKEN_EOF: PRINT_ERROR_HERE("Reached the end of the file when expecting a statement."); return poisoned_ast; case TOKEN_DOCS_EOL: PRINT_ERROR_HERE("Unexpectedly reached end of line."); return poisoned_ast; } UNREACHABLE } /** * compound_stmt * : '{' stmt_list '}' * ; * * stmt_list * : stmt * | stmt stmt_list * ; * * @param c * @return a compound statement */ Ast* parse_compound_stmt(ParseContext *c) { Ast *ast = ast_new_curr(c, AST_COMPOUND_STMT); CONSUME_OR_RET(TOKEN_LBRACE, poisoned_ast); AstId *next = &ast->compound_stmt.first_stmt; while (!try_consume(c, TOKEN_RBRACE)) { ASSIGN_AST_OR_RET(Ast *stmt, parse_stmt(c), poisoned_ast); ast_append(&next, stmt); } RANGE_EXTEND_PREV(ast); return ast; } Ast *parse_short_body(ParseContext *c, TypeInfoId return_type, bool is_regular_fn) { advance(c); Ast *ast = ast_new_curr(c, AST_COMPOUND_STMT); AstId *next = &ast->compound_stmt.first_stmt; Ast *ret = ast_new_curr(c, AST_RETURN_STMT); ast_append(&next, ret); TypeInfo *rtype = return_type ? type_infoptr(return_type) : NULL; bool is_void_return = rtype && rtype->resolve_status == RESOLVE_DONE && rtype->type->type_kind == TYPE_VOID; ASSIGN_EXPR_OR_RET(Expr *expr, parse_expr(c), poisoned_ast); if (expr->expr_kind == EXPR_CALL && expr->call_expr.macro_body) { ret->ast_kind = AST_EXPR_STMT; ret->expr_stmt = expr; is_regular_fn = false; expr->call_expr.is_outer_call = true; goto END; } if (is_void_return) { ret->ast_kind = AST_EXPR_STMT; ret->expr_stmt = expr; goto END; } ret->return_stmt.expr = expr; END:; RANGE_EXTEND_PREV(ast); if (is_regular_fn) { CONSUME_EOS_OR_RET(poisoned_ast); } return ast; }