1355 lines
49 KiB
C
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);
|
|
}
|
|
}
|