From 6cfd9c1ebc3c1523e0e978dc3aca18b8bbc43087 Mon Sep 17 00:00:00 2001 From: cassowarii Date: Mon, 29 Jun 2026 16:29:54 -0700 Subject: [PATCH] better error reporting + a few variable scope fixes --- src/compile/ir.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++--------- src/compile/ir.h | 17 ++++++--- src/main.c | 9 ++++- 3 files changed, 112 insertions(+), 25 deletions(-) diff --git a/src/compile/ir.c b/src/compile/ir.c index 841a38a..ba06457 100644 --- a/src/compile/ir.c +++ b/src/compile/ir.c @@ -5,6 +5,8 @@ #include "mem/debug.h" +#include + typedef struct varmapentry { const char *name; usize name_len; @@ -46,7 +48,7 @@ void sbIrProgram_print(hIrProgram ir) { for (usize i = 0; i < nchunks; i++) { sbIrChunk *ck = ((sbIrChunk**)ir->chunks.data)[i]; - printf("\nCHUNK %zu with %zu variables\n", ck->id, ck->variable_count); + printf("\nCHUNK %d with %d variables\n", ck->id, ck->variable_count); usize nstmts = ck->stmts.size / sizeof(sbIrStmt*); for (usize j = 0; j < nstmts; j++) { @@ -60,6 +62,26 @@ void sbIrProgram_print(hIrProgram ir) { /* --- */ +static void vprogram_error(hIrProgram ir, const char *error, va_list args) { + ir->error_count ++; + fprintf(stderr, "error: "); + vfprintf(stderr, error, args); +} + +static void program_error(hIrProgram ir, const char *error, ...) { + va_list args; + va_start(args, error); + vprogram_error(ir, error, args); + va_end(args); +} + +static void chunk_error(hIrChunk ck, const char *error, ...) { + va_list args; + va_start(args, error); + vprogram_error(ck->program, error, args); + va_end(args); +} + static sbIrChunk *new_chunk(hIrProgram ir) { usize nchunks = ir->chunks.size / sizeof(sbIrChunk*); sbIrChunk *chunk = sbArena_alloc(&ir->arena, sizeof(sbIrChunk)); @@ -108,7 +130,7 @@ static sbIrVariable *create_var(hIrChunk ck, const char *name, usize name_len) { /* find variables that already exist */ static sbIrVariable *var_name(hIrChunk ck, const char *name, usize name_len) { usize nvars = ck->program->varmapping.size / sizeof(varmapentry); - for (usize i = 0; i < nvars; i++) { + for (usize i = ck->lowest_var_id; i < nvars; i++) { varmapentry *entry = &((varmapentry*)ck->program->varmapping.data)[i]; if (sbstrncmp(name, entry->name, name_len) == 0) { return entry->var; @@ -116,7 +138,7 @@ static sbIrVariable *var_name(hIrChunk ck, const char *name, usize name_len) { } /* TODO do a different thing here */ - fprintf(stderr, "unknown variable name! '%s'\n", name); + chunk_error(ck, "unknown variable name! '%s'\n", name); return NULL; } @@ -264,6 +286,7 @@ static void put_expr(hIrChunk ck, sbIrExpr *expr) { static void print_expr(sbIrExpr *e); static void put_assign(hIrChunk ck, sbIrVariable *assign_to, sbIrExpr *expr) { + assign_to->initialized = TRUE; put_ir_stmt(ck, &(sbIrStmt) { .type = IR_S_ASSIGN, .assign.var = assign_to, @@ -271,6 +294,14 @@ static void put_assign(hIrChunk ck, sbIrVariable *assign_to, sbIrExpr *expr) { }); } +static void put_argument(hIrChunk ck, sbIrVariable *v, flag last) { + put_ir_stmt(ck, &(sbIrStmt) { + .type = IR_S_ARG, + .arg.last = last, + .arg.var = v, + }); +} + static void put_return(hIrChunk ck, sbIrExpr *e) { put_ir_stmt(ck, &(sbIrStmt) { .type = IR_S_RETURN, @@ -300,23 +331,43 @@ static sbIrVariable *compile_ast_var(hIrChunk ck, sbAst node); static sbIrChunk *compile_ast_function(hIrProgram ir, sbAst paramsAst, sbAst seqast) { sbIrChunk *ck = new_chunk(ir); + /* create new scope to hold function parameters */ + usize parameter_scope_level = ck->program->varmapping.size / sizeof(varmapentry); + + sbBuffer arg_vars; + sbBuffer_initialize(&arg_vars, 1024); sbAst param_comma = paramsAst; while (param_comma != NO_NODE) { if (param_comma->seq.left->type != AST_NODE_NAME) { - fprintf(stderr, "Only variable names are permitted as function parameters!\n"); /* TODO: Actually... */ + program_error(ir, "Only variable names are permitted as function parameters!"); break; } const char *vname = sbSymbol_name(param_comma->seq.left->symb); - create_var(ck, vname, strlen(vname)); + sbIrVariable *v1 = create_var(ck, vname, strlen(vname)); + sbBuffer_append(&arg_vars, &v1, sizeof(sbIrVariable*)); + v1->initialized = TRUE; param_comma = param_comma->seq.right; } + while (arg_vars.size > 0) { + /* We need to bind function arguments in reverse order that they are passed, + * because arguments are going to be passed on a stack, and we want to evaluate + * function arguments left-to-right. */ + sbIrVariable *var = *(sbIrVariable**)sbBuffer_shrink(&arg_vars, sizeof(sbIrVariable*)); + put_argument(ck, var, arg_vars.size == 0); + } + compile_ast_stmtseq(ck, seqast, TRUE); + + /* exit parameters' lexical scope */ + sbBuffer_set_size(&ck->program->varmapping, parameter_scope_level * sizeof(varmapentry)); + return ck; } static void compile_ast_stmtseq(hIrChunk ck, sbAst seqast, flag implicit_return) { + /* create new scope to hold lexical block variables */ usize lowest_var = ck->program->varmapping.size / sizeof(varmapentry); sbAst considering = seqast; @@ -368,8 +419,7 @@ static void compile_ast_stmtseq(hIrChunk ck, sbAst seqast, flag implicit_return) /* now, i is the number of statements we were left with after deleting some */ sbBuffer_set_size(&ck->stmts, i * sizeof(sbIrStmt*)); - /* after the block is done, we need to reset the varmapping to not include - * any variables that were introduced inside this block */ + /* exit block lexical scope */ sbBuffer_set_size(&ck->program->varmapping, lowest_var * sizeof(varmapentry)); } @@ -420,7 +470,7 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { N1 = node->seq.left; /* things to bind to */ while (N1 != NO_NODE) { if (N1->seq.left->type != AST_NODE_NAME) { - fprintf(stderr, "Only variable names are permitted after 'let'!\n"); + chunk_error(ck, "Only variable names are permitted after 'let'!"); break; } const char *vname = sbSymbol_name(N1->seq.left->symb); @@ -431,14 +481,26 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { /* let ... = ... */ if (node->seq.right->type != AST_NODE_MULTIVAL) PANIC("expected multival on right side of let!"); N1 = node->seq.left; /* things to bind to */ - N2 = node->seq.right; /* values to assign */ - while (N1 != NO_NODE && N2 != NO_NODE) { + while (N1 != NO_NODE) { + /* create all variables first, then initialize them. + * we can write something like "let q, r = 5, q + 1", but if we write something + * like "let q, r = r + 1, 5", we should fail with "r is not initialized," not + * "i don't know what r means". */ if (N1->seq.left->type != AST_NODE_NAME) { - fprintf(stderr, "Only variable names are permitted on the left side of 'let'!\n"); + chunk_error(ck, "Only variable names are permitted on the left side of 'let'!"); break; } const char *vname = sbSymbol_name(N1->seq.left->symb); V1 = create_var(ck, vname, strlen(vname)); + N1 = N1->seq.right; + } + + N1 = node->seq.left; /* things to bind to */ + N2 = node->seq.right; /* values to assign */ + while (N1 != NO_NODE && N2 != NO_NODE) { + /* now assign to all the variables we just created */ + const char *vname = sbSymbol_name(N1->seq.left->symb); + V1 = var_name(ck, vname, strlen(vname)); E1 = compile_ast_expr(ck, N2->seq.left); put_assign(ck, V1, E1); N1 = N1->seq.right; @@ -446,9 +508,9 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { } if (N1 != NO_NODE) { - fprintf(stderr, "too many bindings on left side of assignment!"); + chunk_error(ck, "too many bindings on left side of assignment!"); } else if (N2 != NO_NODE) { - fprintf(stderr, "too many values on right side of assignment!"); + chunk_error(ck, "too many values on right side of assignment!"); } } break; @@ -481,9 +543,9 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { } if (N1 != NO_NODE) { - fprintf(stderr, "too many bindings on left side of assignment!"); + chunk_error(ck, "Too many bindings on left side of assignment"); } else if (N2 != NO_NODE) { - fprintf(stderr, "too many values on right side of assignment!"); + chunk_error(ck, "Too many values on right side of assignment"); } break; @@ -614,7 +676,7 @@ static sbIrVariable *compile_ast_var(hIrChunk ck, sbAst node) { return var_name(ck, sbSymbol_name(node->symb), strlen(sbSymbol_name(node->symb))); } else { /* TODO make errors not bad */ - fprintf(stderr, "expecting a name at this point! (got %d)\n", node->type); + chunk_error(ck, "Only a variable name is permitted here. (got %d)\n", node->type); return NULL; } } @@ -649,7 +711,13 @@ static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node) { return expr_value(ck, &HVINT(node->i)); } else if (node->type == AST_NODE_NAME) { /* TODO: I don't want to use strlen. Can we remember the lengths of symbols? */ - return expr_var(ck, compile_ast_var(ck, node)); + sbIrVariable *var = compile_ast_var(ck, node); + if (var->initialized) { + return expr_var(ck, var); + } else { + chunk_error(ck, "Variable '%s' has not yet been initialized here!\n", sbSymbol_name(node->symb)); + return NULL; + } } else { PANIC("expr todo! (%d)", node->type); } @@ -662,7 +730,7 @@ static void print_expr(sbIrExpr *e) { printf("variable %zu", e->var->slot_id); break; case IR_E_FUNC: - printf("{ chunk %zu }", e->func->id); + printf("{ chunk %d }", e->func->id); break; case IR_E_VALUE: if (e->value.type == IT_NIL) { @@ -699,6 +767,13 @@ static void print_expr(sbIrExpr *e) { static void print_stmt(sbIrStmt *s) { switch (s->type) { + case IR_S_ARG: + if (s->arg.last) { + printf(" bind last function argument to variable %zu\n", s->arg.var->slot_id); + } else { + printf(" bind function argument to variable %zu\n", s->arg.var->slot_id); + } + break; case IR_S_LABEL: printf("label %zu:\n", s->label->id); break; diff --git a/src/compile/ir.h b/src/compile/ir.h index 561cb7d..719a34c 100644 --- a/src/compile/ir.h +++ b/src/compile/ir.h @@ -35,6 +35,7 @@ typedef enum sbIrStmtType { IR_S_EXPR, IR_S_ASSIGN, IR_S_JUMP, + IR_S_ARG, IR_S_RETURN, } sbIrStmtType; @@ -57,6 +58,7 @@ typedef struct sbIrLabel { typedef struct sbIrVariable { usize slot_id; + flag initialized; usize first_appearance; usize last_appearance; usize assigned_index; @@ -96,13 +98,17 @@ typedef struct sbIrAssign { } sbIrAssign; typedef struct sbIrStmt { - usize position; + i32 position; sbIrStmtType type; union { sbIrExpr *expr; sbIrLabel *label; sbIrAssign assign; sbIrJump jump; + struct { + flag last; + sbIrVariable *var; + } arg; }; } sbIrStmt; @@ -110,10 +116,10 @@ struct sbIrProgram; typedef struct sbIrChunk { struct sbIrProgram *program; - usize id; - usize label_count; - usize variable_count; - usize lowest_var_id; + i32 id; + i32 label_count; + i32 variable_count; + i32 lowest_var_id; sbBuffer stmts; } sbIrChunk; @@ -121,6 +127,7 @@ typedef struct sbIrProgram { sbArena arena; sbBuffer varmapping; sbBuffer chunks; + i32 error_count; } sbIrProgram; typedef struct sbIrProgram *hIrProgram; diff --git a/src/main.c b/src/main.c index e66bfb4..272ae2e 100644 --- a/src/main.c +++ b/src/main.c @@ -37,8 +37,13 @@ int main(int argc, char **argv) { /* free AST, don't need it anymore */ sbParser_deinitialize(&pr); - // print out program - sbIrProgram_print(&ir); + if (ir.error_count > 0) { + fprintf(stderr, "Could not run '%s' due to errors.\n", argv[1]); + return -3; + } else { + // print out program + sbIrProgram_print(&ir); + } sbIrProgram_deinitialize(&ir); -- 1.8.3.1