diff --git a/stage1/spl_comp.h b/stage1/spl_comp.h index 3702900..646b22c 100644 --- a/stage1/spl_comp.h +++ b/stage1/spl_comp.h @@ -245,6 +245,16 @@ typedef struct { } spl_expr_result_t; spl_expr_result_t spl_parse_expr(spl_comp_t *ctx, int min_prec); +spl_expr_result_t spl_parse_struct_literal(spl_comp_t *ctx, spl_type_info_t *type); + +/* Match arm pattern comparison — emit code to compare saved match value + * against a parsed pattern. Comparison logic belongs in expr layer + * so that match arms behave as "enhanced if-conditions". + * For enum: parses .VariantName, emits tag comparison, returns variant. + * For value: parses expression, emits equality comparison. */ +spl_enum_variant_t *spl_emit_match_enum_cmp(spl_comp_t *ctx, + spl_type_info_t *enum_type, int val_offset); +void spl_emit_match_value_cmp(spl_comp_t *ctx, int val_offset); /* ============================================================ * Statement functions (spl_stmt.c) @@ -252,6 +262,7 @@ spl_expr_result_t spl_parse_expr(spl_comp_t *ctx, int min_prec); void spl_parse_stmt(spl_comp_t *ctx); void spl_parse_block(spl_comp_t *ctx); +spl_expr_result_t spl_parse_block_expr(spl_comp_t *ctx); /* ============================================================ * Codegen helpers (spl_comp.c) @@ -268,6 +279,8 @@ void spl_patch_to_here(spl_comp_t *ctx, spl_val_t addr); * Stack: [..., temp_addr] → [...] * Copies nslots slots (each sizeof(spl_val_t) bytes) from temp offset to dest_offset. */ void spl_emit_copy_slots(spl_comp_t *ctx, int dest_offset, usize nslots); +void spl_emit_ret(spl_comp_t *ctx, spl_type_info_t *ret_type); +void spl_emit_store_init(spl_comp_t *ctx, int var_offset, spl_type_info_t *var_type); /* Variable management */ int spl_declare_var(spl_comp_t *ctx, const char *name, spl_type_info_t *type, int is_const); diff --git a/stage1/spl_expr.c b/stage1/spl_expr.c index 81f217e..216adaa 100644 --- a/stage1/spl_expr.c +++ b/stage1/spl_expr.c @@ -372,10 +372,133 @@ static spl_expr_result_t parse_array_literal(spl_comp_t *ctx) { return (spl_expr_result_t){arr_type, 0}; } +/* Parse slice inline literal: { .ptr = expr, .len = expr } + * Expects base address on TOS, consumes it. Stores ptr to base+0, len to base+sizeof(spl_val_t). */ +static void parse_slice_inline(spl_comp_t *ctx) { + advance(ctx); /* { */ + skip_nl(ctx); + while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) { + if (peek(ctx)->type == TOK_COMMA) { + advance(ctx); + skip_nl(ctx); + continue; + } + if (peek(ctx)->type == TOK_DOT) + advance(ctx); + spl_tok_t *ftok = advance(ctx); + char fname[256]; + usize fnl = ftok->len < 255 ? ftok->len : 255; + memcpy(fname, ftok->lexeme, fnl); + fname[fnl] = '\0'; + skip_nl(ctx); + if (peek(ctx)->type == TOK_ASSIGN) + advance(ctx); + skip_nl(ctx); + + spl_emit(ctx, SPL_DUP, SPL_VOID, 0); /* [addr, addr] */ + if (strcmp(fname, "ptr") == 0) { + spl_parse_expr(ctx, PREC_MIN); + spl_emit(ctx, SPL_STORE, SPL_PTR, 0); /* [addr] */ + } else if (strcmp(fname, "len") == 0) { + spl_emit(ctx, SPL_PUSH, SPL_USIZE, sizeof(spl_val_t)); + spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); /* [addr, addr+8] */ + spl_parse_expr(ctx, PREC_MIN); + spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); /* [addr] */ + } + skip_nl(ctx); + } + expect(ctx, TOK_R_BRACE); + spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* drop addr */ +} + +/* Parse a single .field = value in a struct literal. + * Iterates fields to find the match, emits LADDR + offset, and handles + * value parsing (slice inline, array inline, multi-slot struct, basic store). + * Reused by both TYPE_STRUCT and TYPE_ENUM variant struct data. */ +static void parse_one_field_init(spl_comp_t *ctx, spl_field_vec_t *fields, + int base_offset, usize extra_offset) { + if (peek(ctx)->type == TOK_DOT) + advance(ctx); + spl_tok_t *ftok = advance(ctx); + char fname[256]; + spl_tok_copy_name(ftok, fname, sizeof(fname)); + skip_nl(ctx); + if (peek(ctx)->type == TOK_ASSIGN) + advance(ctx); + skip_nl(ctx); + + vec_for(*fields, fi) { + spl_field_t *f = &vec_at(*fields, fi); + if (strcmp(f->name, fname) == 0) { + spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset); + usize byte_off = extra_offset + f->offset; + if (byte_off > 0) { + spl_emit(ctx, SPL_PUSH, SPL_USIZE, byte_off); + spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); + } + if (f->type && f->type->kind == TYPE_SLICE && + peek(ctx)->type == TOK_L_BRACE) { + parse_slice_inline(ctx); + } else if (f->type && f->type->kind == TYPE_ARRAY && + peek(ctx)->type == TOK_L_BRACKET) { + /* Inline array initializer: [N]Type{val1, val2, ...} */ + advance(ctx); /* [ */ + skip_nl(ctx); + int len_val; + if (!spl_parse_int_literal(ctx, &len_val)) { + spl_comp_error(ctx, "expected array length"); + return; + } + usize arr_len = (usize)len_val; + skip_nl(ctx); + expect(ctx, TOK_R_BRACKET); + skip_nl(ctx); + spl_type_info_t *elem_type = spl_parse_type(ctx); + skip_nl(ctx); + expect(ctx, TOK_L_BRACE); + skip_nl(ctx); + usize stride = spl_type_elem_stride(elem_type); + spl_type_t st = spl_type_emit_type(elem_type); + for (usize i = 0; i < arr_len; i++) { + if (i > 0) { + if (peek(ctx)->type == TOK_COMMA) advance(ctx); + skip_nl(ctx); + } + spl_emit(ctx, SPL_DUP, SPL_VOID, 0); + if (i > 0) { + spl_emit(ctx, SPL_PUSH, SPL_USIZE, i * stride); + spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); + } + spl_parse_expr(ctx, PREC_MIN); + spl_emit(ctx, SPL_STORE, st, 0); + skip_nl(ctx); + } + if (peek(ctx)->type == TOK_COMMA) advance(ctx); + skip_nl(ctx); + expect(ctx, TOK_R_BRACE); + spl_emit(ctx, SPL_DROP, SPL_VOID, 0); + } else if (f->type && + (f->type->kind == TYPE_STRUCT || f->type->kind == TYPE_ENUM) && + spl_type_size(f->type) > sizeof(spl_val_t)) { + spl_expr_result_t fv = spl_parse_expr(ctx, PREC_MIN); + (void)fv; + usize nslots = (spl_type_size(f->type) + sizeof(spl_val_t) - 1) / sizeof(spl_val_t); + spl_emit_copy_slots(ctx, base_offset + (int)extra_offset + f->offset, nslots); + spl_emit(ctx, SPL_DROP, SPL_VOID, 0); + } else { + spl_expr_result_t fv = spl_parse_expr(ctx, PREC_MIN); + (void)fv; + spl_emit(ctx, SPL_STORE, spl_type_emit_type(f->type), 0); + } + break; + } + } +} + /* Parse struct/enum literal: Type { .field = val, ... } * Allocates temp slots for the value, returns lvalue (addr on stack). * For types fitting in one slot, pushes the packed value directly. */ -static spl_expr_result_t parse_struct_literal(spl_comp_t *ctx, spl_type_info_t *type) { +spl_expr_result_t spl_parse_struct_literal(spl_comp_t *ctx, spl_type_info_t *type) { usize sz = spl_type_size(type); int base_offset = ctx->current_local_bytes; ctx->current_local_bytes += (int)((sz + sizeof(spl_val_t) - 1) & ~(sizeof(spl_val_t) - 1)); @@ -396,134 +519,41 @@ static spl_expr_result_t parse_struct_literal(spl_comp_t *ctx, spl_type_info_t * skip_nl(ctx); continue; } - if (peek(ctx)->type == TOK_DOT) { - advance(ctx); - spl_tok_t *ftok = advance(ctx); - char fname[256]; - usize fnl = ftok->len < 255 ? ftok->len : 255; - memcpy(fname, ftok->lexeme, fnl); - fname[fnl] = '\0'; - skip_nl(ctx); - if (peek(ctx)->type == TOK_ASSIGN) - advance(ctx); - skip_nl(ctx); - - vec_for(type->fields, fi) { - spl_field_t *f = &vec_at(type->fields, fi); - if (strcmp(f->name, fname) == 0) { - spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset); - if (f->offset > 0) { - spl_emit(ctx, SPL_PUSH, SPL_USIZE, f->offset); - spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); - } - /* Handle inline slice initializer: { .ptr = expr, .len = expr } */ - if (f->type && f->type->kind == TYPE_SLICE && - peek(ctx)->type == TOK_L_BRACE) { - /* Stack: [field_addr] — slice struct start addr */ - advance(ctx); /* { */ - skip_nl(ctx); - while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) { - if (peek(ctx)->type == TOK_COMMA) { - advance(ctx); - skip_nl(ctx); - continue; - } - if (peek(ctx)->type == TOK_DOT) - advance(ctx); - spl_tok_t *sftok = advance(ctx); - char sfname[256]; - usize sfnl = sftok->len < 255 ? sftok->len : 255; - memcpy(sfname, sftok->lexeme, sfnl); - sfname[sfnl] = '\0'; - skip_nl(ctx); - if (peek(ctx)->type == TOK_ASSIGN) - advance(ctx); - skip_nl(ctx); - - spl_emit(ctx, SPL_DUP, SPL_VOID, 0); /* [addr, addr] */ - if (strcmp(sfname, "ptr") == 0) { - spl_expr_result_t pv = spl_parse_expr(ctx, PREC_MIN); - (void)pv; - /* Stack: [addr, addr, ptr_val] — STORE needs [addr, val] */ - spl_emit(ctx, SPL_STORE, SPL_PTR, 0); /* [addr] */ - } else if (strcmp(sfname, "len") == 0) { - spl_emit(ctx, SPL_PUSH, SPL_USIZE, sizeof(spl_val_t)); - spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); /* [addr, addr+8] */ - spl_expr_result_t lv = spl_parse_expr(ctx, PREC_MIN); - (void)lv; - /* Stack: [addr, addr+8, len_val] — STORE needs [addr, val] */ - spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); /* [addr] */ - } - skip_nl(ctx); - } - expect(ctx, TOK_R_BRACE); - spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* drop addr */ - } else if (f->type && f->type->kind == TYPE_ARRAY && - peek(ctx)->type == TOK_L_BRACKET) { - /* Inline array initializer: [N]Type{val1, val2, ...} - * Stack: [field_addr] — store each element at computed offsets */ - advance(ctx); /* [ */ - skip_nl(ctx); - int len_val; - if (!spl_parse_int_literal(ctx, &len_val)) { - spl_comp_error(ctx, "expected array length"); - spl_expr_result_t r = {0}; - return r; - } - usize arr_len = (usize)len_val; - skip_nl(ctx); - expect(ctx, TOK_R_BRACKET); - skip_nl(ctx); - spl_type_info_t *elem_type = spl_parse_type(ctx); - skip_nl(ctx); - expect(ctx, TOK_L_BRACE); - skip_nl(ctx); - usize stride = spl_type_elem_stride(elem_type); - spl_type_t st = spl_type_emit_type(elem_type); - for (usize i = 0; i < arr_len; i++) { - if (i > 0) { - if (peek(ctx)->type == TOK_COMMA) - advance(ctx); - skip_nl(ctx); - } - spl_emit(ctx, SPL_DUP, SPL_VOID, 0); - if (i > 0) { - spl_emit(ctx, SPL_PUSH, SPL_USIZE, i * stride); - spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); - } - spl_parse_expr(ctx, PREC_MIN); - spl_emit(ctx, SPL_STORE, st, 0); - skip_nl(ctx); - } - if (peek(ctx)->type == TOK_COMMA) - advance(ctx); - skip_nl(ctx); - expect(ctx, TOK_R_BRACE); - spl_emit(ctx, SPL_DROP, SPL_VOID, 0); - } else if (f->type && - (f->type->kind == TYPE_STRUCT || f->type->kind == TYPE_ENUM) && - spl_type_size(f->type) > sizeof(spl_val_t)) { - /* Multi-slot struct/enum value: copy temp → field slot by slot - * Stack: [field_addr, temp_addr] */ - spl_expr_result_t fv = spl_parse_expr(ctx, PREC_MIN); - (void)fv; - usize nslots = (spl_type_size(f->type) + sizeof(spl_val_t) - 1) / - sizeof(spl_val_t); - spl_emit_copy_slots(ctx, base_offset + f->offset, nslots); - /* Drop field_addr from stack (copy_slots consumed temp_addr) */ - spl_emit(ctx, SPL_DROP, SPL_VOID, 0); - } else { - spl_expr_result_t fv = spl_parse_expr(ctx, PREC_MIN); - (void)fv; - spl_emit(ctx, SPL_STORE, spl_type_emit_type(f->type), 0); - } - break; - } - } - } else { - /* Unrecognized token (not .field or ,), advance to prevent infinite loop */ + if (peek(ctx)->type != TOK_DOT) { if (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) advance(ctx); + skip_nl(ctx); + continue; + } + parse_one_field_init(ctx, &type->fields, base_offset, 0); + skip_nl(ctx); + } + } else if (type->kind == TYPE_SLICE) { + while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) { + if (peek(ctx)->type == TOK_COMMA) { + advance(ctx); + skip_nl(ctx); + continue; + } + if (peek(ctx)->type == TOK_DOT) + advance(ctx); + spl_tok_t *ftok = advance(ctx); + char fname[256]; + spl_tok_copy_name(ftok, fname, sizeof(fname)); + skip_nl(ctx); + if (peek(ctx)->type == TOK_ASSIGN) + advance(ctx); + skip_nl(ctx); + if (strcmp(fname, "ptr") == 0) { + spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset); + spl_parse_expr(ctx, PREC_MIN); + spl_emit(ctx, SPL_STORE, SPL_PTR, 0); + } else if (strcmp(fname, "len") == 0) { + spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset); + spl_emit(ctx, SPL_PUSH, SPL_USIZE, sizeof(spl_val_t)); + spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); + spl_parse_expr(ctx, PREC_MIN); + spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); } skip_nl(ctx); } @@ -564,92 +594,9 @@ static spl_expr_result_t parse_struct_literal(spl_comp_t *ctx, spl_type_info_t * skip_nl(ctx); continue; } - if (peek(ctx)->type == TOK_DOT) - advance(ctx); - spl_tok_t *sftok = advance(ctx); - char sfname[256]; - usize sfnl = sftok->len < 255 ? sftok->len : 255; - memcpy(sfname, sftok->lexeme, sfnl); - sfname[sfnl] = '\0'; - skip_nl(ctx); - if (peek(ctx)->type == TOK_ASSIGN) - advance(ctx); - skip_nl(ctx); - - vec_for(v->data_type->fields, sfi) { - spl_field_t *sf = &vec_at(v->data_type->fields, sfi); - if (strcmp(sf->name, sfname) == 0) { - spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset); - usize byte_off = DATA_OFFSET + sf->offset; - if (byte_off > 0) { - spl_emit(ctx, SPL_PUSH, SPL_USIZE, byte_off); - spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); - } - /* Handle inline slice initializer */ - if (sf->type && sf->type->kind == TYPE_SLICE && - peek(ctx)->type == TOK_L_BRACE) { - advance(ctx); /* { */ - skip_nl(ctx); - while (peek(ctx)->type != TOK_R_BRACE && - peek(ctx)->type != TOK_EOF) { - if (peek(ctx)->type == TOK_COMMA) { - advance(ctx); - skip_nl(ctx); - continue; - } - if (peek(ctx)->type == TOK_DOT) - advance(ctx); - spl_tok_t *ssftok = advance(ctx); - char ssfname[256]; - usize ssfnl = ssftok->len < 255 ? ssftok->len : 255; - memcpy(ssfname, ssftok->lexeme, ssfnl); - ssfname[ssfnl] = '\0'; - skip_nl(ctx); - if (peek(ctx)->type == TOK_ASSIGN) - advance(ctx); - skip_nl(ctx); - - spl_emit(ctx, SPL_DUP, SPL_VOID, 0); - if (strcmp(ssfname, "ptr") == 0) { - spl_expr_result_t pv = - spl_parse_expr(ctx, PREC_MIN); - (void)pv; - spl_emit(ctx, SPL_STORE, SPL_PTR, 0); - } else if (strcmp(ssfname, "len") == 0) { - spl_emit(ctx, SPL_PUSH, SPL_USIZE, - sizeof(spl_val_t)); - spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); - spl_expr_result_t lv = - spl_parse_expr(ctx, PREC_MIN); - (void)lv; - spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); - } - skip_nl(ctx); - } - expect(ctx, TOK_R_BRACE); - spl_emit(ctx, SPL_DROP, SPL_VOID, 0); - } else if (sf->type && - (sf->type->kind == TYPE_STRUCT || - sf->type->kind == TYPE_ENUM) && - spl_type_size(sf->type) > sizeof(spl_val_t)) { - /* Multi-slot struct field in enum variant data */ - spl_expr_result_t sfv = spl_parse_expr(ctx, PREC_MIN); - (void)sfv; - usize nslots = - (spl_type_size(sf->type) + sizeof(spl_val_t) - 1) / - sizeof(spl_val_t); - spl_emit_copy_slots( - ctx, base_offset + (int)DATA_OFFSET + sf->offset, - nslots); - spl_emit(ctx, SPL_DROP, SPL_VOID, 0); - } else { - spl_expr_result_t sfv = spl_parse_expr(ctx, PREC_MIN); - (void)sfv; - spl_emit(ctx, SPL_STORE, spl_type_emit_type(sf->type), 0); - } - break; - } - } + if (peek(ctx)->type != TOK_DOT) + break; + parse_one_field_init(ctx, &v->data_type->fields, base_offset, DATA_OFFSET); skip_nl(ctx); } expect(ctx, TOK_R_BRACE); @@ -805,7 +752,7 @@ static spl_expr_result_t parse_ident(spl_comp_t *ctx) { /* Struct/enum literal: Type { .field = val, ... } */ if (peek(ctx)->type == TOK_L_BRACE && (ttype->kind == TYPE_STRUCT || ttype->kind == TYPE_ENUM)) { - return parse_struct_literal(ctx, ttype); + return spl_parse_struct_literal(ctx, ttype); } spl_expr_result_t r = {ttype, 0}; return r; @@ -880,84 +827,71 @@ static spl_expr_result_t parse_prefix_op(spl_comp_t *ctx) { } /* ============================================================ - * Main expression parser (top-level) + * Primary expression (prefix part of Pratt parser) * ============================================================ */ -spl_expr_result_t spl_parse_expr(spl_comp_t *ctx, int min_prec) { - skip_nl(ctx); - +static spl_expr_result_t parse_primary_expr(spl_comp_t *ctx) { spl_tok_t *tok = peek(ctx); if (!tok) { spl_expr_result_t r = {0}; return r; } - spl_expr_result_t left = {0}; - switch (tok->type) { case TOK_INT_LITERAL: - left = parse_int_literal(ctx); - break; + return parse_int_literal(ctx); case TOK_FLOAT_LITERAL: - left = parse_float_literal(ctx); - break; + return parse_float_literal(ctx); case TOK_CHAR_LITERAL: - left = parse_char_literal(ctx); - break; + return parse_char_literal(ctx); case TOK_STRING_LITERAL: - left = parse_string_literal(ctx); - break; + return parse_string_literal(ctx); case KW_TRUE: advance(ctx); spl_emit(ctx, SPL_PUSH, SPL_I32, 1); - left = (spl_expr_result_t){spl_type_basic(SPL_I32), 0}; - break; + return (spl_expr_result_t){spl_type_basic(SPL_I32), 0}; case KW_FALSE: advance(ctx); spl_emit(ctx, SPL_PUSH, SPL_I32, 0); - left = (spl_expr_result_t){spl_type_basic(SPL_I32), 0}; - break; + return (spl_expr_result_t){spl_type_basic(SPL_I32), 0}; case KW_NULL: advance(ctx); spl_emit(ctx, SPL_PUSH, SPL_PTR, 0); - left = (spl_expr_result_t){spl_type_basic(SPL_PTR), 0}; - break; + return (spl_expr_result_t){spl_type_basic(SPL_PTR), 0}; case TOK_IDENT: case KW_BOOL: case KW_VOID: case KW_ANY: - left = parse_ident(ctx); - break; + return parse_ident(ctx); case TOK_L_PAREN: - left = parse_group(ctx); - break; + return parse_group(ctx); case TOK_SUB: case TOK_NOT: case TOK_BIT_NOT: case TOK_AND: case TOK_MUL: - left = parse_prefix_op(ctx); - break; + return parse_prefix_op(ctx); case TOK_L_BRACKET: - left = parse_array_literal(ctx); - break; + return parse_array_literal(ctx); + case TOK_L_BRACE: + return spl_parse_block_expr(ctx); case TOK_AT: { /* @builtin(...) — compiler intrinsic */ - advance(ctx); /* consume @ */ + advance(ctx); skip_nl(ctx); tok = peek(ctx); if (!tok || tok->type != TOK_IDENT) { spl_comp_error(ctx, "expected builtin name after '@'"); - break; + spl_expr_result_t r = {0}; + return r; } char bname[256]; spl_tok_copy_name(tok, bname, sizeof bname); - advance(ctx); /* consume builtin name */ + advance(ctx); if (strcmp(bname, "dbg") == 0) { - /* @dbg(...) — print VM debug info */ if (peek(ctx)->type == TOK_L_PAREN) { - advance(ctx); /* skip ( */ + advance(ctx); int nargs = 0; if (peek(ctx)->type != TOK_R_PAREN) { for (;;) { @@ -971,37 +905,40 @@ spl_expr_result_t spl_parse_expr(spl_comp_t *ctx, int min_prec) { } } expect(ctx, TOK_R_PAREN); - - /* Emit SPL_DBG and DROP for each arg */ for (int i = 0; i < nargs; i++) { spl_emit(ctx, SPL_DBG, SPL_USIZE, 0); spl_emit(ctx, SPL_DROP, SPL_VOID, 0); } - if (nargs == 0) { + if (nargs == 0) spl_emit(ctx, SPL_DBG, SPL_VOID, 0); - } } else { - /* @dbg with no parens — just debug */ spl_emit(ctx, SPL_DBG, SPL_VOID, 0); } - /* @dbg is a void expression */ - left = (spl_expr_result_t){spl_type_basic(SPL_VOID), 0}; + return (spl_expr_result_t){spl_type_basic(SPL_VOID), 0}; } else { spl_comp_error(ctx, "unknown builtin '@%s'", bname); - } - break; - } - default: - /* If it's a keyword-as-type (i32, u8, etc.), parse as function call target or type - * constructor */ - if (peek(ctx)->type >= KW_AS && peek(ctx)->type <= KW_ANY) { - left = parse_ident(ctx); - } else { spl_expr_result_t r = {0}; return r; } - break; } + default: + if (peek(ctx)->type >= KW_AS && peek(ctx)->type <= KW_ANY) { + return parse_ident(ctx); + } + spl_expr_result_t r = {0}; + return r; + } +} + +/* ============================================================ + * Main expression parser (top-level) + * ============================================================ */ + +spl_expr_result_t spl_parse_expr(spl_comp_t *ctx, int min_prec) { + skip_nl(ctx); + spl_expr_result_t left = parse_primary_expr(ctx); + if (!left.type) + return left; /* Infix parsing (precedence climbing) */ while (1) { @@ -1220,8 +1157,10 @@ spl_expr_result_t spl_parse_expr(spl_comp_t *ctx, int min_prec) { continue; } + /* Parse index/begin expression before checking for range */ + spl_parse_expr(ctx, PREC_MIN); + /* Check for slice: expr[begin..end] or expr[begin..] */ - spl_expr_result_t index = spl_parse_expr(ctx, PREC_MIN); if (peek(ctx)->type == TOK_RANGE) { advance(ctx); /* skip .. */ spl_expr_result_t end_expr = {0}; @@ -1394,3 +1333,105 @@ static spl_expr_result_t parse_infix(spl_comp_t *ctx, spl_expr_result_t left, sp } return (spl_expr_result_t){spl_type_basic(SPL_I32), 0}; } + +/* ============================================================ + * Match arm pattern comparison + * + * These functions handle the expression-level comparison between + * a saved match value and an arm pattern. They belong here in + * the expr layer so match arms compose as "enhanced if-conditions" + * rather than requiring stmt.c to emit raw comparison instructions. + * ============================================================ */ + +spl_enum_variant_t *spl_emit_match_enum_cmp(spl_comp_t *ctx, spl_type_info_t *enum_type, + int val_offset) { + if (peek(ctx)->type == TOK_DOT) + advance(ctx); + 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'; + + vec_for(enum_type->variants, vi) { + spl_enum_variant_t *v = &vec_at(enum_type->variants, vi); + if (strcmp(v->name, vname) == 0) { + spl_emit(ctx, SPL_LADDR, SPL_PTR, val_offset); + spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); + spl_emit(ctx, SPL_PUSH, SPL_USIZE, 0); + spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); + spl_emit(ctx, SPL_LOAD, SPL_I32, 0); + spl_emit(ctx, SPL_PUSH, SPL_I32, v->value); + spl_emit(ctx, SPL_EQ, SPL_I32, 0); + return v; + } + } + spl_comp_error(ctx, "unknown variant '%s' in match", vname); + return NULL; +} + +void spl_emit_match_value_cmp(spl_comp_t *ctx, int val_offset) { + spl_emit(ctx, SPL_LADDR, SPL_PTR, val_offset); + spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); + spl_parse_expr(ctx, PREC_LOGOR); + spl_emit(ctx, SPL_EQ, SPL_I32, 0); +} + +/* ============================================================ + * Return instruction — emit SPL_RET with correct type + * ============================================================ */ + +void spl_emit_ret(spl_comp_t *ctx, spl_type_info_t *ret_type) { + spl_type_t rt = SPL_VOID; + if (ret_type) { + if (ret_type->kind == TYPE_BASIC) { + rt = ret_type->basic_type; + } else if (ret_type->kind == TYPE_PTR) { + rt = SPL_PTR; + } else if (spl_type_size(ret_type) <= sizeof(spl_val_t)) { + rt = SPL_PTR; + } + } + spl_emit(ctx, SPL_RET, rt, 0); +} + +/* ============================================================ + * Variable init store — store value from stack to variable + * ============================================================ */ + +void spl_emit_store_init(spl_comp_t *ctx, int var_offset, spl_type_info_t *var_type) { + if (var_type && (var_type->kind == TYPE_STRUCT || var_type->kind == TYPE_ENUM)) { + usize sz = spl_type_size(var_type); + if (sz <= sizeof(spl_val_t)) { + spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset); + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); + spl_emit(ctx, SPL_STORE, SPL_PTR, 0); + } else { + usize nslots = (sz + sizeof(spl_val_t) - 1) / sizeof(spl_val_t); + spl_emit_copy_slots(ctx, var_offset, nslots); + } + } else if (var_type && var_type->kind == TYPE_ARRAY) { + spl_type_info_t *elem = var_type->elem; + spl_type_t bt = (elem && elem->kind == TYPE_BASIC) ? elem->basic_type : SPL_I32; + usize stride = spl_type_elem_stride(elem); + for (int i = (int)var_type->array_len - 1; i >= 0; i--) { + spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset + (int)(i * stride)); + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); + spl_emit(ctx, SPL_STORE, bt, 0); + } + } else if (var_type && var_type->kind == TYPE_SLICE) { + spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset + (int)sizeof(spl_val_t)); + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); + spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); + spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset); + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); + spl_emit(ctx, SPL_STORE, SPL_PTR, 0); + } else { + spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset); + 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); + } +} diff --git a/stage1/spl_stmt.c b/stage1/spl_stmt.c index e9142d1..022baa2 100644 --- a/stage1/spl_stmt.c +++ b/stage1/spl_stmt.c @@ -25,18 +25,7 @@ static void parse_ret_stmt(spl_comp_t *ctx) { } else { spl_expr_result_t val = spl_parse_expr(ctx, PREC_MIN); (void)val; - spl_type_t rt = SPL_VOID; - if (ctx->current_ret_type) { - if (ctx->current_ret_type->kind == TYPE_BASIC) { - rt = ctx->current_ret_type->basic_type; - } else if (ctx->current_ret_type->kind == TYPE_PTR) { - rt = SPL_PTR; - } else if (spl_type_size(ctx->current_ret_type) <= sizeof(spl_val_t)) { - /* 1-slot struct/enum/etc: use PTR type */ - rt = SPL_PTR; - } - } - spl_emit(ctx, SPL_RET, rt, 0); + spl_emit_ret(ctx, ctx->current_ret_type); } if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); @@ -84,9 +73,18 @@ static void parse_var_decl(spl_comp_t *ctx, int is_const) { /* Init expression: parse before declare to enable type inference */ spl_expr_result_t init = {0}; + int inline_lit = 0; if (has_init) { skip_nl(ctx); - init = spl_parse_expr(ctx, PREC_MIN); + /* Inline struct/enum/slice literal: var x: Type = { .field = val } + * Don't call spl_parse_expr — { would be consumed as block expression */ + if (var_type && peek(ctx)->type == TOK_L_BRACE && + (var_type->kind == TYPE_STRUCT || var_type->kind == TYPE_ENUM || + var_type->kind == TYPE_SLICE)) { + inline_lit = 1; + } else { + init = spl_parse_expr(ctx, PREC_MIN); + } } if (!var_type) { @@ -96,84 +94,19 @@ static void parse_var_decl(spl_comp_t *ctx, int is_const) { /* Store init value */ if (has_init) { - /* Handle slice struct literal: { .ptr = ..., .len = ... } */ - if (var_type && var_type->kind == TYPE_SLICE && peek(ctx)->type == TOK_L_BRACE && - !init.type) { - advance(ctx); /* { */ - skip_nl(ctx); - while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) { - if (peek(ctx)->type == TOK_COMMA) { - advance(ctx); - skip_nl(ctx); - continue; - } - if (peek(ctx)->type == TOK_DOT) - advance(ctx); - spl_tok_t *ftok = advance(ctx); - char fname[256]; - usize fnl = ftok->len < 255 ? ftok->len : 255; - memcpy(fname, ftok->lexeme, fnl); - fname[fnl] = '\0'; - skip_nl(ctx); - if (peek(ctx)->type == TOK_ASSIGN) - advance(ctx); - skip_nl(ctx); - - spl_expr_result_t fv = spl_parse_expr(ctx, PREC_MIN); - (void)fv; - - if (strcmp(fname, "ptr") == 0) { - spl_emit(ctx, SPL_LADDR, SPL_PTR, offset); - spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); - spl_emit(ctx, SPL_STORE, SPL_PTR, 0); - } else if (strcmp(fname, "len") == 0) { - spl_emit(ctx, SPL_LADDR, SPL_PTR, offset + (int)sizeof(spl_val_t)); - spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); - spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); - } - skip_nl(ctx); - } - expect(ctx, TOK_R_BRACE); - } else if (var_type && (var_type->kind == TYPE_STRUCT || var_type->kind == TYPE_ENUM)) { - /* Struct/enum initialization */ - usize sz = spl_type_size(var_type); - if (sz <= sizeof(spl_val_t)) { - /* Fits in one slot: value on stack, store directly */ - spl_emit(ctx, SPL_LADDR, SPL_PTR, offset); - spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); - spl_emit(ctx, SPL_STORE, SPL_PTR, 0); + if (inline_lit && (var_type->kind == TYPE_STRUCT || var_type->kind == TYPE_ENUM || + var_type->kind == TYPE_SLICE)) { + /* Inline struct/enum/slice literal: var x: Type = { .field = val } + * Use spl_parse_struct_literal to handle field parsing uniformly. */ + spl_parse_struct_literal(ctx, var_type); + if (var_type->kind == TYPE_SLICE) { + /* Slice: copy 2 temp slots to variable */ + spl_emit_copy_slots(ctx, offset, 2); } else { - /* Multi-slot: copy from temp addr to var, slot by slot */ - usize nslots = (sz + sizeof(spl_val_t) - 1) / sizeof(spl_val_t); - spl_emit_copy_slots(ctx, offset, nslots); + spl_emit_store_init(ctx, offset, var_type); } - } else 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; - usize stride = spl_type_elem_stride(elem); - for (int i = (int)var_type->array_len - 1; i >= 0; i--) { - spl_emit(ctx, SPL_LADDR, SPL_PTR, offset + (int)(i * stride)); - 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 offset+8, then ptr at offset. */ - spl_emit(ctx, SPL_LADDR, SPL_PTR, offset + (int)sizeof(spl_val_t)); - spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); - spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); - spl_emit(ctx, SPL_LADDR, SPL_PTR, offset); - 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, offset); - 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); + spl_emit_store_init(ctx, offset, var_type); } } @@ -206,6 +139,88 @@ void spl_parse_block(spl_comp_t *ctx) { } } +/* Forward declaration for block expr */ +static int is_assign_op(spl_tok_type_t t); + +/* ============================================================ + * Block expression: { stmts; [trailing_expr] } + * Parses a block and returns the trailing expression type. + * Like Rust/Zig: the last expression without semicolon is the block's value. + * ============================================================ */ + +spl_expr_result_t spl_parse_block_expr(spl_comp_t *ctx) { + advance(ctx); /* { */ + spl_push_scope(ctx); + + spl_expr_result_t result = {0}; /* void by default */ + + skip_nl(ctx); + while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) { + if (peek(ctx)->type == TOK_SEMICOLON || peek(ctx)->type == TOK_ENDLINE) { + advance(ctx); + skip_nl(ctx); + continue; + } + + spl_tok_type_t t = peek(ctx)->type; + int is_keyword = (t == KW_RET || t == KW_VAR || t == KW_CONST || t == KW_IF || + t == KW_WHILE || t == KW_LOOP || t == KW_FOR || t == KW_BREAK || + t == KW_CONTINUE || t == KW_DEFER || t == KW_MATCH || t == KW_TYPE || + t == TOK_L_BRACE || t == TOK_SHARP || t == TOK_LINE_COMMENT); + + if (is_keyword) { + /* Keyword statement — produces void */ + spl_parse_stmt(ctx); + result = (spl_expr_result_t){0}; + } else { + /* Expression — look ahead for assignment */ + int is_assign = 0; + { + usize look = ctx->tok_idx; + while (look < vec_size(ctx->toks)) { + spl_tok_type_t lt = vec_at(ctx->toks, look).type; + if (lt == TOK_SEMICOLON || lt == TOK_ENDLINE || lt == TOK_R_BRACE || + lt == TOK_EOF) + break; + if (lt == TOK_L_PAREN) + break; + if (is_assign_op(lt)) { + is_assign = 1; + break; + } + look++; + } + } + + int saved_addr = ctx->addr_of_mode; + if (is_assign) + ctx->addr_of_mode = 1; + spl_expr_result_t expr = spl_parse_expr(ctx, PREC_MIN); + ctx->addr_of_mode = saved_addr; + + skip_nl(ctx); + if (peek(ctx)->type == TOK_SEMICOLON || peek(ctx)->type == TOK_ENDLINE) { + /* Expression statement: drop value, consume ; */ + if (!is_assign && expr.type && expr.type->kind == TYPE_BASIC && + expr.type->basic_type != SPL_VOID) + spl_emit(ctx, SPL_DROP, SPL_VOID, 0); + while (peek(ctx)->type == TOK_SEMICOLON || peek(ctx)->type == TOK_ENDLINE) + advance(ctx); + result = (spl_expr_result_t){0}; + } else { + /* Trailing expression — this is the block's value */ + result = expr; + } + } + skip_nl(ctx); + } + + spl_emit_defer_epilogue(ctx, ctx->scope_depth); + spl_pop_scope(ctx); + expect(ctx, TOK_R_BRACE); + return result; +} + /* ============================================================ * If statement: if expr { ... } [else { ... }] * ============================================================ */ @@ -234,41 +249,53 @@ static void parse_if_stmt(spl_comp_t *ctx) { } } +/* ============================================================ + * Loop context helpers (save/restore loop state for while/loop/for) + * ============================================================ */ + +typedef struct { + int saved_loop; + usize saved_continue; + usize saved_bp_count; + spl_val_t loop_start; +} spl_loop_save_t; + +static void loop_enter(spl_comp_t *ctx, spl_loop_save_t *save) { + save->loop_start = vec_size(ctx->prog.insns); + save->saved_loop = ctx->in_loop; + save->saved_continue = ctx->continue_target; + save->saved_bp_count = ctx->break_patch_count; + ctx->in_loop = 1; + ctx->continue_target = save->loop_start; +} + +static void loop_exit(spl_comp_t *ctx, spl_loop_save_t *save) { + spl_val_t here = vec_size(ctx->prog.insns); + spl_emit(ctx, SPL_JMP, SPL_VOID, (spl_val_t)((isize)save->loop_start - (isize)here - 1)); + for (usize i = save->saved_bp_count; i < ctx->break_patch_count; i++) + spl_patch_to_here(ctx, ctx->break_patches[i]); + ctx->break_patch_count = save->saved_bp_count; + ctx->in_loop = save->saved_loop; + ctx->continue_target = save->saved_continue; +} + /* ============================================================ * 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_loop_save_t save; + loop_enter(ctx, &save); 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)); + loop_exit(ctx, &save); 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; } /* ============================================================ @@ -276,30 +303,12 @@ static void parse_while_stmt(spl_comp_t *ctx) { * ============================================================ */ 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; - + spl_loop_save_t save; + loop_enter(ctx, &save); 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; + loop_exit(ctx, &save); } /* ============================================================ @@ -344,21 +353,15 @@ static void parse_for_stmt(spl_comp_t *ctx) { spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); /* Stack: [end] */ - spl_val_t loop_start = vec_size(ctx->prog.insns); - /* Condition: i < end */ + spl_loop_save_t save; + loop_enter(ctx, &save); spl_emit(ctx, SPL_LADDR, SPL_PTR, ioffset); 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 */ @@ -370,21 +373,12 @@ static void parse_for_stmt(spl_comp_t *ctx) { 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)); + loop_exit(ctx, &save); /* 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_emit_defer_epilogue(ctx, ctx->scope_depth); spl_pop_scope(ctx); return; @@ -458,7 +452,8 @@ static void parse_for_stmt(spl_comp_t *ctx) { spl_emit(ctx, SPL_PUSH, SPL_USIZE, 0); /* Stack: [ptr, len, idx=0] */ - spl_val_t loop_start = vec_size(ctx->prog.insns); + spl_loop_save_t save; + loop_enter(ctx, &save); /* Condition: idx < len */ spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* copy len */ @@ -487,13 +482,6 @@ static void parse_for_stmt(spl_comp_t *ctx) { 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 */ @@ -504,9 +492,7 @@ static void parse_for_stmt(spl_comp_t *ctx) { 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)); + loop_exit(ctx, &save); /* Exit: patch bz, drop idx, len, ptr */ spl_patch_to_here(ctx, bz_addr); @@ -514,13 +500,6 @@ static void parse_for_stmt(spl_comp_t *ctx) { 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); } @@ -582,10 +561,92 @@ static void parse_defer_stmt(spl_comp_t *ctx) { } } +/* ============================================================ + * Match statement helpers + * ============================================================ */ + +/* Parse an enum variant pattern in a match arm: .VariantName + * Delegates comparison to expr layer (spl_emit_match_enum_cmp). + * Returns the variant for binding parsing, or NULL on error. */ +static spl_enum_variant_t *parse_match_enum_variant(spl_comp_t *ctx, spl_type_info_t *enum_type, + int val_offset) { + return spl_emit_match_enum_cmp(ctx, enum_type, val_offset); +} + +/* Parse a value pattern in a match arm (non-enum). + * Delegates comparison to expr layer (spl_emit_match_value_cmp). + * Uses PREC_LOGOR internally to prevent => from being consumed as assignment. */ +static void parse_match_value_pattern(spl_comp_t *ctx, int val_offset) { + spl_emit_match_value_cmp(ctx, val_offset); +} + +/* Parse enum variant data bindings: (name1, name2, ...) + * Declares local variables and loads corresponding field data + * from the matched value's data area (offset 4+). + * Sets *scope_pushed = 1 if bindings declared. */ +static void parse_match_enum_bindings(spl_comp_t *ctx, spl_enum_variant_t *variant, int val_offset, + int *scope_pushed) { + advance(ctx); /* ( */ + skip_nl(ctx); + if (peek(ctx)->type == TOK_R_PAREN) { + expect(ctx, TOK_R_PAREN); + return; + } + + spl_push_scope(ctx); + *scope_pushed = 1; + + if (variant->data_type && variant->data_type->kind == TYPE_STRUCT) { + /* Multi-field struct destructuring: one binding per struct field */ + int bi = 0; + for (;;) { + spl_tok_t *btok = advance(ctx); + char bname[256]; + usize bnl = btok->len < 255 ? btok->len : 255; + memcpy(bname, btok->lexeme, bnl); + bname[bnl] = '\0'; + + spl_type_info_t *btype = spl_type_basic(SPL_I32); + usize field_byte_off = 4; /* skip tag */ + if ((usize)bi < vec_size(variant->data_type->fields)) { + btype = vec_at(variant->data_type->fields, bi).type; + field_byte_off = 4 + vec_at(variant->data_type->fields, bi).offset; + } + int boffset = spl_declare_var(ctx, bname, btype, 0); + spl_emit_load_to_var(ctx, val_offset, field_byte_off, btype, boffset); + + bi++; + skip_nl(ctx); + if (peek(ctx)->type == TOK_COMMA) { + advance(ctx); + skip_nl(ctx); + continue; + } + break; + } + } else if (variant->data_type) { + /* Single-value binding */ + spl_tok_t *btok = advance(ctx); + char bname[256]; + usize bnl = btok->len < 255 ? btok->len : 255; + memcpy(bname, btok->lexeme, bnl); + bname[bnl] = '\0'; + + spl_type_info_t *btype = variant->data_type; + int boffset = spl_declare_var(ctx, bname, btype, 0); + spl_emit_load_to_var(ctx, val_offset, 4, btype, boffset); + } + + expect(ctx, TOK_R_PAREN); +} + /* ============================================================ * Match statement: * Enum: match expr { .Variant(bindings) => stmt, ... } - * Int: match expr { literal => stmt, ..., _ => stmt } + * Value: match expr { lit, lit => stmt, ..., _ => stmt } + * + * Built as enhanced if-else: each arm is a condition chain. + * Fallthrough (comma-separated patterns) uses BNZ for OR. * ============================================================ */ static void parse_match_stmt(spl_comp_t *ctx) { @@ -594,11 +655,9 @@ static void parse_match_stmt(spl_comp_t *ctx) { spl_expr_result_t expr = spl_parse_expr(ctx, PREC_MIN); - /* Determine match type (enum or integer) */ + /* Determine match type */ int is_enum_match = 0; - int is_int_match = 0; spl_type_info_t *enum_type = NULL; - spl_type_info_t *t = expr.type; if (t && t->kind == TYPE_ENUM) { is_enum_match = 1; @@ -606,16 +665,12 @@ static void parse_match_stmt(spl_comp_t *ctx) { } else if (t && t->kind == TYPE_PTR && t->elem && t->elem->kind == TYPE_ENUM) { is_enum_match = 1; enum_type = t->elem; - } else if (t && t->kind == TYPE_BASIC && spl_type_is_integer(t->basic_type)) { - is_int_match = 1; - } - - if (!is_enum_match && !is_int_match) { + } else if (!(t && t->kind == TYPE_BASIC && spl_type_is_integer(t->basic_type))) { spl_comp_error(ctx, "match expression must be an enum or integer type"); return; } - /* Save the value/address to a temp slot */ + /* Save match value to temp slot */ int val_offset = ctx->current_local_bytes; ctx->current_local_bytes += (int)sizeof(spl_val_t); if (ctx->current_local_bytes > ctx->peak_local_bytes) @@ -650,57 +705,23 @@ static void parse_match_stmt(spl_comp_t *ctx) { int n_bnz = 0; spl_enum_variant_t *arm_variant = NULL; - /* --- Parse arm pattern(s): comma-separated values with fallthrough --- */ + /* --- Parse arm pattern(s): comma-separated with fallthrough --- */ if (peek(ctx)->type == KW_ANY) { - /* _ default arm: always matches, no comparison */ + /* _ default arm: always matches, skip comparison */ advance(ctx); } else { for (;;) { if (is_enum_match) { - /* .VariantName */ - if (peek(ctx)->type == TOK_DOT) - advance(ctx); - 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'; - - spl_enum_variant_t *variant = NULL; - vec_for(enum_type->variants, vi) { - if (strcmp(vec_at(enum_type->variants, vi).name, vname) == 0) { - variant = &vec_at(enum_type->variants, vi); - break; - } - } - if (!variant) { - spl_comp_error(ctx, "unknown variant '%s' in match", vname); + arm_variant = parse_match_enum_variant(ctx, enum_type, val_offset); + if (ctx->has_error) break; - } - arm_variant = variant; - - /* Compare tag: load [val_offset] → addr → +0 → tag */ - spl_emit(ctx, SPL_LADDR, SPL_PTR, val_offset); - spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); - spl_emit(ctx, SPL_PUSH, SPL_USIZE, 0); - spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); - spl_emit(ctx, SPL_LOAD, SPL_I32, 0); - spl_emit(ctx, SPL_PUSH, SPL_I32, variant->value); - spl_emit(ctx, SPL_EQ, SPL_I32, 0); - - /* If bindings follow, this must be the last pattern */ skip_nl(ctx); if (peek(ctx)->type == TOK_L_PAREN) { has_parens = 1; - break; + break; /* bindings → must be last in fallthrough group */ } - } else if (is_int_match) { - /* Parse expression as arm value */ - spl_parse_expr(ctx, PREC_MIN); - /* Compare: load saved match value, then EQ */ - spl_emit(ctx, SPL_LADDR, SPL_PTR, val_offset); - spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); - spl_emit(ctx, SPL_EQ, SPL_I32, 0); + } else { + parse_match_value_pattern(ctx, val_offset); } /* Check for more comma-separated patterns */ @@ -708,9 +729,8 @@ static void parse_match_stmt(spl_comp_t *ctx) { if (peek(ctx)->type == TOK_COMMA) { advance(ctx); skip_nl(ctx); - /* If another pattern follows (not ⇒), emit BNZ for fallthrough */ if (peek(ctx)->type != TOK_ASSIGN) { - bnz_addrs[n_bnz++] = spl_emit_bnz(ctx); + bnz_addrs[n_bnz++] = spl_emit_bnz(ctx); /* fallthrough to body */ continue; } break; @@ -718,7 +738,7 @@ static void parse_match_stmt(spl_comp_t *ctx) { break; } - /* Last pattern: BZ to skip arm if no value matched */ + /* Last pattern: BZ past body if no match */ if (!ctx->has_error) { bz_addr = spl_emit_bz(ctx); body_start = vec_size(ctx->prog.insns); @@ -727,53 +747,7 @@ static void parse_match_stmt(spl_comp_t *ctx) { /* --- Parse enum bindings (only for last variant) --- */ if (is_enum_match && has_parens && arm_variant) { - advance(ctx); /* ( */ - skip_nl(ctx); - if (peek(ctx)->type != TOK_R_PAREN) { - spl_push_scope(ctx); - scope_pushed = 1; - - if (arm_variant->data_type && arm_variant->data_type->kind == TYPE_STRUCT) { - int bi = 0; - for (;;) { - spl_tok_t *btok = advance(ctx); - char bname[256]; - usize bnl = btok->len < 255 ? btok->len : 255; - memcpy(bname, btok->lexeme, bnl); - bname[bnl] = '\0'; - - spl_type_info_t *btype = spl_type_basic(SPL_I32); - usize field_byte_off = 4; - if ((usize)bi < vec_size(arm_variant->data_type->fields)) { - btype = vec_at(arm_variant->data_type->fields, bi).type; - field_byte_off = 4 + vec_at(arm_variant->data_type->fields, bi).offset; - } - - int boffset = spl_declare_var(ctx, bname, btype, 0); - spl_emit_load_to_var(ctx, val_offset, field_byte_off, btype, boffset); - - bi++; - skip_nl(ctx); - if (peek(ctx)->type == TOK_COMMA) { - advance(ctx); - skip_nl(ctx); - continue; - } - break; - } - } else if (arm_variant->data_type) { - spl_tok_t *btok = advance(ctx); - char bname[256]; - usize bnl = btok->len < 255 ? btok->len : 255; - memcpy(bname, btok->lexeme, bnl); - bname[bnl] = '\0'; - - spl_type_info_t *btype = arm_variant->data_type; - int boffset = spl_declare_var(ctx, bname, btype, 0); - spl_emit_load_to_var(ctx, val_offset, 4, btype, boffset); - } - } - expect(ctx, TOK_R_PAREN); + parse_match_enum_bindings(ctx, arm_variant, val_offset, &scope_pushed); } skip_nl(ctx); @@ -786,10 +760,10 @@ static void parse_match_stmt(spl_comp_t *ctx) { } skip_nl(ctx); - /* Parse arm body statement */ + /* Parse arm body (reuses stmt infrastructure) */ spl_parse_stmt(ctx); - /* Pop scope if we pushed one */ + /* Pop scope if bindings were declared */ if (scope_pushed) { spl_emit_defer_epilogue(ctx, ctx->scope_depth); spl_pop_scope(ctx); @@ -803,7 +777,7 @@ static void parse_match_stmt(spl_comp_t *ctx) { if (bz_addr) spl_patch_to_here(ctx, bz_addr); - /* Patch BNZs to body start (fallthrough values matched) */ + /* Patch BNZ fallthroughs to body start */ for (int i = 0; i < n_bnz; i++) { spl_val_t offset = body_start - bnz_addrs[i] - 1; spl_patch(ctx, bnz_addrs[i], offset);