869 lines
33 KiB
C
869 lines
33 KiB
C
/* spl_expr.c — Expression parser + codegen (Pratt parser / precedence climbing) */
|
|
|
|
#include "spl_comp.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
/* ============================================================
|
|
* Token helpers
|
|
* ============================================================ */
|
|
|
|
static spl_tok_t *peek(spl_comp_t *ctx) {
|
|
return &vec_at(ctx->toks, ctx->tok_idx);
|
|
}
|
|
|
|
static spl_tok_t *advance(spl_comp_t *ctx) {
|
|
spl_tok_t *t = &vec_at(ctx->toks, ctx->tok_idx);
|
|
if (t->type != TOK_EOF) ctx->tok_idx++;
|
|
return t;
|
|
}
|
|
|
|
static int match(spl_comp_t *ctx, spl_tok_type_t type) {
|
|
if (peek(ctx)->type == type) { advance(ctx); return 1; }
|
|
return 0;
|
|
}
|
|
|
|
static int expect(spl_comp_t *ctx, spl_tok_type_t type) {
|
|
if (peek(ctx)->type == type) { advance(ctx); return 1; }
|
|
spl_comp_error(ctx, "expected '%s', got '%s'", spl_tok_type_name(type), spl_tok_type_name(peek(ctx)->type));
|
|
return 0;
|
|
}
|
|
|
|
/* Skip newline tokens */
|
|
static void skip_nl(spl_comp_t *ctx) {
|
|
while (peek(ctx)->type == TOK_ENDLINE) advance(ctx);
|
|
}
|
|
|
|
/* Check if current token starts a statement */
|
|
static int is_stmt_start(spl_comp_t *ctx) {
|
|
spl_tok_type_t t = peek(ctx)->type;
|
|
return t == TOK_EOF || t == TOK_R_BRACE ? 0 : 1;
|
|
}
|
|
|
|
/* ============================================================
|
|
* Precedence table
|
|
* ============================================================ */
|
|
|
|
static int tok_prec(spl_tok_type_t t) {
|
|
switch (t) {
|
|
case TOK_OR_OR: return PREC_LOGOR;
|
|
case TOK_AND_AND: return PREC_LOGAND;
|
|
case TOK_OR: return PREC_OR;
|
|
case TOK_XOR: return PREC_XOR;
|
|
case TOK_AND: return PREC_AND;
|
|
case TOK_EQ: case TOK_NEQ: return PREC_CMPEQ;
|
|
case TOK_LT: case TOK_LE: case TOK_GT: case TOK_GE: return PREC_CMP;
|
|
case TOK_L_SH: case TOK_R_SH: return PREC_SHIFT;
|
|
case TOK_ADD: case TOK_SUB: return PREC_ADD;
|
|
case TOK_MUL: case TOK_DIV: case TOK_MOD: return PREC_MUL;
|
|
case TOK_ASSIGN: case TOK_ASSIGN_ADD: case TOK_ASSIGN_SUB:
|
|
case TOK_ASSIGN_MUL: case TOK_ASSIGN_DIV: case TOK_ASSIGN_MOD:
|
|
case TOK_ASSIGN_AND: case TOK_ASSIGN_OR: case TOK_ASSIGN_XOR:
|
|
case TOK_ASSIGN_L_SH: case TOK_ASSIGN_R_SH:
|
|
return PREC_ASSIGN;
|
|
default: return PREC_MIN;
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
* Forward declarations
|
|
* ============================================================ */
|
|
|
|
static spl_expr_result_t parse_prefix(spl_comp_t *ctx);
|
|
static spl_expr_result_t parse_infix(spl_comp_t *ctx, spl_expr_result_t left, spl_tok_type_t op);
|
|
|
|
/* Slice creation from array/slice range expression.
|
|
* Stack in: [base_addr, begin, end]
|
|
* Stack out: [ptr, len]
|
|
*
|
|
* Uses pure stack operations — no temp slots needed.
|
|
* ptr = base + begin * stride
|
|
* len = end - begin
|
|
*
|
|
* Strategy: PICK copies of values we need, compute results on stack,
|
|
* then ROT inaccessible values to TOS and DROP them. */
|
|
static void emit_slice_create(spl_comp_t *ctx, usize stride) {
|
|
/* Stack: [base, begin, end] */
|
|
|
|
/* --- Compute ptr = base + begin * stride --- */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [base, begin, end, base] */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [base, begin, end, base, begin] */
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, stride);
|
|
spl_emit(ctx, SPL_MUL, SPL_U64, 0); /* [base, begin, end, base, begin*stride] */
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [base, begin, end, ptr] */
|
|
|
|
/* --- Compute len = end - begin --- */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* [base, begin, end, ptr, end] */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 3); /* [base, begin, end, ptr, end, begin] */
|
|
spl_emit(ctx, SPL_SUB, SPL_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 = elem ? elem->byte_size : 4;
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [index, slice_struct_addr] */
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); /* [index, data_ptr] */
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [data_ptr, index] */
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, elem_size);
|
|
spl_emit(ctx, SPL_MUL, SPL_U64, 0);
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [data_ptr + index * elem_size] */
|
|
}
|
|
|
|
/* ============================================================
|
|
* SIR opcode for binary operator
|
|
* ============================================================ */
|
|
|
|
static int binop_to_sir(spl_tok_type_t t, spl_type_t bt) {
|
|
int is_signed = (bt == SPL_I32 || bt == SPL_I64 || bt == SPL_I8 || bt == SPL_I16);
|
|
switch (t) {
|
|
case TOK_ADD: return SPL_ADD;
|
|
case TOK_SUB: return SPL_SUB;
|
|
case TOK_MUL: return SPL_MUL;
|
|
case TOK_DIV: return is_signed ? SPL_DIV_S : SPL_DIV_U;
|
|
case TOK_MOD: return is_signed ? SPL_REM_S : SPL_REM_U;
|
|
case TOK_AND: return SPL_AND;
|
|
case TOK_OR: return SPL_OR;
|
|
case TOK_XOR: return SPL_XOR;
|
|
case TOK_L_SH: return SPL_SHL;
|
|
case TOK_R_SH: return is_signed ? SPL_SHR_S : SPL_SHR_U;
|
|
case TOK_EQ: return SPL_EQ;
|
|
case TOK_NEQ: return SPL_NE;
|
|
case TOK_LT: return is_signed ? SPL_SLT : SPL_ULT;
|
|
case TOK_LE: return is_signed ? SPL_SLE : SPL_ULE;
|
|
case TOK_GT: return is_signed ? SPL_SGT : SPL_UGT;
|
|
case TOK_GE: return is_signed ? SPL_SGE : SPL_UGE;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
* Parse integer literal from lexeme
|
|
* ============================================================ */
|
|
|
|
static int64_t parse_int(const char *s, usize len) {
|
|
char buf[64];
|
|
usize clen = len < 63 ? len : 63;
|
|
memcpy(buf, s, clen); buf[clen] = '\0';
|
|
if (clen > 2 && buf[0] == '0') {
|
|
if (buf[1] == 'x' || buf[1] == 'X') return (int64_t)strtoll(buf, NULL, 16);
|
|
if (buf[1] == 'b' || buf[1] == 'B') return (int64_t)strtoll(buf + 2, NULL, 2);
|
|
if (buf[1] == 'o' || buf[1] == 'O') return (int64_t)strtoll(buf + 2, NULL, 8);
|
|
}
|
|
return (int64_t)strtoll(buf, NULL, 10);
|
|
}
|
|
|
|
/* ============================================================
|
|
* Prefix expression parsers
|
|
* ============================================================ */
|
|
|
|
static spl_expr_result_t parse_int_literal(spl_comp_t *ctx) {
|
|
spl_tok_t *t = advance(ctx);
|
|
int64_t val = parse_int(t->lexeme, t->len);
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, (spl_val_t)val);
|
|
spl_expr_result_t r = { spl_type_basic(SPL_I32), 0 };
|
|
return r;
|
|
}
|
|
|
|
static spl_expr_result_t parse_float_literal(spl_comp_t *ctx) {
|
|
spl_tok_t *t = advance(ctx);
|
|
char buf[64];
|
|
usize clen = t->len < 63 ? t->len : 63;
|
|
memcpy(buf, t->lexeme, clen); buf[clen] = '\0';
|
|
double val = strtod(buf, NULL);
|
|
spl_emit(ctx, SPL_PUSH, SPL_F64, (spl_val_t)(int64_t)val);
|
|
(void)val;
|
|
spl_expr_result_t r = { spl_type_basic(SPL_F64), 0 };
|
|
return r;
|
|
}
|
|
|
|
static spl_expr_result_t parse_char_literal(spl_comp_t *ctx) {
|
|
spl_tok_t *t = advance(ctx);
|
|
/* Lexeme: 'x' or '\n' etc, extract the character value */
|
|
const char *s = t->lexeme;
|
|
usize l = t->len;
|
|
int64_t val = 0;
|
|
if (l >= 3) {
|
|
if (s[1] == '\\' && l >= 4) {
|
|
/* Escape sequence */
|
|
char buf = 0;
|
|
const char *cp = s + 1;
|
|
spl_decode_escape(&cp, &buf);
|
|
val = (unsigned char)buf;
|
|
} else {
|
|
val = (unsigned char)s[1];
|
|
}
|
|
}
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, (spl_val_t)val);
|
|
spl_expr_result_t r = { spl_type_basic(SPL_I32), 0 };
|
|
return r;
|
|
}
|
|
|
|
static spl_expr_result_t parse_string_literal(spl_comp_t *ctx) {
|
|
spl_tok_t *t = advance(ctx);
|
|
/* Decode the string content (strip quotes, process escapes) */
|
|
usize slen = t->len;
|
|
if (slen >= 2) {
|
|
slen -= 2; /* remove outer quotes */
|
|
}
|
|
/* Build decoded string */
|
|
char *decoded = malloc(slen + 1);
|
|
usize di = 0;
|
|
for (usize i = 1; i + 1 < t->len; i++) {
|
|
if (t->lexeme[i] == '\\' && i + 1 < t->len - 1) {
|
|
char buf = 0;
|
|
const char *cp = t->lexeme + i;
|
|
spl_decode_escape(&cp, &buf);
|
|
decoded[di++] = buf;
|
|
i += (usize)(cp - (t->lexeme + i)) - 1;
|
|
} else {
|
|
decoded[di++] = t->lexeme[i];
|
|
}
|
|
}
|
|
decoded[di] = '\0';
|
|
/* Add to global data */
|
|
int gdi = spl_add_global_data(ctx, decoded, di + 1);
|
|
free(decoded);
|
|
spl_emit(ctx, SPL_GADDR, SPL_PTR, gdi - 1); /* gdi is 1-based from spl_prog_add_data */
|
|
spl_expr_result_t r = { spl_type_ptr(spl_type_basic(SPL_U8)), 0 };
|
|
return r;
|
|
}
|
|
|
|
/* Parse array literal: [N]Type{val1, val2, ...} */
|
|
static spl_expr_result_t parse_array_literal(spl_comp_t *ctx) {
|
|
advance(ctx); /* skip [ */
|
|
skip_nl(ctx);
|
|
|
|
/* Parse array length */
|
|
spl_tok_t *len_tok = advance(ctx);
|
|
if (len_tok->type != TOK_INT_LITERAL) {
|
|
spl_comp_error(ctx, "expected array length");
|
|
spl_expr_result_t r = {0}; return r;
|
|
}
|
|
usize len = (usize)strtoull(len_tok->lexeme, NULL, 0);
|
|
|
|
skip_nl(ctx);
|
|
expect(ctx, TOK_R_BRACKET);
|
|
skip_nl(ctx);
|
|
|
|
/* Parse element type */
|
|
spl_type_info_t *elem_type = spl_parse_type(ctx);
|
|
if (!elem_type) {
|
|
spl_expr_result_t r = {0}; return r;
|
|
}
|
|
|
|
skip_nl(ctx);
|
|
expect(ctx, TOK_L_BRACE);
|
|
skip_nl(ctx);
|
|
|
|
/* Parse each element value */
|
|
for (usize i = 0; i < len; i++) {
|
|
if (i > 0) {
|
|
if (peek(ctx)->type == TOK_COMMA) advance(ctx);
|
|
skip_nl(ctx);
|
|
}
|
|
spl_parse_expr(ctx, PREC_MIN);
|
|
skip_nl(ctx);
|
|
}
|
|
if (peek(ctx)->type == TOK_COMMA) advance(ctx); /* trailing comma */
|
|
|
|
skip_nl(ctx);
|
|
expect(ctx, TOK_R_BRACE);
|
|
|
|
spl_type_info_t *arr_type = spl_type_array(elem_type, len);
|
|
return (spl_expr_result_t){ arr_type, 0 };
|
|
}
|
|
|
|
/* Parse a type name with optional .field suffix for enum variants:
|
|
* TypeName
|
|
* TypeName.field
|
|
*/
|
|
static spl_type_info_t *parse_qualified_type(spl_comp_t *ctx, const char *name) {
|
|
/* Check if it's a known type */
|
|
spl_type_info_t *t = spl_resolve_type(ctx, name);
|
|
if (t && peek(ctx)->type == TOK_DOT) {
|
|
/* Enum type with variant: Type.variant */
|
|
advance(ctx); /* skip . */
|
|
spl_tok_t *vtok = advance(ctx);
|
|
char vname[256];
|
|
usize vn = vtok->len < 255 ? vtok->len : 255;
|
|
memcpy(vname, vtok->lexeme, vn); vname[vn] = '\0';
|
|
/* For enum values, just emit the tag as integer */
|
|
if (t->kind == TYPE_ENUM) {
|
|
vec_for(t->variants, vi) {
|
|
if (strcmp(vec_at(t->variants, vi).name, vname) == 0) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, vec_at(t->variants, vi).value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return t;
|
|
}
|
|
|
|
static spl_expr_result_t parse_ident(spl_comp_t *ctx) {
|
|
spl_tok_t *t = advance(ctx);
|
|
char name[256];
|
|
usize nlen = t->len < 255 ? t->len : 255;
|
|
memcpy(name, t->lexeme, nlen); name[nlen] = '\0';
|
|
|
|
/* Check if it's a function call: ident(...) */
|
|
if (peek(ctx)->type == TOK_L_PAREN) {
|
|
int fi = spl_lookup_func(ctx, name);
|
|
if (fi < 0) {
|
|
spl_comp_error(ctx, "unknown function '%s'", name);
|
|
spl_expr_result_t r = {0}; return r;
|
|
}
|
|
spl_func_info_t *f = &vec_at(ctx->funcs, fi);
|
|
advance(ctx); /* skip ( */
|
|
|
|
int nargs = 0;
|
|
if (peek(ctx)->type != TOK_R_PAREN) {
|
|
for (;;) {
|
|
spl_expr_result_t arg = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)arg;
|
|
nargs++;
|
|
if (peek(ctx)->type == TOK_COMMA) { advance(ctx); continue; }
|
|
break;
|
|
}
|
|
}
|
|
expect(ctx, TOK_R_PAREN);
|
|
|
|
if (f->is_extern) {
|
|
/* Find the native function index in prog */
|
|
int nidx = -1;
|
|
vec_for(ctx->prog.natives, ni) {
|
|
if (strcmp(vec_at(ctx->prog.natives, ni).name, f->name) == 0) {
|
|
nidx = (int)ni; break;
|
|
}
|
|
}
|
|
if (nidx < 0) {
|
|
/* Register it */
|
|
spl_native_t nat;
|
|
nat.name = strdup(f->name);
|
|
nat.idx_of_strtab = 0;
|
|
nat.impl_fn = NULL;
|
|
vec_push(ctx->prog.natives, nat);
|
|
nidx = (int)vec_size(ctx->prog.natives) - 1;
|
|
}
|
|
/* Push native index, then NCALL with imm = nargs */
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, nidx);
|
|
spl_emit(ctx, SPL_NCALL, SPL_VOID, nargs);
|
|
} else {
|
|
/* Regular function call: push func addr, then CALL */
|
|
spl_val_t addr = vec_at(ctx->prog.funcs, f->func_idx).address;
|
|
spl_emit(ctx, SPL_PUSH, SPL_PTR, addr);
|
|
spl_emit(ctx, SPL_CALL, SPL_VOID, nargs);
|
|
}
|
|
|
|
spl_expr_result_t r = { f->ret_type, 0 };
|
|
return r;
|
|
}
|
|
|
|
/* Variable reference */
|
|
spl_var_info_t *v = spl_lookup_var(ctx, name);
|
|
if (v) {
|
|
/* Check const values first */
|
|
spl_val_t cv = 0;
|
|
if (map_get(ctx->const_values, name, &cv)) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, cv);
|
|
spl_expr_result_t r = { v->type, 0 };
|
|
return r;
|
|
}
|
|
|
|
spl_type_info_t *vt = v->type;
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, v->slot);
|
|
|
|
if (vt->kind == TYPE_BASIC || vt->kind == TYPE_PTR) {
|
|
if (!ctx->addr_of_mode) {
|
|
/* Load value for basic types and pointers */
|
|
spl_type_t bt = (vt->kind == TYPE_BASIC) ? vt->basic_type : SPL_PTR;
|
|
spl_emit(ctx, SPL_LOAD, bt, 0);
|
|
spl_expr_result_t r = { vt, 0 };
|
|
return r;
|
|
}
|
|
/* In addr_of_mode, keep address on stack */
|
|
spl_expr_result_t r = { vt, 1 };
|
|
return r;
|
|
}
|
|
/* Array/struct/slice: address stays on stack */
|
|
spl_expr_result_t r = { vt, 1 };
|
|
return r;
|
|
}
|
|
|
|
spl_comp_error(ctx, "undefined variable '%s'", name);
|
|
spl_expr_result_t r = {0};
|
|
return r;
|
|
}
|
|
|
|
static spl_expr_result_t parse_group(spl_comp_t *ctx) {
|
|
advance(ctx); /* ( */
|
|
spl_expr_result_t r = spl_parse_expr(ctx, PREC_MIN);
|
|
expect(ctx, TOK_R_PAREN);
|
|
return r;
|
|
}
|
|
|
|
static spl_expr_result_t parse_prefix_op(spl_comp_t *ctx) {
|
|
spl_tok_t *op = advance(ctx);
|
|
|
|
if (op->type == TOK_AND) ctx->addr_of_mode = 1;
|
|
spl_expr_result_t right = spl_parse_expr(ctx, PREC_PREFIX);
|
|
if (op->type == TOK_AND) ctx->addr_of_mode = 0;
|
|
|
|
switch (op->type) {
|
|
case TOK_SUB:
|
|
spl_emit(ctx, SPL_NEG, SPL_I32, 0);
|
|
break;
|
|
case TOK_NOT:
|
|
/* !expr → EQ 0 */
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, 0);
|
|
spl_emit(ctx, SPL_EQ, SPL_I32, 0);
|
|
break;
|
|
case TOK_BIT_NOT:
|
|
spl_emit(ctx, SPL_NOT, SPL_I32, 0);
|
|
break;
|
|
case TOK_AND:
|
|
/* &expr — address-of, already an lvalue */
|
|
if (!right.is_lvalue) {
|
|
spl_comp_error(ctx, "cannot take address of rvalue");
|
|
}
|
|
break;
|
|
case TOK_MUL:
|
|
/* *expr — dereference */
|
|
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];
|
|
usize bnlen = tok->len < 255 ? tok->len : 255;
|
|
memcpy(bname, tok->lexeme, bnlen); bname[bnlen] = '\0';
|
|
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';
|
|
|
|
/* Check for enum variant: Type.Variant */
|
|
if (left.type && left.type->kind == TYPE_ENUM) {
|
|
/* It's a type-level enum reference */
|
|
vec_for(left.type->variants, vi) {
|
|
if (strcmp(vec_at(left.type->variants, vi).name, fname) == 0) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, vec_at(left.type->variants, vi).value);
|
|
left = (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 };
|
|
break;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Struct field access */
|
|
if (left.type && left.type->kind == TYPE_STRUCT) {
|
|
/* The struct address is on stack (as lvalue or from previous computation) */
|
|
vec_for(left.type->fields, fi) {
|
|
if (strcmp(vec_at(left.type->fields, fi).name, fname) == 0) {
|
|
spl_field_t *f = &vec_at(left.type->fields, fi);
|
|
if (f->offset > 0) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_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) {
|
|
/* Slice: 2 slots [ptr, len]. len is at offset sizeof(spl_val_t) */
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, (spl_val_t)sizeof(spl_val_t));
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
|
|
spl_emit(ctx, SPL_LOAD, SPL_USIZE, 0);
|
|
left = (spl_expr_result_t){ spl_type_basic(SPL_USIZE), 0 };
|
|
} else if (strcmp(fname, "ptr") == 0) {
|
|
/* ptr is at offset 0 */
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
|
|
left = (spl_expr_result_t){ left.type->elem ? spl_type_ptr(left.type->elem) : spl_type_basic(SPL_PTR), 0 };
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* 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..] */
|
|
int is_slice = 0;
|
|
usize slice_start = ctx->tok_idx;
|
|
spl_expr_result_t index = spl_parse_expr(ctx, PREC_MIN);
|
|
if (peek(ctx)->type == TOK_RANGE) {
|
|
is_slice = 1;
|
|
advance(ctx); /* skip .. */
|
|
spl_expr_result_t end_expr = {0};
|
|
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 = (left.type->kind == TYPE_ARRAY) ? sizeof(spl_val_t) : (left.type->elem ? left.type->elem->byte_size : 4);
|
|
|
|
/* For TYPE_SLICE: convert [struct_addr, begin, end] → [data_ptr, begin, end] */
|
|
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_ROT, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_ROT, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
|
|
}
|
|
|
|
emit_slice_create(ctx, stride);
|
|
}
|
|
left = (spl_expr_result_t){ left.type ? spl_type_slice(left.type->elem) : NULL, 0 };
|
|
continue;
|
|
}
|
|
|
|
expect(ctx, TOK_R_BRACKET);
|
|
|
|
/* Array/slice/pointer indexing */
|
|
if (left.type && (left.type->kind == TYPE_ARRAY || left.type->kind == TYPE_PTR || left.type->kind == TYPE_SLICE)) {
|
|
spl_type_info_t *elem = left.type->elem;
|
|
|
|
if (left.type->kind == TYPE_SLICE) {
|
|
/* Slice: stack has [slice_struct_addr, index].
|
|
* Load data ptr first, then compute element address. */
|
|
emit_slice_index(ctx, elem);
|
|
} else {
|
|
/* Stack arrays: each element occupies a full slot (sizeof(spl_val_t)) */
|
|
usize elem_size = (left.type->kind == TYPE_ARRAY) ? sizeof(spl_val_t) : (elem ? elem->byte_size : 4);
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, elem_size);
|
|
spl_emit(ctx, SPL_MUL, SPL_U64, 0);
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
|
|
}
|
|
if (elem && (elem->kind == TYPE_BASIC || elem->kind == TYPE_PTR)) {
|
|
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_expr_result_t right = spl_parse_expr(ctx, next_prec);
|
|
spl_patch_to_here(ctx, bz_addr);
|
|
return (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 };
|
|
}
|
|
if (op == TOK_OR_OR) {
|
|
/* If left is true (non-zero), skip right. */
|
|
spl_val_t bnz_addr = spl_emit_bnz(ctx);
|
|
spl_expr_result_t right = spl_parse_expr(ctx, next_prec);
|
|
spl_patch_to_here(ctx, bnz_addr);
|
|
return (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 };
|
|
}
|
|
|
|
/* Assignment operators */
|
|
if (op == TOK_ASSIGN || op == TOK_ASSIGN_ADD || op == TOK_ASSIGN_SUB ||
|
|
op == TOK_ASSIGN_MUL || op == TOK_ASSIGN_DIV || op == TOK_ASSIGN_MOD ||
|
|
op == TOK_ASSIGN_AND || op == TOK_ASSIGN_OR || op == TOK_ASSIGN_XOR ||
|
|
op == TOK_ASSIGN_L_SH || op == TOK_ASSIGN_R_SH) {
|
|
|
|
/* RHS must not inherit addr_of_mode from LHS */
|
|
int saved_addr_of_mode = ctx->addr_of_mode;
|
|
ctx->addr_of_mode = 0;
|
|
spl_expr_result_t right = spl_parse_expr(ctx, PREC_MIN);
|
|
ctx->addr_of_mode = saved_addr_of_mode;
|
|
|
|
if (left.is_lvalue) {
|
|
spl_type_t bt = left.type ?
|
|
(left.type->kind == TYPE_BASIC ? left.type->basic_type :
|
|
left.type->kind == TYPE_PTR ? SPL_PTR : SPL_I32) : SPL_I32;
|
|
if (op == TOK_ASSIGN) {
|
|
/* Simple assignment: stack is [addr, rhs] */
|
|
/* STORE pops TOS=value, TOS-1=address — already correct order */
|
|
spl_emit(ctx, SPL_STORE, bt, 0);
|
|
} else {
|
|
/* Compound: left = left op right — stack: [addr, rhs] */
|
|
spl_emit(ctx, SPL_DUP, SPL_VOID, 0); /* [addr, rhs, addr] */
|
|
spl_emit(ctx, SPL_LOAD, bt, 0); /* [addr, rhs, old_val] */
|
|
/* FIXME: compound needs to compute old_val op rhs, then store */
|
|
/* For now just drop old_val and store rhs */
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [addr, rhs] */
|
|
spl_emit(ctx, SPL_STORE, bt, 0);
|
|
}
|
|
}
|
|
return right;
|
|
}
|
|
|
|
/* Regular binary op */
|
|
spl_expr_result_t right = spl_parse_expr(ctx, next_prec);
|
|
int sop = binop_to_sir(op, left.type ? left.type->basic_type : SPL_I32);
|
|
spl_type_t bt = left.type && left.type->kind == TYPE_BASIC ? left.type->basic_type : SPL_I32;
|
|
if (sop >= 0) {
|
|
spl_emit(ctx, sop, bt, 0);
|
|
}
|
|
return (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 };
|
|
}
|