952 lines
32 KiB
C
952 lines
32 KiB
C
/* spl_stmt.c — Statement parser + codegen */
|
|
|
|
#include "spl_comp.h"
|
|
#include "spl_lex_util.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* ============================================================
|
|
* Return statement: ret expr;
|
|
* ============================================================ */
|
|
|
|
static void parse_ret_stmt(spl_comp_t *ctx) {
|
|
advance(ctx); /* ret */
|
|
skip_nl(ctx);
|
|
|
|
/* Before returning, execute all pending defers from innermost scope outward */
|
|
for (int d = ctx->scope_depth; d >= 1; d--) {
|
|
spl_emit_defer_epilogue(ctx, d);
|
|
}
|
|
|
|
if (peek(ctx)->type == TOK_SEMICOLON || peek(ctx)->type == TOK_R_BRACE ||
|
|
peek(ctx)->type == TOK_ENDLINE) {
|
|
/* void return */
|
|
spl_emit(ctx, SPL_RET, SPL_VOID, 0);
|
|
} else {
|
|
spl_expr_result_t val = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)val;
|
|
spl_type_t rt = SPL_VOID;
|
|
if (ctx->current_ret_type) {
|
|
if (ctx->current_ret_type->kind == TYPE_BASIC) {
|
|
rt = ctx->current_ret_type->basic_type;
|
|
} else if (ctx->current_ret_type->kind == TYPE_PTR) {
|
|
rt = SPL_PTR;
|
|
} else if (spl_type_slot_count(ctx->current_ret_type) == 1) {
|
|
/* 1-slot struct/enum/etc: use PTR type */
|
|
rt = SPL_PTR;
|
|
}
|
|
}
|
|
spl_emit(ctx, SPL_RET, rt, 0);
|
|
}
|
|
if (peek(ctx)->type == TOK_SEMICOLON)
|
|
advance(ctx);
|
|
}
|
|
|
|
/* ============================================================
|
|
* Variable declaration: var name: Type [= expr];
|
|
* ============================================================ */
|
|
|
|
static void parse_var_decl(spl_comp_t *ctx, int is_const) {
|
|
advance(ctx); /* var or const */
|
|
skip_nl(ctx);
|
|
|
|
spl_tok_t *name_tok = advance(ctx);
|
|
char vname[256];
|
|
usize nlen = name_tok->len < 255 ? name_tok->len : 255;
|
|
memcpy(vname, name_tok->lexeme, nlen);
|
|
vname[nlen] = '\0';
|
|
|
|
spl_type_info_t *var_type = NULL;
|
|
int has_init = 0;
|
|
|
|
skip_nl(ctx);
|
|
|
|
if (peek(ctx)->type == TOK_COLON) {
|
|
advance(ctx); /* : */
|
|
skip_nl(ctx);
|
|
/* Check for := */
|
|
if (peek(ctx)->type == TOK_ASSIGN) {
|
|
/* := is colon-assign */
|
|
has_init = 1;
|
|
} else {
|
|
var_type = spl_parse_type(ctx);
|
|
skip_nl(ctx);
|
|
}
|
|
}
|
|
|
|
if (peek(ctx)->type == TOK_COLON_ASSIGN || peek(ctx)->type == TOK_ASSIGN) {
|
|
has_init = 1;
|
|
if (peek(ctx)->type == TOK_COLON_ASSIGN)
|
|
advance(ctx);
|
|
else
|
|
advance(ctx); /* = */
|
|
}
|
|
|
|
/* Init expression: parse before declare to enable type inference */
|
|
spl_expr_result_t init = {0};
|
|
if (has_init) {
|
|
skip_nl(ctx);
|
|
init = spl_parse_expr(ctx, PREC_MIN);
|
|
}
|
|
|
|
if (!var_type) {
|
|
var_type = init.type ? init.type : spl_type_basic(SPL_I32);
|
|
}
|
|
int slot = spl_declare_var(ctx, vname, var_type, is_const);
|
|
|
|
/* Store init value */
|
|
if (has_init) {
|
|
if (var_type && (var_type->kind == TYPE_STRUCT || var_type->kind == TYPE_ENUM)) {
|
|
/* Struct/enum initialization */
|
|
usize sc = spl_type_slot_count(var_type);
|
|
if (sc == 1) {
|
|
/* Single-slot: value on stack, store directly */
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, slot);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, SPL_PTR, 0);
|
|
} else {
|
|
/* Multi-slot: copy from temp addr to var slots */
|
|
for (usize i = 0; i < sc; i++) {
|
|
if (i < sc - 1)
|
|
spl_emit(ctx, SPL_DUP, SPL_VOID, 0);
|
|
if (i > 0) {
|
|
spl_emit(ctx, SPL_PUSH, SPL_USIZE, i * sizeof(spl_val_t));
|
|
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
|
|
}
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, slot + (int)i);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, SPL_PTR, 0);
|
|
}
|
|
}
|
|
} else if (var_type && var_type->kind == TYPE_ARRAY) {
|
|
/* Array initialization: store each element in reverse stack order */
|
|
spl_type_info_t *elem = var_type->elem;
|
|
spl_type_t bt = (elem && elem->kind == TYPE_BASIC) ? elem->basic_type : SPL_I32;
|
|
for (int i = (int)var_type->array_len - 1; i >= 0; i--) {
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, slot + i);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, bt, 0);
|
|
}
|
|
} else if (var_type && var_type->kind == TYPE_SLICE) {
|
|
/* Slice initialization: stack has [ptr, len] from slice expression.
|
|
* Store len at slot+1, then ptr at slot. */
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, slot + 1);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, SPL_USIZE, 0);
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, slot);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, SPL_PTR, 0);
|
|
} else {
|
|
/* Single value store */
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, slot);
|
|
spl_type_t bt = (var_type->kind == TYPE_BASIC) ? var_type->basic_type
|
|
: (var_type->kind == TYPE_PTR) ? SPL_PTR
|
|
: SPL_I32;
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, bt, 0);
|
|
}
|
|
}
|
|
|
|
if (peek(ctx)->type == TOK_SEMICOLON)
|
|
advance(ctx);
|
|
}
|
|
|
|
/* ============================================================
|
|
* Block: { stmt; stmt; ... }
|
|
* ============================================================ */
|
|
|
|
void spl_parse_block(spl_comp_t *ctx) {
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == TOK_L_BRACE) {
|
|
advance(ctx); /* { */
|
|
spl_push_scope(ctx);
|
|
|
|
while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) {
|
|
spl_parse_stmt(ctx);
|
|
skip_nl(ctx);
|
|
}
|
|
|
|
spl_emit_defer_epilogue(ctx, ctx->scope_depth);
|
|
spl_pop_scope(ctx);
|
|
if (peek(ctx)->type == TOK_R_BRACE)
|
|
advance(ctx);
|
|
} else {
|
|
/* Single statement */
|
|
spl_parse_stmt(ctx);
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
* If statement: if expr { ... } [else { ... }]
|
|
* ============================================================ */
|
|
|
|
static void parse_if_stmt(spl_comp_t *ctx) {
|
|
advance(ctx); /* if */
|
|
skip_nl(ctx);
|
|
spl_expr_result_t cond = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)cond;
|
|
|
|
spl_val_t bz_addr = spl_emit_bz(ctx);
|
|
skip_nl(ctx);
|
|
spl_parse_block(ctx);
|
|
|
|
spl_val_t jmp_addr = 0;
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == KW_ELSE) {
|
|
jmp_addr = spl_emit_jmp(ctx);
|
|
spl_patch_to_here(ctx, bz_addr);
|
|
advance(ctx); /* else */
|
|
skip_nl(ctx);
|
|
spl_parse_block(ctx);
|
|
spl_patch_to_here(ctx, jmp_addr);
|
|
} else {
|
|
spl_patch_to_here(ctx, bz_addr);
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
* While statement: while expr { ... }
|
|
* ============================================================ */
|
|
|
|
static void parse_while_stmt(spl_comp_t *ctx) {
|
|
spl_val_t loop_start = vec_size(ctx->prog.insns);
|
|
int saved_loop = ctx->in_loop;
|
|
usize saved_continue = ctx->continue_target;
|
|
usize saved_bp_count = ctx->break_patch_count;
|
|
|
|
ctx->in_loop = 1;
|
|
ctx->continue_target = loop_start;
|
|
|
|
advance(ctx); /* while */
|
|
skip_nl(ctx);
|
|
spl_expr_result_t cond = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)cond;
|
|
|
|
spl_val_t bz_addr = spl_emit_bz(ctx);
|
|
skip_nl(ctx);
|
|
spl_parse_block(ctx);
|
|
|
|
/* JMP back to loop condition — use relative offset */
|
|
spl_val_t here = vec_size(ctx->prog.insns);
|
|
spl_emit(ctx, SPL_JMP, SPL_VOID, (spl_val_t)((isize)loop_start - (isize)here - 1));
|
|
spl_patch_to_here(ctx, bz_addr);
|
|
|
|
/* Patch break statements — compute relative offset */
|
|
for (usize i = saved_bp_count; i < ctx->break_patch_count; i++) {
|
|
spl_patch_to_here(ctx, ctx->break_patches[i]);
|
|
}
|
|
ctx->break_patch_count = saved_bp_count;
|
|
|
|
ctx->in_loop = saved_loop;
|
|
ctx->continue_target = saved_continue;
|
|
}
|
|
|
|
/* ============================================================
|
|
* Loop statement: loop { ... }
|
|
* ============================================================ */
|
|
|
|
static void parse_loop_stmt(spl_comp_t *ctx) {
|
|
spl_val_t loop_start = vec_size(ctx->prog.insns);
|
|
int saved_loop = ctx->in_loop;
|
|
usize saved_continue = ctx->continue_target;
|
|
usize saved_bp_count = ctx->break_patch_count;
|
|
|
|
ctx->in_loop = 1;
|
|
ctx->continue_target = loop_start;
|
|
|
|
advance(ctx); /* loop */
|
|
skip_nl(ctx);
|
|
spl_parse_block(ctx);
|
|
|
|
/* JMP back to loop start — use relative offset */
|
|
spl_val_t here = vec_size(ctx->prog.insns);
|
|
spl_emit(ctx, SPL_JMP, SPL_VOID, (spl_val_t)((isize)loop_start - (isize)here - 1));
|
|
|
|
/* Patch break statements — compute relative offset */
|
|
for (usize i = saved_bp_count; i < ctx->break_patch_count; i++) {
|
|
spl_patch_to_here(ctx, ctx->break_patches[i]);
|
|
}
|
|
ctx->break_patch_count = saved_bp_count;
|
|
|
|
ctx->in_loop = saved_loop;
|
|
ctx->continue_target = saved_continue;
|
|
}
|
|
|
|
/* ============================================================
|
|
* For loop: for begin..end as i { ... }
|
|
* ============================================================ */
|
|
|
|
static void parse_for_stmt(spl_comp_t *ctx) {
|
|
advance(ctx); /* for */
|
|
skip_nl(ctx);
|
|
|
|
/* Parse the iteration expression(s) */
|
|
spl_expr_result_t start_expr = spl_parse_expr(ctx, PREC_MIN);
|
|
|
|
if (peek(ctx)->type == TOK_RANGE) {
|
|
/* ===== Numeric range: for begin..end as i { body } ===== */
|
|
advance(ctx); /* .. */
|
|
spl_parse_expr(ctx, PREC_MIN); /* end expression */
|
|
/* Stack: [begin, end] */
|
|
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type != KW_AS) {
|
|
skip_nl(ctx);
|
|
spl_parse_block(ctx);
|
|
return;
|
|
}
|
|
advance(ctx); /* as */
|
|
|
|
spl_tok_t *ivar = advance(ctx);
|
|
char iname[256];
|
|
usize inl = ivar->len < 255 ? ivar->len : 255;
|
|
memcpy(iname, ivar->lexeme, inl);
|
|
iname[inl] = '\0';
|
|
|
|
spl_push_scope(ctx);
|
|
int islot = spl_declare_var(ctx, iname, spl_type_basic(SPL_USIZE), 0);
|
|
|
|
/* Stack: [begin, end]; swap so TOS = begin */
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
/* Store begin to i */
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, islot);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, SPL_USIZE, 0);
|
|
/* Stack: [end] */
|
|
|
|
spl_val_t loop_start = vec_size(ctx->prog.insns);
|
|
|
|
/* Condition: i < end */
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, islot);
|
|
spl_emit(ctx, SPL_LOAD, SPL_USIZE, 0);
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 1);
|
|
spl_emit(ctx, SPL_ULT, SPL_USIZE, 0);
|
|
spl_val_t bz_addr = spl_emit_bz(ctx);
|
|
|
|
int saved_loop = ctx->in_loop;
|
|
usize saved_continue = ctx->continue_target;
|
|
usize saved_bp_count = ctx->break_patch_count;
|
|
ctx->in_loop = 1;
|
|
ctx->continue_target = loop_start;
|
|
|
|
skip_nl(ctx);
|
|
spl_parse_block(ctx); /* body */
|
|
|
|
/* Increment: i = i + 1 */
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, islot);
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, islot);
|
|
spl_emit(ctx, SPL_LOAD, SPL_USIZE, 0);
|
|
spl_emit(ctx, SPL_PUSH, SPL_USIZE, 1);
|
|
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
|
|
spl_emit(ctx, SPL_STORE, SPL_USIZE, 0);
|
|
|
|
/* JMP back to condition */
|
|
spl_val_t jmp_here = vec_size(ctx->prog.insns);
|
|
spl_emit(ctx, SPL_JMP, SPL_VOID, (spl_val_t)((isize)loop_start - (isize)jmp_here - 1));
|
|
|
|
/* Exit: patch bz, drop end */
|
|
spl_patch_to_here(ctx, bz_addr);
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
|
|
|
|
/* Patch breaks */
|
|
for (usize i = saved_bp_count; i < ctx->break_patch_count; i++)
|
|
spl_patch_to_here(ctx, ctx->break_patches[i]);
|
|
ctx->break_patch_count = saved_bp_count;
|
|
ctx->in_loop = saved_loop;
|
|
ctx->continue_target = saved_continue;
|
|
|
|
spl_emit_defer_epilogue(ctx, ctx->scope_depth);
|
|
spl_pop_scope(ctx);
|
|
return;
|
|
}
|
|
|
|
/* ===== Slice iteration: for slice [as val |, range as val, idx] ===== */
|
|
int has_idx = 0;
|
|
if (peek(ctx)->type == TOK_COMMA) {
|
|
advance(ctx); /* , */
|
|
spl_parse_expr(ctx, PREC_MIN); /* parse start of range (e.g. 0) */
|
|
if (peek(ctx)->type == TOK_RANGE) {
|
|
advance(ctx); /* .. */
|
|
/* consume optional end expression */
|
|
if (peek(ctx)->type != KW_AS && peek(ctx)->type != TOK_COMMA &&
|
|
peek(ctx)->type != TOK_L_BRACE && peek(ctx)->type != TOK_ENDLINE &&
|
|
peek(ctx)->type != TOK_EOF)
|
|
spl_parse_expr(ctx, PREC_MIN);
|
|
}
|
|
has_idx = 1;
|
|
/* Range pushed a value; we manage our own idx, drop it */
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
|
|
}
|
|
|
|
if (peek(ctx)->type != KW_AS) {
|
|
skip_nl(ctx);
|
|
spl_parse_block(ctx);
|
|
return;
|
|
}
|
|
advance(ctx); /* as */
|
|
|
|
/* Parse val variable name */
|
|
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';
|
|
|
|
/* Parse optional idx variable name */
|
|
char iname[256] = {0};
|
|
if (peek(ctx)->type == TOK_COMMA) {
|
|
advance(ctx);
|
|
spl_tok_t *itok = advance(ctx);
|
|
usize inl = itok->len < 255 ? itok->len : 255;
|
|
memcpy(iname, itok->lexeme, inl);
|
|
iname[inl] = '\0';
|
|
}
|
|
|
|
/* Stack: [slice_addr] or whatever the slice expression left */
|
|
spl_push_scope(ctx);
|
|
|
|
int idx_slot = -1;
|
|
if (iname[0])
|
|
idx_slot = spl_declare_var(ctx, iname, spl_type_basic(SPL_USIZE), 0);
|
|
|
|
spl_type_info_t *elem_type = NULL;
|
|
if (start_expr.type) {
|
|
if (start_expr.type->kind == TYPE_SLICE)
|
|
elem_type = start_expr.type->elem;
|
|
else if (start_expr.type->kind == TYPE_PTR && start_expr.type->elem)
|
|
elem_type = start_expr.type->elem;
|
|
}
|
|
if (!elem_type)
|
|
elem_type = spl_type_basic(SPL_I32);
|
|
int val_slot = spl_declare_var(ctx, vname, elem_type, 0);
|
|
|
|
/* Extract ptr and len from slice, push index=0: stack [ptr, len, idx] */
|
|
spl_emit(ctx, SPL_DUP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
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);
|
|
spl_emit(ctx, SPL_PUSH, SPL_USIZE, 0);
|
|
/* Stack: [ptr, len, idx=0] */
|
|
|
|
spl_val_t loop_start = vec_size(ctx->prog.insns);
|
|
|
|
/* Condition: idx < len */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* copy len */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* copy idx */
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [idx, len] → [len, idx] → SWAP → [idx, len] */
|
|
spl_emit(ctx, SPL_ULT, SPL_USIZE, 0);
|
|
spl_val_t bz_addr = spl_emit_bz(ctx);
|
|
|
|
/* Store current idx to idx variable */
|
|
if (idx_slot >= 0) {
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 0); /* copy idx (TOS) */
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, idx_slot);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, SPL_USIZE, 0);
|
|
}
|
|
|
|
/* Load slice[idx] and store to val */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* copy ptr: [ptr, len, idx, ptr] */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* copy idx: [ptr, len, idx, ptr, idx] */
|
|
usize elem_byte_size = (elem_type && elem_type->byte_size) ? elem_type->byte_size : 4;
|
|
spl_emit(ctx, SPL_PUSH, SPL_U64, sizeof(spl_val_t));
|
|
spl_emit(ctx, SPL_MUL, SPL_U64, 0);
|
|
spl_emit(ctx, SPL_ADD, SPL_U64, 0);
|
|
spl_emit(ctx, SPL_LOAD, SPL_I32, 0);
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, val_slot);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, SPL_I32, 0);
|
|
|
|
/* Loop context */
|
|
int saved_loop = ctx->in_loop;
|
|
usize saved_continue = ctx->continue_target;
|
|
usize saved_bp_count = ctx->break_patch_count;
|
|
ctx->in_loop = 1;
|
|
ctx->continue_target = loop_start;
|
|
|
|
skip_nl(ctx);
|
|
spl_parse_block(ctx); /* body */
|
|
|
|
/* Increment idx */
|
|
spl_emit(ctx, SPL_PICK, SPL_VOID, 0); /* copy idx */
|
|
spl_emit(ctx, SPL_PUSH, SPL_USIZE, 1);
|
|
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* replace old idx with new */
|
|
|
|
/* JMP back to condition */
|
|
spl_val_t jmp_here = vec_size(ctx->prog.insns);
|
|
spl_emit(ctx, SPL_JMP, SPL_VOID, (spl_val_t)((isize)loop_start - (isize)jmp_here - 1));
|
|
|
|
/* Exit: patch bz, drop idx, len, ptr */
|
|
spl_patch_to_here(ctx, bz_addr);
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* drop idx */
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* drop len */
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* drop ptr */
|
|
|
|
/* Patch breaks */
|
|
for (usize i = saved_bp_count; i < ctx->break_patch_count; i++)
|
|
spl_patch_to_here(ctx, ctx->break_patches[i]);
|
|
ctx->break_patch_count = saved_bp_count;
|
|
ctx->in_loop = saved_loop;
|
|
ctx->continue_target = saved_continue;
|
|
|
|
spl_pop_scope(ctx);
|
|
}
|
|
|
|
/* ============================================================
|
|
* Break / Continue
|
|
* ============================================================ */
|
|
|
|
static void parse_break_stmt(spl_comp_t *ctx) {
|
|
advance(ctx); /* break */
|
|
if (!ctx->in_loop) {
|
|
spl_comp_error(ctx, "break outside loop");
|
|
}
|
|
/* Emit JMP with placeholder, add to patch list */
|
|
spl_val_t addr = spl_emit_jmp(ctx);
|
|
if (ctx->break_patch_cap <= ctx->break_patch_count) {
|
|
usize new_cap = ctx->break_patch_cap ? ctx->break_patch_cap * 2 : 8;
|
|
ctx->break_patches = realloc(ctx->break_patches, new_cap * sizeof(usize));
|
|
ctx->break_patch_cap = new_cap;
|
|
}
|
|
ctx->break_patches[ctx->break_patch_count++] = addr;
|
|
if (peek(ctx)->type == TOK_SEMICOLON)
|
|
advance(ctx);
|
|
}
|
|
|
|
static void parse_continue_stmt(spl_comp_t *ctx) {
|
|
advance(ctx); /* continue */
|
|
if (!ctx->in_loop) {
|
|
spl_comp_error(ctx, "continue outside loop");
|
|
}
|
|
/* Emit JMP with relative offset to continue_target */
|
|
spl_val_t here = vec_size(ctx->prog.insns);
|
|
spl_emit(ctx, SPL_JMP, SPL_VOID, (spl_val_t)((isize)ctx->continue_target - (isize)here - 1));
|
|
if (peek(ctx)->type == TOK_SEMICOLON)
|
|
advance(ctx);
|
|
}
|
|
|
|
/* ============================================================
|
|
* Defer statement: defer { ... } or defer expr;
|
|
* ============================================================ */
|
|
|
|
static void parse_defer_stmt(spl_comp_t *ctx) {
|
|
advance(ctx); /* defer */
|
|
skip_nl(ctx);
|
|
|
|
/* Record current position for defer (emits a JMP skip placeholder) */
|
|
spl_emit_defer(ctx);
|
|
|
|
/* Parse the deferred statement/block */
|
|
if (peek(ctx)->type == TOK_L_BRACE) {
|
|
spl_parse_block(ctx);
|
|
} else {
|
|
spl_parse_stmt(ctx);
|
|
}
|
|
|
|
/* Emit JMP exit placeholder — will be patched at scope exit */
|
|
if (ctx->defer_count > 0) {
|
|
spl_defer_entry_t *e = &ctx->defer_stack[ctx->defer_count - 1];
|
|
e->jmp_exit = spl_emit_jmp(ctx);
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
* Match statement: match expr { .Variant(bindings) => stmt, ... }
|
|
* ============================================================ */
|
|
|
|
static void parse_match_stmt(spl_comp_t *ctx) {
|
|
advance(ctx); /* match */
|
|
skip_nl(ctx);
|
|
|
|
spl_expr_result_t expr = spl_parse_expr(ctx, PREC_MIN);
|
|
|
|
/* Determine the enum type (deref pointer if needed) */
|
|
spl_type_info_t *enum_type = expr.type;
|
|
if (enum_type && enum_type->kind == TYPE_PTR && enum_type->elem &&
|
|
enum_type->elem->kind == TYPE_ENUM) {
|
|
enum_type = enum_type->elem;
|
|
}
|
|
if (!enum_type || enum_type->kind != TYPE_ENUM) {
|
|
spl_comp_error(ctx, "match expression must be an enum");
|
|
return;
|
|
}
|
|
|
|
/* Save the enum address (pointer value) to a temp slot */
|
|
int addr_slot = ctx->current_local_slot;
|
|
ctx->current_local_slot++;
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, addr_slot);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, SPL_PTR, 0);
|
|
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == TOK_L_BRACE)
|
|
advance(ctx); /* { */
|
|
|
|
/* Collect JMP-to-end addresses for patching */
|
|
enum { MAX_MATCH_ARMS = 32 };
|
|
spl_val_t jmp_to_end[MAX_MATCH_ARMS];
|
|
int n_jmps = 0;
|
|
|
|
skip_nl(ctx);
|
|
while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) {
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == TOK_COMMA) {
|
|
advance(ctx);
|
|
skip_nl(ctx);
|
|
continue;
|
|
}
|
|
|
|
/* Parse .VariantName */
|
|
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';
|
|
|
|
/* Find the variant in the enum type */
|
|
spl_enum_variant_t *variant = NULL;
|
|
int vi;
|
|
vec_for(enum_type->variants, vi) {
|
|
if (strcmp(vec_at(enum_type->variants, vi).name, vname) == 0) {
|
|
variant = &vec_at(enum_type->variants, vi);
|
|
break;
|
|
}
|
|
}
|
|
if (!variant) {
|
|
spl_comp_error(ctx, "unknown variant '%s' in match", vname);
|
|
break;
|
|
}
|
|
|
|
/* Compare tag: load [addr_slot+0] == variant->value */
|
|
/* Load tag and compare */
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, addr_slot);
|
|
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, variant->value);
|
|
spl_emit(ctx, SPL_EQ, SPL_I32, 0);
|
|
spl_val_t bz_addr = spl_emit_bz(ctx);
|
|
|
|
/* This arm matches — parse bindings and body */
|
|
skip_nl(ctx);
|
|
|
|
/* Parse binding list: (name, name, ...) */
|
|
int has_parens = 0;
|
|
if (peek(ctx)->type == TOK_L_PAREN) {
|
|
has_parens = 1;
|
|
advance(ctx); /* ( */
|
|
skip_nl(ctx);
|
|
}
|
|
|
|
/* Push scope for bindings if there are any */
|
|
int scope_pushed = 0;
|
|
if (has_parens && peek(ctx)->type != TOK_R_PAREN) {
|
|
spl_push_scope(ctx);
|
|
scope_pushed = 1;
|
|
|
|
if (variant->data_type && variant->data_type->kind == TYPE_STRUCT) {
|
|
/* Struct data: each struct field is a binding */
|
|
int bi = 0;
|
|
for (;;) {
|
|
spl_tok_t *btok = advance(ctx);
|
|
char bname[256];
|
|
usize bnl = btok->len < 255 ? btok->len : 255;
|
|
memcpy(bname, btok->lexeme, bnl);
|
|
bname[bnl] = '\0';
|
|
|
|
spl_type_info_t *btype = spl_type_basic(SPL_I32);
|
|
usize field_byte_off = 4;
|
|
if ((usize)bi < vec_size(variant->data_type->fields)) {
|
|
btype = vec_at(variant->data_type->fields, bi).type;
|
|
field_byte_off = 4 + vec_at(variant->data_type->fields, bi).offset;
|
|
}
|
|
|
|
int bslot = spl_declare_var(ctx, bname, btype, 0);
|
|
|
|
/* Load from enum data and store to variable */
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, addr_slot);
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
|
|
spl_emit(ctx, SPL_PUSH, SPL_USIZE, field_byte_off);
|
|
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
|
|
spl_type_t bt = (btype->kind == TYPE_BASIC) ? btype->basic_type
|
|
: (btype->kind == TYPE_PTR) ? SPL_PTR
|
|
: SPL_I32;
|
|
spl_emit(ctx, SPL_LOAD, bt, 0);
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, bslot);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, bt, 0);
|
|
|
|
bi++;
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == TOK_COMMA) {
|
|
advance(ctx);
|
|
skip_nl(ctx);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
} else if (variant->data_type) {
|
|
/* Simple data type: one binding */
|
|
spl_tok_t *btok = advance(ctx);
|
|
char bname[256];
|
|
usize bnl = btok->len < 255 ? btok->len : 255;
|
|
memcpy(bname, btok->lexeme, bnl);
|
|
bname[bnl] = '\0';
|
|
|
|
spl_type_info_t *btype = variant->data_type;
|
|
int bslot = spl_declare_var(ctx, bname, btype, 0);
|
|
|
|
/* Load from enum data at offset 4 */
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, addr_slot);
|
|
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
|
|
spl_emit(ctx, SPL_PUSH, SPL_USIZE, 4);
|
|
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
|
|
spl_type_t bt = (btype->kind == TYPE_BASIC) ? btype->basic_type
|
|
: (btype->kind == TYPE_PTR) ? SPL_PTR
|
|
: SPL_I32;
|
|
spl_emit(ctx, SPL_LOAD, bt, 0);
|
|
spl_emit(ctx, SPL_LADDR, SPL_PTR, bslot);
|
|
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
|
|
spl_emit(ctx, SPL_STORE, bt, 0);
|
|
}
|
|
}
|
|
|
|
if (has_parens)
|
|
expect(ctx, TOK_R_PAREN);
|
|
skip_nl(ctx);
|
|
|
|
/* => (two tokens: = >) */
|
|
if (peek(ctx)->type == TOK_ASSIGN) {
|
|
advance(ctx);
|
|
if (peek(ctx)->type == TOK_GT)
|
|
advance(ctx);
|
|
}
|
|
skip_nl(ctx);
|
|
|
|
/* Parse arm body statement */
|
|
spl_parse_stmt(ctx);
|
|
|
|
/* Pop scope if we pushed one */
|
|
if (scope_pushed) {
|
|
spl_emit_defer_epilogue(ctx, ctx->scope_depth);
|
|
spl_pop_scope(ctx);
|
|
}
|
|
|
|
/* JMP to end (skip remaining arms) */
|
|
if (n_jmps < MAX_MATCH_ARMS)
|
|
jmp_to_end[n_jmps++] = spl_emit_jmp(ctx);
|
|
|
|
/* Patch BZ to here (next arm or end) */
|
|
spl_patch_to_here(ctx, bz_addr);
|
|
|
|
skip_nl(ctx);
|
|
}
|
|
|
|
expect(ctx, TOK_R_BRACE);
|
|
|
|
/* Patch all JMPs to end */
|
|
for (int i = 0; i < n_jmps; i++)
|
|
spl_patch_to_here(ctx, jmp_to_end[i]);
|
|
|
|
/* Release temp slot */
|
|
ctx->current_local_slot--;
|
|
}
|
|
|
|
/* ============================================================
|
|
* Extern declaration: #[extern("vm")] fn name(...) ret;
|
|
* ============================================================ */
|
|
|
|
static void parse_extern_decl(spl_comp_t *ctx) {
|
|
advance(ctx); /* # */
|
|
expect(ctx, TOK_L_BRACKET);
|
|
/* Skip extern("vm") or just look for fn */
|
|
while (peek(ctx)->type != TOK_R_BRACKET && peek(ctx)->type != TOK_EOF)
|
|
advance(ctx);
|
|
if (peek(ctx)->type == TOK_R_BRACKET)
|
|
advance(ctx);
|
|
|
|
skip_nl(ctx);
|
|
|
|
if (peek(ctx)->type == KW_FN) {
|
|
advance(ctx); /* fn */
|
|
skip_nl(ctx);
|
|
spl_tok_t *fname_tok = advance(ctx);
|
|
char fn_name[256];
|
|
usize fnl = fname_tok->len < 255 ? fname_tok->len : 255;
|
|
memcpy(fn_name, fname_tok->lexeme, fnl);
|
|
fn_name[fnl] = '\0';
|
|
|
|
skip_nl(ctx);
|
|
expect(ctx, TOK_L_PAREN);
|
|
|
|
/* Count params (we don't store them for extern) */
|
|
int nparams = 0;
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type != TOK_R_PAREN) {
|
|
while (1) {
|
|
advance(ctx); /* param name */
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == TOK_COLON) {
|
|
advance(ctx); /* : */
|
|
skip_nl(ctx);
|
|
spl_parse_type(ctx); /* skip type */
|
|
}
|
|
nparams++;
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == TOK_COMMA) {
|
|
advance(ctx);
|
|
skip_nl(ctx);
|
|
continue;
|
|
}
|
|
if (peek(ctx)->type == TOK_ELLIPSIS) {
|
|
advance(ctx);
|
|
skip_nl(ctx);
|
|
} /* variadic */
|
|
break;
|
|
}
|
|
}
|
|
expect(ctx, TOK_R_PAREN);
|
|
skip_nl(ctx);
|
|
|
|
/* Return type */
|
|
spl_type_info_t *ret_type = spl_type_basic(SPL_VOID);
|
|
if (peek(ctx)->type != TOK_SEMICOLON && peek(ctx)->type != TOK_L_BRACE) {
|
|
ret_type = spl_parse_type(ctx);
|
|
if (!ret_type)
|
|
ret_type = spl_type_basic(SPL_VOID);
|
|
skip_nl(ctx);
|
|
}
|
|
|
|
spl_declare_func(ctx, fn_name, ret_type, nparams, 1, 0);
|
|
|
|
if (peek(ctx)->type == TOK_SEMICOLON)
|
|
advance(ctx);
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
* Expression statement (may include assignment)
|
|
* ============================================================ */
|
|
|
|
/* Check if token type is an assignment operator */
|
|
static int is_assign_op(spl_tok_type_t t) {
|
|
return t == TOK_ASSIGN || t == TOK_ASSIGN_ADD || t == TOK_ASSIGN_SUB || t == TOK_ASSIGN_MUL ||
|
|
t == TOK_ASSIGN_DIV || t == TOK_ASSIGN_MOD || t == TOK_ASSIGN_AND ||
|
|
t == TOK_ASSIGN_OR || t == TOK_ASSIGN_XOR || t == TOK_ASSIGN_L_SH ||
|
|
t == TOK_ASSIGN_R_SH;
|
|
}
|
|
|
|
static void parse_expr_stmt(spl_comp_t *ctx) {
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == KW_RET) {
|
|
parse_ret_stmt(ctx);
|
|
return;
|
|
}
|
|
|
|
int prev = ctx->tok_idx;
|
|
|
|
/* Look ahead for assignment operator (scan past expression tokens) */
|
|
int is_assign = 0;
|
|
{
|
|
usize look = ctx->tok_idx;
|
|
while (look < vec_size(ctx->toks)) {
|
|
spl_tok_type_t t = vec_at(ctx->toks, look).type;
|
|
if (t == TOK_SEMICOLON || t == TOK_ENDLINE || t == TOK_L_BRACE || t == TOK_R_BRACE ||
|
|
t == TOK_EOF)
|
|
break;
|
|
if (t == TOK_L_PAREN)
|
|
break; /* function call, not assignment */
|
|
if (is_assign_op(t)) {
|
|
is_assign = 1;
|
|
break;
|
|
}
|
|
look++;
|
|
}
|
|
}
|
|
|
|
if (is_assign)
|
|
ctx->addr_of_mode = 1;
|
|
spl_expr_result_t expr = spl_parse_expr(ctx, PREC_MIN);
|
|
if (is_assign)
|
|
ctx->addr_of_mode = 0;
|
|
|
|
if (!is_assign && expr.type && expr.type->kind == TYPE_BASIC &&
|
|
expr.type->basic_type != SPL_VOID)
|
|
spl_emit(ctx, SPL_DROP, SPL_VOID, 0);
|
|
if (peek(ctx)->type == TOK_SEMICOLON)
|
|
advance(ctx);
|
|
/* Safety: if no token was consumed, advance to prevent infinite loop */
|
|
if (ctx->tok_idx == prev)
|
|
advance(ctx);
|
|
}
|
|
|
|
/* ============================================================
|
|
* Main statement dispatcher
|
|
* ============================================================ */
|
|
|
|
void spl_parse_stmt(spl_comp_t *ctx) {
|
|
skip_nl(ctx);
|
|
|
|
if (peek(ctx)->type == TOK_EOF || peek(ctx)->type == TOK_R_BRACE)
|
|
return;
|
|
|
|
switch (peek(ctx)->type) {
|
|
case KW_RET:
|
|
parse_ret_stmt(ctx);
|
|
break;
|
|
case KW_VAR:
|
|
parse_var_decl(ctx, 0);
|
|
break;
|
|
case KW_CONST:
|
|
parse_var_decl(ctx, 1);
|
|
break;
|
|
case KW_IF:
|
|
parse_if_stmt(ctx);
|
|
break;
|
|
case KW_WHILE:
|
|
parse_while_stmt(ctx);
|
|
break;
|
|
case KW_LOOP:
|
|
parse_loop_stmt(ctx);
|
|
break;
|
|
case KW_FOR:
|
|
parse_for_stmt(ctx);
|
|
break;
|
|
case KW_BREAK:
|
|
parse_break_stmt(ctx);
|
|
break;
|
|
case KW_CONTINUE:
|
|
parse_continue_stmt(ctx);
|
|
break;
|
|
case KW_DEFER:
|
|
parse_defer_stmt(ctx);
|
|
break;
|
|
case KW_MATCH:
|
|
parse_match_stmt(ctx);
|
|
break;
|
|
case KW_TYPE:
|
|
parse_type_decl(ctx);
|
|
break;
|
|
case TOK_L_BRACE:
|
|
spl_parse_block(ctx);
|
|
break;
|
|
case TOK_LINE_COMMENT:
|
|
advance(ctx);
|
|
break;
|
|
case TOK_SHARP: /* #[extern(...)] */
|
|
parse_extern_decl(ctx);
|
|
break;
|
|
default:
|
|
parse_expr_stmt(ctx);
|
|
break;
|
|
}
|
|
}
|