/* spl_stmt.c — Statement parser + codegen */ #include "spl_comp.h" #include #include static spl_tok_t *peek(spl_comp_t *ctx) { return &vec_at(ctx->toks, ctx->tok_idx); } static spl_tok_t *advance(spl_comp_t *ctx) { spl_tok_t *t = &vec_at(ctx->toks, ctx->tok_idx); if (t->type != TOK_EOF) ctx->tok_idx++; return t; } static int expect(spl_comp_t *ctx, spl_tok_type_t type) { if (peek(ctx)->type == type) { advance(ctx); return 1; } spl_comp_error(ctx, "expected '%s', got '%s'", spl_tok_type_name(type), spl_tok_type_name(peek(ctx)->type)); return 0; } static int match(spl_comp_t *ctx, spl_tok_type_t type) { if (peek(ctx)->type == type) { advance(ctx); return 1; } return 0; } static void skip_nl(spl_comp_t *ctx) { while (peek(ctx)->type == TOK_ENDLINE) advance(ctx); } /* ============================================================ * Return statement: ret expr; * ============================================================ */ static void parse_ret_stmt(spl_comp_t *ctx) { advance(ctx); /* ret */ skip_nl(ctx); if (peek(ctx)->type == TOK_SEMICOLON || peek(ctx)->type == TOK_R_BRACE || peek(ctx)->type == TOK_ENDLINE) { /* void return */ spl_emit(ctx, SPL_RET, SPL_VOID, 0); } else { spl_expr_result_t val = spl_parse_expr(ctx, PREC_MIN); (void)val; spl_type_t rt = ctx->current_ret_type ? ctx->current_ret_type->basic_type : SPL_VOID; spl_emit(ctx, SPL_RET, rt, 0); } if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); } /* ============================================================ * Variable declaration: var name: Type [= expr]; * ============================================================ */ static void parse_var_decl(spl_comp_t *ctx, int is_const) { advance(ctx); /* var or const */ skip_nl(ctx); spl_tok_t *name_tok = advance(ctx); char vname[256]; usize nlen = name_tok->len < 255 ? name_tok->len : 255; memcpy(vname, name_tok->lexeme, nlen); vname[nlen] = '\0'; spl_type_info_t *var_type = NULL; int has_init = 0; skip_nl(ctx); if (peek(ctx)->type == TOK_COLON) { advance(ctx); /* : */ skip_nl(ctx); /* Check for := */ if (peek(ctx)->type == TOK_ASSIGN) { /* := is colon-assign */ has_init = 1; } else { var_type = spl_parse_type(ctx); skip_nl(ctx); } } if (peek(ctx)->type == TOK_COLON_ASSIGN || peek(ctx)->type == TOK_ASSIGN) { has_init = 1; if (peek(ctx)->type == TOK_COLON_ASSIGN) advance(ctx); else advance(ctx); /* = */ } /* Allocate stack slot */ if (!var_type) var_type = spl_type_basic(SPL_I32); /* default type */ int slot = spl_declare_var(ctx, vname, var_type, is_const); skip_nl(ctx); /* Init expression */ if (has_init) { spl_expr_result_t init = spl_parse_expr(ctx, PREC_MIN); (void)init; if (var_type && var_type->kind == TYPE_ARRAY) { /* Array initialization: store each element in reverse stack order */ spl_type_info_t *elem = var_type->elem; spl_type_t bt = (elem && elem->kind == TYPE_BASIC) ? elem->basic_type : SPL_I32; for (int i = (int)var_type->array_len - 1; i >= 0; i--) { spl_emit(ctx, SPL_LADDR, SPL_PTR, slot + i); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); spl_emit(ctx, SPL_STORE, bt, 0); } } else if (var_type && var_type->kind == TYPE_SLICE) { /* Slice initialization: stack has [ptr, len] from slice expression. * Store len at slot+1, then ptr at slot. */ spl_emit(ctx, SPL_LADDR, SPL_PTR, slot + 1); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); spl_emit(ctx, SPL_LADDR, SPL_PTR, slot); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); spl_emit(ctx, SPL_STORE, SPL_PTR, 0); } else { /* Single value store */ spl_emit(ctx, SPL_LADDR, SPL_PTR, slot); spl_type_t bt = (var_type->kind == TYPE_BASIC) ? var_type->basic_type : (var_type->kind == TYPE_PTR) ? SPL_PTR : SPL_I32; spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); spl_emit(ctx, SPL_STORE, bt, 0); } } if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); } /* ============================================================ * Block: { stmt; stmt; ... } * ============================================================ */ void spl_parse_block(spl_comp_t *ctx) { skip_nl(ctx); if (peek(ctx)->type == TOK_L_BRACE) { advance(ctx); /* { */ spl_push_scope(ctx); while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) { spl_parse_stmt(ctx); skip_nl(ctx); } spl_pop_scope(ctx); if (peek(ctx)->type == TOK_R_BRACE) advance(ctx); } else { /* Single statement */ spl_parse_stmt(ctx); } } /* ============================================================ * If statement: if expr { ... } [else { ... }] * ============================================================ */ static void parse_if_stmt(spl_comp_t *ctx) { advance(ctx); /* if */ skip_nl(ctx); spl_expr_result_t cond = spl_parse_expr(ctx, PREC_MIN); (void)cond; spl_val_t bz_addr = spl_emit_bz(ctx); skip_nl(ctx); spl_parse_block(ctx); spl_val_t jmp_addr = 0; skip_nl(ctx); if (peek(ctx)->type == KW_ELSE) { jmp_addr = spl_emit_jmp(ctx); spl_patch_to_here(ctx, bz_addr); advance(ctx); /* else */ skip_nl(ctx); spl_parse_block(ctx); spl_patch_to_here(ctx, jmp_addr); } else { spl_patch_to_here(ctx, bz_addr); } } /* ============================================================ * While statement: while expr { ... } * ============================================================ */ static void parse_while_stmt(spl_comp_t *ctx) { spl_val_t loop_start = vec_size(ctx->prog.insns); int saved_loop = ctx->in_loop; usize saved_continue = ctx->continue_target; usize saved_bp_count = ctx->break_patch_count; ctx->in_loop = 1; ctx->continue_target = loop_start; advance(ctx); /* while */ skip_nl(ctx); spl_expr_result_t cond = spl_parse_expr(ctx, PREC_MIN); (void)cond; spl_val_t bz_addr = spl_emit_bz(ctx); skip_nl(ctx); spl_parse_block(ctx); /* JMP back to loop condition — use relative offset */ spl_val_t here = vec_size(ctx->prog.insns); spl_emit(ctx, SPL_JMP, SPL_VOID, (spl_val_t)((isize)loop_start - (isize)here - 1)); spl_patch_to_here(ctx, bz_addr); /* Patch break statements — compute relative offset */ for (usize i = saved_bp_count; i < ctx->break_patch_count; i++) { spl_patch_to_here(ctx, ctx->break_patches[i]); } ctx->break_patch_count = saved_bp_count; ctx->in_loop = saved_loop; ctx->continue_target = saved_continue; } /* ============================================================ * Loop statement: loop { ... } * ============================================================ */ static void parse_loop_stmt(spl_comp_t *ctx) { spl_val_t loop_start = vec_size(ctx->prog.insns); int saved_loop = ctx->in_loop; usize saved_continue = ctx->continue_target; usize saved_bp_count = ctx->break_patch_count; ctx->in_loop = 1; ctx->continue_target = loop_start; advance(ctx); /* loop */ skip_nl(ctx); spl_parse_block(ctx); /* JMP back to loop start — use relative offset */ spl_val_t here = vec_size(ctx->prog.insns); spl_emit(ctx, SPL_JMP, SPL_VOID, (spl_val_t)((isize)loop_start - (isize)here - 1)); /* Patch break statements — compute relative offset */ for (usize i = saved_bp_count; i < ctx->break_patch_count; i++) { spl_patch_to_here(ctx, ctx->break_patches[i]); } ctx->break_patch_count = saved_bp_count; ctx->in_loop = saved_loop; ctx->continue_target = saved_continue; } /* ============================================================ * For loop: for begin..end as i { ... } * ============================================================ */ static void parse_for_stmt(spl_comp_t *ctx) { advance(ctx); /* for */ skip_nl(ctx); /* Parse the iteration expression(s) */ spl_expr_result_t start_expr = spl_parse_expr(ctx, PREC_MIN); if (peek(ctx)->type == TOK_RANGE) { /* ===== Numeric range: for begin..end as i { body } ===== */ advance(ctx); /* .. */ spl_parse_expr(ctx, PREC_MIN); /* end expression */ /* Stack: [begin, end] */ skip_nl(ctx); if (peek(ctx)->type != KW_AS) { skip_nl(ctx); spl_parse_block(ctx); return; } advance(ctx); /* as */ spl_tok_t *ivar = advance(ctx); char iname[256]; usize inl = ivar->len < 255 ? ivar->len : 255; memcpy(iname, ivar->lexeme, inl); iname[inl] = '\0'; spl_push_scope(ctx); int islot = spl_declare_var(ctx, iname, spl_type_basic(SPL_USIZE), 0); /* Stack: [begin, end]; swap so TOS = begin */ spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* Store begin to i */ spl_emit(ctx, SPL_LADDR, SPL_PTR, islot); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); /* Stack: [end] */ spl_val_t loop_start = vec_size(ctx->prog.insns); /* Condition: i < end */ spl_emit(ctx, SPL_LADDR, SPL_PTR, islot); spl_emit(ctx, SPL_LOAD, SPL_USIZE, 0); spl_emit(ctx, SPL_PICK, SPL_VOID, 1); spl_emit(ctx, SPL_ULT, SPL_USIZE, 0); spl_val_t bz_addr = spl_emit_bz(ctx); int saved_loop = ctx->in_loop; usize saved_continue = ctx->continue_target; usize saved_bp_count = ctx->break_patch_count; ctx->in_loop = 1; ctx->continue_target = loop_start; skip_nl(ctx); spl_parse_block(ctx); /* body */ /* Increment: i = i + 1 */ spl_emit(ctx, SPL_LADDR, SPL_PTR, islot); spl_emit(ctx, SPL_LADDR, SPL_PTR, islot); spl_emit(ctx, SPL_LOAD, SPL_USIZE, 0); spl_emit(ctx, SPL_PUSH, SPL_USIZE, 1); spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); /* JMP back to condition */ spl_val_t jmp_here = vec_size(ctx->prog.insns); spl_emit(ctx, SPL_JMP, SPL_VOID, (spl_val_t)((isize)loop_start - (isize)jmp_here - 1)); /* Exit: patch bz, drop end */ spl_patch_to_here(ctx, bz_addr); spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* Patch breaks */ for (usize i = saved_bp_count; i < ctx->break_patch_count; i++) spl_patch_to_here(ctx, ctx->break_patches[i]); ctx->break_patch_count = saved_bp_count; ctx->in_loop = saved_loop; ctx->continue_target = saved_continue; spl_pop_scope(ctx); return; } /* ===== Slice iteration: for slice [as val |, range as val, idx] ===== */ int has_idx = 0; if (peek(ctx)->type == TOK_COMMA) { advance(ctx); /* , */ spl_parse_expr(ctx, PREC_MIN); /* parse start of range (e.g. 0) */ if (peek(ctx)->type == TOK_RANGE) { advance(ctx); /* .. */ /* consume optional end expression */ if (peek(ctx)->type != KW_AS && peek(ctx)->type != TOK_COMMA && peek(ctx)->type != TOK_L_BRACE && peek(ctx)->type != TOK_ENDLINE && peek(ctx)->type != TOK_EOF) spl_parse_expr(ctx, PREC_MIN); } has_idx = 1; /* Range pushed a value; we manage our own idx, drop it */ spl_emit(ctx, SPL_DROP, SPL_VOID, 0); } if (peek(ctx)->type != KW_AS) { skip_nl(ctx); spl_parse_block(ctx); return; } advance(ctx); /* as */ /* Parse val variable name */ spl_tok_t *vtok = advance(ctx); char vname[256]; usize vnl = vtok->len < 255 ? vtok->len : 255; memcpy(vname, vtok->lexeme, vnl); vname[vnl] = '\0'; /* Parse optional idx variable name */ char iname[256] = {0}; if (peek(ctx)->type == TOK_COMMA) { advance(ctx); spl_tok_t *itok = advance(ctx); usize inl = itok->len < 255 ? itok->len : 255; memcpy(iname, itok->lexeme, inl); iname[inl] = '\0'; } /* Stack: [slice_addr] or whatever the slice expression left */ spl_push_scope(ctx); int idx_slot = -1; if (iname[0]) idx_slot = spl_declare_var(ctx, iname, spl_type_basic(SPL_USIZE), 0); spl_type_info_t *elem_type = NULL; if (start_expr.type) { if (start_expr.type->kind == TYPE_SLICE) elem_type = start_expr.type->elem; else if (start_expr.type->kind == TYPE_PTR && start_expr.type->elem) elem_type = start_expr.type->elem; } if (!elem_type) elem_type = spl_type_basic(SPL_I32); int val_slot = spl_declare_var(ctx, vname, elem_type, 0); /* Extract ptr and len from slice, push index=0: stack [ptr, len, idx] */ spl_emit(ctx, SPL_DUP, SPL_VOID, 0); spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); spl_emit(ctx, SPL_PUSH, SPL_U64, sizeof(spl_val_t)); spl_emit(ctx, SPL_ADD, SPL_U64, 0); spl_emit(ctx, SPL_LOAD, SPL_USIZE, 0); spl_emit(ctx, SPL_PUSH, SPL_USIZE, 0); /* Stack: [ptr, len, idx=0] */ spl_val_t loop_start = vec_size(ctx->prog.insns); /* Condition: idx < len */ spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* copy len */ spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* copy idx */ spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [idx, len] → [len, idx] → SWAP → [idx, len] */ spl_emit(ctx, SPL_ULT, SPL_USIZE, 0); spl_val_t bz_addr = spl_emit_bz(ctx); /* Store current idx to idx variable */ if (idx_slot >= 0) { spl_emit(ctx, SPL_PICK, SPL_VOID, 0); /* copy idx (TOS) */ spl_emit(ctx, SPL_LADDR, SPL_PTR, idx_slot); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); } /* Load slice[idx] and store to val */ spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* copy ptr: [ptr, len, idx, ptr] */ spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* copy idx: [ptr, len, idx, ptr, idx] */ usize elem_byte_size = (elem_type && elem_type->byte_size) ? elem_type->byte_size : 4; spl_emit(ctx, SPL_PUSH, SPL_U64, sizeof(spl_val_t)); spl_emit(ctx, SPL_MUL, SPL_U64, 0); spl_emit(ctx, SPL_ADD, SPL_U64, 0); spl_emit(ctx, SPL_LOAD, SPL_I32, 0); spl_emit(ctx, SPL_LADDR, SPL_PTR, val_slot); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); spl_emit(ctx, SPL_STORE, SPL_I32, 0); /* Loop context */ int saved_loop = ctx->in_loop; usize saved_continue = ctx->continue_target; usize saved_bp_count = ctx->break_patch_count; ctx->in_loop = 1; ctx->continue_target = loop_start; skip_nl(ctx); spl_parse_block(ctx); /* body */ /* Increment idx */ spl_emit(ctx, SPL_PICK, SPL_VOID, 0); /* copy idx */ spl_emit(ctx, SPL_PUSH, SPL_USIZE, 1); spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* replace old idx with new */ /* JMP back to condition */ spl_val_t jmp_here = vec_size(ctx->prog.insns); spl_emit(ctx, SPL_JMP, SPL_VOID, (spl_val_t)((isize)loop_start - (isize)jmp_here - 1)); /* Exit: patch bz, drop idx, len, ptr */ spl_patch_to_here(ctx, bz_addr); spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* drop idx */ spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* drop len */ spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* drop ptr */ /* Patch breaks */ for (usize i = saved_bp_count; i < ctx->break_patch_count; i++) spl_patch_to_here(ctx, ctx->break_patches[i]); ctx->break_patch_count = saved_bp_count; ctx->in_loop = saved_loop; ctx->continue_target = saved_continue; spl_pop_scope(ctx); } /* ============================================================ * Break / Continue * ============================================================ */ static void parse_break_stmt(spl_comp_t *ctx) { advance(ctx); /* break */ if (!ctx->in_loop) { spl_comp_error(ctx, "break outside loop"); } /* Emit JMP with placeholder, add to patch list */ spl_val_t addr = spl_emit_jmp(ctx); if (ctx->break_patch_cap <= ctx->break_patch_count) { usize new_cap = ctx->break_patch_cap ? ctx->break_patch_cap * 2 : 8; ctx->break_patches = realloc(ctx->break_patches, new_cap * sizeof(usize)); ctx->break_patch_cap = new_cap; } ctx->break_patches[ctx->break_patch_count++] = addr; if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); } static void parse_continue_stmt(spl_comp_t *ctx) { advance(ctx); /* continue */ if (!ctx->in_loop) { spl_comp_error(ctx, "continue outside loop"); } /* Emit JMP with relative offset to continue_target */ spl_val_t here = vec_size(ctx->prog.insns); spl_emit(ctx, SPL_JMP, SPL_VOID, (spl_val_t)((isize)ctx->continue_target - (isize)here - 1)); if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); } /* ============================================================ * Defer statement: defer { ... } or defer expr; * ============================================================ */ static void parse_defer_stmt(spl_comp_t *ctx) { advance(ctx); /* defer */ skip_nl(ctx); /* Record current position for defer */ spl_emit_defer(ctx); /* Parse the deferred statement/block */ if (peek(ctx)->type == TOK_L_BRACE) { spl_parse_block(ctx); } else { spl_parse_stmt(ctx); } } /* ============================================================ * Extern declaration: #[extern("vm")] fn name(...) ret; * ============================================================ */ static void parse_extern_decl(spl_comp_t *ctx) { advance(ctx); /* # */ expect(ctx, TOK_L_BRACKET); /* Skip extern("vm") or just look for fn */ while (peek(ctx)->type != TOK_R_BRACKET && peek(ctx)->type != TOK_EOF) advance(ctx); if (peek(ctx)->type == TOK_R_BRACKET) advance(ctx); skip_nl(ctx); if (peek(ctx)->type == KW_FN) { advance(ctx); /* fn */ skip_nl(ctx); spl_tok_t *fname_tok = advance(ctx); char fn_name[256]; usize fnl = fname_tok->len < 255 ? fname_tok->len : 255; memcpy(fn_name, fname_tok->lexeme, fnl); fn_name[fnl] = '\0'; skip_nl(ctx); expect(ctx, TOK_L_PAREN); /* Count params (we don't store them for extern) */ int nparams = 0; skip_nl(ctx); if (peek(ctx)->type != TOK_R_PAREN) { while (1) { advance(ctx); /* param name */ skip_nl(ctx); if (peek(ctx)->type == TOK_COLON) { advance(ctx); /* : */ skip_nl(ctx); spl_parse_type(ctx); /* skip type */ } nparams++; skip_nl(ctx); if (peek(ctx)->type == TOK_COMMA) { advance(ctx); skip_nl(ctx); continue; } if (peek(ctx)->type == TOK_ELLIPSIS) { advance(ctx); skip_nl(ctx); } /* variadic */ break; } } expect(ctx, TOK_R_PAREN); skip_nl(ctx); /* Return type */ spl_type_info_t *ret_type = spl_type_basic(SPL_VOID); if (peek(ctx)->type != TOK_SEMICOLON && peek(ctx)->type != TOK_L_BRACE) { ret_type = spl_parse_type(ctx); if (!ret_type) ret_type = spl_type_basic(SPL_VOID); skip_nl(ctx); } spl_declare_func(ctx, fn_name, ret_type, nparams, 1, 0); if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); } } /* ============================================================ * Expression statement (may include assignment) * ============================================================ */ /* Check if token type is an assignment operator */ static int is_assign_op(spl_tok_type_t t) { return t == TOK_ASSIGN || t == TOK_ASSIGN_ADD || t == TOK_ASSIGN_SUB || t == TOK_ASSIGN_MUL || t == TOK_ASSIGN_DIV || t == TOK_ASSIGN_MOD || t == TOK_ASSIGN_AND || t == TOK_ASSIGN_OR || t == TOK_ASSIGN_XOR || t == TOK_ASSIGN_L_SH || t == TOK_ASSIGN_R_SH; } static void parse_expr_stmt(spl_comp_t *ctx) { skip_nl(ctx); if (peek(ctx)->type == KW_RET) { parse_ret_stmt(ctx); return; } int prev = ctx->tok_idx; /* Look ahead for assignment operator (scan past expression tokens) */ int is_assign = 0; { usize look = ctx->tok_idx; while (look < vec_size(ctx->toks)) { spl_tok_type_t t = vec_at(ctx->toks, look).type; if (t == TOK_SEMICOLON || t == TOK_ENDLINE || t == TOK_L_BRACE || t == TOK_R_BRACE || t == TOK_EOF) break; if (t == TOK_L_PAREN) break; /* function call, not assignment */ if (is_assign_op(t)) { is_assign = 1; break; } look++; } } if (is_assign) ctx->addr_of_mode = 1; spl_expr_result_t expr = spl_parse_expr(ctx, PREC_MIN); if (is_assign) ctx->addr_of_mode = 0; if (!is_assign && expr.type) spl_emit(ctx, SPL_DROP, SPL_VOID, 0); if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); /* Safety: if no token was consumed, advance to prevent infinite loop */ if (ctx->tok_idx == prev) advance(ctx); } /* ============================================================ * Main statement dispatcher * ============================================================ */ void spl_parse_stmt(spl_comp_t *ctx) { skip_nl(ctx); if (peek(ctx)->type == TOK_EOF || peek(ctx)->type == TOK_R_BRACE) return; switch (peek(ctx)->type) { case KW_RET: parse_ret_stmt(ctx); break; case KW_VAR: parse_var_decl(ctx, 0); break; case KW_CONST: parse_var_decl(ctx, 1); break; case KW_IF: parse_if_stmt(ctx); break; case KW_WHILE: parse_while_stmt(ctx); break; case KW_LOOP: parse_loop_stmt(ctx); break; case KW_FOR: parse_for_stmt(ctx); break; case KW_BREAK: parse_break_stmt(ctx); break; case KW_CONTINUE: parse_continue_stmt(ctx); break; case KW_DEFER: parse_defer_stmt(ctx); break; case KW_TYPE: parse_type_decl(ctx); break; case TOK_L_BRACE: spl_parse_block(ctx); break; case TOK_LINE_COMMENT: advance(ctx); break; case TOK_SHARP: /* #[extern(...)] */ parse_extern_decl(ctx); break; default: parse_expr_stmt(ctx); break; } }