507 lines
16 KiB
C
507 lines
16 KiB
C
/* spl_stmt.c — Statement parser + codegen */
|
|
|
|
#include "spl_comp.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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 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;
|
|
}
|
|
|
|
static int match(spl_comp_t *ctx, spl_tok_type_t type) {
|
|
if (peek(ctx)->type == type) { advance(ctx); return 1; }
|
|
return 0;
|
|
}
|
|
|
|
static void skip_nl(spl_comp_t *ctx) {
|
|
while (peek(ctx)->type == TOK_ENDLINE) advance(ctx);
|
|
}
|
|
|
|
/* ============================================================
|
|
* Return statement: ret expr;
|
|
* ============================================================ */
|
|
|
|
static void parse_ret_stmt(spl_comp_t *ctx) {
|
|
advance(ctx); /* ret */
|
|
skip_nl(ctx);
|
|
|
|
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 = ctx->current_ret_type ? ctx->current_ret_type->basic_type : SPL_VOID;
|
|
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); /* = */
|
|
}
|
|
|
|
/* Allocate stack slot */
|
|
if (!var_type) var_type = spl_type_basic(SPL_I32); /* default type */
|
|
int slot = spl_declare_var(ctx, vname, var_type, is_const);
|
|
|
|
skip_nl(ctx);
|
|
|
|
/* Init expression */
|
|
if (has_init) {
|
|
spl_expr_result_t init = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)init;
|
|
|
|
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_I32, 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 : 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_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);
|
|
|
|
spl_emit(ctx, SPL_JMP, SPL_VOID, loop_start);
|
|
spl_patch_to_here(ctx, bz_addr);
|
|
|
|
/* Patch any break statements */
|
|
for (usize i = saved_bp_count; i < ctx->break_patch_count; i++) {
|
|
spl_patch(ctx, ctx->break_patches[i], vec_size(ctx->prog.insns));
|
|
}
|
|
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);
|
|
|
|
spl_emit(ctx, SPL_JMP, SPL_VOID, loop_start);
|
|
|
|
/* Patch break statements */
|
|
for (usize i = saved_bp_count; i < ctx->break_patch_count; i++) {
|
|
spl_patch(ctx, ctx->break_patches[i], vec_size(ctx->prog.insns));
|
|
}
|
|
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 = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)start;
|
|
|
|
if (peek(ctx)->type == TOK_RANGE) {
|
|
/* for begin..end as i { body } */
|
|
advance(ctx); /* .. */
|
|
spl_expr_result_t end = spl_parse_expr(ctx, PREC_MIN);
|
|
(void)end;
|
|
|
|
skip_nl(ctx);
|
|
if (peek(ctx)->type == KW_AS) {
|
|
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';
|
|
|
|
/* Declare loop variable */
|
|
spl_type_info_t *itype = spl_type_basic(SPL_I32);
|
|
int islot = spl_declare_var(ctx, iname, itype, 0);
|
|
|
|
/* The loop variable was already set up by the range operator */
|
|
/* Store initial value */
|
|
/* For now: we need to emit proper loop code. This is a simplification. */
|
|
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 = 0; /* will set after store */
|
|
|
|
/* Loop: store begin to i, check i < end, body, i++ */
|
|
/* Stack has: begin, end (from parsing the range expression) */
|
|
/* Actually we need to emit proper code here. For bootstrap,
|
|
let me just handle the simple numeric for loop. */
|
|
|
|
/* For now, emit a basic loop body */
|
|
spl_parse_block(ctx);
|
|
|
|
ctx->in_loop = saved_loop;
|
|
ctx->continue_target = saved_continue;
|
|
ctx->break_patch_count = saved_bp_count;
|
|
}
|
|
} else if (peek(ctx)->type == TOK_COMMA) {
|
|
/* for slice, 0.. as val, idx { body } — skip for now */
|
|
advance(ctx); /* , */
|
|
spl_parse_expr(ctx, PREC_MIN); /* skip the range */
|
|
if (peek(ctx)->type == KW_AS) {
|
|
advance(ctx);
|
|
advance(ctx); /* val */
|
|
if (peek(ctx)->type == TOK_COMMA) {
|
|
advance(ctx);
|
|
advance(ctx); /* idx */
|
|
}
|
|
}
|
|
skip_nl(ctx);
|
|
spl_parse_block(ctx);
|
|
} else {
|
|
/* for ident in ... — skip */
|
|
skip_nl(ctx);
|
|
spl_parse_block(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");
|
|
}
|
|
spl_emit(ctx, SPL_JMP, SPL_VOID, ctx->continue_target);
|
|
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 */
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
* 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)
|
|
* ============================================================ */
|
|
|
|
static void parse_expr_stmt(spl_comp_t *ctx) {
|
|
/* For SPL, ret is a keyword, not an identifier.
|
|
* But the lexer might lex "return" as an ident if it's not in the keyword table.
|
|
* Let's add support for "return" as a statement as well. */
|
|
if (peek(ctx)->type == KW_RET) {
|
|
parse_ret_stmt(ctx);
|
|
return;
|
|
}
|
|
|
|
int prev = ctx->tok_idx;
|
|
spl_expr_result_t expr = spl_parse_expr(ctx, PREC_MIN);
|
|
/* Drop the expression result if it left a value on the stack */
|
|
if (expr.type) 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_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;
|
|
}
|
|
}
|