/* 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); /* 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); /* 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}; } /* Determine correct STORE type for a type (handles ptr = 8 bytes). */ static spl_type_t spl_store_type(spl_type_info_t *type) { if (!type) return SPL_I32; if (type->kind == TYPE_BASIC) return type->basic_type; if (type->kind == TYPE_PTR) return SPL_PTR; if (spl_type_size(type) <= sizeof(spl_val_t)) return SPL_PTR; return SPL_I32; } /* 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) { 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 - base_offset < (int)sizeof(spl_val_t)) ctx->current_local_bytes = base_offset + (int)sizeof(spl_val_t); 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) { 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); } spl_expr_result_t fv = spl_parse_expr(ctx, PREC_MIN); (void)fv; spl_emit(ctx, SPL_STORE, spl_store_type(f->type), 0); break; } } } skip_nl(ctx); } } else if (type->kind == TYPE_ENUM) { /* Parse .Variant [= value] */ 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'; 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; /* Store tag at offset 0 */ 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); /* Store variant data at offset 4 */ const usize DATA_OFFSET = 4; if (v->data_type) { if (v->data_type->kind == TYPE_STRUCT && peek(ctx)->type == TOK_L_BRACE) { /* Struct data: { .field = val, ... } */ 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); 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); } spl_expr_result_t sfv = spl_parse_expr(ctx, PREC_MIN); (void)sfv; spl_emit(ctx, SPL_STORE, spl_store_type(sf->type), 0); break; } } skip_nl(ctx); } expect(ctx, TOK_R_BRACE); } else { /* Simple data: parse expression */ 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_store_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); /* Return: if size fits in one slot, push packed value. Otherwise push address. */ 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}; /* value on stack */ } else { spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset); return (spl_expr_result_t){type, 1}; /* address on stack */ } } 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); /* Fallback: try qualified name for short-name resolution inside methods */ 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 = 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->offset); 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; } /* 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) { /* 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); } 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 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 */ if (right.type && right.type->kind == TYPE_PTR && right.type->elem) { /* If right is still an lvalue, load the pointer value for the target */ if (right.is_lvalue) { spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); } spl_type_info_t *elem = right.type->elem; if (elem->kind == TYPE_BASIC || elem->kind == TYPE_PTR) { if (!ctx->addr_of_mode) { spl_type_t bt = (elem->kind == TYPE_BASIC) ? elem->basic_type : SPL_PTR; spl_emit(ctx, SPL_LOAD, bt, 0); right = (spl_expr_result_t){elem, 0}; } else { right = (spl_expr_result_t){elem, 1}; } } else { /* Struct/array/slice: keep address on stack */ right = (spl_expr_result_t){elem, 1}; } } 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; case TOK_AT: { /* @builtin(...) — compiler intrinsic */ advance(ctx); /* consume @ */ skip_nl(ctx); tok = peek(ctx); if (!tok || tok->type != TOK_IDENT) { spl_comp_error(ctx, "expected builtin name after '@'"); break; } char bname[256]; spl_tok_copy_name(tok, bname, sizeof bname); advance(ctx); /* consume builtin name */ if (strcmp(bname, "dbg") == 0) { /* @dbg(...) — print VM debug info */ if (peek(ctx)->type == TOK_L_PAREN) { advance(ctx); /* skip ( */ 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; } } 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) { 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}; } 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; } /* 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'; /* 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) { /* Determine the type that owns methods (deref pointer if needed) */ 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; /* Check if instance method: first param is self: *Type */ 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; /* self already on stack */ } /* Parse remaining arguments */ 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); 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; /* If method not found, fall through to field access below */ } /* 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_USIZE, f->offset); spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); } /* Load value if basic type */ spl_type_info_t *ft = f->type; if (ft->kind == TYPE_BASIC || ft->kind == TYPE_PTR) { if (!ctx->addr_of_mode) { 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}; } } 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; /* Load pointer value to get struct address if left is still lvalue */ if (left.is_lvalue) { 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_USIZE, f->offset); spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); } spl_type_info_t *ft = f->type; if (ft->kind == TYPE_BASIC || ft->kind == TYPE_PTR) { if (!ctx->addr_of_mode) { 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}; } } 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) { if (ctx->addr_of_mode) { /* lvalue: push address of len field 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); left = (spl_expr_result_t){spl_type_basic(SPL_USIZE), 1}; } else { /* rvalue: load len value */ 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) { /* lvalue: ptr is at offset 0, address already on stack */ 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); } spl_type_info_t *elem = left.type->elem; if (elem->kind == TYPE_BASIC || elem->kind == TYPE_PTR) { if (!ctx->addr_of_mode) { 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}; } } else { left = (spl_expr_result_t){elem, 1}; } 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); /* empty brackets */ continue; } /* 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}; 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); /* Push implicit end value (array length/slice len) before the outer * type-check so it always runs regardless of left.type validity. */ 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) { /* TYPE_SLICE: load len from struct at offset sizeof(spl_val_t) */ 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); } } /* 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 = spl_type_elem_stride(left.type->elem); if (left.type->kind == TYPE_SLICE) { /* Stack: [struct_addr, begin, end]. * Inline ptr/len: ptr = data_ptr + begin*stride, len = end - begin. */ spl_emit(ctx, SPL_PICK, SPL_VOID, 2); spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); /* [s,b,e,data_ptr] */ spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [s,b,e,d,begin] */ spl_emit(ctx, SPL_PUSH, SPL_U64, stride); spl_emit(ctx, SPL_MUL, SPL_U64, 0); spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [s,b,e,ptr] */ spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* [s,b,e,ptr,end] */ spl_emit(ctx, SPL_PICK, SPL_VOID, 3); /* [s,b,e,ptr,end,begin] */ spl_emit(ctx, SPL_SUB, SPL_USIZE, 0); /* [s,b,e,ptr,len] */ /* cleanup: drop [s,b,e] */ spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [s,b,ptr,len,e] */ spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [s,b,ptr,len] */ spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [s,ptr,len,b] */ spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [s,ptr,len] */ spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [ptr,len,s] */ spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [ptr,len] */ } 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); /* 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 and pointer indexing */ 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 && (elem->kind == TYPE_BASIC || elem->kind == TYPE_PTR)) { if (!ctx->addr_of_mode) { 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}; } } 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_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_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 : left.type->kind == TYPE_PTR ? SPL_PTR : SPL_I32) : 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_PICK, SPL_VOID, 1); /* [addr, rhs, addr] */ spl_emit(ctx, SPL_LOAD, bt, 0); /* [addr, rhs, old_val] */ spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [addr, old_val, rhs] */ int sop = binop_to_sir(assign_to_binop(op), bt); if (sop >= 0) spl_emit(ctx, sop, bt, 0); /* [addr, result] */ 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}; }