/* spl_expr.c — Expression parser + codegen (Pratt parser / precedence climbing) */ #include "spl_comp.h" #include "spl_lex_util.h" #include #include #include /* ============================================================ * Precedence table * ============================================================ */ static int tok_prec(spl_tok_type_t t) { switch (t) { case TOK_OR_OR: return PREC_LOGOR; case TOK_AND_AND: return PREC_LOGAND; case TOK_OR: return PREC_OR; case TOK_XOR: return PREC_XOR; case TOK_AND: return PREC_AND; case TOK_EQ: case TOK_NEQ: return PREC_CMPEQ; case TOK_LT: case TOK_LE: case TOK_GT: case TOK_GE: return PREC_CMP; case TOK_L_SH: case TOK_R_SH: return PREC_SHIFT; case TOK_ADD: case TOK_SUB: return PREC_ADD; case TOK_MUL: case TOK_DIV: case TOK_MOD: return PREC_MUL; case TOK_ASSIGN: case TOK_ASSIGN_ADD: case TOK_ASSIGN_SUB: case TOK_ASSIGN_MUL: case TOK_ASSIGN_DIV: case TOK_ASSIGN_MOD: case TOK_ASSIGN_AND: case TOK_ASSIGN_OR: case TOK_ASSIGN_XOR: case TOK_ASSIGN_L_SH: case TOK_ASSIGN_R_SH: return PREC_ASSIGN; default: return PREC_MIN; } } /* ============================================================ * Forward declarations * ============================================================ */ static spl_expr_result_t parse_infix(spl_comp_t *ctx, spl_expr_result_t left, spl_tok_type_t op); static void emit_load_or_addr_type(spl_comp_t *ctx, spl_expr_result_t *result, spl_type_info_t *type); static spl_expr_result_t parse_postfix_expr(spl_comp_t *ctx, spl_expr_result_t left); /* Slice creation from array/slice range expression. * Stack in: [base_addr, begin, end] * Stack out: [ptr, len] * * Uses pure stack operations — no temp slots needed. * ptr = base + begin * stride * len = end - begin * * Strategy: PICK copies of values we need, compute results on stack, * then ROT inaccessible values to TOS and DROP them. */ static void emit_slice_create(spl_comp_t *ctx, usize stride) { /* Stack: [base, begin, end] */ /* --- Compute ptr = base + begin * stride --- */ spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [base, begin, end, base] */ spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [base, begin, end, base, begin] */ spl_emit(ctx, SPL_PUSH, SPL_U64, stride); spl_emit(ctx, SPL_MUL, SPL_U64, 0); /* [base, begin, end, base, begin*stride] */ spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [base, begin, end, ptr] */ /* --- Compute len = end - begin --- */ spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* [base, begin, end, ptr, end] */ spl_emit(ctx, SPL_PICK, SPL_VOID, 3); /* [base, begin, end, ptr, end, begin] */ spl_emit(ctx, SPL_SUB, SPL_USIZE, 0); /* [base, begin, end, ptr, len] */ /* --- Cleanup: drop [base, begin, end], keep [ptr, len] --- */ spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [base, begin, ptr, len, end] */ spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [base, begin, ptr, len] */ spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [base, ptr, len, begin] */ spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [base, ptr, len] */ spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [ptr, len, base] */ spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [ptr, len] */ } /* Emit code to access slice element by index. * For TYPE_SLICE, the stack has [slice_struct_addr, index]. * We need to load the data ptr from the struct before indexing. * Stack in: [slice_struct_addr, index] * Stack out: [element_addr] */ static void emit_slice_index(spl_comp_t *ctx, spl_type_info_t *elem) { usize elem_size = spl_type_elem_stride(elem); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [index, slice_struct_addr] */ spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); /* [index, data_ptr] */ spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [data_ptr, index] */ spl_emit(ctx, SPL_PUSH, SPL_U64, elem_size); spl_emit(ctx, SPL_MUL, SPL_U64, 0); spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [data_ptr + index * elem_size] */ } /* Try to resolve Type.Member for compile-time type access: * - Enum variants → push variant integer value * - Nested types (struct/enum/union) → return nested type * Uses qualified name (TypeName.Member) first for namespace isolation, * then falls back to simple name lookup. * Returns 1 if resolved. */ static int spl_resolve_type_member(spl_comp_t *ctx, spl_type_info_t *type, const char *field, spl_expr_result_t *result) { /* For enum: check variants first */ if (type->kind == TYPE_ENUM) { vec_for(type->variants, i) { if (strcmp(vec_at(type->variants, i).name, field) == 0) { spl_emit(ctx, SPL_PUSH, SPL_I32, vec_at(type->variants, i).value); *result = (spl_expr_result_t){spl_type_basic(SPL_I32), 0}; return 1; } } } /* Check nested types — try qualified name (Type.field) first for namespace isolation */ if (type->name) { char qualified[512]; int qlen = snprintf(qualified, sizeof(qualified), "%s.%s", type->name, field); if (qlen > 0 && (usize)qlen < sizeof(qualified)) { spl_type_info_t *nested = spl_resolve_type(ctx, qualified); if (nested) { *result = (spl_expr_result_t){nested, 1}; return 1; } } } /* Fallback: try simple name lookup */ spl_type_info_t *nested = spl_resolve_type(ctx, field); if (nested) { *result = (spl_expr_result_t){nested, 1}; return 1; } return 0; } /* ============================================================ * SIR opcode for binary operator * ============================================================ */ /* Map assignment operator token (e.g. +=) to corresponding binary operator (e.g. +) */ static spl_tok_type_t assign_to_binop(spl_tok_type_t t) { switch (t) { case TOK_ASSIGN_ADD: return TOK_ADD; case TOK_ASSIGN_SUB: return TOK_SUB; case TOK_ASSIGN_MUL: return TOK_MUL; case TOK_ASSIGN_DIV: return TOK_DIV; case TOK_ASSIGN_MOD: return TOK_MOD; case TOK_ASSIGN_AND: return TOK_AND; case TOK_ASSIGN_OR: return TOK_OR; case TOK_ASSIGN_XOR: return TOK_XOR; case TOK_ASSIGN_L_SH: return TOK_L_SH; case TOK_ASSIGN_R_SH: return TOK_R_SH; default: return (spl_tok_type_t)-1; } } static int binop_to_sir(spl_tok_type_t t, spl_type_t bt) { int is_signed = (bt == SPL_I32 || bt == SPL_I64 || bt == SPL_I8 || bt == SPL_I16); switch (t) { case TOK_ADD: return SPL_ADD; case TOK_SUB: return SPL_SUB; case TOK_MUL: return SPL_MUL; case TOK_DIV: return is_signed ? SPL_DIV_S : SPL_DIV_U; case TOK_MOD: return is_signed ? SPL_REM_S : SPL_REM_U; case TOK_AND: return SPL_AND; case TOK_OR: return SPL_OR; case TOK_XOR: return SPL_XOR; case TOK_L_SH: return SPL_SHL; case TOK_R_SH: return is_signed ? SPL_SHR_S : SPL_SHR_U; case TOK_EQ: return SPL_EQ; case TOK_NEQ: return SPL_NE; case TOK_LT: return is_signed ? SPL_SLT : SPL_ULT; case TOK_LE: return is_signed ? SPL_SLE : SPL_ULE; case TOK_GT: return is_signed ? SPL_SGT : SPL_UGT; case TOK_GE: return is_signed ? SPL_SGE : SPL_UGE; default: return -1; } } /* ============================================================ * Parse integer literal from lexeme * ============================================================ */ static int64_t parse_int(const char *s, usize len) { char buf[64]; usize clen = len < 63 ? len : 63; memcpy(buf, s, clen); buf[clen] = '\0'; if (clen > 2 && buf[0] == '0') { if (buf[1] == 'x' || buf[1] == 'X') return (int64_t)strtoll(buf, NULL, 16); if (buf[1] == 'b' || buf[1] == 'B') return (int64_t)strtoll(buf + 2, NULL, 2); if (buf[1] == 'o' || buf[1] == 'O') return (int64_t)strtoll(buf + 2, NULL, 8); } return (int64_t)strtoll(buf, NULL, 10); } /* ============================================================ * Prefix expression parsers * ============================================================ */ static spl_expr_result_t parse_int_literal(spl_comp_t *ctx) { spl_tok_t *t = advance(ctx); int64_t val = parse_int(t->lexeme, t->len); spl_emit(ctx, SPL_PUSH, SPL_I32, (spl_val_t)val); spl_expr_result_t r = {spl_type_basic(SPL_I32), 0}; return r; } static spl_expr_result_t parse_float_literal(spl_comp_t *ctx) { spl_tok_t *t = advance(ctx); char buf[64]; usize clen = t->len < 63 ? t->len : 63; memcpy(buf, t->lexeme, clen); buf[clen] = '\0'; double val = strtod(buf, NULL); spl_emit(ctx, SPL_PUSH, SPL_F64, (spl_val_t)(int64_t)val); (void)val; spl_expr_result_t r = {spl_type_basic(SPL_F64), 0}; return r; } static spl_expr_result_t parse_char_literal(spl_comp_t *ctx) { spl_tok_t *t = advance(ctx); const char *s = t->lexeme; usize l = t->len; int64_t val = 0; if (l >= 3) { if (s[1] == '\\' && l >= 4) { char buf = 0; const char *cp = s + 1; spl_decode_escape(&cp, &buf); val = (unsigned char)buf; } else { val = (unsigned char)s[1]; } } spl_emit(ctx, SPL_PUSH, SPL_I32, (spl_val_t)val); spl_expr_result_t r = {spl_type_basic(SPL_I32), 0}; return r; } static spl_expr_result_t parse_string_literal(spl_comp_t *ctx) { spl_tok_t *t = advance(ctx); usize slen = t->len; if (slen >= 2) { slen -= 2; } char *decoded = malloc(slen + 1); usize di = 0; for (usize i = 1; i + 1 < t->len; i++) { if (t->lexeme[i] == '\\' && i + 1 < t->len - 1) { char buf = 0; const char *cp = t->lexeme + i; spl_decode_escape(&cp, &buf); decoded[di++] = buf; i += (usize)(cp - (t->lexeme + i)) - 1; } else { decoded[di++] = t->lexeme[i]; } } decoded[di] = '\0'; int gdi = spl_add_global_data(ctx, decoded, di + 1); free(decoded); spl_emit(ctx, SPL_GADDR, SPL_PTR, gdi - 1); spl_expr_result_t r = {spl_type_ptr(spl_type_basic(SPL_U8)), 0}; return r; } /* Parse array literal: [N]Type{val1, val2, ...} */ static spl_expr_result_t parse_array_literal(spl_comp_t *ctx) { advance(ctx); /* skip [ */ 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 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); if (!elem_type) { spl_expr_result_t r = {0}; return r; } skip_nl(ctx); expect(ctx, TOK_L_BRACE); skip_nl(ctx); for (usize i = 0; i < len; i++) { if (i > 0) { if (peek(ctx)->type == TOK_COMMA) advance(ctx); skip_nl(ctx); } spl_parse_expr(ctx, PREC_MIN); skip_nl(ctx); } if (peek(ctx)->type == TOK_COMMA) advance(ctx); skip_nl(ctx); expect(ctx, TOK_R_BRACE); spl_type_info_t *arr_type = spl_type_array(elem_type, len); return (spl_expr_result_t){arr_type, 0}; } /* Parse slice inline literal: { .ptr = expr, .len = expr } * Expects base address on TOS, consumes it. */ 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]; spl_tok_copy_name(ftok, fname, sizeof(fname)); skip_nl(ctx); if (peek(ctx)->type == TOK_ASSIGN) advance(ctx); skip_nl(ctx); spl_emit(ctx, SPL_DUP, SPL_VOID, 0); if (strcmp(fname, "ptr") == 0) { spl_parse_expr(ctx, PREC_MIN); spl_emit(ctx, SPL_STORE, SPL_PTR, 0); } 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); spl_parse_expr(ctx, PREC_MIN); spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); } skip_nl(ctx); } expect(ctx, TOK_R_BRACE); spl_emit(ctx, SPL_DROP, SPL_VOID, 0); } 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) { 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; } } } 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)); if (ctx->current_local_bytes > ctx->peak_local_bytes) ctx->peak_local_bytes = ctx->current_local_bytes; if (ctx->current_local_bytes - base_offset < (int)sizeof(spl_val_t)) ctx->current_local_bytes = base_offset + (int)sizeof(spl_val_t); if (ctx->current_local_bytes > ctx->peak_local_bytes) ctx->peak_local_bytes = ctx->current_local_bytes; advance(ctx); /* { */ skip_nl(ctx); if (type->kind == TYPE_STRUCT) { 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) { 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); } } else if (type->kind == TYPE_ENUM) { if (peek(ctx)->type == TOK_DOT) advance(ctx); spl_tok_t *vtok = advance(ctx); char vname[256]; spl_tok_copy_name(vtok, vname, sizeof(vname)); skip_nl(ctx); if (peek(ctx)->type == TOK_ASSIGN) advance(ctx); skip_nl(ctx); int found = 0; vec_for(type->variants, vi) { spl_enum_variant_t *v = &vec_at(type->variants, vi); if (strcmp(v->name, vname) == 0) { found = 1; spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset); spl_emit(ctx, SPL_PUSH, SPL_I32, v->value); spl_emit(ctx, SPL_STORE, SPL_I32, 0); const usize DATA_OFFSET = 4; if (v->data_type) { if (v->data_type->kind == TYPE_STRUCT && 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) break; parse_one_field_init(ctx, &v->data_type->fields, base_offset, DATA_OFFSET); skip_nl(ctx); } expect(ctx, TOK_R_BRACE); } else if (v->data_type && (v->data_type->kind == TYPE_STRUCT || v->data_type->kind == TYPE_ENUM) && spl_type_size(v->data_type) > sizeof(spl_val_t)) { spl_expr_result_t dv = spl_parse_expr(ctx, PREC_MIN); (void)dv; usize nslots = (spl_type_size(v->data_type) + sizeof(spl_val_t) - 1) / sizeof(spl_val_t); spl_emit_copy_slots(ctx, base_offset + (int)DATA_OFFSET, nslots); spl_emit(ctx, SPL_DROP, SPL_VOID, 0); } else { spl_expr_result_t dv = spl_parse_expr(ctx, PREC_MIN); (void)dv; spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset); spl_emit(ctx, SPL_PUSH, SPL_USIZE, DATA_OFFSET); spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); spl_type_t bt = spl_type_emit_type(v->data_type); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); spl_emit(ctx, SPL_STORE, bt, 0); } } break; } } if (!found) spl_comp_error(ctx, "unknown enum variant '%s'", vname); } skip_nl(ctx); expect(ctx, TOK_R_BRACE); if (sz <= sizeof(spl_val_t)) { spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset); spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); return (spl_expr_result_t){type, 0}; } else { spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset); return (spl_expr_result_t){type, 1}; } } static int parse_call_args(spl_comp_t *ctx) { int nargs = 0; if (peek(ctx)->type != TOK_R_PAREN) { for (;;) { spl_parse_expr(ctx, PREC_MIN); nargs++; if (peek(ctx)->type == TOK_COMMA) { advance(ctx); continue; } break; } } return nargs; } static spl_expr_result_t parse_ident(spl_comp_t *ctx) { spl_tok_t *t = advance(ctx); char name[256]; spl_tok_copy_name(t, name, sizeof(name)); /* Check if it's a function call: ident(...) */ if (peek(ctx)->type == TOK_L_PAREN) { int fi = spl_lookup_func(ctx, name); if (fi < 0 && ctx->current_type_name) { char qualified[512]; snprintf(qualified, sizeof(qualified), "%s.%s", ctx->current_type_name, name); fi = spl_lookup_func(ctx, qualified); } if (fi < 0) { spl_comp_error(ctx, "unknown function '%s'", name); spl_expr_result_t r = {0}; return r; } spl_func_info_t *f = &vec_at(ctx->funcs, fi); advance(ctx); /* skip ( */ int nargs = parse_call_args(ctx); expect(ctx, TOK_R_PAREN); if (f->is_extern) { int nidx = spl_ensure_native(ctx, f->name); spl_emit(ctx, SPL_PUSH, SPL_I32, nidx); spl_emit(ctx, SPL_NCALL, SPL_VOID, nargs); } else { spl_val_t addr = vec_at(ctx->prog.funcs, f->func_idx).address; spl_emit(ctx, SPL_PUSH, SPL_PTR, addr); spl_emit(ctx, SPL_CALL, SPL_VOID, nargs); } spl_expr_result_t r = {f->ret_type, 0}; return r; } /* Variable reference */ spl_var_info_t *v = spl_lookup_var(ctx, name); if (v) { spl_val_t cv = 0; if (map_get(ctx->const_values, name, &cv)) { spl_emit(ctx, SPL_PUSH, SPL_I32, cv); spl_expr_result_t r = {v->type, 0}; return r; } spl_type_info_t *vt = v->type; spl_emit(ctx, SPL_LADDR, SPL_PTR, v->offset); spl_expr_result_t r; emit_load_or_addr_type(ctx, &r, vt); return r; } /* Check if it's a type name (for enum variant access like Color.Red) */ spl_type_info_t *ttype = spl_resolve_type(ctx, name); if (ttype) { if (peek(ctx)->type == TOK_L_BRACE && (ttype->kind == TYPE_STRUCT || ttype->kind == TYPE_ENUM)) { return spl_parse_struct_literal(ctx, ttype); } spl_expr_result_t r = {ttype, 0}; return r; } spl_comp_error(ctx, "undefined variable '%s'", name); spl_expr_result_t r = {0}; return r; } static spl_expr_result_t parse_group(spl_comp_t *ctx) { advance(ctx); /* ( */ spl_expr_result_t r = spl_parse_expr(ctx, PREC_MIN); expect(ctx, TOK_R_PAREN); return r; } static void emit_load_or_addr_type(spl_comp_t *ctx, spl_expr_result_t *result, spl_type_info_t *type) { if (type->kind == TYPE_BASIC || type->kind == TYPE_PTR) { if (!ctx->addr_of_mode) { spl_type_t bt = (type->kind == TYPE_BASIC) ? type->basic_type : SPL_PTR; spl_emit(ctx, SPL_LOAD, bt, 0); *result = (spl_expr_result_t){type, 0}; return; } } *result = (spl_expr_result_t){type, 1}; } static spl_expr_result_t parse_prefix_op(spl_comp_t *ctx) { spl_tok_t *op = advance(ctx); if (op->type == TOK_AND) ctx->addr_of_mode = 1; spl_expr_result_t right = spl_parse_expr(ctx, PREC_PREFIX); if (op->type == TOK_AND) ctx->addr_of_mode = 0; switch (op->type) { case TOK_SUB: spl_emit(ctx, SPL_NEG, SPL_I32, 0); break; case TOK_NOT: spl_emit(ctx, SPL_PUSH, SPL_I32, 0); spl_emit(ctx, SPL_EQ, SPL_I32, 0); break; case TOK_BIT_NOT: spl_emit(ctx, SPL_NOT, SPL_I32, 0); break; case TOK_AND: if (!right.is_lvalue) { spl_comp_error(ctx, "cannot take address of rvalue"); } break; case TOK_MUL: if (right.type && right.type->kind == TYPE_PTR && right.type->elem) { if (right.is_lvalue) { spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); } emit_load_or_addr_type(ctx, &right, right.type->elem); } break; default: break; } return right; } /* ============================================================ * Primary expression (prefix part of Pratt parser) * ============================================================ */ 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; } switch (tok->type) { case TOK_INT_LITERAL: return parse_int_literal(ctx); case TOK_FLOAT_LITERAL: return parse_float_literal(ctx); case TOK_CHAR_LITERAL: return parse_char_literal(ctx); case TOK_STRING_LITERAL: return parse_string_literal(ctx); case KW_TRUE: advance(ctx); spl_emit(ctx, SPL_PUSH, SPL_I32, 1); return (spl_expr_result_t){spl_type_basic(SPL_I32), 0}; case KW_FALSE: advance(ctx); spl_emit(ctx, SPL_PUSH, SPL_I32, 0); return (spl_expr_result_t){spl_type_basic(SPL_I32), 0}; case KW_NULL: advance(ctx); spl_emit(ctx, SPL_PUSH, SPL_PTR, 0); return (spl_expr_result_t){spl_type_basic(SPL_PTR), 0}; case TOK_IDENT: case KW_BOOL: case KW_VOID: case KW_ANY: return parse_ident(ctx); case TOK_L_PAREN: return parse_group(ctx); case TOK_SUB: case TOK_NOT: case TOK_BIT_NOT: case TOK_AND: case TOK_MUL: return parse_prefix_op(ctx); case TOK_L_BRACKET: return parse_array_literal(ctx); case TOK_L_BRACE: return spl_parse_block_expr(ctx); case TOK_AT: { advance(ctx); skip_nl(ctx); tok = peek(ctx); if (!tok || tok->type != TOK_IDENT) { spl_comp_error(ctx, "expected builtin name after '@'"); spl_expr_result_t r = {0}; return r; } char bname[256]; spl_tok_copy_name(tok, bname, sizeof bname); advance(ctx); if (strcmp(bname, "dbg") == 0) { if (peek(ctx)->type == TOK_L_PAREN) { advance(ctx); int nargs = parse_call_args(ctx); expect(ctx, TOK_R_PAREN); 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) spl_emit(ctx, SPL_DBG, SPL_VOID, 0); } else { spl_emit(ctx, SPL_DBG, SPL_VOID, 0); } return (spl_expr_result_t){spl_type_basic(SPL_VOID), 0}; } else { spl_comp_error(ctx, "unknown builtin '@%s'", bname); spl_expr_result_t r = {0}; return r; } } default: if (peek(ctx)->type >= KW_AS && peek(ctx)->type <= KW_ANY) { return parse_ident(ctx); } spl_expr_result_t r = {0}; return r; } } /* ============================================================ * Postfix expression parser (.field, [index], [begin..end]) * ============================================================ */ static spl_expr_result_t parse_postfix_expr(spl_comp_t *ctx, spl_expr_result_t left) { if (!left.type) return left; for (;;) { skip_nl(ctx); spl_tok_type_t opt = peek(ctx)->type; /* Postfix . operator */ if (opt == TOK_DOT) { advance(ctx); spl_tok_t *field = advance(ctx); char fname[256]; spl_tok_copy_name(field, fname, sizeof(fname)); /* Compile-time type member resolution (enum variants, nested types) */ if (left.type) { spl_expr_result_t mresult = {0}; if (spl_resolve_type_member(ctx, left.type, fname, &mresult)) { left = mresult; continue; } } /* Method call on type/instance: left.field(args) */ if (left.type && peek(ctx)->type == TOK_L_PAREN) { spl_type_info_t *methods_type = left.type; int is_ptr_self = 0; if (methods_type->kind == TYPE_PTR && methods_type->elem && (methods_type->elem->kind == TYPE_STRUCT || methods_type->elem->kind == TYPE_ENUM)) { is_ptr_self = 1; methods_type = methods_type->elem; } int found_method = 0; vec_for(methods_type->methods, mi) { if (strcmp(vec_at(methods_type->methods, mi).name, fname) == 0) { spl_method_info_t *method = &vec_at(methods_type->methods, mi); spl_func_info_t *func = &vec_at(ctx->funcs, method->func_idx); advance(ctx); /* ( */ found_method = 1; int nargs = 0; int is_instance = 0; if (func->nparams > 0 && func->param_types[0] && func->param_types[0]->kind == TYPE_PTR && func->param_types[0]->elem == methods_type) { is_instance = 1; } if (is_instance) { if (!left.is_lvalue && !is_ptr_self) { spl_comp_error(ctx, "cannot call instance method '%s' on type", fname); } nargs = 1; } nargs += parse_call_args(ctx); expect(ctx, TOK_R_PAREN); spl_val_t addr = vec_at(ctx->prog.funcs, func->func_idx).address; spl_emit(ctx, SPL_PUSH, SPL_PTR, addr); spl_emit(ctx, SPL_CALL, SPL_VOID, nargs); left = (spl_expr_result_t){func->ret_type, 0}; break; } } if (found_method) continue; } /* Struct field access */ if (left.type && left.type->kind == TYPE_STRUCT) { vec_for(left.type->fields, fi) { if (strcmp(vec_at(left.type->fields, fi).name, fname) == 0) { spl_field_t *f = &vec_at(left.type->fields, fi); if (f->offset > 0) { spl_emit(ctx, SPL_PUSH, SPL_USIZE, f->offset); spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); } emit_load_or_addr_type(ctx, &left, f->type); break; } } continue; } /* Pointer auto-deref: if left is a pointer to struct, deref first */ if (left.type && left.type->kind == TYPE_PTR && left.type->elem && left.type->elem->kind == TYPE_STRUCT) { spl_type_info_t *st = left.type->elem; if (left.is_lvalue) { spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); } vec_for(st->fields, fi) { if (strcmp(vec_at(st->fields, fi).name, fname) == 0) { spl_field_t *f = &vec_at(st->fields, fi); if (f->offset > 0) { spl_emit(ctx, SPL_PUSH, SPL_USIZE, f->offset); spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); } emit_load_or_addr_type(ctx, &left, f->type); break; } } continue; } /* Slice .len or .ptr */ if (left.type && left.type->kind == TYPE_SLICE) { if (strcmp(fname, "len") == 0) { if (ctx->addr_of_mode) { spl_emit(ctx, SPL_PUSH, SPL_U64, (spl_val_t)sizeof(spl_val_t)); spl_emit(ctx, SPL_ADD, SPL_U64, 0); left = (spl_expr_result_t){spl_type_basic(SPL_USIZE), 1}; } else { spl_emit(ctx, SPL_PUSH, SPL_U64, (spl_val_t)sizeof(spl_val_t)); spl_emit(ctx, SPL_ADD, SPL_U64, 0); spl_emit(ctx, SPL_LOAD, SPL_USIZE, 0); left = (spl_expr_result_t){spl_type_basic(SPL_USIZE), 0}; } } else if (strcmp(fname, "ptr") == 0) { if (ctx->addr_of_mode) { left = (spl_expr_result_t){left.type->elem ? spl_type_ptr(left.type->elem) : spl_type_basic(SPL_PTR), 1}; } else { spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); left = (spl_expr_result_t){left.type->elem ? spl_type_ptr(left.type->elem) : spl_type_basic(SPL_PTR), 0}; } } continue; } /* Postfix dereference: expr.* */ if (strcmp(fname, "*") == 0 && left.type && left.type->kind == TYPE_PTR && left.type->elem) { if (left.is_lvalue) { spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); } emit_load_or_addr_type(ctx, &left, left.type->elem); continue; } spl_comp_error(ctx, "unknown field '%s'", fname); continue; } /* Array/slice indexing: expr[expr] or expr[begin..end] */ if (opt == TOK_L_BRACKET) { advance(ctx); /* skip [ */ if (peek(ctx)->type == TOK_R_BRACKET) { advance(ctx); continue; } spl_parse_expr(ctx, PREC_MIN); if (peek(ctx)->type == TOK_RANGE) { advance(ctx); spl_expr_result_t end_expr = {0}; int has_explicit_end = (peek(ctx)->type != TOK_R_BRACKET); if (has_explicit_end) { end_expr = spl_parse_expr(ctx, PREC_MIN); } expect(ctx, TOK_R_BRACKET); if (!has_explicit_end) { if (left.type && left.type->kind == TYPE_ARRAY) { spl_emit(ctx, SPL_PUSH, SPL_U64, left.type->array_len); } else if (left.type && left.type->kind == TYPE_SLICE) { spl_emit(ctx, SPL_PICK, SPL_VOID, 1); 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); } } if (left.type && (left.type->kind == TYPE_ARRAY || left.type->kind == TYPE_SLICE)) { usize stride = spl_type_elem_stride(left.type->elem); if (left.type->kind == TYPE_SLICE) { spl_emit(ctx, SPL_PICK, SPL_VOID, 2); spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); spl_emit(ctx, SPL_PICK, SPL_VOID, 2); spl_emit(ctx, SPL_PUSH, SPL_U64, stride); spl_emit(ctx, SPL_MUL, SPL_U64, 0); spl_emit(ctx, SPL_ADD, SPL_U64, 0); spl_emit(ctx, SPL_PICK, SPL_VOID, 1); spl_emit(ctx, SPL_PICK, SPL_VOID, 3); spl_emit(ctx, SPL_SUB, SPL_USIZE, 0); spl_emit(ctx, SPL_ROT, SPL_VOID, 0); spl_emit(ctx, SPL_DROP, SPL_VOID, 0); spl_emit(ctx, SPL_ROT, SPL_VOID, 0); spl_emit(ctx, SPL_DROP, SPL_VOID, 0); spl_emit(ctx, SPL_ROT, SPL_VOID, 0); spl_emit(ctx, SPL_DROP, SPL_VOID, 0); } else { emit_slice_create(ctx, stride); } } left = (spl_expr_result_t){left.type ? spl_type_slice(left.type->elem) : NULL, 0}; continue; } expect(ctx, TOK_R_BRACKET); if (left.type && (left.type->kind == TYPE_ARRAY || left.type->kind == TYPE_PTR || left.type->kind == TYPE_SLICE)) { spl_type_info_t *elem = left.type->elem; if (left.type->kind == TYPE_SLICE) { emit_slice_index(ctx, elem); } else { if (left.type->kind == TYPE_PTR && left.is_lvalue) { spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); } usize stride = spl_type_elem_stride(elem); spl_emit(ctx, SPL_PUSH, SPL_U64, stride); spl_emit(ctx, SPL_MUL, SPL_U64, 0); spl_emit(ctx, SPL_ADD, SPL_U64, 0); } if (elem) emit_load_or_addr_type(ctx, &left, elem); } continue; } break; } return left; } /* ============================================================ * 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) { left = parse_postfix_expr(ctx, left); skip_nl(ctx); spl_tok_type_t opt = peek(ctx)->type; /* Binary operators */ int prec = tok_prec(opt); if (prec == 0 || prec < min_prec) break; advance(ctx); left = parse_infix(ctx, left, opt); } return left; } /* ============================================================ * Infix operators * ============================================================ */ static spl_expr_result_t parse_infix(spl_comp_t *ctx, spl_expr_result_t left, spl_tok_type_t op) { int prec = tok_prec(op); int next_prec = prec + 1; /* Short-circuit logical operators */ if (op == TOK_AND_AND) { spl_val_t bz_addr = spl_emit_bz(ctx); spl_parse_expr(ctx, next_prec); spl_patch_to_here(ctx, bz_addr); return (spl_expr_result_t){spl_type_basic(SPL_I32), 0}; } if (op == TOK_OR_OR) { spl_val_t bnz_addr = spl_emit_bnz(ctx); spl_parse_expr(ctx, next_prec); spl_patch_to_here(ctx, bnz_addr); return (spl_expr_result_t){spl_type_basic(SPL_I32), 0}; } /* Assignment operators */ if (op == TOK_ASSIGN || op == TOK_ASSIGN_ADD || op == TOK_ASSIGN_SUB || op == TOK_ASSIGN_MUL || op == TOK_ASSIGN_DIV || op == TOK_ASSIGN_MOD || op == TOK_ASSIGN_AND || op == TOK_ASSIGN_OR || op == TOK_ASSIGN_XOR || op == TOK_ASSIGN_L_SH || op == TOK_ASSIGN_R_SH) { int saved_addr_of_mode = ctx->addr_of_mode; ctx->addr_of_mode = 0; spl_expr_result_t right = spl_parse_expr(ctx, PREC_MIN); ctx->addr_of_mode = saved_addr_of_mode; if (left.is_lvalue) { spl_type_t bt = left.type ? (left.type->kind == TYPE_BASIC ? left.type->basic_type : SPL_PTR) : SPL_I32; if (op == TOK_ASSIGN) { spl_emit(ctx, SPL_STORE, bt, 0); } else { spl_emit(ctx, SPL_PICK, SPL_VOID, 1); spl_emit(ctx, SPL_LOAD, bt, 0); spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); int sop = binop_to_sir(assign_to_binop(op), bt); if (sop >= 0) spl_emit(ctx, sop, bt, 0); spl_emit(ctx, SPL_STORE, bt, 0); } } return right; } /* Regular binary op */ spl_parse_expr(ctx, next_prec); int sop = binop_to_sir(op, left.type ? left.type->basic_type : SPL_I32); spl_type_t bt = left.type && left.type->kind == TYPE_BASIC ? left.type->basic_type : SPL_I32; if (sop >= 0) { spl_emit(ctx, sop, bt, 0); } 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]; spl_tok_copy_name(vtok, vname, sizeof(vname)); 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); } }