/* spl_expr.c — Expression parser + codegen (Pratt parser / precedence climbing) */ #include "spl_comp.h" #include #include #include #include /* ============================================================ * Token helpers * ============================================================ */ 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 match(spl_comp_t *ctx, spl_tok_type_t type) { if (peek(ctx)->type == type) { advance(ctx); return 1; } return 0; } 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; } /* Skip newline tokens */ static void skip_nl(spl_comp_t *ctx) { while (peek(ctx)->type == TOK_ENDLINE) advance(ctx); } /* Check if current token starts a statement */ static int is_stmt_start(spl_comp_t *ctx) { spl_tok_type_t t = peek(ctx)->type; return t == TOK_EOF || t == TOK_R_BRACE ? 0 : 1; } /* ============================================================ * 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_prefix(spl_comp_t *ctx); static spl_expr_result_t parse_infix(spl_comp_t *ctx, spl_expr_result_t left, spl_tok_type_t op); /* 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_I32, 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 = elem ? elem->byte_size : 4; 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] */ } /* ============================================================ * SIR opcode for binary operator * ============================================================ */ 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); /* Lexeme: 'x' or '\n' etc, extract the character value */ const char *s = t->lexeme; usize l = t->len; int64_t val = 0; if (l >= 3) { if (s[1] == '\\' && l >= 4) { /* Escape sequence */ 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); /* Decode the string content (strip quotes, process escapes) */ usize slen = t->len; if (slen >= 2) { slen -= 2; /* remove outer quotes */ } /* Build decoded string */ 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'; /* Add to global data */ int gdi = spl_add_global_data(ctx, decoded, di + 1); free(decoded); spl_emit(ctx, SPL_GADDR, SPL_PTR, gdi - 1); /* gdi is 1-based from spl_prog_add_data */ 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); /* Parse array length */ spl_tok_t *len_tok = advance(ctx); if (len_tok->type != TOK_INT_LITERAL) { spl_comp_error(ctx, "expected array length"); spl_expr_result_t r = {0}; return r; } usize len = (usize)strtoull(len_tok->lexeme, NULL, 0); skip_nl(ctx); expect(ctx, TOK_R_BRACKET); skip_nl(ctx); /* Parse element type */ 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); /* Parse each element value */ 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); /* trailing comma */ 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 a type name with optional .field suffix for enum variants: * TypeName * TypeName.field */ static spl_type_info_t *parse_qualified_type(spl_comp_t *ctx, const char *name) { /* Check if it's a known type */ spl_type_info_t *t = spl_resolve_type(ctx, name); if (t && peek(ctx)->type == TOK_DOT) { /* Enum type with variant: Type.variant */ advance(ctx); /* skip . */ spl_tok_t *vtok = advance(ctx); char vname[256]; usize vn = vtok->len < 255 ? vtok->len : 255; memcpy(vname, vtok->lexeme, vn); vname[vn] = '\0'; /* For enum values, just emit the tag as integer */ if (t->kind == TYPE_ENUM) { vec_for(t->variants, vi) { if (strcmp(vec_at(t->variants, vi).name, vname) == 0) { spl_emit(ctx, SPL_PUSH, SPL_I32, vec_at(t->variants, vi).value); break; } } } } return t; } static spl_expr_result_t parse_ident(spl_comp_t *ctx) { spl_tok_t *t = advance(ctx); char name[256]; usize nlen = t->len < 255 ? t->len : 255; memcpy(name, t->lexeme, nlen); name[nlen] = '\0'; /* 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) { 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 = 0; if (peek(ctx)->type != TOK_R_PAREN) { for (;;) { spl_expr_result_t arg = spl_parse_expr(ctx, PREC_MIN); (void)arg; nargs++; if (peek(ctx)->type == TOK_COMMA) { advance(ctx); continue; } break; } } expect(ctx, TOK_R_PAREN); if (f->is_extern) { /* Find the native function index in prog */ int nidx = -1; vec_for(ctx->prog.natives, ni) { if (strcmp(vec_at(ctx->prog.natives, ni).name, f->name) == 0) { nidx = (int)ni; break; } } if (nidx < 0) { /* Register it */ spl_native_t nat; nat.name = strdup(f->name); nat.idx_of_strtab = 0; nat.impl_fn = NULL; vec_push(ctx->prog.natives, nat); nidx = (int)vec_size(ctx->prog.natives) - 1; } /* Push native index, then NCALL with imm = nargs */ spl_emit(ctx, SPL_PUSH, SPL_I32, nidx); spl_emit(ctx, SPL_NCALL, SPL_VOID, nargs); } else { /* Regular function call: push func addr, then CALL */ 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) { /* Check const values first */ 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->slot); if (vt->kind == TYPE_BASIC || vt->kind == TYPE_PTR) { if (!ctx->addr_of_mode) { /* Load value for basic types and pointers */ spl_type_t bt = (vt->kind == TYPE_BASIC) ? vt->basic_type : SPL_PTR; spl_emit(ctx, SPL_LOAD, bt, 0); spl_expr_result_t r = { vt, 0 }; return r; } /* In addr_of_mode, keep address on stack */ spl_expr_result_t r = { vt, 1 }; return r; } /* Array/struct/slice: address stays on stack */ spl_expr_result_t r = { vt, 1 }; 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 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: /* !expr → EQ 0 */ 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: /* &expr — address-of, already an lvalue */ if (!right.is_lvalue) { spl_comp_error(ctx, "cannot take address of rvalue"); } break; case TOK_MUL: /* *expr — dereference: push the pointer value, then load */ /* right should evaluate to the pointer */ if (right.type && right.type->kind == TYPE_PTR && right.type->elem) { spl_emit(ctx, SPL_LOAD, right.type->elem->basic_type, 0); } break; default: break; } return right; } /* ============================================================ * Main expression parser (top-level) * ============================================================ */ spl_expr_result_t spl_parse_expr(spl_comp_t *ctx, int min_prec) { skip_nl(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; case TOK_FLOAT_LITERAL: left = parse_float_literal(ctx); break; case TOK_CHAR_LITERAL: left = parse_char_literal(ctx); break; case TOK_STRING_LITERAL: left = parse_string_literal(ctx); break; 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; 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; 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; case TOK_IDENT: case KW_BOOL: case KW_VOID: case KW_ANY: left = parse_ident(ctx); break; case TOK_L_PAREN: left = parse_group(ctx); break; case TOK_SUB: case TOK_NOT: case TOK_BIT_NOT: case TOK_AND: case TOK_MUL: left = parse_prefix_op(ctx); break; case TOK_L_BRACKET: left = parse_array_literal(ctx); 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; } /* Infix parsing (precedence climbing) */ while (1) { skip_nl(ctx); spl_tok_type_t opt = peek(ctx)->type; /* Postfix operators */ if (opt == TOK_DOT) { advance(ctx); spl_tok_t *field = advance(ctx); char fname[256]; usize fnl = field->len < 255 ? field->len : 255; memcpy(fname, field->lexeme, fnl); fname[fnl] = '\0'; /* Check for enum variant: Type.Variant */ if (left.type && left.type->kind == TYPE_ENUM) { /* It's a type-level enum reference */ vec_for(left.type->variants, vi) { if (strcmp(vec_at(left.type->variants, vi).name, fname) == 0) { spl_emit(ctx, SPL_PUSH, SPL_I32, vec_at(left.type->variants, vi).value); left = (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 }; break; } } continue; } /* Struct field access */ if (left.type && left.type->kind == TYPE_STRUCT) { /* The struct address is on stack (as lvalue or from previous computation) */ 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_I32, f->offset); spl_emit(ctx, SPL_ADD, SPL_I32, 0); } /* Load value if basic type */ spl_type_info_t *ft = f->type; if (ft->kind == TYPE_BASIC || ft->kind == TYPE_PTR) { spl_type_t bt = (ft->kind == TYPE_BASIC) ? ft->basic_type : SPL_PTR; spl_emit(ctx, SPL_LOAD, bt, 0); left = (spl_expr_result_t){ ft, 0 }; } else { left = (spl_expr_result_t){ ft, 1 }; } 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; /* Left has the pointer value on stack. Load it to get the struct address. */ spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); /* Now search field */ 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_I32, f->offset); spl_emit(ctx, SPL_ADD, SPL_I32, 0); } spl_type_info_t *ft = f->type; if (ft->kind == TYPE_BASIC || ft->kind == TYPE_PTR) { spl_type_t bt = (ft->kind == TYPE_BASIC) ? ft->basic_type : SPL_PTR; spl_emit(ctx, SPL_LOAD, bt, 0); left = (spl_expr_result_t){ ft, 0 }; } else { left = (spl_expr_result_t){ ft, 1 }; } break; } } continue; } /* Slice .len or .ptr */ if (left.type && left.type->kind == TYPE_SLICE) { if (strcmp(fname, "len") == 0) { /* Slice: 2 slots [ptr, len]. len is at offset sizeof(spl_val_t) */ 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_I32, 0); left = (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 }; } else if (strcmp(fname, "ptr") == 0) { /* ptr is at offset 0 */ 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; } spl_comp_error(ctx, "unknown field '%s'", fname); continue; } /* Postfix deref: expr.* */ if (opt == TOK_MUL && ctx->tok_idx + 1 < vec_size(ctx->toks) && peek(ctx)->type == TOK_MUL && vec_at(ctx->toks, ctx->tok_idx + 1).type == TOK_MUL) { /* Handle .* — actually just * (multiplication) */ /* The spec defines .* as postfix deref, but in token stream, * the lexer tokenizes .* as TOK_DOT + TOK_MUL */ /* Actually, .* is not a multi-char token. Let's check the lexer. */ /* If we see . then *, it's field access then multiplication? No. */ /* In .*, we see TOK_DOT then TOK_MUL. But after TOK_DOT we already consumed * the next token as a field name. So .* doesn't work with the current approach. */ } /* 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); /* empty brackets */ continue; } /* Check for slice: expr[begin..end] or expr[begin..] */ int is_slice = 0; usize slice_start = ctx->tok_idx; spl_expr_result_t index = spl_parse_expr(ctx, PREC_MIN); if (peek(ctx)->type == TOK_RANGE) { is_slice = 1; advance(ctx); /* skip .. */ spl_expr_result_t end_expr = {0}; if (peek(ctx)->type != TOK_R_BRACKET) { end_expr = spl_parse_expr(ctx, PREC_MIN); } expect(ctx, TOK_R_BRACKET); /* Generate slice: compute ptr = base + begin * stride, len = end - begin */ if (left.type && (left.type->kind == TYPE_ARRAY || left.type->kind == TYPE_SLICE)) { usize stride = (left.type->kind == TYPE_ARRAY) ? sizeof(spl_val_t) : (left.type->elem ? left.type->elem->byte_size : 4); /* For TYPE_SLICE, load data ptr from slice struct first */ if (left.type->kind == TYPE_SLICE) { spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); } 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); /* Array/slice/pointer indexing */ 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) { /* Slice: stack has [slice_struct_addr, index]. * Load data ptr first, then compute element address. */ emit_slice_index(ctx, elem); } else { /* Stack arrays: each element occupies a full slot (sizeof(spl_val_t)) */ usize elem_size = (left.type->kind == TYPE_ARRAY) ? sizeof(spl_val_t) : (elem ? elem->byte_size : 4); 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); } if (elem && (elem->kind == TYPE_BASIC || elem->kind == TYPE_PTR)) { spl_type_t bt = (elem->kind == TYPE_BASIC) ? elem->basic_type : SPL_PTR; spl_emit(ctx, SPL_LOAD, bt, 0); left = (spl_expr_result_t){ elem, 0 }; } else { left = (spl_expr_result_t){ elem, 1 }; } } continue; } /* 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) { /* left is already evaluated and on stack. If it's false (0), skip right. */ spl_val_t bz_addr = spl_emit_bz(ctx); spl_expr_result_t right = 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) { /* If left is true (non-zero), skip right. */ spl_val_t bnz_addr = spl_emit_bnz(ctx); spl_expr_result_t right = 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) { /* RHS must not inherit addr_of_mode from LHS */ 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_I32; if (op == TOK_ASSIGN) { /* Simple assignment: stack is [addr, rhs] */ /* STORE pops TOS=value, TOS-1=address — already correct order */ spl_emit(ctx, SPL_STORE, bt, 0); } else { /* Compound: left = left op right — stack: [addr, rhs] */ spl_emit(ctx, SPL_DUP, SPL_VOID, 0); /* [addr, rhs, addr] */ spl_emit(ctx, SPL_LOAD, bt, 0); /* [addr, rhs, old_val] */ /* FIXME: compound needs to compute old_val op rhs, then store */ /* For now just drop old_val and store rhs */ spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [addr, rhs] */ spl_emit(ctx, SPL_STORE, bt, 0); } } return right; } /* Regular binary op */ spl_expr_result_t right = 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 }; }