Files
spl/stage1/spl_expr.c
2026-07-13 21:50:28 +08:00

1355 lines
49 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);
static void emit_load_or_addr_type(spl_comp_t *ctx, spl_expr_result_t *result,
spl_type_info_t *type);
static spl_expr_result_t parse_postfix_expr(spl_comp_t *ctx, spl_expr_result_t left);
/* Slice creation from array/slice range expression.
* Stack in: [base_addr, begin, end]
* Stack out: [ptr, len]
*
* Uses pure stack operations — no temp slots needed.
* ptr = base + begin * stride
* len = end - begin
*
* Strategy: PICK copies of values we need, compute results on stack,
* then ROT inaccessible values to TOS and DROP them. */
static void emit_slice_create(spl_comp_t *ctx, usize stride) {
/* Stack: [base, begin, end] */
/* --- Compute ptr = base + begin * stride --- */
spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [base, begin, end, base] */
spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [base, begin, end, base, begin] */
spl_emit(ctx, SPL_PUSH, SPL_U64, stride);
spl_emit(ctx, SPL_MUL, SPL_U64, 0); /* [base, begin, end, base, begin*stride] */
spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [base, begin, end, ptr] */
/* --- Compute len = end - begin --- */
spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* [base, begin, end, ptr, end] */
spl_emit(ctx, SPL_PICK, SPL_VOID, 3); /* [base, begin, end, ptr, end, begin] */
spl_emit(ctx, SPL_SUB, SPL_USIZE, 0); /* [base, begin, end, ptr, len] */
/* --- Cleanup: drop [base, begin, end], keep [ptr, len] --- */
spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [base, begin, ptr, len, end] */
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [base, begin, ptr, len] */
spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [base, ptr, len, begin] */
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [base, ptr, len] */
spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [ptr, len, base] */
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [ptr, len] */
}
/* Emit code to access slice element by index.
* For TYPE_SLICE, the stack has [slice_struct_addr, index].
* We need to load the data ptr from the struct before indexing.
* Stack in: [slice_struct_addr, index]
* Stack out: [element_addr] */
static void emit_slice_index(spl_comp_t *ctx, spl_type_info_t *elem) {
usize elem_size = spl_type_elem_stride(elem);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [index, slice_struct_addr] */
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); /* [index, data_ptr] */
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [data_ptr, index] */
spl_emit(ctx, SPL_PUSH, SPL_U64, elem_size);
spl_emit(ctx, SPL_MUL, SPL_U64, 0);
spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [data_ptr + index * elem_size] */
}
/* Try to resolve Type.Member for compile-time type access:
* - Enum variants → push variant integer value
* - Nested types (struct/enum/union) → return nested type
* Uses qualified name (TypeName.Member) first for namespace isolation,
* then falls back to simple name lookup.
* Returns 1 if resolved. */
static int spl_resolve_type_member(spl_comp_t *ctx, spl_type_info_t *type, const char *field,
spl_expr_result_t *result) {
/* For enum: check variants first */
if (type->kind == TYPE_ENUM) {
vec_for(type->variants, i) {
if (strcmp(vec_at(type->variants, i).name, field) == 0) {
spl_emit(ctx, SPL_PUSH, SPL_I32, vec_at(type->variants, i).value);
*result = (spl_expr_result_t){type, 0};
return 1;
}
}
}
/* Check nested types — try qualified name (Type.field) first for namespace isolation */
if (type->name) {
char qualified[512];
int qlen = snprintf(qualified, sizeof(qualified), "%s.%s", type->name, field);
if (qlen > 0 && (usize)qlen < sizeof(qualified)) {
spl_type_info_t *nested = spl_resolve_type(ctx, qualified);
if (nested) {
*result = (spl_expr_result_t){nested, 1};
return 1;
}
}
}
/* Fallback: try simple name lookup */
spl_type_info_t *nested = spl_resolve_type(ctx, field);
if (nested) {
*result = (spl_expr_result_t){nested, 1};
return 1;
}
return 0;
}
/* ============================================================
* SIR opcode for binary operator
* ============================================================ */
/* Map assignment operator token (e.g. +=) to corresponding binary operator (e.g. +) */
static spl_tok_type_t assign_to_binop(spl_tok_type_t t) {
switch (t) {
case TOK_ASSIGN_ADD:
return TOK_ADD;
case TOK_ASSIGN_SUB:
return TOK_SUB;
case TOK_ASSIGN_MUL:
return TOK_MUL;
case TOK_ASSIGN_DIV:
return TOK_DIV;
case TOK_ASSIGN_MOD:
return TOK_MOD;
case TOK_ASSIGN_AND:
return TOK_AND;
case TOK_ASSIGN_OR:
return TOK_OR;
case TOK_ASSIGN_XOR:
return TOK_XOR;
case TOK_ASSIGN_L_SH:
return TOK_L_SH;
case TOK_ASSIGN_R_SH:
return TOK_R_SH;
default:
return (spl_tok_type_t)-1;
}
}
static int binop_to_sir(spl_tok_type_t t, spl_type_t bt) {
int is_signed = (bt == SPL_I32 || bt == SPL_I64 || bt == SPL_I8 || bt == SPL_I16);
switch (t) {
case TOK_ADD:
return SPL_ADD;
case TOK_SUB:
return SPL_SUB;
case TOK_MUL:
return SPL_MUL;
case TOK_DIV:
return is_signed ? SPL_DIV_S : SPL_DIV_U;
case TOK_MOD:
return is_signed ? SPL_REM_S : SPL_REM_U;
case TOK_AND:
return SPL_AND;
case TOK_OR:
return SPL_OR;
case TOK_XOR:
return SPL_XOR;
case TOK_L_SH:
return SPL_SHL;
case TOK_R_SH:
return is_signed ? SPL_SHR_S : SPL_SHR_U;
case TOK_EQ:
return SPL_EQ;
case TOK_NEQ:
return SPL_NE;
case TOK_LT:
return is_signed ? SPL_SLT : SPL_ULT;
case TOK_LE:
return is_signed ? SPL_SLE : SPL_ULE;
case TOK_GT:
return is_signed ? SPL_SGT : SPL_UGT;
case TOK_GE:
return is_signed ? SPL_SGE : SPL_UGE;
default:
return -1;
}
}
/* ============================================================
* Parse integer literal from lexeme
* ============================================================ */
static int64_t parse_int(const char *s, usize len) {
char buf[64];
usize clen = len < 63 ? len : 63;
memcpy(buf, s, clen);
buf[clen] = '\0';
if (clen > 2 && buf[0] == '0') {
if (buf[1] == 'x' || buf[1] == 'X')
return (int64_t)strtoll(buf, NULL, 16);
if (buf[1] == 'b' || buf[1] == 'B')
return (int64_t)strtoll(buf + 2, NULL, 2);
if (buf[1] == 'o' || buf[1] == 'O')
return (int64_t)strtoll(buf + 2, NULL, 8);
}
return (int64_t)strtoll(buf, NULL, 10);
}
/* ============================================================
* Prefix expression parsers
* ============================================================ */
static spl_expr_result_t parse_int_literal(spl_comp_t *ctx) {
spl_tok_t *t = advance(ctx);
int64_t val = parse_int(t->lexeme, t->len);
spl_emit(ctx, SPL_PUSH, SPL_I32, (spl_val_t)val);
spl_expr_result_t r = {spl_type_basic(SPL_I32), 0};
return r;
}
static spl_expr_result_t parse_float_literal(spl_comp_t *ctx) {
spl_tok_t *t = advance(ctx);
char buf[64];
usize clen = t->len < 63 ? t->len : 63;
memcpy(buf, t->lexeme, clen);
buf[clen] = '\0';
double val = strtod(buf, NULL);
spl_emit(ctx, SPL_PUSH, SPL_F64, (spl_val_t)(int64_t)val);
(void)val;
spl_expr_result_t r = {spl_type_basic(SPL_F64), 0};
return r;
}
static spl_expr_result_t parse_char_literal(spl_comp_t *ctx) {
spl_tok_t *t = advance(ctx);
const char *s = t->lexeme;
usize l = t->len;
int64_t val = 0;
if (l >= 3) {
if (s[1] == '\\' && l >= 4) {
char buf = 0;
const char *cp = s + 1;
spl_decode_escape(&cp, &buf);
val = (unsigned char)buf;
} else {
val = (unsigned char)s[1];
}
}
spl_emit(ctx, SPL_PUSH, SPL_I32, (spl_val_t)val);
spl_expr_result_t r = {spl_type_basic(SPL_I32), 0};
return r;
}
static spl_expr_result_t parse_string_literal(spl_comp_t *ctx) {
spl_tok_t *t = advance(ctx);
usize slen = t->len;
if (slen >= 2) {
slen -= 2;
}
char *decoded = malloc(slen + 1);
usize di = 0;
for (usize i = 1; i + 1 < t->len; i++) {
if (t->lexeme[i] == '\\' && i + 1 < t->len - 1) {
char buf = 0;
const char *cp = t->lexeme + i;
spl_decode_escape(&cp, &buf);
decoded[di++] = buf;
i += (usize)(cp - (t->lexeme + i)) - 1;
} else {
decoded[di++] = t->lexeme[i];
}
}
decoded[di] = '\0';
int gdi = spl_add_global_data(ctx, decoded, di + 1);
free(decoded);
spl_emit(ctx, SPL_GADDR, SPL_PTR, gdi - 1);
spl_expr_result_t r = {spl_type_ptr(spl_type_basic(SPL_U8)), 0};
return r;
}
/* Parse array literal: [N]Type{val1, val2, ...} */
static spl_expr_result_t parse_array_literal(spl_comp_t *ctx) {
advance(ctx); /* skip [ */
skip_nl(ctx);
int len_val;
if (!spl_parse_int_literal(ctx, &len_val)) {
spl_comp_error(ctx, "expected array length");
spl_expr_result_t r = {0};
return r;
}
usize len = (usize)len_val;
skip_nl(ctx);
expect(ctx, TOK_R_BRACKET);
skip_nl(ctx);
spl_type_info_t *elem_type = spl_parse_type(ctx);
if (!elem_type) {
spl_expr_result_t r = {0};
return r;
}
skip_nl(ctx);
expect(ctx, TOK_L_BRACE);
skip_nl(ctx);
for (usize i = 0; i < len; i++) {
if (i > 0) {
if (peek(ctx)->type == TOK_COMMA)
advance(ctx);
skip_nl(ctx);
}
spl_parse_expr(ctx, PREC_MIN);
skip_nl(ctx);
}
if (peek(ctx)->type == TOK_COMMA)
advance(ctx);
skip_nl(ctx);
expect(ctx, TOK_R_BRACE);
spl_type_info_t *arr_type = spl_type_array(elem_type, len);
return (spl_expr_result_t){arr_type, 0};
}
/* Parse slice inline literal: { .ptr = expr, .len = expr }
* Expects base address on TOS, consumes it. */
static void parse_slice_inline(spl_comp_t *ctx) {
advance(ctx); /* { */
skip_nl(ctx);
while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) {
if (peek(ctx)->type == TOK_COMMA) {
advance(ctx);
skip_nl(ctx);
continue;
}
if (peek(ctx)->type == TOK_DOT)
advance(ctx);
spl_tok_t *ftok = advance(ctx);
char fname[256];
spl_tok_copy_name(ftok, fname, sizeof(fname));
skip_nl(ctx);
if (peek(ctx)->type == TOK_ASSIGN)
advance(ctx);
skip_nl(ctx);
spl_emit(ctx, SPL_DUP, SPL_VOID, 0);
if (strcmp(fname, "ptr") == 0) {
spl_parse_expr(ctx, PREC_MIN);
spl_emit(ctx, SPL_STORE, SPL_PTR, 0);
} else if (strcmp(fname, "len") == 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, sizeof(spl_val_t));
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
spl_parse_expr(ctx, PREC_MIN);
spl_emit(ctx, SPL_STORE, SPL_USIZE, 0);
}
skip_nl(ctx);
}
expect(ctx, TOK_R_BRACE);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
}
static void parse_one_field_init(spl_comp_t *ctx, spl_field_vec_t *fields, int base_offset,
usize extra_offset) {
if (peek(ctx)->type == TOK_DOT)
advance(ctx);
spl_tok_t *ftok = advance(ctx);
char fname[256];
spl_tok_copy_name(ftok, fname, sizeof(fname));
skip_nl(ctx);
if (peek(ctx)->type == TOK_ASSIGN)
advance(ctx);
skip_nl(ctx);
vec_for(*fields, fi) {
spl_field_t *f = &vec_at(*fields, fi);
if (strcmp(f->name, fname) == 0) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
usize byte_off = extra_offset + f->offset;
if (byte_off > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, byte_off);
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
if (f->type && f->type->kind == TYPE_SLICE && peek(ctx)->type == TOK_L_BRACE) {
parse_slice_inline(ctx);
} else if (f->type && f->type->kind == TYPE_ARRAY && peek(ctx)->type == TOK_L_BRACKET) {
advance(ctx);
skip_nl(ctx);
int len_val;
if (!spl_parse_int_literal(ctx, &len_val)) {
spl_comp_error(ctx, "expected array length");
return;
}
usize arr_len = (usize)len_val;
skip_nl(ctx);
expect(ctx, TOK_R_BRACKET);
skip_nl(ctx);
spl_type_info_t *elem_type = spl_parse_type(ctx);
skip_nl(ctx);
expect(ctx, TOK_L_BRACE);
skip_nl(ctx);
usize stride = spl_type_elem_stride(elem_type);
spl_type_t st = spl_type_emit_type(elem_type);
for (usize i = 0; i < arr_len; i++) {
if (i > 0) {
if (peek(ctx)->type == TOK_COMMA)
advance(ctx);
skip_nl(ctx);
}
spl_emit(ctx, SPL_DUP, SPL_VOID, 0);
if (i > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, i * stride);
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
spl_parse_expr(ctx, PREC_MIN);
spl_emit(ctx, SPL_STORE, st, 0);
skip_nl(ctx);
}
if (peek(ctx)->type == TOK_COMMA)
advance(ctx);
skip_nl(ctx);
expect(ctx, TOK_R_BRACE);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
} else if (f->type && (f->type->kind == TYPE_STRUCT || f->type->kind == TYPE_ENUM) &&
spl_type_size(f->type) > sizeof(spl_val_t)) {
spl_expr_result_t fv;
if (peek(ctx)->type == TOK_L_BRACE) {
fv = spl_parse_struct_literal(ctx, f->type);
} else {
fv = spl_parse_expr(ctx, PREC_MIN);
}
(void)fv;
usize nslots = (spl_type_size(f->type) + sizeof(spl_val_t) - 1) / sizeof(spl_val_t);
spl_emit_copy_slots(ctx, base_offset + (int)extra_offset + f->offset, nslots);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
} else {
spl_expr_result_t fv = spl_parse_expr(ctx, PREC_MIN);
(void)fv;
spl_emit(ctx, SPL_STORE, spl_type_emit_type(f->type), 0);
}
break;
}
}
}
spl_expr_result_t spl_parse_struct_literal(spl_comp_t *ctx, spl_type_info_t *type) {
usize sz = spl_type_size(type);
int base_offset = ctx->current_local_bytes;
ctx->current_local_bytes += (int)((sz + sizeof(spl_val_t) - 1) & ~(sizeof(spl_val_t) - 1));
if (ctx->current_local_bytes > ctx->peak_local_bytes)
ctx->peak_local_bytes = ctx->current_local_bytes;
if (ctx->current_local_bytes - base_offset < (int)sizeof(spl_val_t))
ctx->current_local_bytes = base_offset + (int)sizeof(spl_val_t);
if (ctx->current_local_bytes > ctx->peak_local_bytes)
ctx->peak_local_bytes = ctx->current_local_bytes;
advance(ctx); /* { */
skip_nl(ctx);
if (type->kind == TYPE_STRUCT) {
while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) {
if (peek(ctx)->type == TOK_COMMA) {
advance(ctx);
skip_nl(ctx);
continue;
}
if (peek(ctx)->type != TOK_DOT) {
if (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF)
advance(ctx);
skip_nl(ctx);
continue;
}
parse_one_field_init(ctx, &type->fields, base_offset, 0);
skip_nl(ctx);
}
} else if (type->kind == TYPE_SLICE) {
while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) {
if (peek(ctx)->type == TOK_COMMA) {
advance(ctx);
skip_nl(ctx);
continue;
}
if (peek(ctx)->type == TOK_DOT)
advance(ctx);
spl_tok_t *ftok = advance(ctx);
char fname[256];
spl_tok_copy_name(ftok, fname, sizeof(fname));
skip_nl(ctx);
if (peek(ctx)->type == TOK_ASSIGN)
advance(ctx);
skip_nl(ctx);
if (strcmp(fname, "ptr") == 0) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
spl_parse_expr(ctx, PREC_MIN);
spl_emit(ctx, SPL_STORE, SPL_PTR, 0);
} else if (strcmp(fname, "len") == 0) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
spl_emit(ctx, SPL_PUSH, SPL_USIZE, sizeof(spl_val_t));
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
spl_parse_expr(ctx, PREC_MIN);
spl_emit(ctx, SPL_STORE, SPL_USIZE, 0);
}
skip_nl(ctx);
}
} else if (type->kind == TYPE_ENUM) {
/* Parse variant name with optional type qualifier(s):
* .name, TypeName.name, or bare name (same pattern as
* spl_emit_match_enum_cmp for consistency) */
spl_tok_t *vtok;
if (peek(ctx)->type == TOK_DOT) {
advance(ctx);
vtok = advance(ctx);
} else {
vtok = advance(ctx);
while (peek(ctx)->type == TOK_DOT) {
advance(ctx);
vtok = advance(ctx);
}
}
char vname[256];
spl_tok_copy_name(vtok, vname, sizeof(vname));
skip_nl(ctx);
if (peek(ctx)->type == TOK_ASSIGN)
advance(ctx);
skip_nl(ctx);
int found = 0;
vec_for(type->variants, vi) {
spl_enum_variant_t *v = &vec_at(type->variants, vi);
if (strcmp(v->name, vname) == 0) {
found = 1;
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
spl_emit(ctx, SPL_PUSH, SPL_I32, v->value);
spl_emit(ctx, SPL_STORE, SPL_I32, 0);
const usize DATA_OFFSET = 4;
if (v->data_type) {
if (v->data_type->kind == TYPE_STRUCT && peek(ctx)->type == TOK_L_BRACE) {
advance(ctx); /* { */
skip_nl(ctx);
while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) {
if (peek(ctx)->type == TOK_COMMA) {
advance(ctx);
skip_nl(ctx);
continue;
}
if (peek(ctx)->type != TOK_DOT)
break;
parse_one_field_init(ctx, &v->data_type->fields, base_offset,
DATA_OFFSET);
skip_nl(ctx);
}
expect(ctx, TOK_R_BRACE);
} else if (v->data_type &&
(v->data_type->kind == TYPE_STRUCT ||
v->data_type->kind == TYPE_ENUM) &&
spl_type_size(v->data_type) > sizeof(spl_val_t)) {
spl_expr_result_t dv;
if (peek(ctx)->type == TOK_L_BRACE) {
dv = spl_parse_struct_literal(ctx, v->data_type);
} else {
dv = spl_parse_expr(ctx, PREC_MIN);
}
(void)dv;
usize nslots = (spl_type_size(v->data_type) + sizeof(spl_val_t) - 1) /
sizeof(spl_val_t);
spl_emit_copy_slots(ctx, base_offset + (int)DATA_OFFSET, nslots);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
} else {
spl_expr_result_t dv = spl_parse_expr(ctx, PREC_MIN);
(void)dv;
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
spl_emit(ctx, SPL_PUSH, SPL_USIZE, DATA_OFFSET);
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
spl_type_t bt = spl_type_emit_type(v->data_type);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, bt, 0);
}
}
break;
}
}
if (!found)
spl_comp_error(ctx, "unknown enum variant '%s'", vname);
}
skip_nl(ctx);
expect(ctx, TOK_R_BRACE);
if (sz <= sizeof(spl_val_t)) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
spl_emit(ctx, SPL_LOAD, spl_type_emit_type(type), 0);
return (spl_expr_result_t){type, 0};
} else {
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
return (spl_expr_result_t){type, 1};
}
}
static int parse_call_args(spl_comp_t *ctx) {
int nargs = 0;
if (peek(ctx)->type != TOK_R_PAREN) {
for (;;) {
spl_parse_expr(ctx, PREC_MIN);
nargs++;
if (peek(ctx)->type == TOK_COMMA) {
advance(ctx);
continue;
}
break;
}
}
return nargs;
}
static spl_expr_result_t parse_ident(spl_comp_t *ctx) {
spl_tok_t *t = advance(ctx);
char name[256];
spl_tok_copy_name(t, name, sizeof(name));
/* Check if it's a function call: ident(...) */
if (peek(ctx)->type == TOK_L_PAREN) {
int fi = spl_lookup_func(ctx, name);
if (fi < 0) {
spl_comp_error(ctx, "unknown function '%s'", name);
spl_expr_result_t r = {0};
return r;
}
spl_func_info_t *f = &vec_at(ctx->funcs, fi);
advance(ctx); /* skip ( */
int nargs = parse_call_args(ctx);
expect(ctx, TOK_R_PAREN);
if (f->is_extern) {
int nidx = spl_ensure_native(ctx, f->name);
spl_emit(ctx, SPL_PUSH, SPL_I32, nidx);
spl_emit(ctx, SPL_NCALL, SPL_VOID, nargs);
} else {
/* Record fixup for forward-reference resolution */
spl_val_t fixup_idx = vec_size(ctx->prog.insns);
spl_emit(ctx, SPL_PUSH, SPL_PTR, 0); /* placeholder address */
spl_emit(ctx, SPL_CALL, SPL_VOID, nargs);
vec_push(ctx->call_fixups, fixup_idx);
vec_push(ctx->call_fixup_funcs, f->func_idx);
}
spl_expr_result_t r = {f->ret_type, 0};
return r;
}
/* Variable reference */
spl_var_info_t *v = spl_lookup_var(ctx, name);
if (v) {
spl_val_t cv = 0;
if (map_get(ctx->const_values, name, &cv)) {
spl_emit(ctx, SPL_PUSH, spl_type_emit_type(v->type), cv);
spl_expr_result_t r = {v->type, 0};
return r;
}
spl_type_info_t *vt = v->type;
spl_emit(ctx, SPL_LADDR, SPL_PTR, v->offset);
spl_expr_result_t r;
emit_load_or_addr_type(ctx, &r, vt);
return r;
}
/* Check if it's a type name (for enum variant access like Color.Red) */
spl_type_info_t *ttype = spl_resolve_type(ctx, name);
if (ttype) {
if (peek(ctx)->type == TOK_L_BRACE &&
(ttype->kind == TYPE_STRUCT || ttype->kind == TYPE_ENUM)) {
return spl_parse_struct_literal(ctx, ttype);
}
spl_expr_result_t r = {ttype, 0};
return r;
}
spl_comp_error(ctx, "undefined variable '%s'", name);
spl_expr_result_t r = {0};
return r;
}
static spl_expr_result_t parse_group(spl_comp_t *ctx) {
advance(ctx); /* ( */
spl_expr_result_t r = spl_parse_expr(ctx, PREC_MIN);
expect(ctx, TOK_R_PAREN);
return r;
}
static void emit_load_or_addr_type(spl_comp_t *ctx, spl_expr_result_t *result,
spl_type_info_t *type) {
if (spl_type_is_scalar(type)) {
if (!ctx->addr_of_mode) {
spl_emit(ctx, SPL_LOAD, spl_type_emit_type(type), 0);
*result = (spl_expr_result_t){type, 0};
return;
}
}
*result = (spl_expr_result_t){type, 1};
}
static spl_expr_result_t parse_prefix_op(spl_comp_t *ctx) {
spl_tok_t *op = advance(ctx);
if (op->type == TOK_AND)
ctx->addr_of_mode = 1;
spl_expr_result_t right = spl_parse_expr(ctx, PREC_PREFIX);
if (op->type == TOK_AND)
ctx->addr_of_mode = 0;
switch (op->type) {
case TOK_SUB:
spl_emit(ctx, SPL_NEG, spl_type_emit_type(right.type), 0);
break;
case TOK_NOT:
spl_emit(ctx, SPL_PUSH, SPL_I32, 0);
spl_emit(ctx, SPL_EQ, spl_type_emit_type(right.type), 0);
break;
case TOK_BIT_NOT:
spl_emit(ctx, SPL_NOT, spl_type_emit_type(right.type), 0);
break;
case TOK_AND:
if (!right.is_lvalue) {
spl_comp_error(ctx, "cannot take address of rvalue");
}
break;
case TOK_MUL:
if (right.type && right.type->kind == TYPE_PTR && right.type->elem) {
if (right.is_lvalue) {
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
}
emit_load_or_addr_type(ctx, &right, right.type->elem);
}
break;
default:
break;
}
return right;
}
/* ============================================================
* Primary expression (prefix part of Pratt parser)
* ============================================================ */
static spl_expr_result_t parse_primary_expr(spl_comp_t *ctx) {
spl_tok_t *tok = peek(ctx);
if (!tok) {
spl_expr_result_t r = {0};
return r;
}
switch (tok->type) {
case TOK_INT_LITERAL:
return parse_int_literal(ctx);
case TOK_FLOAT_LITERAL:
return parse_float_literal(ctx);
case TOK_CHAR_LITERAL:
return parse_char_literal(ctx);
case TOK_STRING_LITERAL:
return parse_string_literal(ctx);
case KW_TRUE:
advance(ctx);
spl_emit(ctx, SPL_PUSH, SPL_I32, 1);
return (spl_expr_result_t){spl_type_basic(SPL_I32), 0};
case KW_FALSE:
advance(ctx);
spl_emit(ctx, SPL_PUSH, SPL_I32, 0);
return (spl_expr_result_t){spl_type_basic(SPL_I32), 0};
case KW_NULL:
advance(ctx);
spl_emit(ctx, SPL_PUSH, SPL_PTR, 0);
return (spl_expr_result_t){spl_type_basic(SPL_PTR), 0};
case TOK_IDENT:
case KW_BOOL:
case KW_VOID:
case KW_ANY:
return parse_ident(ctx);
case TOK_L_PAREN:
return parse_group(ctx);
case TOK_SUB:
case TOK_NOT:
case TOK_BIT_NOT:
case TOK_AND:
case TOK_MUL:
return parse_prefix_op(ctx);
case TOK_L_BRACKET:
return parse_array_literal(ctx);
case TOK_L_BRACE:
return spl_parse_block_expr(ctx);
case TOK_AT: {
advance(ctx);
skip_nl(ctx);
tok = peek(ctx);
if (!tok || tok->type != TOK_IDENT) {
spl_comp_error(ctx, "expected builtin name after '@'");
spl_expr_result_t r = {0};
return r;
}
char bname[256];
spl_tok_copy_name(tok, bname, sizeof bname);
advance(ctx);
if (strcmp(bname, "dbg") == 0) {
if (peek(ctx)->type == TOK_L_PAREN) {
advance(ctx);
int nargs = parse_call_args(ctx);
expect(ctx, TOK_R_PAREN);
for (int i = 0; i < nargs; i++) {
spl_emit(ctx, SPL_DBG, SPL_USIZE, 0);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
}
if (nargs == 0)
spl_emit(ctx, SPL_DBG, SPL_VOID, 0);
} else {
spl_emit(ctx, SPL_DBG, SPL_VOID, 0);
}
return (spl_expr_result_t){spl_type_basic(SPL_VOID), 0};
} else {
spl_comp_error(ctx, "unknown builtin '@%s'", bname);
spl_expr_result_t r = {0};
return r;
}
}
default:
if (peek(ctx)->type >= KW_AS && peek(ctx)->type <= KW_ANY) {
return parse_ident(ctx);
}
spl_expr_result_t r = {0};
return r;
}
}
/* ============================================================
* Postfix expression parser (.field, [index], [begin..end])
* ============================================================ */
static spl_expr_result_t parse_postfix_expr(spl_comp_t *ctx, spl_expr_result_t left) {
if (!left.type)
return left;
for (;;) {
skip_nl(ctx);
spl_tok_type_t opt = peek(ctx)->type;
/* Postfix . operator */
if (opt == TOK_DOT) {
advance(ctx);
spl_tok_t *field = advance(ctx);
char fname[256];
spl_tok_copy_name(field, fname, sizeof(fname));
/* Compile-time type member resolution (enum variants, nested types) */
if (left.type) {
spl_expr_result_t mresult = {0};
if (spl_resolve_type_member(ctx, left.type, fname, &mresult)) {
left = mresult;
continue;
}
}
/* Method call on type/instance: left.field(args) */
if (left.type && peek(ctx)->type == TOK_L_PAREN) {
spl_type_info_t *methods_type = left.type;
int is_ptr_self = 0;
if (methods_type->kind == TYPE_PTR && methods_type->elem &&
(methods_type->elem->kind == TYPE_STRUCT ||
methods_type->elem->kind == TYPE_ENUM)) {
is_ptr_self = 1;
methods_type = methods_type->elem;
}
int found_method = 0;
vec_for(methods_type->methods, mi) {
if (strcmp(vec_at(methods_type->methods, mi).name, fname) == 0) {
spl_method_info_t *method = &vec_at(methods_type->methods, mi);
spl_func_info_t *func = &vec_at(ctx->funcs, method->func_idx);
advance(ctx); /* ( */
found_method = 1;
int nargs = 0;
int is_instance = 0;
if (func->nparams > 0 && func->param_types[0] &&
func->param_types[0]->kind == TYPE_PTR &&
func->param_types[0]->elem == methods_type) {
is_instance = 1;
}
if (is_instance) {
if (!left.is_lvalue && !is_ptr_self) {
spl_comp_error(ctx, "cannot call instance method '%s' on type",
fname);
}
/* Drop implicit self from left operand — user passes
* self explicitly as first argument */
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
nargs = 0;
}
nargs += parse_call_args(ctx);
expect(ctx, TOK_R_PAREN);
/* Record fixup for forward-reference resolution */
spl_val_t fixup_idx = vec_size(ctx->prog.insns);
spl_emit(ctx, SPL_PUSH, SPL_PTR, 0); /* placeholder address */
spl_emit(ctx, SPL_CALL, SPL_VOID, nargs);
vec_push(ctx->call_fixups, fixup_idx);
vec_push(ctx->call_fixup_funcs, func->func_idx);
left = (spl_expr_result_t){func->ret_type, 0};
break;
}
}
if (found_method)
continue;
}
/* Struct field access */
if (left.type && left.type->kind == TYPE_STRUCT) {
vec_for(left.type->fields, fi) {
if (strcmp(vec_at(left.type->fields, fi).name, fname) == 0) {
spl_field_t *f = &vec_at(left.type->fields, fi);
if (f->offset > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, f->offset);
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
emit_load_or_addr_type(ctx, &left, f->type);
break;
}
}
continue;
}
/* Pointer auto-deref: if left is a pointer to struct, deref first */
if (left.type && left.type->kind == TYPE_PTR && left.type->elem &&
left.type->elem->kind == TYPE_STRUCT) {
spl_type_info_t *st = left.type->elem;
if (left.is_lvalue) {
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
}
vec_for(st->fields, fi) {
if (strcmp(vec_at(st->fields, fi).name, fname) == 0) {
spl_field_t *f = &vec_at(st->fields, fi);
if (f->offset > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, f->offset);
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
emit_load_or_addr_type(ctx, &left, f->type);
break;
}
}
continue;
}
/* Slice .len or .ptr */
if (left.type && left.type->kind == TYPE_SLICE) {
if (strcmp(fname, "len") == 0) {
if (ctx->addr_of_mode) {
spl_emit(ctx, SPL_PUSH, SPL_U64, (spl_val_t)sizeof(spl_val_t));
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
left = (spl_expr_result_t){spl_type_basic(SPL_USIZE), 1};
} else {
spl_emit(ctx, SPL_PUSH, SPL_U64, (spl_val_t)sizeof(spl_val_t));
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
spl_emit(ctx, SPL_LOAD, SPL_USIZE, 0);
left = (spl_expr_result_t){spl_type_basic(SPL_USIZE), 0};
}
} else if (strcmp(fname, "ptr") == 0) {
if (ctx->addr_of_mode) {
left = (spl_expr_result_t){left.type->elem ? spl_type_ptr(left.type->elem)
: spl_type_basic(SPL_PTR),
1};
} else {
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
left = (spl_expr_result_t){left.type->elem ? spl_type_ptr(left.type->elem)
: spl_type_basic(SPL_PTR),
0};
}
}
continue;
}
/* Postfix dereference: expr.* */
if (strcmp(fname, "*") == 0 && left.type && left.type->kind == TYPE_PTR &&
left.type->elem) {
if (left.is_lvalue) {
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
}
emit_load_or_addr_type(ctx, &left, left.type->elem);
continue;
}
spl_comp_error(ctx, "unknown field '%s'", fname);
continue;
}
/* Array/slice indexing: expr[expr] or expr[begin..end] */
if (opt == TOK_L_BRACKET) {
advance(ctx); /* skip [ */
if (peek(ctx)->type == TOK_R_BRACKET) {
advance(ctx);
continue;
}
spl_parse_expr(ctx, PREC_MIN);
if (peek(ctx)->type == TOK_RANGE) {
advance(ctx);
spl_expr_result_t end_expr = {0};
int has_explicit_end = (peek(ctx)->type != TOK_R_BRACKET);
if (has_explicit_end) {
end_expr = spl_parse_expr(ctx, PREC_MIN);
}
expect(ctx, TOK_R_BRACKET);
if (!has_explicit_end) {
if (left.type && left.type->kind == TYPE_ARRAY) {
spl_emit(ctx, SPL_PUSH, SPL_U64, left.type->array_len);
} else if (left.type && left.type->kind == TYPE_SLICE) {
spl_emit(ctx, SPL_PICK, SPL_VOID, 1);
spl_emit(ctx, SPL_PUSH, SPL_U64, sizeof(spl_val_t));
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
spl_emit(ctx, SPL_LOAD, SPL_USIZE, 0);
}
}
if (left.type && (left.type->kind == TYPE_ARRAY || left.type->kind == TYPE_SLICE)) {
usize stride = spl_type_elem_stride(left.type->elem);
if (left.type->kind == TYPE_SLICE) {
spl_emit(ctx, SPL_PICK, SPL_VOID, 2);
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
spl_emit(ctx, SPL_PICK, SPL_VOID, 2);
spl_emit(ctx, SPL_PUSH, SPL_U64, stride);
spl_emit(ctx, SPL_MUL, SPL_U64, 0);
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
spl_emit(ctx, SPL_PICK, SPL_VOID, 1);
spl_emit(ctx, SPL_PICK, SPL_VOID, 3);
spl_emit(ctx, SPL_SUB, SPL_USIZE, 0);
spl_emit(ctx, SPL_ROT, SPL_VOID, 0);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
spl_emit(ctx, SPL_ROT, SPL_VOID, 0);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
spl_emit(ctx, SPL_ROT, SPL_VOID, 0);
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
} else {
emit_slice_create(ctx, stride);
}
}
left = (spl_expr_result_t){left.type ? spl_type_slice(left.type->elem) : NULL, 0};
continue;
}
expect(ctx, TOK_R_BRACKET);
if (left.type && (left.type->kind == TYPE_ARRAY || left.type->kind == TYPE_PTR ||
left.type->kind == TYPE_SLICE)) {
spl_type_info_t *elem = left.type->elem;
if (left.type->kind == TYPE_SLICE) {
emit_slice_index(ctx, elem);
} else {
if (left.type->kind == TYPE_PTR && left.is_lvalue) {
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
}
usize stride = spl_type_elem_stride(elem);
spl_emit(ctx, SPL_PUSH, SPL_U64, stride);
spl_emit(ctx, SPL_MUL, SPL_U64, 0);
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
}
if (elem)
emit_load_or_addr_type(ctx, &left, elem);
}
continue;
}
break;
}
return left;
}
/* ============================================================
* Main expression parser (top-level)
* ============================================================ */
spl_expr_result_t spl_parse_expr(spl_comp_t *ctx, int min_prec) {
skip_nl(ctx);
spl_expr_result_t left = parse_primary_expr(ctx);
if (!left.type)
return left;
/* Infix parsing (precedence climbing) */
while (1) {
left = parse_postfix_expr(ctx, left);
skip_nl(ctx);
spl_tok_type_t opt = peek(ctx)->type;
/* Binary operators */
int prec = tok_prec(opt);
if (prec == 0 || prec < min_prec)
break;
advance(ctx);
left = parse_infix(ctx, left, opt);
}
return left;
}
/* ============================================================
* Infix operators
* ============================================================ */
static spl_expr_result_t parse_infix(spl_comp_t *ctx, spl_expr_result_t left, spl_tok_type_t op) {
int prec = tok_prec(op);
int next_prec = prec + 1;
/* Short-circuit logical operators */
if (op == TOK_AND_AND) {
spl_val_t bz_addr = spl_emit_bz(ctx);
spl_parse_expr(ctx, next_prec);
spl_patch_to_here(ctx, bz_addr);
return (spl_expr_result_t){spl_type_basic(SPL_I32), 0};
}
if (op == TOK_OR_OR) {
spl_val_t bnz_addr = spl_emit_bnz(ctx);
spl_parse_expr(ctx, next_prec);
spl_patch_to_here(ctx, bnz_addr);
return (spl_expr_result_t){spl_type_basic(SPL_I32), 0};
}
/* Assignment operators */
if (op == TOK_ASSIGN || op == TOK_ASSIGN_ADD || op == TOK_ASSIGN_SUB || op == TOK_ASSIGN_MUL ||
op == TOK_ASSIGN_DIV || op == TOK_ASSIGN_MOD || op == TOK_ASSIGN_AND ||
op == TOK_ASSIGN_OR || op == TOK_ASSIGN_XOR || op == TOK_ASSIGN_L_SH ||
op == TOK_ASSIGN_R_SH) {
int saved_addr_of_mode = ctx->addr_of_mode;
ctx->addr_of_mode = 0;
spl_expr_result_t right = spl_parse_expr(ctx, PREC_MIN);
ctx->addr_of_mode = saved_addr_of_mode;
if (left.is_lvalue) {
spl_type_t bt = spl_type_emit_type(left.type);
if (op == TOK_ASSIGN) {
spl_emit(ctx, SPL_STORE, bt, 0);
} else {
spl_emit(ctx, SPL_PICK, SPL_VOID, 1);
spl_emit(ctx, SPL_LOAD, bt, 0);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
int sop = binop_to_sir(assign_to_binop(op), bt);
if (sop >= 0)
spl_emit(ctx, sop, bt, 0);
spl_emit(ctx, SPL_STORE, bt, 0);
}
}
return right;
}
/* Regular binary op */
spl_parse_expr(ctx, next_prec);
/* Forbid comparison of non-scalar types (struct, enum-with-data) */
if ((op == TOK_EQ || op == TOK_NEQ) && left.type && !spl_type_is_scalar(left.type)) {
spl_comp_error(ctx, "type '%s' does not support comparison", spl_type_str(left.type));
}
spl_type_t bt = spl_type_emit_type(left.type);
int sop = binop_to_sir(op, bt);
if (sop >= 0) {
spl_emit(ctx, sop, bt, 0);
}
/* Comparisons return boolean (I32), other binary ops match operand type */
int is_compare = (op == TOK_EQ || op == TOK_NEQ || op == TOK_LT || op == TOK_GT ||
op == TOK_LE || op == TOK_GE);
spl_type_info_t *result_type = is_compare ? spl_type_basic(SPL_I32) : left.type;
return (spl_expr_result_t){result_type, 0};
}
/* ============================================================
* Match arm pattern comparison
*
* These functions handle the expression-level comparison between
* a saved match value and an arm pattern. They belong here in
* the expr layer so match arms compose as "enhanced if-conditions"
* rather than requiring stmt.c to emit raw comparison instructions.
* ============================================================ */
spl_enum_variant_t *spl_emit_match_enum_cmp(spl_comp_t *ctx, spl_type_info_t *enum_type,
int val_offset, int by_value) {
char vname[256];
/* Parse variant reference, reusing the same type resolution path
* as the expression parser (parse_ident + postfix DOT handler):
*
* .unknown — leading DOT shortcut
* unknown — bare variant name
* Tag.unknown — type-qualified (resolves Tag via spl_resolve_type)
* Token.Tag.unknown — fully qualified (walks all segments)
*
* Only the final segment is the variant name; preceding segments
* are optional type qualifiers resolved through the type system. */
if (peek(ctx)->type == TOK_DOT) {
advance(ctx); /* . */
} else {
/* Read first name; if followed by DOT, it's a type qualifier —
* verify it resolves via spl_resolve_type (same as parse_ident). */
spl_tok_t *tok = advance(ctx);
spl_tok_copy_name(tok, vname, sizeof(vname));
while (peek(ctx)->type == TOK_DOT) {
spl_type_info_t *qt = spl_resolve_type(ctx, vname);
(void)qt; /* qualifier verified through type system */
advance(ctx); /* . */
tok = advance(ctx);
spl_tok_copy_name(tok, vname, sizeof(vname));
}
/* Now vname holds the final segment — the variant name */
goto lookup;
}
{
spl_tok_t *vtok = advance(ctx);
spl_tok_copy_name(vtok, vname, sizeof(vname));
}
lookup:
vec_for(enum_type->variants, vi) {
spl_enum_variant_t *v = &vec_at(enum_type->variants, vi);
if (strcmp(v->name, vname) == 0) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, val_offset);
if (by_value) {
/* Scalar enum: raw tag stored directly in slot */
spl_emit(ctx, SPL_LOAD, SPL_I32, 0);
} else {
/* Enum with data or *Enum: slot has a pointer, dereference it */
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
spl_emit(ctx, SPL_PUSH, SPL_USIZE, 0);
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
spl_emit(ctx, SPL_LOAD, SPL_I32, 0);
}
spl_emit(ctx, SPL_PUSH, SPL_I32, v->value);
spl_emit(ctx, SPL_EQ, SPL_I32, 0);
return v;
}
}
spl_comp_error(ctx, "unknown variant '%s' in match", vname);
return NULL;
}
void spl_emit_match_value_cmp(spl_comp_t *ctx, int val_offset) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, val_offset);
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
spl_parse_expr(ctx, PREC_LOGOR);
spl_emit(ctx, SPL_EQ, SPL_I32, 0);
}
/* ============================================================
* Return instruction — emit SPL_RET with correct type
* ============================================================ */
void spl_emit_ret(spl_comp_t *ctx, spl_type_info_t *ret_type) {
spl_type_t rt = ret_type ? spl_type_emit_type(ret_type) : SPL_VOID;
spl_emit(ctx, SPL_RET, rt, 0);
}
/* ============================================================
* Variable init store — store value from stack to variable
* ============================================================ */
void spl_emit_store_init(spl_comp_t *ctx, int var_offset, spl_type_info_t *var_type) {
if (var_type && (var_type->kind == TYPE_STRUCT || var_type->kind == TYPE_ENUM)) {
usize sz = spl_type_size(var_type);
if (sz <= sizeof(spl_val_t)) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, spl_type_emit_type(var_type), 0);
} else {
usize nslots = (sz + sizeof(spl_val_t) - 1) / sizeof(spl_val_t);
spl_emit_copy_slots(ctx, var_offset, nslots);
}
} else if (var_type && var_type->kind == TYPE_ARRAY) {
spl_type_info_t *elem = var_type->elem;
spl_type_t bt = spl_type_emit_type(elem);
usize stride = spl_type_elem_stride(elem);
for (int i = (int)var_type->array_len - 1; i >= 0; i--) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset + (int)(i * stride));
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, bt, 0);
}
} else if (var_type && var_type->kind == TYPE_SLICE) {
spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset + (int)sizeof(spl_val_t));
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, SPL_USIZE, 0);
spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, SPL_PTR, 0);
} else {
spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset);
spl_type_t bt = spl_type_emit_type(var_type);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, bt, 0);
}
}