1228 lines
46 KiB
C
1228 lines
46 KiB
C
/* spl_expr.c — Expression parser + codegen (Pratt parser / precedence climbing) */
|
|
|
|
#include "spl_comp.h"
|
|
#include "spl_lex_util.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* ============================================================
|
|
* Precedence table
|
|
* ============================================================ */
|
|
|
|
static int tok_prec(spl_tok_type_t t) {
|
|
switch (t) {
|
|
case TOK_OR_OR:
|
|
return PREC_LOGOR;
|
|
case TOK_AND_AND:
|
|
return PREC_LOGAND;
|
|
case TOK_OR:
|
|
return PREC_OR;
|
|
case TOK_XOR:
|
|
return PREC_XOR;
|
|
case TOK_AND:
|
|
return PREC_AND;
|
|
case TOK_EQ:
|
|
case TOK_NEQ:
|
|
return PREC_CMPEQ;
|
|
case TOK_LT:
|
|
case TOK_LE:
|
|
case TOK_GT:
|
|
case TOK_GE:
|
|
return PREC_CMP;
|
|
case TOK_L_SH:
|
|
case TOK_R_SH:
|
|
return PREC_SHIFT;
|
|
case TOK_ADD:
|
|
case TOK_SUB:
|
|
return PREC_ADD;
|
|
case TOK_MUL:
|
|
case TOK_DIV:
|
|
case TOK_MOD:
|
|
return PREC_MUL;
|
|
case TOK_ASSIGN:
|
|
case TOK_ASSIGN_ADD:
|
|
case TOK_ASSIGN_SUB:
|
|
case TOK_ASSIGN_MUL:
|
|
case TOK_ASSIGN_DIV:
|
|
case TOK_ASSIGN_MOD:
|
|
case TOK_ASSIGN_AND:
|
|
case TOK_ASSIGN_OR:
|
|
case TOK_ASSIGN_XOR:
|
|
case TOK_ASSIGN_L_SH:
|
|
case TOK_ASSIGN_R_SH:
|
|
return PREC_ASSIGN;
|
|
default:
|
|
return PREC_MIN;
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
* Forward declarations
|
|
* ============================================================ */
|
|
|
|
static spl_expr_result_t parse_infix(spl_comp_t *ctx, spl_expr_result_t left, spl_tok_type_t op);
|
|
|
|
/* Slice creation from array/slice range expression.
|
|
* Stack in: [base_addr, begin, end]
|
|
* Stack out: [ptr, len]
|
|
*
|
|
* Uses pure stack operations — no temp slots needed.
|
|
* ptr = base + begin * stride
|
|
* len = end - begin
|
|
*
|
|
* Strategy: PICK copies of values we need, compute results on stack,
|
|
* then ROT inaccessible values to TOS and DROP them. */
|
|
static void emit_slice_create(spl_comp_t *ctx, usize stride) {
|
|
/* Stack: [base, begin, end] */
|
|
|
|
/* --- Compute ptr = base + begin * stride --- */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [base, begin, end, base] */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [base, begin, end, base, begin] */
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, stride);
|
|
spl_emit(ctx, SPL_MUL, SPL_U64, 0); /* [base, begin, end, base, begin*stride] */
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [base, begin, end, ptr] */
|
|
|
|
/* --- Compute len = end - begin --- */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* [base, begin, end, ptr, end] */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 3); /* [base, begin, end, ptr, end, begin] */
|
|
spl_emit(ctx, SPL_SUB, SPL_USIZE, 0); /* [base, begin, end, ptr, len] */
|
|
|
|
/* --- Cleanup: drop [base, begin, end], keep [ptr, len] --- */
|
|
spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [base, begin, ptr, len, end] */
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [base, begin, ptr, len] */
|
|
spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [base, ptr, len, begin] */
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [base, ptr, len] */
|
|
spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [ptr, len, base] */
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [ptr, len] */
|
|
}
|
|
|
|
/* Emit code to access slice element by index.
|
|
* For TYPE_SLICE, the stack has [slice_struct_addr, index].
|
|
* We need to load the data ptr from the struct before indexing.
|
|
* Stack in: [slice_struct_addr, index]
|
|
* Stack out: [element_addr] */
|
|
static void emit_slice_index(spl_comp_t *ctx, spl_type_info_t *elem) {
|
|
usize elem_size = spl_type_elem_stride(elem);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [index, slice_struct_addr] */
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); /* [index, data_ptr] */
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [data_ptr, index] */
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, elem_size);
|
|
spl_emit(ctx, SPL_MUL, SPL_U64, 0);
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [data_ptr + index * elem_size] */
|
|
}
|
|
|
|
/* Try to resolve Type.Member for compile-time type access:
|
|
* - Enum variants → push variant integer value
|
|
* - Nested types (struct/enum/union) → return nested type
|
|
* Uses qualified name (TypeName.Member) first for namespace isolation,
|
|
* then falls back to simple name lookup.
|
|
* Returns 1 if resolved. */
|
|
static int spl_resolve_type_member(spl_comp_t *ctx, spl_type_info_t *type, const char *field,
|
|
spl_expr_result_t *result) {
|
|
/* For enum: check variants first */
|
|
if (type->kind == TYPE_ENUM) {
|
|
vec_for(type->variants, i) {
|
|
if (strcmp(vec_at(type->variants, i).name, field) == 0) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, vec_at(type->variants, i).value);
|
|
*result = (spl_expr_result_t){spl_type_basic(SPL_I32), 0};
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check nested types — try qualified name (Type.field) first for namespace isolation */
|
|
if (type->name) {
|
|
char qualified[512];
|
|
int qlen = snprintf(qualified, sizeof(qualified), "%s.%s", type->name, field);
|
|
if (qlen > 0 && (usize)qlen < sizeof(qualified)) {
|
|
spl_type_info_t *nested = spl_resolve_type(ctx, qualified);
|
|
if (nested) {
|
|
*result = (spl_expr_result_t){nested, 1};
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fallback: try simple name lookup */
|
|
spl_type_info_t *nested = spl_resolve_type(ctx, field);
|
|
if (nested) {
|
|
*result = (spl_expr_result_t){nested, 1};
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ============================================================
|
|
* SIR opcode for binary operator
|
|
* ============================================================ */
|
|
|
|
/* Map assignment operator token (e.g. +=) to corresponding binary operator (e.g. +) */
|
|
static spl_tok_type_t assign_to_binop(spl_tok_type_t t) {
|
|
switch (t) {
|
|
case TOK_ASSIGN_ADD:
|
|
return TOK_ADD;
|
|
case TOK_ASSIGN_SUB:
|
|
return TOK_SUB;
|
|
case TOK_ASSIGN_MUL:
|
|
return TOK_MUL;
|
|
case TOK_ASSIGN_DIV:
|
|
return TOK_DIV;
|
|
case TOK_ASSIGN_MOD:
|
|
return TOK_MOD;
|
|
case TOK_ASSIGN_AND:
|
|
return TOK_AND;
|
|
case TOK_ASSIGN_OR:
|
|
return TOK_OR;
|
|
case TOK_ASSIGN_XOR:
|
|
return TOK_XOR;
|
|
case TOK_ASSIGN_L_SH:
|
|
return TOK_L_SH;
|
|
case TOK_ASSIGN_R_SH:
|
|
return TOK_R_SH;
|
|
default:
|
|
return (spl_tok_type_t)-1;
|
|
}
|
|
}
|
|
|
|
static int binop_to_sir(spl_tok_type_t t, spl_type_t bt) {
|
|
int is_signed = (bt == SPL_I32 || bt == SPL_I64 || bt == SPL_I8 || bt == SPL_I16);
|
|
switch (t) {
|
|
case TOK_ADD:
|
|
return SPL_ADD;
|
|
case TOK_SUB:
|
|
return SPL_SUB;
|
|
case TOK_MUL:
|
|
return SPL_MUL;
|
|
case TOK_DIV:
|
|
return is_signed ? SPL_DIV_S : SPL_DIV_U;
|
|
case TOK_MOD:
|
|
return is_signed ? SPL_REM_S : SPL_REM_U;
|
|
case TOK_AND:
|
|
return SPL_AND;
|
|
case TOK_OR:
|
|
return SPL_OR;
|
|
case TOK_XOR:
|
|
return SPL_XOR;
|
|
case TOK_L_SH:
|
|
return SPL_SHL;
|
|
case TOK_R_SH:
|
|
return is_signed ? SPL_SHR_S : SPL_SHR_U;
|
|
case TOK_EQ:
|
|
return SPL_EQ;
|
|
case TOK_NEQ:
|
|
return SPL_NE;
|
|
case TOK_LT:
|
|
return is_signed ? SPL_SLT : SPL_ULT;
|
|
case TOK_LE:
|
|
return is_signed ? SPL_SLE : SPL_ULE;
|
|
case TOK_GT:
|
|
return is_signed ? SPL_SGT : SPL_UGT;
|
|
case TOK_GE:
|
|
return is_signed ? SPL_SGE : SPL_UGE;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
* Parse integer literal from lexeme
|
|
* ============================================================ */
|
|
|
|
static int64_t parse_int(const char *s, usize len) {
|
|
char buf[64];
|
|
usize clen = len < 63 ? len : 63;
|
|
memcpy(buf, s, clen);
|
|
buf[clen] = '\0';
|
|
if (clen > 2 && buf[0] == '0') {
|
|
if (buf[1] == 'x' || buf[1] == 'X')
|
|
return (int64_t)strtoll(buf, NULL, 16);
|
|
if (buf[1] == 'b' || buf[1] == 'B')
|
|
return (int64_t)strtoll(buf + 2, NULL, 2);
|
|
if (buf[1] == 'o' || buf[1] == 'O')
|
|
return (int64_t)strtoll(buf + 2, NULL, 8);
|
|
}
|
|
return (int64_t)strtoll(buf, NULL, 10);
|
|
}
|
|
|
|
/* ============================================================
|
|
* Prefix expression parsers
|
|
* ============================================================ */
|
|
|
|
static spl_expr_result_t parse_int_literal(spl_comp_t *ctx) {
|
|
spl_tok_t *t = advance(ctx);
|
|
int64_t val = parse_int(t->lexeme, t->len);
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, (spl_val_t)val);
|
|
spl_expr_result_t r = {spl_type_basic(SPL_I32), 0};
|
|
return r;
|
|
}
|
|
|
|
static spl_expr_result_t parse_float_literal(spl_comp_t *ctx) {
|
|
spl_tok_t *t = advance(ctx);
|
|
char buf[64];
|
|
usize clen = t->len < 63 ? t->len : 63;
|
|
memcpy(buf, t->lexeme, clen);
|
|
buf[clen] = '\0';
|
|
double val = strtod(buf, NULL);
|
|
spl_emit(ctx, SPL_PUSH, SPL_F64, (spl_val_t)(int64_t)val);
|
|
(void)val;
|
|
spl_expr_result_t r = {spl_type_basic(SPL_F64), 0};
|
|
return r;
|
|
}
|
|
|
|
static spl_expr_result_t parse_char_literal(spl_comp_t *ctx) {
|
|
spl_tok_t *t = advance(ctx);
|
|
/* Lexeme: 'x' or '\n' etc, extract the character value */
|
|
const char *s = t->lexeme;
|
|
usize l = t->len;
|
|
int64_t val = 0;
|
|
if (l >= 3) {
|
|
if (s[1] == '\\' && l >= 4) {
|
|
/* Escape sequence */
|
|
char buf = 0;
|
|
const char *cp = s + 1;
|
|
spl_decode_escape(&cp, &buf);
|
|
val = (unsigned char)buf;
|
|
} else {
|
|
val = (unsigned char)s[1];
|
|
}
|
|
}
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, (spl_val_t)val);
|
|
spl_expr_result_t r = {spl_type_basic(SPL_I32), 0};
|
|
return r;
|
|
}
|
|
|
|
static spl_expr_result_t parse_string_literal(spl_comp_t *ctx) {
|
|
spl_tok_t *t = advance(ctx);
|
|
/* Decode the string content (strip quotes, process escapes) */
|
|
usize slen = t->len;
|
|
if (slen >= 2) {
|
|
slen -= 2; /* remove outer quotes */
|
|
}
|
|
/* Build decoded string */
|
|
char *decoded = malloc(slen + 1);
|
|
usize di = 0;
|
|
for (usize i = 1; i + 1 < t->len; i++) {
|
|
if (t->lexeme[i] == '\\' && i + 1 < t->len - 1) {
|
|
char buf = 0;
|
|
const char *cp = t->lexeme + i;
|
|
spl_decode_escape(&cp, &buf);
|
|
decoded[di++] = buf;
|
|
i += (usize)(cp - (t->lexeme + i)) - 1;
|
|
} else {
|
|
decoded[di++] = t->lexeme[i];
|
|
}
|
|
}
|
|
decoded[di] = '\0';
|
|
/* Add to global data */
|
|
int gdi = spl_add_global_data(ctx, decoded, di + 1);
|
|
free(decoded);
|
|
spl_emit(ctx, SPL_GADDR, SPL_PTR, gdi - 1); /* gdi is 1-based from spl_prog_add_data */
|
|
spl_expr_result_t r = {spl_type_ptr(spl_type_basic(SPL_U8)), 0};
|
|
return r;
|
|
}
|
|
|
|
/* Parse array literal: [N]Type{val1, val2, ...} */
|
|
static spl_expr_result_t parse_array_literal(spl_comp_t *ctx) {
|
|
advance(ctx); /* skip [ */
|
|
skip_nl(ctx);
|
|
|
|
/* Parse array length */
|
|
spl_tok_t *len_tok = advance(ctx);
|
|
if (len_tok->type != TOK_INT_LITERAL) {
|
|
spl_comp_error(ctx, "expected array length");
|
|
spl_expr_result_t r = {0};
|
|
return r;
|
|
}
|
|
usize len = (usize)strtoull(len_tok->lexeme, NULL, 0);
|
|
|
|
skip_nl(ctx);
|
|
expect(ctx, TOK_R_BRACKET);
|
|
skip_nl(ctx);
|
|
|
|
/* Parse element type */
|
|
spl_type_info_t *elem_type = spl_parse_type(ctx);
|
|
if (!elem_type) {
|
|
spl_expr_result_t r = {0};
|
|
return r;
|
|
}
|
|
|
|
skip_nl(ctx);
|
|
expect(ctx, TOK_L_BRACE);
|
|
skip_nl(ctx);
|
|
|
|
/* Parse each element value */
|
|
for (usize i = 0; i < len; i++) {
|
|
if (i > 0) {
|
|
if (peek(ctx)->type == TOK_COMMA)
|
|
advance(ctx);
|
|
skip_nl(ctx);
|
|
}
|
|
spl_parse_expr(ctx, PREC_MIN);
|
|
skip_nl(ctx);
|
|
}
|
|
if (peek(ctx)->type == TOK_COMMA)
|
|
advance(ctx); /* trailing comma */
|
|
|
|
skip_nl(ctx);
|
|
expect(ctx, TOK_R_BRACE);
|
|
|
|
spl_type_info_t *arr_type = spl_type_array(elem_type, len);
|
|
return (spl_expr_result_t){arr_type, 0};
|
|
}
|
|
|
|
/* Determine correct STORE type for a type (handles ptr = 8 bytes). */
|
|
static spl_type_t spl_store_type(spl_type_info_t *type) {
|
|
if (!type)
|
|
return SPL_I32;
|
|
if (type->kind == TYPE_BASIC)
|
|
return type->basic_type;
|
|
if (type->kind == TYPE_PTR)
|
|
return SPL_PTR;
|
|
if (spl_type_size(type) <= sizeof(spl_val_t))
|
|
return SPL_PTR;
|
|
return SPL_I32;
|
|
}
|
|
|
|
/* Parse struct/enum literal: Type { .field = val, ... }
|
|
* Allocates temp slots for the value, returns lvalue (addr on stack).
|
|
* For types fitting in one slot, pushes the packed value directly. */
|
|
static spl_expr_result_t parse_struct_literal(spl_comp_t *ctx, spl_type_info_t *type) {
|
|
usize sz = spl_type_size(type);
|
|
int base_offset = ctx->current_local_bytes;
|
|
ctx->current_local_bytes += (int)((sz + sizeof(spl_val_t) - 1) & ~(sizeof(spl_val_t) - 1));
|
|
if (ctx->current_local_bytes - base_offset < (int)sizeof(spl_val_t))
|
|
ctx->current_local_bytes = base_offset + (int)sizeof(spl_val_t);
|
|
|
|
advance(ctx); /* { */
|
|
skip_nl(ctx);
|
|
|
|
if (type->kind == TYPE_STRUCT) {
|
|
while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) {
|
|
if (peek(ctx)->type == TOK_COMMA) {
|
|
advance(ctx);
|
|
skip_nl(ctx);
|
|
continue;
|
|
}
|
|
if (peek(ctx)->type == TOK_DOT) {
|
|
advance(ctx);
|
|
spl_tok_t *ftok = advance(ctx);
|
|
char fname[256];
|
|
usize fnl = ftok->len < 255 ? ftok->len : 255;
|
|
memcpy(fname, ftok->lexeme, fnl);
|
|
fname[fnl] = '\0';
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == TOK_ASSIGN)
|
|
advance(ctx);
|
|
skip_nl(ctx);
|
|
|
|
vec_for(type->fields, fi) {
|
|
spl_field_t *f = &vec_at(type->fields, fi);
|
|
if (strcmp(f->name, fname) == 0) {
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
|
|
if (f->offset > 0) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_USIZE, f->offset);
|
|
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
|
|
}
|
|
spl_expr_result_t fv = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)fv;
|
|
spl_emit(ctx, SPL_STORE, spl_store_type(f->type), 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
skip_nl(ctx);
|
|
}
|
|
} else if (type->kind == TYPE_ENUM) {
|
|
/* Parse .Variant [= value] */
|
|
if (peek(ctx)->type == TOK_DOT)
|
|
advance(ctx);
|
|
spl_tok_t *vtok = advance(ctx);
|
|
char vname[256];
|
|
usize vnl = vtok->len < 255 ? vtok->len : 255;
|
|
memcpy(vname, vtok->lexeme, vnl);
|
|
vname[vnl] = '\0';
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == TOK_ASSIGN)
|
|
advance(ctx);
|
|
skip_nl(ctx);
|
|
|
|
int found = 0;
|
|
vec_for(type->variants, vi) {
|
|
spl_enum_variant_t *v = &vec_at(type->variants, vi);
|
|
if (strcmp(v->name, vname) == 0) {
|
|
found = 1;
|
|
/* Store tag at offset 0 */
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, v->value);
|
|
spl_emit(ctx, SPL_STORE, SPL_I32, 0);
|
|
|
|
/* Store variant data at offset 4 */
|
|
const usize DATA_OFFSET = 4;
|
|
if (v->data_type) {
|
|
if (v->data_type->kind == TYPE_STRUCT && peek(ctx)->type == TOK_L_BRACE) {
|
|
/* Struct data: { .field = val, ... } */
|
|
advance(ctx); /* { */
|
|
skip_nl(ctx);
|
|
while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) {
|
|
if (peek(ctx)->type == TOK_COMMA) {
|
|
advance(ctx);
|
|
skip_nl(ctx);
|
|
continue;
|
|
}
|
|
if (peek(ctx)->type == TOK_DOT)
|
|
advance(ctx);
|
|
spl_tok_t *sftok = advance(ctx);
|
|
char sfname[256];
|
|
usize sfnl = sftok->len < 255 ? sftok->len : 255;
|
|
memcpy(sfname, sftok->lexeme, sfnl);
|
|
sfname[sfnl] = '\0';
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == TOK_ASSIGN)
|
|
advance(ctx);
|
|
skip_nl(ctx);
|
|
|
|
vec_for(v->data_type->fields, sfi) {
|
|
spl_field_t *sf = &vec_at(v->data_type->fields, sfi);
|
|
if (strcmp(sf->name, sfname) == 0) {
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
|
|
usize byte_off = DATA_OFFSET + sf->offset;
|
|
if (byte_off > 0) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_USIZE, byte_off);
|
|
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
|
|
}
|
|
spl_expr_result_t sfv = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)sfv;
|
|
spl_emit(ctx, SPL_STORE, spl_store_type(sf->type), 0);
|
|
break;
|
|
}
|
|
}
|
|
skip_nl(ctx);
|
|
}
|
|
expect(ctx, TOK_R_BRACE);
|
|
} else {
|
|
/* Simple data: parse expression */
|
|
spl_expr_result_t dv = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)dv;
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
|
|
spl_emit(ctx, SPL_PUSH, SPL_USIZE, DATA_OFFSET);
|
|
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
|
|
spl_type_t bt = spl_store_type(v->data_type);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, bt, 0);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
spl_comp_error(ctx, "unknown enum variant '%s'", vname);
|
|
}
|
|
|
|
skip_nl(ctx);
|
|
expect(ctx, TOK_R_BRACE);
|
|
|
|
/* Return: if size fits in one slot, push packed value. Otherwise push address. */
|
|
if (sz <= sizeof(spl_val_t)) {
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
|
|
return (spl_expr_result_t){type, 0}; /* value on stack */
|
|
} else {
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, base_offset);
|
|
return (spl_expr_result_t){type, 1}; /* address on stack */
|
|
}
|
|
}
|
|
|
|
static spl_expr_result_t parse_ident(spl_comp_t *ctx) {
|
|
spl_tok_t *t = advance(ctx);
|
|
char name[256];
|
|
usize nlen = t->len < 255 ? t->len : 255;
|
|
memcpy(name, t->lexeme, nlen);
|
|
name[nlen] = '\0';
|
|
|
|
/* Check if it's a function call: ident(...) */
|
|
if (peek(ctx)->type == TOK_L_PAREN) {
|
|
int fi = spl_lookup_func(ctx, name);
|
|
/* Fallback: try qualified name for short-name resolution inside methods */
|
|
if (fi < 0 && ctx->current_type_name) {
|
|
char qualified[512];
|
|
snprintf(qualified, sizeof(qualified), "%s.%s", ctx->current_type_name, name);
|
|
fi = spl_lookup_func(ctx, qualified);
|
|
}
|
|
if (fi < 0) {
|
|
spl_comp_error(ctx, "unknown function '%s'", name);
|
|
spl_expr_result_t r = {0};
|
|
return r;
|
|
}
|
|
spl_func_info_t *f = &vec_at(ctx->funcs, fi);
|
|
advance(ctx); /* skip ( */
|
|
|
|
int nargs = 0;
|
|
if (peek(ctx)->type != TOK_R_PAREN) {
|
|
for (;;) {
|
|
spl_expr_result_t arg = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)arg;
|
|
nargs++;
|
|
if (peek(ctx)->type == TOK_COMMA) {
|
|
advance(ctx);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
expect(ctx, TOK_R_PAREN);
|
|
|
|
if (f->is_extern) {
|
|
/* Find the native function index in prog */
|
|
int nidx = -1;
|
|
vec_for(ctx->prog.natives, ni) {
|
|
if (strcmp(vec_at(ctx->prog.natives, ni).name, f->name) == 0) {
|
|
nidx = (int)ni;
|
|
break;
|
|
}
|
|
}
|
|
if (nidx < 0) {
|
|
/* Register it */
|
|
spl_native_t nat;
|
|
nat.name = strdup(f->name);
|
|
nat.idx_of_strtab = 0;
|
|
nat.impl_fn = NULL;
|
|
vec_push(ctx->prog.natives, nat);
|
|
nidx = (int)vec_size(ctx->prog.natives) - 1;
|
|
}
|
|
/* Push native index, then NCALL with imm = nargs */
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, nidx);
|
|
spl_emit(ctx, SPL_NCALL, SPL_VOID, nargs);
|
|
} else {
|
|
/* Regular function call: push func addr, then CALL */
|
|
spl_val_t addr = vec_at(ctx->prog.funcs, f->func_idx).address;
|
|
spl_emit(ctx, SPL_PUSH, SPL_PTR, addr);
|
|
spl_emit(ctx, SPL_CALL, SPL_VOID, nargs);
|
|
}
|
|
|
|
spl_expr_result_t r = {f->ret_type, 0};
|
|
return r;
|
|
}
|
|
|
|
/* Variable reference */
|
|
spl_var_info_t *v = spl_lookup_var(ctx, name);
|
|
if (v) {
|
|
/* Check const values first */
|
|
spl_val_t cv = 0;
|
|
if (map_get(ctx->const_values, name, &cv)) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, cv);
|
|
spl_expr_result_t r = {v->type, 0};
|
|
return r;
|
|
}
|
|
|
|
spl_type_info_t *vt = v->type;
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, v->offset);
|
|
|
|
if (vt->kind == TYPE_BASIC || vt->kind == TYPE_PTR) {
|
|
if (!ctx->addr_of_mode) {
|
|
/* Load value for basic types and pointers */
|
|
spl_type_t bt = (vt->kind == TYPE_BASIC) ? vt->basic_type : SPL_PTR;
|
|
spl_emit(ctx, SPL_LOAD, bt, 0);
|
|
spl_expr_result_t r = {vt, 0};
|
|
return r;
|
|
}
|
|
/* In addr_of_mode, keep address on stack */
|
|
spl_expr_result_t r = {vt, 1};
|
|
return r;
|
|
}
|
|
/* Array/struct/slice: address stays on stack */
|
|
spl_expr_result_t r = {vt, 1};
|
|
return r;
|
|
}
|
|
|
|
/* Check if it's a type name (for enum variant access like Color.Red) */
|
|
spl_type_info_t *ttype = spl_resolve_type(ctx, name);
|
|
if (ttype) {
|
|
/* Struct/enum literal: Type { .field = val, ... } */
|
|
if (peek(ctx)->type == TOK_L_BRACE &&
|
|
(ttype->kind == TYPE_STRUCT || ttype->kind == TYPE_ENUM)) {
|
|
return parse_struct_literal(ctx, ttype);
|
|
}
|
|
spl_expr_result_t r = {ttype, 0};
|
|
return r;
|
|
}
|
|
|
|
spl_comp_error(ctx, "undefined variable '%s'", name);
|
|
spl_expr_result_t r = {0};
|
|
return r;
|
|
}
|
|
|
|
static spl_expr_result_t parse_group(spl_comp_t *ctx) {
|
|
advance(ctx); /* ( */
|
|
spl_expr_result_t r = spl_parse_expr(ctx, PREC_MIN);
|
|
expect(ctx, TOK_R_PAREN);
|
|
return r;
|
|
}
|
|
|
|
static spl_expr_result_t parse_prefix_op(spl_comp_t *ctx) {
|
|
spl_tok_t *op = advance(ctx);
|
|
|
|
if (op->type == TOK_AND)
|
|
ctx->addr_of_mode = 1;
|
|
spl_expr_result_t right = spl_parse_expr(ctx, PREC_PREFIX);
|
|
if (op->type == TOK_AND)
|
|
ctx->addr_of_mode = 0;
|
|
|
|
switch (op->type) {
|
|
case TOK_SUB:
|
|
spl_emit(ctx, SPL_NEG, SPL_I32, 0);
|
|
break;
|
|
case TOK_NOT:
|
|
/* !expr → EQ 0 */
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, 0);
|
|
spl_emit(ctx, SPL_EQ, SPL_I32, 0);
|
|
break;
|
|
case TOK_BIT_NOT:
|
|
spl_emit(ctx, SPL_NOT, SPL_I32, 0);
|
|
break;
|
|
case TOK_AND:
|
|
/* &expr — address-of, already an lvalue */
|
|
if (!right.is_lvalue) {
|
|
spl_comp_error(ctx, "cannot take address of rvalue");
|
|
}
|
|
break;
|
|
case TOK_MUL:
|
|
/* *expr — dereference */
|
|
if (right.type && right.type->kind == TYPE_PTR && right.type->elem) {
|
|
/* If right is still an lvalue, load the pointer value for the target */
|
|
if (right.is_lvalue) {
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
|
|
}
|
|
spl_type_info_t *elem = right.type->elem;
|
|
if (elem->kind == TYPE_BASIC || elem->kind == TYPE_PTR) {
|
|
if (!ctx->addr_of_mode) {
|
|
spl_type_t bt = (elem->kind == TYPE_BASIC) ? elem->basic_type : SPL_PTR;
|
|
spl_emit(ctx, SPL_LOAD, bt, 0);
|
|
right = (spl_expr_result_t){elem, 0};
|
|
} else {
|
|
right = (spl_expr_result_t){elem, 1};
|
|
}
|
|
} else {
|
|
/* Struct/array/slice: keep address on stack */
|
|
right = (spl_expr_result_t){elem, 1};
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return right;
|
|
}
|
|
|
|
/* ============================================================
|
|
* Main expression parser (top-level)
|
|
* ============================================================ */
|
|
|
|
spl_expr_result_t spl_parse_expr(spl_comp_t *ctx, int min_prec) {
|
|
skip_nl(ctx);
|
|
|
|
spl_tok_t *tok = peek(ctx);
|
|
if (!tok) {
|
|
spl_expr_result_t r = {0};
|
|
return r;
|
|
}
|
|
|
|
spl_expr_result_t left = {0};
|
|
|
|
switch (tok->type) {
|
|
case TOK_INT_LITERAL:
|
|
left = parse_int_literal(ctx);
|
|
break;
|
|
case TOK_FLOAT_LITERAL:
|
|
left = parse_float_literal(ctx);
|
|
break;
|
|
case TOK_CHAR_LITERAL:
|
|
left = parse_char_literal(ctx);
|
|
break;
|
|
case TOK_STRING_LITERAL:
|
|
left = parse_string_literal(ctx);
|
|
break;
|
|
case KW_TRUE:
|
|
advance(ctx);
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, 1);
|
|
left = (spl_expr_result_t){spl_type_basic(SPL_I32), 0};
|
|
break;
|
|
case KW_FALSE:
|
|
advance(ctx);
|
|
spl_emit(ctx, SPL_PUSH, SPL_I32, 0);
|
|
left = (spl_expr_result_t){spl_type_basic(SPL_I32), 0};
|
|
break;
|
|
case KW_NULL:
|
|
advance(ctx);
|
|
spl_emit(ctx, SPL_PUSH, SPL_PTR, 0);
|
|
left = (spl_expr_result_t){spl_type_basic(SPL_PTR), 0};
|
|
break;
|
|
case TOK_IDENT:
|
|
case KW_BOOL:
|
|
case KW_VOID:
|
|
case KW_ANY:
|
|
left = parse_ident(ctx);
|
|
break;
|
|
case TOK_L_PAREN:
|
|
left = parse_group(ctx);
|
|
break;
|
|
case TOK_SUB:
|
|
case TOK_NOT:
|
|
case TOK_BIT_NOT:
|
|
case TOK_AND:
|
|
case TOK_MUL:
|
|
left = parse_prefix_op(ctx);
|
|
break;
|
|
case TOK_L_BRACKET:
|
|
left = parse_array_literal(ctx);
|
|
break;
|
|
case TOK_AT: {
|
|
/* @builtin(...) — compiler intrinsic */
|
|
advance(ctx); /* consume @ */
|
|
skip_nl(ctx);
|
|
tok = peek(ctx);
|
|
if (!tok || tok->type != TOK_IDENT) {
|
|
spl_comp_error(ctx, "expected builtin name after '@'");
|
|
break;
|
|
}
|
|
char bname[256];
|
|
spl_tok_copy_name(tok, bname, sizeof bname);
|
|
advance(ctx); /* consume builtin name */
|
|
|
|
if (strcmp(bname, "dbg") == 0) {
|
|
/* @dbg(...) — print VM debug info */
|
|
if (peek(ctx)->type == TOK_L_PAREN) {
|
|
advance(ctx); /* skip ( */
|
|
int nargs = 0;
|
|
if (peek(ctx)->type != TOK_R_PAREN) {
|
|
for (;;) {
|
|
spl_parse_expr(ctx, PREC_MIN);
|
|
nargs++;
|
|
if (peek(ctx)->type == TOK_COMMA) {
|
|
advance(ctx);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
expect(ctx, TOK_R_PAREN);
|
|
|
|
/* Emit SPL_DBG and DROP for each arg */
|
|
for (int i = 0; i < nargs; i++) {
|
|
spl_emit(ctx, SPL_DBG, SPL_USIZE, 0);
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
|
|
}
|
|
if (nargs == 0) {
|
|
spl_emit(ctx, SPL_DBG, SPL_VOID, 0);
|
|
}
|
|
} else {
|
|
/* @dbg with no parens — just debug */
|
|
spl_emit(ctx, SPL_DBG, SPL_VOID, 0);
|
|
}
|
|
/* @dbg is a void expression */
|
|
left = (spl_expr_result_t){spl_type_basic(SPL_VOID), 0};
|
|
} else {
|
|
spl_comp_error(ctx, "unknown builtin '@%s'", bname);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
/* If it's a keyword-as-type (i32, u8, etc.), parse as function call target or type
|
|
* constructor */
|
|
if (peek(ctx)->type >= KW_AS && peek(ctx)->type <= KW_ANY) {
|
|
left = parse_ident(ctx);
|
|
} else {
|
|
spl_expr_result_t r = {0};
|
|
return r;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Infix parsing (precedence climbing) */
|
|
while (1) {
|
|
skip_nl(ctx);
|
|
spl_tok_type_t opt = peek(ctx)->type;
|
|
|
|
/* Postfix operators */
|
|
if (opt == TOK_DOT) {
|
|
advance(ctx);
|
|
spl_tok_t *field = advance(ctx);
|
|
char fname[256];
|
|
usize fnl = field->len < 255 ? field->len : 255;
|
|
memcpy(fname, field->lexeme, fnl);
|
|
fname[fnl] = '\0';
|
|
|
|
/* Compile-time type member resolution (enum variants, nested types) */
|
|
if (left.type) {
|
|
spl_expr_result_t mresult = {0};
|
|
if (spl_resolve_type_member(ctx, left.type, fname, &mresult)) {
|
|
left = mresult;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Method call on type/instance: left.field(args) */
|
|
if (left.type && peek(ctx)->type == TOK_L_PAREN) {
|
|
/* Determine the type that owns methods (deref pointer if needed) */
|
|
spl_type_info_t *methods_type = left.type;
|
|
int is_ptr_self = 0;
|
|
if (methods_type->kind == TYPE_PTR && methods_type->elem &&
|
|
(methods_type->elem->kind == TYPE_STRUCT ||
|
|
methods_type->elem->kind == TYPE_ENUM)) {
|
|
is_ptr_self = 1;
|
|
methods_type = methods_type->elem;
|
|
}
|
|
|
|
int found_method = 0;
|
|
vec_for(methods_type->methods, mi) {
|
|
if (strcmp(vec_at(methods_type->methods, mi).name, fname) == 0) {
|
|
spl_method_info_t *method = &vec_at(methods_type->methods, mi);
|
|
spl_func_info_t *func = &vec_at(ctx->funcs, method->func_idx);
|
|
|
|
advance(ctx); /* ( */
|
|
found_method = 1;
|
|
|
|
int nargs = 0;
|
|
|
|
/* Check if instance method: first param is self: *Type */
|
|
int is_instance = 0;
|
|
if (func->nparams > 0 && func->param_types[0] &&
|
|
func->param_types[0]->kind == TYPE_PTR &&
|
|
func->param_types[0]->elem == methods_type) {
|
|
is_instance = 1;
|
|
}
|
|
|
|
if (is_instance) {
|
|
if (!left.is_lvalue && !is_ptr_self) {
|
|
spl_comp_error(ctx, "cannot call instance method '%s' on type",
|
|
fname);
|
|
}
|
|
nargs = 1; /* self already on stack */
|
|
}
|
|
|
|
/* Parse remaining arguments */
|
|
if (peek(ctx)->type != TOK_R_PAREN) {
|
|
for (;;) {
|
|
spl_expr_result_t arg = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)arg;
|
|
nargs++;
|
|
if (peek(ctx)->type == TOK_COMMA) {
|
|
advance(ctx);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
expect(ctx, TOK_R_PAREN);
|
|
|
|
spl_val_t addr = vec_at(ctx->prog.funcs, func->func_idx).address;
|
|
spl_emit(ctx, SPL_PUSH, SPL_PTR, addr);
|
|
spl_emit(ctx, SPL_CALL, SPL_VOID, nargs);
|
|
|
|
left = (spl_expr_result_t){func->ret_type, 0};
|
|
break;
|
|
}
|
|
}
|
|
if (found_method)
|
|
continue;
|
|
/* If method not found, fall through to field access below */
|
|
}
|
|
|
|
/* Struct field access */
|
|
if (left.type && left.type->kind == TYPE_STRUCT) {
|
|
/* The struct address is on stack (as lvalue or from previous computation) */
|
|
vec_for(left.type->fields, fi) {
|
|
if (strcmp(vec_at(left.type->fields, fi).name, fname) == 0) {
|
|
spl_field_t *f = &vec_at(left.type->fields, fi);
|
|
if (f->offset > 0) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_USIZE, f->offset);
|
|
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
|
|
}
|
|
/* Load value if basic type */
|
|
spl_type_info_t *ft = f->type;
|
|
if (ft->kind == TYPE_BASIC || ft->kind == TYPE_PTR) {
|
|
if (!ctx->addr_of_mode) {
|
|
spl_type_t bt = (ft->kind == TYPE_BASIC) ? ft->basic_type : SPL_PTR;
|
|
spl_emit(ctx, SPL_LOAD, bt, 0);
|
|
left = (spl_expr_result_t){ft, 0};
|
|
} else {
|
|
left = (spl_expr_result_t){ft, 1};
|
|
}
|
|
} else {
|
|
left = (spl_expr_result_t){ft, 1};
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Pointer auto-deref: if left is a pointer to struct, deref first */
|
|
if (left.type && left.type->kind == TYPE_PTR && left.type->elem &&
|
|
left.type->elem->kind == TYPE_STRUCT) {
|
|
spl_type_info_t *st = left.type->elem;
|
|
/* Load pointer value to get struct address if left is still lvalue */
|
|
if (left.is_lvalue) {
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
|
|
}
|
|
/* Now search field */
|
|
vec_for(st->fields, fi) {
|
|
if (strcmp(vec_at(st->fields, fi).name, fname) == 0) {
|
|
spl_field_t *f = &vec_at(st->fields, fi);
|
|
if (f->offset > 0) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_USIZE, f->offset);
|
|
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
|
|
}
|
|
spl_type_info_t *ft = f->type;
|
|
if (ft->kind == TYPE_BASIC || ft->kind == TYPE_PTR) {
|
|
if (!ctx->addr_of_mode) {
|
|
spl_type_t bt = (ft->kind == TYPE_BASIC) ? ft->basic_type : SPL_PTR;
|
|
spl_emit(ctx, SPL_LOAD, bt, 0);
|
|
left = (spl_expr_result_t){ft, 0};
|
|
} else {
|
|
left = (spl_expr_result_t){ft, 1};
|
|
}
|
|
} else {
|
|
left = (spl_expr_result_t){ft, 1};
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Slice .len or .ptr */
|
|
if (left.type && left.type->kind == TYPE_SLICE) {
|
|
if (strcmp(fname, "len") == 0) {
|
|
if (ctx->addr_of_mode) {
|
|
/* lvalue: push address of len field at offset sizeof(spl_val_t) */
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, (spl_val_t)sizeof(spl_val_t));
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
|
|
left = (spl_expr_result_t){spl_type_basic(SPL_USIZE), 1};
|
|
} else {
|
|
/* rvalue: load len value */
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, (spl_val_t)sizeof(spl_val_t));
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
|
|
spl_emit(ctx, SPL_LOAD, SPL_USIZE, 0);
|
|
left = (spl_expr_result_t){spl_type_basic(SPL_USIZE), 0};
|
|
}
|
|
} else if (strcmp(fname, "ptr") == 0) {
|
|
if (ctx->addr_of_mode) {
|
|
/* lvalue: ptr is at offset 0, address already on stack */
|
|
left = (spl_expr_result_t){left.type->elem ? spl_type_ptr(left.type->elem)
|
|
: spl_type_basic(SPL_PTR),
|
|
1};
|
|
} else {
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
|
|
left = (spl_expr_result_t){left.type->elem ? spl_type_ptr(left.type->elem)
|
|
: spl_type_basic(SPL_PTR),
|
|
0};
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Postfix dereference: expr.* */
|
|
if (strcmp(fname, "*") == 0 && left.type && left.type->kind == TYPE_PTR &&
|
|
left.type->elem) {
|
|
if (left.is_lvalue) {
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
|
|
}
|
|
spl_type_info_t *elem = left.type->elem;
|
|
if (elem->kind == TYPE_BASIC || elem->kind == TYPE_PTR) {
|
|
if (!ctx->addr_of_mode) {
|
|
spl_type_t bt = (elem->kind == TYPE_BASIC) ? elem->basic_type : SPL_PTR;
|
|
spl_emit(ctx, SPL_LOAD, bt, 0);
|
|
left = (spl_expr_result_t){elem, 0};
|
|
} else {
|
|
left = (spl_expr_result_t){elem, 1};
|
|
}
|
|
} else {
|
|
left = (spl_expr_result_t){elem, 1};
|
|
}
|
|
continue;
|
|
}
|
|
|
|
spl_comp_error(ctx, "unknown field '%s'", fname);
|
|
continue;
|
|
}
|
|
|
|
/* Array/slice indexing: expr[expr] or expr[begin..end] */
|
|
if (opt == TOK_L_BRACKET) {
|
|
advance(ctx); /* skip [ */
|
|
if (peek(ctx)->type == TOK_R_BRACKET) {
|
|
advance(ctx); /* empty brackets */
|
|
continue;
|
|
}
|
|
|
|
/* Check for slice: expr[begin..end] or expr[begin..] */
|
|
spl_expr_result_t index = spl_parse_expr(ctx, PREC_MIN);
|
|
if (peek(ctx)->type == TOK_RANGE) {
|
|
advance(ctx); /* skip .. */
|
|
spl_expr_result_t end_expr = {0};
|
|
int has_explicit_end = (peek(ctx)->type != TOK_R_BRACKET);
|
|
if (has_explicit_end) {
|
|
end_expr = spl_parse_expr(ctx, PREC_MIN);
|
|
}
|
|
expect(ctx, TOK_R_BRACKET);
|
|
|
|
/* Push implicit end value (array length/slice len) before the outer
|
|
* type-check so it always runs regardless of left.type validity. */
|
|
if (!has_explicit_end) {
|
|
if (left.type && left.type->kind == TYPE_ARRAY) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, left.type->array_len);
|
|
} else if (left.type && left.type->kind == TYPE_SLICE) {
|
|
/* TYPE_SLICE: load len from struct at offset sizeof(spl_val_t) */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 1);
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, sizeof(spl_val_t));
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
|
|
spl_emit(ctx, SPL_LOAD, SPL_USIZE, 0);
|
|
}
|
|
}
|
|
|
|
/* Generate slice: compute ptr = base + begin * stride, len = end - begin */
|
|
if (left.type && (left.type->kind == TYPE_ARRAY || left.type->kind == TYPE_SLICE)) {
|
|
usize stride = spl_type_elem_stride(left.type->elem);
|
|
|
|
if (left.type->kind == TYPE_SLICE) {
|
|
/* Stack: [struct_addr, begin, end].
|
|
* Inline ptr/len: ptr = data_ptr + begin*stride, len = end - begin. */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 2);
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); /* [s,b,e,data_ptr] */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [s,b,e,d,begin] */
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, stride);
|
|
spl_emit(ctx, SPL_MUL, SPL_U64, 0);
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [s,b,e,ptr] */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* [s,b,e,ptr,end] */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 3); /* [s,b,e,ptr,end,begin] */
|
|
spl_emit(ctx, SPL_SUB, SPL_USIZE, 0); /* [s,b,e,ptr,len] */
|
|
/* cleanup: drop [s,b,e] */
|
|
spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [s,b,ptr,len,e] */
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [s,b,ptr,len] */
|
|
spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [s,ptr,len,b] */
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [s,ptr,len] */
|
|
spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [ptr,len,s] */
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [ptr,len] */
|
|
} else {
|
|
emit_slice_create(ctx, stride);
|
|
}
|
|
}
|
|
left = (spl_expr_result_t){left.type ? spl_type_slice(left.type->elem) : NULL, 0};
|
|
continue;
|
|
}
|
|
|
|
expect(ctx, TOK_R_BRACKET);
|
|
|
|
/* Array/slice/pointer indexing */
|
|
if (left.type && (left.type->kind == TYPE_ARRAY || left.type->kind == TYPE_PTR ||
|
|
left.type->kind == TYPE_SLICE)) {
|
|
spl_type_info_t *elem = left.type->elem;
|
|
|
|
if (left.type->kind == TYPE_SLICE) {
|
|
/* Slice: stack has [slice_struct_addr, index].
|
|
* Load data ptr first, then compute element address. */
|
|
emit_slice_index(ctx, elem);
|
|
} else {
|
|
/* Stack arrays and pointer indexing */
|
|
usize stride = spl_type_elem_stride(elem);
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, stride);
|
|
spl_emit(ctx, SPL_MUL, SPL_U64, 0);
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
|
|
}
|
|
if (elem && (elem->kind == TYPE_BASIC || elem->kind == TYPE_PTR)) {
|
|
if (!ctx->addr_of_mode) {
|
|
spl_type_t bt = (elem->kind == TYPE_BASIC) ? elem->basic_type : SPL_PTR;
|
|
spl_emit(ctx, SPL_LOAD, bt, 0);
|
|
left = (spl_expr_result_t){elem, 0};
|
|
} else {
|
|
left = (spl_expr_result_t){elem, 1};
|
|
}
|
|
} else {
|
|
left = (spl_expr_result_t){elem, 1};
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Binary operators */
|
|
int prec = tok_prec(opt);
|
|
if (prec == 0 || prec < min_prec)
|
|
break;
|
|
advance(ctx);
|
|
left = parse_infix(ctx, left, opt);
|
|
}
|
|
|
|
return left;
|
|
}
|
|
|
|
/* ============================================================
|
|
* Infix operators
|
|
* ============================================================ */
|
|
|
|
static spl_expr_result_t parse_infix(spl_comp_t *ctx, spl_expr_result_t left, spl_tok_type_t op) {
|
|
int prec = tok_prec(op);
|
|
int next_prec = prec + 1;
|
|
|
|
/* Short-circuit logical operators */
|
|
if (op == TOK_AND_AND) {
|
|
/* left is already evaluated and on stack. If it's false (0), skip right. */
|
|
spl_val_t bz_addr = spl_emit_bz(ctx);
|
|
spl_parse_expr(ctx, next_prec);
|
|
spl_patch_to_here(ctx, bz_addr);
|
|
return (spl_expr_result_t){spl_type_basic(SPL_I32), 0};
|
|
}
|
|
if (op == TOK_OR_OR) {
|
|
/* If left is true (non-zero), skip right. */
|
|
spl_val_t bnz_addr = spl_emit_bnz(ctx);
|
|
spl_parse_expr(ctx, next_prec);
|
|
spl_patch_to_here(ctx, bnz_addr);
|
|
return (spl_expr_result_t){spl_type_basic(SPL_I32), 0};
|
|
}
|
|
|
|
/* Assignment operators */
|
|
if (op == TOK_ASSIGN || op == TOK_ASSIGN_ADD || op == TOK_ASSIGN_SUB || op == TOK_ASSIGN_MUL ||
|
|
op == TOK_ASSIGN_DIV || op == TOK_ASSIGN_MOD || op == TOK_ASSIGN_AND ||
|
|
op == TOK_ASSIGN_OR || op == TOK_ASSIGN_XOR || op == TOK_ASSIGN_L_SH ||
|
|
op == TOK_ASSIGN_R_SH) {
|
|
|
|
/* RHS must not inherit addr_of_mode from LHS */
|
|
int saved_addr_of_mode = ctx->addr_of_mode;
|
|
ctx->addr_of_mode = 0;
|
|
spl_expr_result_t right = spl_parse_expr(ctx, PREC_MIN);
|
|
ctx->addr_of_mode = saved_addr_of_mode;
|
|
|
|
if (left.is_lvalue) {
|
|
spl_type_t bt = left.type ? (left.type->kind == TYPE_BASIC ? left.type->basic_type
|
|
: left.type->kind == TYPE_PTR ? SPL_PTR
|
|
: SPL_I32)
|
|
: SPL_I32;
|
|
if (op == TOK_ASSIGN) {
|
|
/* Simple assignment: stack is [addr, rhs] */
|
|
/* STORE pops TOS=value, TOS-1=address — already correct order */
|
|
spl_emit(ctx, SPL_STORE, bt, 0);
|
|
} else {
|
|
/* Compound: left = left op right — stack: [addr, rhs] */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* [addr, rhs, addr] */
|
|
spl_emit(ctx, SPL_LOAD, bt, 0); /* [addr, rhs, old_val] */
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [addr, old_val, rhs] */
|
|
int sop = binop_to_sir(assign_to_binop(op), bt);
|
|
if (sop >= 0)
|
|
spl_emit(ctx, sop, bt, 0); /* [addr, result] */
|
|
spl_emit(ctx, SPL_STORE, bt, 0);
|
|
}
|
|
}
|
|
return right;
|
|
}
|
|
|
|
/* Regular binary op */
|
|
spl_parse_expr(ctx, next_prec);
|
|
int sop = binop_to_sir(op, left.type ? left.type->basic_type : SPL_I32);
|
|
spl_type_t bt = left.type && left.type->kind == TYPE_BASIC ? left.type->basic_type : SPL_I32;
|
|
if (sop >= 0) {
|
|
spl_emit(ctx, sop, bt, 0);
|
|
}
|
|
return (spl_expr_result_t){spl_type_basic(SPL_I32), 0};
|
|
}
|