Files
spl/stage1/spl_expr.c

1397 lines
57 KiB
C

/* spl_expr.c — Expression parser + codegen (Pratt parser / precedence climbing) */
#include "spl_comp.h"
#include "spl_lex_util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ============================================================
* 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 */
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);
/* 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 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 > 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) {
advance(ctx);
spl_tok_t *ftok = advance(ctx);
char fname[256];
usize fnl = ftok->len < 255 ? ftok->len : 255;
memcpy(fname, ftok->lexeme, fnl);
fname[fnl] = '\0';
skip_nl(ctx);
if (peek(ctx)->type == TOK_ASSIGN)
advance(ctx);
skip_nl(ctx);
vec_for(type->fields, fi) {
spl_field_t *f = &vec_at(type->fields, fi);
if (strcmp(f->name, fname) == 0) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
if (f->offset > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, f->offset);
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
/* Handle inline slice initializer: { .ptr = expr, .len = expr } */
if (f->type && f->type->kind == TYPE_SLICE &&
peek(ctx)->type == TOK_L_BRACE) {
/* Stack: [field_addr] — slice struct start addr */
advance(ctx); /* { */
skip_nl(ctx);
while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) {
if (peek(ctx)->type == TOK_COMMA) {
advance(ctx);
skip_nl(ctx);
continue;
}
if (peek(ctx)->type == TOK_DOT)
advance(ctx);
spl_tok_t *sftok = advance(ctx);
char sfname[256];
usize sfnl = sftok->len < 255 ? sftok->len : 255;
memcpy(sfname, sftok->lexeme, sfnl);
sfname[sfnl] = '\0';
skip_nl(ctx);
if (peek(ctx)->type == TOK_ASSIGN)
advance(ctx);
skip_nl(ctx);
spl_emit(ctx, SPL_DUP, SPL_VOID, 0); /* [addr, addr] */
if (strcmp(sfname, "ptr") == 0) {
spl_expr_result_t pv = spl_parse_expr(ctx, PREC_MIN);
(void)pv;
/* Stack: [addr, addr, ptr_val] — STORE needs [addr, val] */
spl_emit(ctx, SPL_STORE, SPL_PTR, 0); /* [addr] */
} else if (strcmp(sfname, "len") == 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, sizeof(spl_val_t));
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0); /* [addr, addr+8] */
spl_expr_result_t lv = spl_parse_expr(ctx, PREC_MIN);
(void)lv;
/* Stack: [addr, addr+8, len_val] — STORE needs [addr, val] */
spl_emit(ctx, SPL_STORE, SPL_USIZE, 0); /* [addr] */
}
skip_nl(ctx);
}
expect(ctx, TOK_R_BRACE);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* drop addr */
} else if (f->type && f->type->kind == TYPE_ARRAY &&
peek(ctx)->type == TOK_L_BRACKET) {
/* Inline array initializer: [N]Type{val1, val2, ...}
* Stack: [field_addr] — store each element at computed offsets */
advance(ctx); /* [ */
skip_nl(ctx);
int len_val;
if (!spl_parse_int_literal(ctx, &len_val)) {
spl_comp_error(ctx, "expected array length");
spl_expr_result_t r = {0};
return r;
}
usize arr_len = (usize)len_val;
skip_nl(ctx);
expect(ctx, TOK_R_BRACKET);
skip_nl(ctx);
spl_type_info_t *elem_type = spl_parse_type(ctx);
skip_nl(ctx);
expect(ctx, TOK_L_BRACE);
skip_nl(ctx);
usize stride = spl_type_elem_stride(elem_type);
spl_type_t st = spl_type_emit_type(elem_type);
for (usize i = 0; i < arr_len; i++) {
if (i > 0) {
if (peek(ctx)->type == TOK_COMMA)
advance(ctx);
skip_nl(ctx);
}
spl_emit(ctx, SPL_DUP, SPL_VOID, 0);
if (i > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, i * stride);
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
spl_parse_expr(ctx, PREC_MIN);
spl_emit(ctx, SPL_STORE, st, 0);
skip_nl(ctx);
}
if (peek(ctx)->type == TOK_COMMA)
advance(ctx);
skip_nl(ctx);
expect(ctx, TOK_R_BRACE);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
} else if (f->type &&
(f->type->kind == TYPE_STRUCT || f->type->kind == TYPE_ENUM) &&
spl_type_size(f->type) > sizeof(spl_val_t)) {
/* Multi-slot struct/enum value: copy temp → field slot by slot
* Stack: [field_addr, temp_addr] */
spl_expr_result_t fv = spl_parse_expr(ctx, PREC_MIN);
(void)fv;
usize nslots = (spl_type_size(f->type) + sizeof(spl_val_t) - 1) /
sizeof(spl_val_t);
spl_emit_copy_slots(ctx, base_offset + f->offset, nslots);
/* Drop field_addr from stack (copy_slots consumed temp_addr) */
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
} else {
spl_expr_result_t fv = spl_parse_expr(ctx, PREC_MIN);
(void)fv;
spl_emit(ctx, SPL_STORE, spl_type_emit_type(f->type), 0);
}
break;
}
}
} else {
/* Unrecognized token (not .field or ,), advance to prevent infinite loop */
if (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF)
advance(ctx);
}
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);
}
/* Handle inline slice initializer */
if (sf->type && sf->type->kind == TYPE_SLICE &&
peek(ctx)->type == TOK_L_BRACE) {
advance(ctx); /* { */
skip_nl(ctx);
while (peek(ctx)->type != TOK_R_BRACE &&
peek(ctx)->type != TOK_EOF) {
if (peek(ctx)->type == TOK_COMMA) {
advance(ctx);
skip_nl(ctx);
continue;
}
if (peek(ctx)->type == TOK_DOT)
advance(ctx);
spl_tok_t *ssftok = advance(ctx);
char ssfname[256];
usize ssfnl = ssftok->len < 255 ? ssftok->len : 255;
memcpy(ssfname, ssftok->lexeme, ssfnl);
ssfname[ssfnl] = '\0';
skip_nl(ctx);
if (peek(ctx)->type == TOK_ASSIGN)
advance(ctx);
skip_nl(ctx);
spl_emit(ctx, SPL_DUP, SPL_VOID, 0);
if (strcmp(ssfname, "ptr") == 0) {
spl_expr_result_t pv =
spl_parse_expr(ctx, PREC_MIN);
(void)pv;
spl_emit(ctx, SPL_STORE, SPL_PTR, 0);
} else if (strcmp(ssfname, "len") == 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE,
sizeof(spl_val_t));
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
spl_expr_result_t lv =
spl_parse_expr(ctx, PREC_MIN);
(void)lv;
spl_emit(ctx, SPL_STORE, SPL_USIZE, 0);
}
skip_nl(ctx);
}
expect(ctx, TOK_R_BRACE);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
} else if (sf->type &&
(sf->type->kind == TYPE_STRUCT ||
sf->type->kind == TYPE_ENUM) &&
spl_type_size(sf->type) > sizeof(spl_val_t)) {
/* Multi-slot struct field in enum variant data */
spl_expr_result_t sfv = spl_parse_expr(ctx, PREC_MIN);
(void)sfv;
usize nslots =
(spl_type_size(sf->type) + sizeof(spl_val_t) - 1) /
sizeof(spl_val_t);
spl_emit_copy_slots(
ctx, base_offset + (int)DATA_OFFSET + sf->offset,
nslots);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
} else {
spl_expr_result_t sfv = spl_parse_expr(ctx, PREC_MIN);
(void)sfv;
spl_emit(ctx, SPL_STORE, spl_type_emit_type(sf->type), 0);
}
break;
}
}
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)) {
/* Multi-slot struct/enum data: copy from temp to enum data area */
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 {
/* 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_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);
/* 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 */
if (left.type->kind == TYPE_PTR && left.is_lvalue) {
/* Stack: [addr_of_ptr, index]. Swap to get addr on top, load ptr value,
* swap back */
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 && (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 : SPL_PTR)
: 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};
}